Browse Source

Added partial relative path support in agi

Toby Chui 2 years ago
parent
commit
e9e5d33bc5
13 changed files with 2622 additions and 2551 deletions
  1. 12 2
      AGI Documentation.md
  2. 130 130
      mod/agi/agi.appdata.go
  3. 30 29
      mod/agi/agi.audio.go
  4. 901 863
      mod/agi/agi.file.go
  5. 8 7
      mod/agi/agi.go
  6. 263 262
      mod/agi/agi.http.go
  7. 407 406
      mod/agi/agi.image.go
  8. 289 288
      mod/agi/agi.iot.go
  9. 132 131
      mod/agi/agi.share.go
  10. 61 61
      mod/agi/handler.go
  11. 81 72
      mod/agi/static.go
  12. 1 0
      mod/agi/systemFunc.go
  13. 307 300
      mod/agi/userFunc.go

+ 12 - 2
AGI Documentation.md

@@ -511,7 +511,7 @@ To use the user default option which user has set in File Manager WebApp, pass i
 filelib.aglob("user:/Desktop/*.jpg", "user");
 filelib.aglob("user:/Desktop/*.jpg", "user");
 ```
 ```
 
 
-##### Return type of filelib.readdir (Since ArozOS v2.002)
+##### Return type of filelib.readdir (Since ArozOS v2.002 / AGI 2.1)
 
 
 For filelib.readdir, it will return an array with the following object structure
 For filelib.readdir, it will return an array with the following object structure
 
 
@@ -557,11 +557,21 @@ Example return value (in JSON object, not stringify JSON string)
 ]
 ]
 ```
 ```
 
 
+### Relative path support when execute in personal page (Since ArozOS v2.005 / AGI 2.2)
+
+AGI 2.2 filelib support execution of file operations as relative path when the file is executed via personal page link. For example:
+
+```javascript
+var content = filelib.readFile("./untitled.md");
+```
+
+**In other library or use case, please use the full path of resources instead.**
+
 ### appdata
 ### appdata
 
 
 An API for access files inside the web folder. This API only provide read only functions. Include the appdata lib as follows.
 An API for access files inside the web folder. This API only provide read only functions. Include the appdata lib as follows.
 
 
-```
+```javascript
 requirelib("appdata");
 requirelib("appdata");
 ```
 ```
 
 

+ 130 - 130
mod/agi/agi.appdata.go

@@ -1,130 +1,130 @@
-package agi
-
-import (
-	"encoding/json"
-	"errors"
-	"io/ioutil"
-	"log"
-	"path/filepath"
-
-	"github.com/robertkrimen/otto"
-	"imuslab.com/arozos/mod/filesystem"
-	user "imuslab.com/arozos/mod/user"
-)
-
-/*
-	AGI Appdata Access Library
-	Author: tobychui
-
-	This library allow agi script to access files located in the web root
-	*This library provide READ ONLY function*
-	You cannot write to web folder due to security reasons. If you need to read write
-	web root (which is not recommended), ask the user to mount it top web:/ manually
-*/
-
-var webRoot string = "./web" //The web folder root
-
-func (g *Gateway) AppdataLibRegister() {
-	err := g.RegisterLib("appdata", g.injectAppdataLibFunctions)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func (g *Gateway) injectAppdataLibFunctions(vm *otto.Otto, u *user.User) {
-	vm.Set("_appdata_readfile", func(call otto.FunctionCall) otto.Value {
-		relpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check if this is path escape
-		escaped, err := g.checkRootEscape(webRoot, filepath.Join(webRoot, relpath))
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		if escaped {
-			g.raiseError(errors.New("Path escape detected"))
-			return otto.FalseValue()
-		}
-
-		//Check if file exists
-		targetFile := filepath.Join(webRoot, relpath)
-		if fileExists(targetFile) && !filesystem.IsDir(targetFile) {
-			content, err := ioutil.ReadFile(targetFile)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			//OK. Return the content of the file
-			result, _ := vm.ToValue(string(content))
-			return result
-		} else if filesystem.IsDir(targetFile) {
-			g.raiseError(errors.New("Cannot read from directory"))
-			return otto.FalseValue()
-
-		} else {
-			g.raiseError(errors.New("File not exists"))
-			return otto.FalseValue()
-		}
-	})
-
-	vm.Set("_appdata_listdir", func(call otto.FunctionCall) otto.Value {
-		relpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check if this is path escape
-		escaped, err := g.checkRootEscape(webRoot, filepath.Join(webRoot, relpath))
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		if escaped {
-			g.raiseError(errors.New("Path escape detected"))
-			return otto.FalseValue()
-		}
-
-		//Check if file exists
-		targetFolder := filepath.Join(webRoot, relpath)
-		if fileExists(targetFolder) && filesystem.IsDir(targetFolder) {
-			//Glob the directory for filelist
-			files, err := filepath.Glob(filepath.ToSlash(filepath.Clean(targetFolder)) + "/*")
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			results := []string{}
-			for _, file := range files {
-				rel, _ := filepath.Rel(webRoot, file)
-				rel = filepath.ToSlash(rel)
-				results = append(results, rel)
-			}
-
-			js, _ := json.Marshal(results)
-
-			//OK. Return the content of the file
-			result, _ := vm.ToValue(string(js))
-			return result
-
-		} else {
-			g.raiseError(errors.New("Directory not exists"))
-			return otto.FalseValue()
-		}
-	})
-
-	//Wrap all the native code function into an imagelib class
-	vm.Run(`
-		var appdata = {};
-		appdata.readFile = _appdata_readfile;
-		appdata.listDir = _appdata_listdir;
-	`)
-}
+package agi
+
+import (
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"log"
+	"path/filepath"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	user "imuslab.com/arozos/mod/user"
+)
+
+/*
+	AGI Appdata Access Library
+	Author: tobychui
+
+	This library allow agi script to access files located in the web root
+	*This library provide READ ONLY function*
+	You cannot write to web folder due to security reasons. If you need to read write
+	web root (which is not recommended), ask the user to mount it top web:/ manually
+*/
+
+var webRoot string = "./web" //The web folder root
+
+func (g *Gateway) AppdataLibRegister() {
+	err := g.RegisterLib("appdata", g.injectAppdataLibFunctions)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (g *Gateway) injectAppdataLibFunctions(vm *otto.Otto, u *user.User, scriptFsh *filesystem.FileSystemHandler, scriptPath string) {
+	vm.Set("_appdata_readfile", func(call otto.FunctionCall) otto.Value {
+		relpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Check if this is path escape
+		escaped, err := g.checkRootEscape(webRoot, filepath.Join(webRoot, relpath))
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if escaped {
+			g.raiseError(errors.New("Path escape detected"))
+			return otto.FalseValue()
+		}
+
+		//Check if file exists
+		targetFile := filepath.Join(webRoot, relpath)
+		if fileExists(targetFile) && !filesystem.IsDir(targetFile) {
+			content, err := ioutil.ReadFile(targetFile)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			//OK. Return the content of the file
+			result, _ := vm.ToValue(string(content))
+			return result
+		} else if filesystem.IsDir(targetFile) {
+			g.raiseError(errors.New("Cannot read from directory"))
+			return otto.FalseValue()
+
+		} else {
+			g.raiseError(errors.New("File not exists"))
+			return otto.FalseValue()
+		}
+	})
+
+	vm.Set("_appdata_listdir", func(call otto.FunctionCall) otto.Value {
+		relpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Check if this is path escape
+		escaped, err := g.checkRootEscape(webRoot, filepath.Join(webRoot, relpath))
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if escaped {
+			g.raiseError(errors.New("Path escape detected"))
+			return otto.FalseValue()
+		}
+
+		//Check if file exists
+		targetFolder := filepath.Join(webRoot, relpath)
+		if fileExists(targetFolder) && filesystem.IsDir(targetFolder) {
+			//Glob the directory for filelist
+			files, err := filepath.Glob(filepath.ToSlash(filepath.Clean(targetFolder)) + "/*")
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			results := []string{}
+			for _, file := range files {
+				rel, _ := filepath.Rel(webRoot, file)
+				rel = filepath.ToSlash(rel)
+				results = append(results, rel)
+			}
+
+			js, _ := json.Marshal(results)
+
+			//OK. Return the content of the file
+			result, _ := vm.ToValue(string(js))
+			return result
+
+		} else {
+			g.raiseError(errors.New("Directory not exists"))
+			return otto.FalseValue()
+		}
+	})
+
+	//Wrap all the native code function into an imagelib class
+	vm.Run(`
+		var appdata = {};
+		appdata.readFile = _appdata_readfile;
+		appdata.listDir = _appdata_listdir;
+	`)
+}

+ 30 - 29
mod/agi/agi.audio.go

@@ -1,29 +1,30 @@
-package agi
-
-import (
-	"log"
-
-	"github.com/robertkrimen/otto"
-	user "imuslab.com/arozos/mod/user"
-)
-
-/*
-	AJGI Audio Library
-
-	This is a library for allowing audio playback from AGI script
-	Powered by Go Beep and the usage might be a bit tricky
-
-	Author: tobychui
-
-*/
-
-func (g *Gateway) AudioLibRegister() {
-	err := g.RegisterLib("audio", g.injectAudioFunctions)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func (g *Gateway) injectAudioFunctions(vm *otto.Otto, u *user.User) {
-
-}
+package agi
+
+import (
+	"log"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	user "imuslab.com/arozos/mod/user"
+)
+
+/*
+	AJGI Audio Library
+
+	This is a library for allowing audio playback from AGI script
+	Powered by Go Beep and the usage might be a bit tricky
+
+	Author: tobychui
+
+*/
+
+func (g *Gateway) AudioLibRegister() {
+	err := g.RegisterLib("audio", g.injectAudioFunctions)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (g *Gateway) injectAudioFunctions(vm *otto.Otto, u *user.User, scriptFsh *filesystem.FileSystemHandler, scriptPath string) {
+
+}

+ 901 - 863
mod/agi/agi.file.go

@@ -1,863 +1,901 @@
-package agi
-
-import (
-	"crypto/md5"
-	"encoding/hex"
-	"encoding/json"
-	"errors"
-	"io"
-	"io/fs"
-	"log"
-	"os"
-	"path/filepath"
-
-	"github.com/robertkrimen/otto"
-	"imuslab.com/arozos/mod/filesystem/fssort"
-	"imuslab.com/arozos/mod/filesystem/hidden"
-	user "imuslab.com/arozos/mod/user"
-)
-
-/*
-	AJGI File Processing Library
-
-	This is a library for handling image related functionalities in agi scripts.
-
-	By Alanyueng 2020 <- This person write shitty code that need me to tidy up (by tobychui)
-	Complete rewrite by tobychui in Sept 2020
-*/
-
-func (g *Gateway) FileLibRegister() {
-	err := g.RegisterLib("filelib", g.injectFileLibFunctions)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func (g *Gateway) injectFileLibFunctions(vm *otto.Otto, u *user.User) {
-
-	//Legacy File system API
-	//writeFile(virtualFilepath, content) => return true/false when succeed / failed
-	vm.Set("_filelib_writeFile", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanWrite(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
-		}
-
-		content, err := call.Argument(1).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check if there is quota for the given length
-		if !u.StorageQuota.HaveSpace(int64(len(content))) {
-			//User have no remaining storage quota
-			g.raiseError(errors.New("Storage Quota Fulled"))
-			return otto.FalseValue()
-		}
-
-		//Translate the virtual path to realpath
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check if file already exists.
-		if fsh.FileSystemAbstraction.FileExists(rpath) {
-			//Check if this user own this file
-			isOwner := u.IsOwnerOfFile(fsh, vpath)
-			if isOwner {
-				//This user own this system. Remove this file from his quota
-				u.RemoveOwnershipFromFile(fsh, vpath)
-			}
-		}
-
-		//Create and write to file using ioutil
-		err = fsh.FileSystemAbstraction.WriteFile(rpath, []byte(content), 0755)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Add the filesize to user quota
-		u.SetOwnerOfFile(fsh, vpath)
-
-		reply, _ := vm.ToValue(true)
-		return reply
-	})
-
-	vm.Set("_filelib_deleteFile", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanWrite(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
-		}
-
-		//Translate the virtual path to realpath
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check if file already exists.
-		if fsh.FileSystemAbstraction.FileExists(rpath) {
-			//Check if this user own this file
-			isOwner := u.IsOwnerOfFile(fsh, vpath)
-			if isOwner {
-				//This user own this system. Remove this file from his quota
-				u.RemoveOwnershipFromFile(fsh, vpath)
-			}
-		} else {
-			g.raiseError(errors.New("File not exists"))
-			return otto.FalseValue()
-		}
-
-		//Remove the file
-		fsh.FileSystemAbstraction.Remove(rpath)
-
-		reply, _ := vm.ToValue(true)
-		return reply
-	})
-
-	//readFile(virtualFilepath) => return content in string
-	vm.Set("_filelib_readFile", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
-		}
-
-		//Translate the virtual path to realpath
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Create and write to file using ioUtil
-		content, err := fsh.FileSystemAbstraction.ReadFile(rpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		reply, _ := vm.ToValue(string(content))
-		return reply
-	})
-
-	//Listdir
-	//readdir("user:/Desktop") => return filelist in array
-	/*
-		vm.Set("_filelib_readdir", func(call otto.FunctionCall) otto.Value {
-			vpath, err := call.Argument(0).ToString()
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			//Translate the virtual path to realpath
-			fsh, rpath, err := virtualPathToRealPath(vpath, u)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-			fshAbs := fsh.FileSystemAbstraction
-
-			rpath = filepath.ToSlash(filepath.Clean(rpath)) + "/*"
-			fileList, err := fshAbs.Glob(rpath)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			//Translate all paths to virtual paths
-			results := []string{}
-			for _, file := range fileList {
-				isHidden, _ := hidden.IsHidden(file, true)
-				if !isHidden {
-					thisRpath, _ := fshAbs.RealPathToVirtualPath(file, u.Username)
-					results = append(results, thisRpath)
-				}
-			}
-
-			reply, _ := vm.ToValue(results)
-			return reply
-		})
-	*/
-
-	//Usage
-	//filelib.walk("user:/") => list everything recursively
-	//filelib.walk("user:/", "folder") => list all folder recursively
-	//filelib.walk("user:/", "file") => list all files recursively
-	vm.Set("_filelib_walk", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		mode, err := call.Argument(1).ToString()
-		if err != nil {
-			mode = "all"
-		}
-
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		results := []string{}
-		fsh.FileSystemAbstraction.Walk(rpath, func(path string, info os.FileInfo, err error) error {
-			if err != nil {
-				//Ignore this error file and continue
-				return nil
-			}
-			thisVpath, err := realpathToVirtualpath(fsh, path, u)
-			if err != nil {
-				return nil
-			}
-			if mode == "file" {
-				if !info.IsDir() {
-					results = append(results, thisVpath)
-				}
-			} else if mode == "folder" {
-				if info.IsDir() {
-					results = append(results, thisVpath)
-				}
-			} else {
-				results = append(results, thisVpath)
-			}
-
-			return nil
-		})
-
-		reply, _ := vm.ToValue(results)
-		return reply
-	})
-
-	//Glob
-	//glob("user:/Desktop/*.mp3") => return fileList in array
-	//glob("/") => return a list of root directories
-	//glob("user:/Desktop/*", "mostRecent") => return fileList in mostRecent sorting mode
-	//glob("user:/Desktop/*", "user") => return fileList in array in user prefered sorting method
-	vm.Set("_filelib_glob", func(call otto.FunctionCall) otto.Value {
-		regex, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		userSortMode, err := call.Argument(1).ToString()
-		if err != nil || userSortMode == "" || userSortMode == "undefined" {
-			userSortMode = "default"
-		}
-
-		//Handle when regex = "." or "./" (listroot)
-		if filepath.ToSlash(filepath.Clean(regex)) == "/" || filepath.Clean(regex) == "." {
-			//List Root
-			rootDirs := []string{}
-			fileHandlers := u.GetAllFileSystemHandler()
-			for _, fsh := range fileHandlers {
-				if fsh.Hierarchy == "backup" {
-
-				} else {
-					rootDirs = append(rootDirs, fsh.UUID+":/")
-				}
-			}
-
-			reply, _ := vm.ToValue(rootDirs)
-			return reply
-		} else {
-			//Check for permission
-			if !u.CanRead(regex) {
-				panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-			}
-			//This function can only handle wildcard in filename but not in dir name
-			vrootPath := filepath.Dir(regex)
-			regexFilename := filepath.Base(regex)
-
-			//Rewrite and validate the sort mode
-			if userSortMode == "user" {
-				//Use user sorting mode.
-				if g.Option.UserHandler.GetDatabase().KeyExists("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath))) {
-					g.Option.UserHandler.GetDatabase().Read("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath)), &userSortMode)
-				} else {
-					userSortMode = "default"
-				}
-			}
-
-			if !fssort.SortModeIsSupported(userSortMode) {
-				log.Println("[AGI] Sort mode: " + userSortMode + " not supported. Using default")
-				userSortMode = "default"
-			}
-
-			//Translate the virtual path to realpath
-			fsh, rrootPath, err := virtualPathToRealPath(vrootPath, u)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			suitableFiles, err := fsh.FileSystemAbstraction.Glob(filepath.Join(rrootPath, regexFilename))
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			fileList := []string{}
-			fis := []fs.FileInfo{}
-			for _, thisFile := range suitableFiles {
-				fi, err := fsh.FileSystemAbstraction.Stat(thisFile)
-				if err == nil {
-					fileList = append(fileList, thisFile)
-					fis = append(fis, fi)
-				}
-			}
-
-			//Sort the files
-			newFilelist := fssort.SortFileList(fileList, fis, userSortMode)
-
-			//Return the results in virtual paths
-			results := []string{}
-			for _, file := range newFilelist {
-				isHidden, _ := hidden.IsHidden(file, true)
-				if isHidden {
-					//Hidden file. Skip this
-					continue
-				}
-				thisVpath, _ := realpathToVirtualpath(fsh, file, u)
-				results = append(results, thisVpath)
-			}
-			reply, _ := vm.ToValue(results)
-			return reply
-		}
-	})
-
-	//Advance Glob using file system special Glob, cannot use to scan root dirs
-	vm.Set("_filelib_aglob", func(call otto.FunctionCall) otto.Value {
-		regex, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		userSortMode, err := call.Argument(1).ToString()
-		if err != nil || userSortMode == "" || userSortMode == "undefined" {
-			userSortMode = "default"
-		}
-
-		if regex != "/" && !u.CanRead(regex) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		//This function can only handle wildcard in filename but not in dir name
-		vrootPath := filepath.Dir(regex)
-		regexFilename := filepath.Base(regex)
-
-		//Rewrite and validate the sort mode
-		if userSortMode == "user" {
-			//Use user sorting mode.
-			if g.Option.UserHandler.GetDatabase().KeyExists("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath))) {
-				g.Option.UserHandler.GetDatabase().Read("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath)), &userSortMode)
-			} else {
-				userSortMode = "default"
-			}
-		}
-
-		if !fssort.SortModeIsSupported(userSortMode) {
-			log.Println("[AGI] Sort mode: " + userSortMode + " not supported. Using default")
-			userSortMode = "default"
-		}
-
-		//Translate the virtual path to realpath
-		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vrootPath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		fshAbs := fsh.FileSystemAbstraction
-		rrootPath, _ := fshAbs.VirtualPathToRealPath(vrootPath, u.Username)
-		suitableFiles, err := fshAbs.Glob(filepath.Join(rrootPath, regexFilename))
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		fileList := []string{}
-		fis := []fs.FileInfo{}
-		for _, thisFile := range suitableFiles {
-			fi, err := fsh.FileSystemAbstraction.Stat(thisFile)
-			if err == nil {
-				fileList = append(fileList, thisFile)
-				fis = append(fis, fi)
-			}
-		}
-
-		//Sort the files
-		newFilelist := fssort.SortFileList(fileList, fis, userSortMode)
-
-		//Parse the results (Only extract the filepath)
-		results := []string{}
-		for _, filename := range newFilelist {
-			isHidden, _ := hidden.IsHidden(filename, true)
-			if isHidden {
-				//Hidden file. Skip this
-				continue
-			}
-			thisVpath, _ := realpathToVirtualpath(fsh, filename, u)
-			results = append(results, thisVpath)
-		}
-		reply, _ := vm.ToValue(results)
-		return reply
-	})
-
-	vm.Set("_filelib_readdir", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		userSortMode, err := call.Argument(1).ToString()
-		if err != nil || userSortMode == "" || userSortMode == "undefined" {
-			userSortMode = "default"
-		}
-
-		//Rewrite and validate the sort mode
-		if userSortMode == "user" {
-			//Use user sorting mode.
-			if g.Option.UserHandler.GetDatabase().KeyExists("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vpath))) {
-				g.Option.UserHandler.GetDatabase().Read("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vpath)), &userSortMode)
-			} else {
-				userSortMode = "default"
-			}
-		}
-
-		if !fssort.SortModeIsSupported(userSortMode) {
-			log.Println("[AGI] Sort mode: " + userSortMode + " not supported. Using default")
-			userSortMode = "default"
-		}
-
-		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		fshAbs := fsh.FileSystemAbstraction
-		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		dirEntry, err := fshAbs.ReadDir(rpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		type fileInfo struct {
-			Filename string
-			Filepath string
-			Ext      string
-			Filesize int64
-			Modtime  int64
-			IsDir    bool
-		}
-
-		//Sort the dirEntry by file info, a bit slow :(
-		if userSortMode != "default" {
-			//Prepare the data structure for sorting
-			newDirEntry := fssort.SortDirEntryList(dirEntry, userSortMode)
-			dirEntry = newDirEntry
-		}
-
-		results := []fileInfo{}
-		for _, de := range dirEntry {
-			isHidden, _ := hidden.IsHidden(de.Name(), false)
-			if isHidden {
-				continue
-			}
-			fstat, _ := de.Info()
-			vpath, _ := realpathToVirtualpath(fsh, filepath.ToSlash(filepath.Join(rpath, de.Name())), u)
-			thisInfo := fileInfo{
-				Filename: de.Name(),
-				Filepath: vpath,
-				Ext:      filepath.Ext(de.Name()),
-				Filesize: fstat.Size(),
-				Modtime:  fstat.ModTime().Unix(),
-				IsDir:    de.IsDir(),
-			}
-
-			results = append(results, thisInfo)
-		}
-
-		js, _ := json.Marshal(results)
-		r, _ := vm.ToValue(string(js))
-		return r
-	})
-
-	//filesize("user:/Desktop/test.txt")
-	vm.Set("_filelib_filesize", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		fshAbs := fsh.FileSystemAbstraction
-		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Get filesize of file
-		rawsize := fshAbs.GetFileSize(rpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		reply, _ := vm.ToValue(rawsize)
-		return reply
-	})
-
-	//fileExists("user:/Desktop/test.txt") => return true / false
-	vm.Set("_filelib_fileExists", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		fshAbs := fsh.FileSystemAbstraction
-		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		if fshAbs.FileExists(rpath) {
-			return otto.TrueValue()
-		} else {
-			return otto.FalseValue()
-		}
-	})
-
-	//fileExists("user:/Desktop/test.txt") => return true / false
-	vm.Set("_filelib_isDir", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
-		}
-
-		//Translate the virtual path to realpath
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		if _, err := fsh.FileSystemAbstraction.Stat(rpath); os.IsNotExist(err) {
-			//File not exists
-			panic(vm.MakeCustomError("File Not Exists", "Required path not exists"))
-		}
-
-		if fsh.FileSystemAbstraction.IsDir(rpath) {
-			return otto.TrueValue()
-		} else {
-			return otto.FalseValue()
-		}
-	})
-
-	//Make directory command
-	vm.Set("_filelib_mkdir", func(call otto.FunctionCall) otto.Value {
-		vdir, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanWrite(vdir) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		//Translate the path to realpath
-		fsh, rdir, err := virtualPathToRealPath(vdir, u)
-		if err != nil {
-			log.Println(err.Error())
-			return otto.FalseValue()
-		}
-
-		//Create the directory at rdir location
-		err = fsh.FileSystemAbstraction.MkdirAll(rdir, 0755)
-		if err != nil {
-			log.Println(err.Error())
-			return otto.FalseValue()
-		}
-
-		return otto.TrueValue()
-	})
-
-	//Get MD5 of the given filepath, not implemented
-	vm.Set("_filelib_md5", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		fshAbs := fsh.FileSystemAbstraction
-		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		f, err := fshAbs.ReadStream(rpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		defer f.Close()
-		h := md5.New()
-		if _, err := io.Copy(h, f); err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		md5Sum := hex.EncodeToString(h.Sum(nil))
-		result, _ := vm.ToValue(md5Sum)
-		return result
-	})
-
-	//Get the root name of the given virtual path root
-	vm.Set("_filelib_rname", func(call otto.FunctionCall) otto.Value {
-		//Get virtual path from the function input
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Get fs handler from the vpath
-		fsHandler, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Return the name of the fsHandler
-		name, _ := vm.ToValue(fsHandler.Name)
-		return name
-
-	})
-
-	vm.Set("_filelib_mtime", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		parseToUnix, err := call.Argument(1).ToBoolean()
-		if err != nil {
-			parseToUnix = false
-		}
-
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			log.Println(err.Error())
-			return otto.FalseValue()
-		}
-
-		info, err := fsh.FileSystemAbstraction.Stat(rpath)
-		if err != nil {
-			log.Println(err.Error())
-			return otto.FalseValue()
-		}
-
-		modTime := info.ModTime()
-		if parseToUnix {
-			result, _ := otto.ToValue(modTime.Unix())
-			return result
-		} else {
-			result, _ := otto.ToValue(modTime.Format("2006-01-02 15:04:05"))
-			return result
-		}
-	})
-
-	//ArozOS v2.0 New features
-	//Reading or writing from hex to target virtual filepath
-
-	//Write binary from hex string
-	vm.Set("_filelib_writeBinaryFile", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanWrite(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		hexContent, err := call.Argument(1).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Get the target vpath
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			log.Println(err.Error())
-			return otto.FalseValue()
-		}
-
-		//Decode the hex content to bytes
-		hexContentInByte, err := hex.DecodeString(hexContent)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Write the file to target file
-		err = fsh.FileSystemAbstraction.WriteFile(rpath, hexContentInByte, 0775)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		return otto.TrueValue()
-
-	})
-
-	//Read file from external fsh. Small file only
-	vm.Set("_filelib_readBinaryFile", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.NullValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		//Get the target vpath
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.NullValue()
-		}
-
-		if !fsh.FileSystemAbstraction.FileExists(rpath) {
-			//Check if the target file exists
-			g.raiseError(err)
-			return otto.NullValue()
-		}
-
-		content, err := fsh.FileSystemAbstraction.ReadFile(rpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.NullValue()
-		}
-
-		hexifiedContent := hex.EncodeToString(content)
-		val, _ := vm.ToValue(hexifiedContent)
-		return val
-
-	})
-
-	//Other file operations, wip
-
-	//Wrap all the native code function into an imagelib class
-	vm.Run(`
-		var filelib = {};
-		filelib.writeFile = _filelib_writeFile;
-		filelib.readFile = _filelib_readFile;
-		filelib.deleteFile = _filelib_deleteFile;
-		filelib.walk = _filelib_walk;
-		filelib.glob = _filelib_glob;
-		filelib.aglob = _filelib_aglob;
-		filelib.filesize = _filelib_filesize;
-		filelib.fileExists = _filelib_fileExists;
-		filelib.isDir = _filelib_isDir;
-		filelib.md5 = _filelib_md5;
-		filelib.mkdir = _filelib_mkdir;
-		filelib.mtime = _filelib_mtime;
-		filelib.rootName = _filelib_rname;
-
-		filelib.readdir = function(path, sortmode){
-			var s = _filelib_readdir(path, sortmode);
-			return JSON.parse(s);
-		};
-	`)
-}
+package agi
+
+import (
+	"crypto/md5"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"io"
+	"io/fs"
+	"log"
+	"os"
+	"path/filepath"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	"imuslab.com/arozos/mod/filesystem/fssort"
+	"imuslab.com/arozos/mod/filesystem/hidden"
+	user "imuslab.com/arozos/mod/user"
+)
+
+/*
+	AJGI File Processing Library
+
+	This is a library for handling image related functionalities in agi scripts.
+
+	By Alanyueng 2020 <- This person write shitty code that need me to tidy up (by tobychui)
+	Complete rewrite by tobychui in Sept 2020
+*/
+
+func (g *Gateway) FileLibRegister() {
+	err := g.RegisterLib("filelib", g.injectFileLibFunctions)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (g *Gateway) injectFileLibFunctions(vm *otto.Otto, u *user.User, scriptFsh *filesystem.FileSystemHandler, scriptPath string) {
+	//writeFile(virtualFilepath, content) => return true/false when succeed / failed
+	vm.Set("_filelib_writeFile", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanWrite(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
+		}
+
+		content, err := call.Argument(1).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Check if there is quota for the given length
+		if !u.StorageQuota.HaveSpace(int64(len(content))) {
+			//User have no remaining storage quota
+			g.raiseError(errors.New("Storage Quota Fulled"))
+			return otto.FalseValue()
+		}
+
+		//Translate the virtual path to realpath
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Check if file already exists.
+		if fsh.FileSystemAbstraction.FileExists(rpath) {
+			//Check if this user own this file
+			isOwner := u.IsOwnerOfFile(fsh, vpath)
+			if isOwner {
+				//This user own this system. Remove this file from his quota
+				u.RemoveOwnershipFromFile(fsh, vpath)
+			}
+		}
+
+		//Create and write to file using ioutil
+		err = fsh.FileSystemAbstraction.WriteFile(rpath, []byte(content), 0755)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Add the filesize to user quota
+		u.SetOwnerOfFile(fsh, vpath)
+
+		reply, _ := vm.ToValue(true)
+		return reply
+	})
+
+	vm.Set("_filelib_deleteFile", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanWrite(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
+		}
+
+		//Translate the virtual path to realpath
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Check if file already exists.
+		if fsh.FileSystemAbstraction.FileExists(rpath) {
+			//Check if this user own this file
+			isOwner := u.IsOwnerOfFile(fsh, vpath)
+			if isOwner {
+				//This user own this system. Remove this file from his quota
+				u.RemoveOwnershipFromFile(fsh, vpath)
+			}
+		} else {
+			g.raiseError(errors.New("File not exists"))
+			return otto.FalseValue()
+		}
+
+		//Remove the file
+		fsh.FileSystemAbstraction.Remove(rpath)
+
+		reply, _ := vm.ToValue(true)
+		return reply
+	})
+
+	//readFile(virtualFilepath) => return content in string
+	vm.Set("_filelib_readFile", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
+		}
+
+		//Translate the virtual path to realpath
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Create and write to file using ioUtil
+		content, err := fsh.FileSystemAbstraction.ReadFile(rpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		reply, _ := vm.ToValue(string(content))
+		return reply
+	})
+
+	//Listdir
+	//readdir("user:/Desktop") => return filelist in array
+	/*
+		vm.Set("_filelib_readdir", func(call otto.FunctionCall) otto.Value {
+			vpath, err := call.Argument(0).ToString()
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			//Translate the virtual path to realpath
+			fsh, rpath, err := virtualPathToRealPath(vpath, u)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+			fshAbs := fsh.FileSystemAbstraction
+
+			rpath = filepath.ToSlash(filepath.Clean(rpath)) + "/*"
+			fileList, err := fshAbs.Glob(rpath)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			//Translate all paths to virtual paths
+			results := []string{}
+			for _, file := range fileList {
+				isHidden, _ := hidden.IsHidden(file, true)
+				if !isHidden {
+					thisRpath, _ := fshAbs.RealPathToVirtualPath(file, u.Username)
+					results = append(results, thisRpath)
+				}
+			}
+
+			reply, _ := vm.ToValue(results)
+			return reply
+		})
+	*/
+
+	//Usage
+	//filelib.walk("user:/") => list everything recursively
+	//filelib.walk("user:/", "folder") => list all folder recursively
+	//filelib.walk("user:/", "file") => list all files recursively
+	vm.Set("_filelib_walk", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		mode, err := call.Argument(1).ToString()
+		if err != nil {
+			mode = "all"
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		results := []string{}
+		fsh.FileSystemAbstraction.Walk(rpath, func(path string, info os.FileInfo, err error) error {
+			if err != nil {
+				//Ignore this error file and continue
+				return nil
+			}
+			thisVpath, err := realpathToVirtualpath(fsh, path, u)
+			if err != nil {
+				return nil
+			}
+			if mode == "file" {
+				if !info.IsDir() {
+					results = append(results, thisVpath)
+				}
+			} else if mode == "folder" {
+				if info.IsDir() {
+					results = append(results, thisVpath)
+				}
+			} else {
+				results = append(results, thisVpath)
+			}
+
+			return nil
+		})
+
+		reply, _ := vm.ToValue(results)
+		return reply
+	})
+
+	//Glob
+	//glob("user:/Desktop/*.mp3") => return fileList in array
+	//glob("/") => return a list of root directories
+	//glob("user:/Desktop/*", "mostRecent") => return fileList in mostRecent sorting mode
+	//glob("user:/Desktop/*", "user") => return fileList in array in user prefered sorting method
+	vm.Set("_filelib_glob", func(call otto.FunctionCall) otto.Value {
+		regex, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		userSortMode, err := call.Argument(1).ToString()
+		if err != nil || userSortMode == "" || userSortMode == "undefined" {
+			userSortMode = "default"
+		}
+
+		//Handle when regex = "." or "./" (listroot)
+		if filepath.ToSlash(filepath.Clean(regex)) == "/" || filepath.Clean(regex) == "." {
+			//List Root
+			rootDirs := []string{}
+			fileHandlers := u.GetAllFileSystemHandler()
+			for _, fsh := range fileHandlers {
+				if fsh.Hierarchy == "backup" {
+
+				} else {
+					rootDirs = append(rootDirs, fsh.UUID+":/")
+				}
+			}
+
+			reply, _ := vm.ToValue(rootDirs)
+			return reply
+		} else {
+			//Check for permission
+			if !u.CanRead(regex) {
+				panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+			}
+			//This function can only handle wildcard in filename but not in dir name
+			vrootPath := filepath.Dir(regex)
+			regexFilename := filepath.Base(regex)
+
+			//Rewrite and validate the sort mode
+			if userSortMode == "user" {
+				//Use user sorting mode.
+				if g.Option.UserHandler.GetDatabase().KeyExists("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath))) {
+					g.Option.UserHandler.GetDatabase().Read("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath)), &userSortMode)
+				} else {
+					userSortMode = "default"
+				}
+			}
+
+			if !fssort.SortModeIsSupported(userSortMode) {
+				log.Println("[AGI] Sort mode: " + userSortMode + " not supported. Using default")
+				userSortMode = "default"
+			}
+
+			//Translate the virtual path to realpath
+			fsh, rrootPath, err := virtualPathToRealPath(vrootPath, u)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			suitableFiles, err := fsh.FileSystemAbstraction.Glob(filepath.Join(rrootPath, regexFilename))
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			fileList := []string{}
+			fis := []fs.FileInfo{}
+			for _, thisFile := range suitableFiles {
+				fi, err := fsh.FileSystemAbstraction.Stat(thisFile)
+				if err == nil {
+					fileList = append(fileList, thisFile)
+					fis = append(fis, fi)
+				}
+			}
+
+			//Sort the files
+			newFilelist := fssort.SortFileList(fileList, fis, userSortMode)
+
+			//Return the results in virtual paths
+			results := []string{}
+			for _, file := range newFilelist {
+				isHidden, _ := hidden.IsHidden(file, true)
+				if isHidden {
+					//Hidden file. Skip this
+					continue
+				}
+				thisVpath, _ := realpathToVirtualpath(fsh, file, u)
+				results = append(results, thisVpath)
+			}
+			reply, _ := vm.ToValue(results)
+			return reply
+		}
+	})
+
+	//Advance Glob using file system special Glob, cannot use to scan root dirs
+	vm.Set("_filelib_aglob", func(call otto.FunctionCall) otto.Value {
+		regex, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		userSortMode, err := call.Argument(1).ToString()
+		if err != nil || userSortMode == "" || userSortMode == "undefined" {
+			userSortMode = "default"
+		}
+
+		if regex != "/" && !u.CanRead(regex) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		//This function can only handle wildcard in filename but not in dir name
+		vrootPath := filepath.Dir(regex)
+		regexFilename := filepath.Base(regex)
+
+		//Rewrite and validate the sort mode
+		if userSortMode == "user" {
+			//Use user sorting mode.
+			if g.Option.UserHandler.GetDatabase().KeyExists("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath))) {
+				g.Option.UserHandler.GetDatabase().Read("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath)), &userSortMode)
+			} else {
+				userSortMode = "default"
+			}
+		}
+
+		if !fssort.SortModeIsSupported(userSortMode) {
+			log.Println("[AGI] Sort mode: " + userSortMode + " not supported. Using default")
+			userSortMode = "default"
+		}
+
+		//Translate the virtual path to realpath
+		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vrootPath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		fshAbs := fsh.FileSystemAbstraction
+		rrootPath, _ := fshAbs.VirtualPathToRealPath(vrootPath, u.Username)
+		suitableFiles, err := fshAbs.Glob(filepath.Join(rrootPath, regexFilename))
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		fileList := []string{}
+		fis := []fs.FileInfo{}
+		for _, thisFile := range suitableFiles {
+			fi, err := fsh.FileSystemAbstraction.Stat(thisFile)
+			if err == nil {
+				fileList = append(fileList, thisFile)
+				fis = append(fis, fi)
+			}
+		}
+
+		//Sort the files
+		newFilelist := fssort.SortFileList(fileList, fis, userSortMode)
+
+		//Parse the results (Only extract the filepath)
+		results := []string{}
+		for _, filename := range newFilelist {
+			isHidden, _ := hidden.IsHidden(filename, true)
+			if isHidden {
+				//Hidden file. Skip this
+				continue
+			}
+			thisVpath, _ := realpathToVirtualpath(fsh, filename, u)
+			results = append(results, thisVpath)
+		}
+		reply, _ := vm.ToValue(results)
+		return reply
+	})
+
+	vm.Set("_filelib_readdir", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		userSortMode, err := call.Argument(1).ToString()
+		if err != nil || userSortMode == "" || userSortMode == "undefined" {
+			userSortMode = "default"
+		}
+
+		//Rewrite and validate the sort mode
+		if userSortMode == "user" {
+			//Use user sorting mode.
+			if g.Option.UserHandler.GetDatabase().KeyExists("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vpath))) {
+				g.Option.UserHandler.GetDatabase().Read("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vpath)), &userSortMode)
+			} else {
+				userSortMode = "default"
+			}
+		}
+
+		if !fssort.SortModeIsSupported(userSortMode) {
+			log.Println("[AGI] Sort mode: " + userSortMode + " not supported. Using default")
+			userSortMode = "default"
+		}
+
+		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		fshAbs := fsh.FileSystemAbstraction
+		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		dirEntry, err := fshAbs.ReadDir(rpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		type fileInfo struct {
+			Filename string
+			Filepath string
+			Ext      string
+			Filesize int64
+			Modtime  int64
+			IsDir    bool
+		}
+
+		//Sort the dirEntry by file info, a bit slow :(
+		if userSortMode != "default" {
+			//Prepare the data structure for sorting
+			newDirEntry := fssort.SortDirEntryList(dirEntry, userSortMode)
+			dirEntry = newDirEntry
+		}
+
+		results := []fileInfo{}
+		for _, de := range dirEntry {
+			isHidden, _ := hidden.IsHidden(de.Name(), false)
+			if isHidden {
+				continue
+			}
+			fstat, _ := de.Info()
+			vpath, _ := realpathToVirtualpath(fsh, filepath.ToSlash(filepath.Join(rpath, de.Name())), u)
+			thisInfo := fileInfo{
+				Filename: de.Name(),
+				Filepath: vpath,
+				Ext:      filepath.Ext(de.Name()),
+				Filesize: fstat.Size(),
+				Modtime:  fstat.ModTime().Unix(),
+				IsDir:    de.IsDir(),
+			}
+
+			results = append(results, thisInfo)
+		}
+
+		js, _ := json.Marshal(results)
+		r, _ := vm.ToValue(string(js))
+		return r
+	})
+
+	//filesize("user:/Desktop/test.txt")
+	vm.Set("_filelib_filesize", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		fshAbs := fsh.FileSystemAbstraction
+		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Get filesize of file
+		rawsize := fshAbs.GetFileSize(rpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		reply, _ := vm.ToValue(rawsize)
+		return reply
+	})
+
+	//fileExists("user:/Desktop/test.txt") => return true / false
+	vm.Set("_filelib_fileExists", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		fshAbs := fsh.FileSystemAbstraction
+		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if fshAbs.FileExists(rpath) {
+			return otto.TrueValue()
+		} else {
+			return otto.FalseValue()
+		}
+	})
+
+	//fileExists("user:/Desktop/test.txt") => return true / false
+	vm.Set("_filelib_isDir", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
+		}
+
+		//Translate the virtual path to realpath
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if _, err := fsh.FileSystemAbstraction.Stat(rpath); os.IsNotExist(err) {
+			//File not exists
+			panic(vm.MakeCustomError("File Not Exists", "Required path not exists"))
+		}
+
+		if fsh.FileSystemAbstraction.IsDir(rpath) {
+			return otto.TrueValue()
+		} else {
+			return otto.FalseValue()
+		}
+	})
+
+	//Make directory command
+	vm.Set("_filelib_mkdir", func(call otto.FunctionCall) otto.Value {
+		vdir, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		//Check for permission
+		if !u.CanWrite(vdir) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		//Translate the path to realpath
+		fsh, rdir, err := virtualPathToRealPath(vdir, u)
+		if err != nil {
+			log.Println(err.Error())
+			return otto.FalseValue()
+		}
+
+		//Create the directory at rdir location
+		err = fsh.FileSystemAbstraction.MkdirAll(rdir, 0755)
+		if err != nil {
+			log.Println(err.Error())
+			return otto.FalseValue()
+		}
+
+		return otto.TrueValue()
+	})
+
+	//Get MD5 of the given filepath, not implemented
+	vm.Set("_filelib_md5", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		fshAbs := fsh.FileSystemAbstraction
+		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		f, err := fshAbs.ReadStream(rpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		defer f.Close()
+		h := md5.New()
+		if _, err := io.Copy(h, f); err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		md5Sum := hex.EncodeToString(h.Sum(nil))
+		result, _ := vm.ToValue(md5Sum)
+		return result
+	})
+
+	//Get the root name of the given virtual path root
+	vm.Set("_filelib_rname", func(call otto.FunctionCall) otto.Value {
+		//Get virtual path from the function input
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Get fs handler from the vpath
+		fsHandler, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Return the name of the fsHandler
+		name, _ := vm.ToValue(fsHandler.Name)
+		return name
+
+	})
+
+	vm.Set("_filelib_mtime", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		parseToUnix, err := call.Argument(1).ToBoolean()
+		if err != nil {
+			parseToUnix = false
+		}
+
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			log.Println(err.Error())
+			return otto.FalseValue()
+		}
+
+		info, err := fsh.FileSystemAbstraction.Stat(rpath)
+		if err != nil {
+			log.Println(err.Error())
+			return otto.FalseValue()
+		}
+
+		modTime := info.ModTime()
+		if parseToUnix {
+			result, _ := otto.ToValue(modTime.Unix())
+			return result
+		} else {
+			result, _ := otto.ToValue(modTime.Format("2006-01-02 15:04:05"))
+			return result
+		}
+	})
+
+	//ArozOS v2.0 New features
+	//Reading or writing from hex to target virtual filepath
+
+	//Write binary from hex string
+	vm.Set("_filelib_writeBinaryFile", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanWrite(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		hexContent, err := call.Argument(1).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Get the target vpath
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			log.Println(err.Error())
+			return otto.FalseValue()
+		}
+
+		//Decode the hex content to bytes
+		hexContentInByte, err := hex.DecodeString(hexContent)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Write the file to target file
+		err = fsh.FileSystemAbstraction.WriteFile(rpath, hexContentInByte, 0775)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		return otto.TrueValue()
+
+	})
+
+	//Read file from external fsh. Small file only
+	vm.Set("_filelib_readBinaryFile", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.NullValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		//Get the target vpath
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.NullValue()
+		}
+
+		if !fsh.FileSystemAbstraction.FileExists(rpath) {
+			//Check if the target file exists
+			g.raiseError(err)
+			return otto.NullValue()
+		}
+
+		content, err := fsh.FileSystemAbstraction.ReadFile(rpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.NullValue()
+		}
+
+		hexifiedContent := hex.EncodeToString(content)
+		val, _ := vm.ToValue(hexifiedContent)
+		return val
+
+	})
+
+	//Other file operations, wip
+
+	//Wrap all the native code function into an imagelib class
+	vm.Run(`
+		var filelib = {};
+		filelib.writeFile = _filelib_writeFile;
+		filelib.readFile = _filelib_readFile;
+		filelib.deleteFile = _filelib_deleteFile;
+		filelib.walk = _filelib_walk;
+		filelib.glob = _filelib_glob;
+		filelib.aglob = _filelib_aglob;
+		filelib.filesize = _filelib_filesize;
+		filelib.fileExists = _filelib_fileExists;
+		filelib.isDir = _filelib_isDir;
+		filelib.md5 = _filelib_md5;
+		filelib.mkdir = _filelib_mkdir;
+		filelib.mtime = _filelib_mtime;
+		filelib.rootName = _filelib_rname;
+
+		filelib.readdir = function(path, sortmode){
+			var s = _filelib_readdir(path, sortmode);
+			return JSON.parse(s);
+		};
+	`)
+}

+ 8 - 7
mod/agi/agi.go

@@ -33,14 +33,15 @@ import (
 */
 */
 
 
 var (
 var (
-	AgiVersion string = "2.1" //Defination of the agi runtime version. Update this when new function is added
+	AgiVersion string = "2.2" //Defination of the agi runtime version. Update this when new function is added
 
 
 	//AGI Internal Error Standard
 	//AGI Internal Error Standard
 	exitcall  = errors.New("Exit")
 	exitcall  = errors.New("Exit")
 	timelimit = errors.New("Timelimit")
 	timelimit = errors.New("Timelimit")
 )
 )
 
 
-type AgiLibIntergface func(*otto.Otto, *user.User) //Define the lib loader interface for AGI Libraries
+//Lib interface, require vm, user, target system file handler and the vpath of the running script
+type AgiLibIntergface func(*otto.Otto, *user.User, *filesystem.FileSystemHandler, string) //Define the lib loader interface for AGI Libraries
 type AgiPackage struct {
 type AgiPackage struct {
 	InitRoot string //The initialization of the root for the module that request this package
 	InitRoot string //The initialization of the root for the module that request this package
 }
 }
@@ -205,7 +206,7 @@ func (g *Gateway) APIHandler(w http.ResponseWriter, r *http.Request, thisuser *u
 		w.Write([]byte("400 - Bad Request (Missing script content)"))
 		w.Write([]byte("400 - Bad Request (Missing script content)"))
 		return
 		return
 	}
 	}
-	g.ExecuteAGIScript(scriptContent, "", "", w, r, thisuser)
+	g.ExecuteAGIScript(scriptContent, nil, "", "", w, r, thisuser)
 }
 }
 
 
 //Handle user requests
 //Handle user requests
@@ -269,7 +270,7 @@ func (g *Gateway) InterfaceHandler(w http.ResponseWriter, r *http.Request, thisu
 	scriptContentByte, _ := ioutil.ReadFile(scriptFile)
 	scriptContentByte, _ := ioutil.ReadFile(scriptFile)
 	scriptContent := string(scriptContentByte)
 	scriptContent := string(scriptContentByte)
 
 
-	g.ExecuteAGIScript(scriptContent, scriptFile, scriptScope, w, r, thisuser)
+	g.ExecuteAGIScript(scriptContent, nil, scriptFile, scriptScope, w, r, thisuser)
 }
 }
 
 
 /*
 /*
@@ -281,12 +282,12 @@ func (g *Gateway) InterfaceHandler(w http.ResponseWriter, r *http.Request, thisu
 	thisuser: userObject
 	thisuser: userObject
 
 
 */
 */
-func (g *Gateway) ExecuteAGIScript(scriptContent string, scriptFile string, scriptScope string, w http.ResponseWriter, r *http.Request, thisuser *user.User) {
+func (g *Gateway) ExecuteAGIScript(scriptContent string, fsh *filesystem.FileSystemHandler, scriptFile string, scriptScope string, w http.ResponseWriter, r *http.Request, thisuser *user.User) {
 	//Create a new vm for this request
 	//Create a new vm for this request
 	vm := otto.New()
 	vm := otto.New()
 	//Inject standard libs into the vm
 	//Inject standard libs into the vm
 	g.injectStandardLibs(vm, scriptFile, scriptScope)
 	g.injectStandardLibs(vm, scriptFile, scriptScope)
-	g.injectUserFunctions(vm, scriptFile, scriptScope, thisuser, w, r)
+	g.injectUserFunctions(vm, fsh, scriptFile, scriptScope, thisuser, w, r)
 
 
 	//Detect cotent type
 	//Detect cotent type
 	contentType := r.Header.Get("Content-type")
 	contentType := r.Header.Get("Content-type")
@@ -346,7 +347,7 @@ func (g *Gateway) ExecuteAGIScriptAsUser(fsh *filesystem.FileSystemHandler, scri
 	vm := otto.New()
 	vm := otto.New()
 	//Inject standard libs into the vm
 	//Inject standard libs into the vm
 	g.injectStandardLibs(vm, scriptFile, "")
 	g.injectStandardLibs(vm, scriptFile, "")
-	g.injectUserFunctions(vm, scriptFile, "", targetUser, nil, nil)
+	g.injectUserFunctions(vm, fsh, scriptFile, "", targetUser, nil, nil)
 
 
 	if r != nil {
 	if r != nil {
 		//Inject serverless script to enable access to GET / POST paramters
 		//Inject serverless script to enable access to GET / POST paramters

+ 263 - 262
mod/agi/agi.http.go

@@ -1,262 +1,263 @@
-package agi
-
-import (
-	"bytes"
-	"encoding/base64"
-	"encoding/json"
-	"errors"
-	"io/ioutil"
-	"log"
-	"net/http"
-	"net/url"
-	"path/filepath"
-
-	"github.com/robertkrimen/otto"
-	user "imuslab.com/arozos/mod/user"
-)
-
-/*
-	AJGI HTTP Request Library
-
-	This is a library for allowing AGI script to make HTTP Request from the VM
-	Returning either the head or the body of the request
-
-	Author: tobychui
-*/
-
-func (g *Gateway) HTTPLibRegister() {
-	err := g.RegisterLib("http", g.injectHTTPFunctions)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func (g *Gateway) injectHTTPFunctions(vm *otto.Otto, u *user.User) {
-	vm.Set("_http_get", func(call otto.FunctionCall) otto.Value {
-		//Get URL from function variable
-		url, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		//Get respond of the url
-		res, err := http.Get(url)
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		bodyContent, err := ioutil.ReadAll(res.Body)
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		returnValue, err := vm.ToValue(string(bodyContent))
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		return returnValue
-	})
-
-	vm.Set("_http_post", func(call otto.FunctionCall) otto.Value {
-		//Get URL from function paramter
-		url, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		//Get JSON content from 2nd paramter
-		sendWithPayload := true
-		jsonContent, err := call.Argument(1).ToString()
-		if err != nil {
-			//Disable the payload send
-			sendWithPayload = false
-		}
-
-		//Create the request
-		var req *http.Request
-		if sendWithPayload {
-			req, _ = http.NewRequest("POST", url, bytes.NewBuffer([]byte(jsonContent)))
-		} else {
-			req, _ = http.NewRequest("POST", url, bytes.NewBuffer([]byte("")))
-		}
-
-		req.Header.Set("Content-Type", "application/json")
-
-		//Send the request
-		client := &http.Client{}
-		resp, err := client.Do(req)
-		if err != nil {
-			log.Println(err)
-			return otto.NullValue()
-		}
-		defer resp.Body.Close()
-
-		bodyContent, err := ioutil.ReadAll(resp.Body)
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		returnValue, _ := vm.ToValue(string(bodyContent))
-
-		return returnValue
-	})
-
-	vm.Set("_http_head", func(call otto.FunctionCall) otto.Value {
-		//Get URL from function paramter
-		url, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		//Request the url
-		resp, err := http.Get(url)
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		headerKey, err := call.Argument(1).ToString()
-		if err != nil || headerKey == "undefined" {
-			//No headkey set. Return the whole header as JSON
-			js, _ := json.Marshal(resp.Header)
-			returnValue, _ := vm.ToValue(string(js))
-			return returnValue
-		} else {
-			//headerkey is set. Return if exists
-			possibleValue := resp.Header.Get(headerKey)
-			js, _ := json.Marshal(possibleValue)
-			returnValue, _ := vm.ToValue(string(js))
-			return returnValue
-		}
-
-	})
-
-	//Get target status code for response
-	vm.Set("_http_code", func(call otto.FunctionCall) otto.Value {
-		//Get URL from function paramter
-		url, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		req, err := http.NewRequest("GET", url, nil)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		payload := ""
-		client := new(http.Client)
-		client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
-			//Redirection. Return the target location as well
-			dest, _ := req.Response.Location()
-			payload = dest.String()
-			return errors.New("Redirect")
-		}
-
-		response, err := client.Do(req)
-		if err != nil {
-			return otto.FalseValue()
-		}
-		defer client.CloseIdleConnections()
-		vm.Run(`var _location = "` + payload + `";`)
-		value, _ := otto.ToValue(response.StatusCode)
-		return value
-
-	})
-
-	vm.Set("_http_download", func(call otto.FunctionCall) otto.Value {
-		//Get URL from function paramter
-		downloadURL, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-		decodedURL, _ := url.QueryUnescape(downloadURL)
-
-		//Get download desintation from paramter
-		vpath, err := call.Argument(1).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		//Optional: filename paramter
-		filename, err := call.Argument(2).ToString()
-		if err != nil || filename == "undefined" {
-			//Extract the filename from the url instead
-			filename = filepath.Base(decodedURL)
-		}
-
-		//Check user acess permission
-		if !u.CanWrite(vpath) {
-			g.raiseError(errors.New("Permission Denied"))
-			return otto.FalseValue()
-		}
-
-		//Convert the vpath to realpath. Check if it exists
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		if !fsh.FileSystemAbstraction.FileExists(rpath) || !fsh.FileSystemAbstraction.IsDir(rpath) {
-			g.raiseError(errors.New(vpath + " is a file not a directory."))
-			return otto.FalseValue()
-		}
-
-		downloadDest := filepath.Join(rpath, filename)
-
-		//Ok. Download the file
-		resp, err := http.Get(decodedURL)
-		if err != nil {
-			return otto.FalseValue()
-		}
-		defer resp.Body.Close()
-
-		// Create the file
-		err = fsh.FileSystemAbstraction.WriteStream(downloadDest, resp.Body, 0775)
-		if err != nil {
-			return otto.FalseValue()
-		}
-		return otto.TrueValue()
-	})
-
-	vm.Set("_http_getb64", func(call otto.FunctionCall) otto.Value {
-		//Get URL from function variable and return bytes as base64
-		url, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		//Get respond of the url
-		res, err := http.Get(url)
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		bodyContent, err := ioutil.ReadAll(res.Body)
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		sEnc := base64.StdEncoding.EncodeToString(bodyContent)
-
-		r, err := otto.ToValue(string(sEnc))
-		if err != nil {
-			log.Println(err.Error())
-			return otto.NullValue()
-		}
-		return r
-	})
-
-	//Wrap all the native code function into an imagelib class
-	vm.Run(`
-		var http = {};
-		http.get = _http_get;
-		http.post = _http_post;
-		http.head = _http_head;
-		http.download = _http_download;
-		http.getb64 = _http_getb64;
-		http.getCode = _http_code;
-	`)
-
-}
+package agi
+
+import (
+	"bytes"
+	"encoding/base64"
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"net/url"
+	"path/filepath"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	user "imuslab.com/arozos/mod/user"
+)
+
+/*
+	AJGI HTTP Request Library
+
+	This is a library for allowing AGI script to make HTTP Request from the VM
+	Returning either the head or the body of the request
+
+	Author: tobychui
+*/
+
+func (g *Gateway) HTTPLibRegister() {
+	err := g.RegisterLib("http", g.injectHTTPFunctions)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (g *Gateway) injectHTTPFunctions(vm *otto.Otto, u *user.User, scriptFsh *filesystem.FileSystemHandler, scriptPath string) {
+	vm.Set("_http_get", func(call otto.FunctionCall) otto.Value {
+		//Get URL from function variable
+		url, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		//Get respond of the url
+		res, err := http.Get(url)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		bodyContent, err := ioutil.ReadAll(res.Body)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		returnValue, err := vm.ToValue(string(bodyContent))
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		return returnValue
+	})
+
+	vm.Set("_http_post", func(call otto.FunctionCall) otto.Value {
+		//Get URL from function paramter
+		url, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		//Get JSON content from 2nd paramter
+		sendWithPayload := true
+		jsonContent, err := call.Argument(1).ToString()
+		if err != nil {
+			//Disable the payload send
+			sendWithPayload = false
+		}
+
+		//Create the request
+		var req *http.Request
+		if sendWithPayload {
+			req, _ = http.NewRequest("POST", url, bytes.NewBuffer([]byte(jsonContent)))
+		} else {
+			req, _ = http.NewRequest("POST", url, bytes.NewBuffer([]byte("")))
+		}
+
+		req.Header.Set("Content-Type", "application/json")
+
+		//Send the request
+		client := &http.Client{}
+		resp, err := client.Do(req)
+		if err != nil {
+			log.Println(err)
+			return otto.NullValue()
+		}
+		defer resp.Body.Close()
+
+		bodyContent, err := ioutil.ReadAll(resp.Body)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		returnValue, _ := vm.ToValue(string(bodyContent))
+
+		return returnValue
+	})
+
+	vm.Set("_http_head", func(call otto.FunctionCall) otto.Value {
+		//Get URL from function paramter
+		url, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		//Request the url
+		resp, err := http.Get(url)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		headerKey, err := call.Argument(1).ToString()
+		if err != nil || headerKey == "undefined" {
+			//No headkey set. Return the whole header as JSON
+			js, _ := json.Marshal(resp.Header)
+			returnValue, _ := vm.ToValue(string(js))
+			return returnValue
+		} else {
+			//headerkey is set. Return if exists
+			possibleValue := resp.Header.Get(headerKey)
+			js, _ := json.Marshal(possibleValue)
+			returnValue, _ := vm.ToValue(string(js))
+			return returnValue
+		}
+
+	})
+
+	//Get target status code for response
+	vm.Set("_http_code", func(call otto.FunctionCall) otto.Value {
+		//Get URL from function paramter
+		url, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		req, err := http.NewRequest("GET", url, nil)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		payload := ""
+		client := new(http.Client)
+		client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
+			//Redirection. Return the target location as well
+			dest, _ := req.Response.Location()
+			payload = dest.String()
+			return errors.New("Redirect")
+		}
+
+		response, err := client.Do(req)
+		if err != nil {
+			return otto.FalseValue()
+		}
+		defer client.CloseIdleConnections()
+		vm.Run(`var _location = "` + payload + `";`)
+		value, _ := otto.ToValue(response.StatusCode)
+		return value
+
+	})
+
+	vm.Set("_http_download", func(call otto.FunctionCall) otto.Value {
+		//Get URL from function paramter
+		downloadURL, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+		decodedURL, _ := url.QueryUnescape(downloadURL)
+
+		//Get download desintation from paramter
+		vpath, err := call.Argument(1).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		//Optional: filename paramter
+		filename, err := call.Argument(2).ToString()
+		if err != nil || filename == "undefined" {
+			//Extract the filename from the url instead
+			filename = filepath.Base(decodedURL)
+		}
+
+		//Check user acess permission
+		if !u.CanWrite(vpath) {
+			g.raiseError(errors.New("Permission Denied"))
+			return otto.FalseValue()
+		}
+
+		//Convert the vpath to realpath. Check if it exists
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		if !fsh.FileSystemAbstraction.FileExists(rpath) || !fsh.FileSystemAbstraction.IsDir(rpath) {
+			g.raiseError(errors.New(vpath + " is a file not a directory."))
+			return otto.FalseValue()
+		}
+
+		downloadDest := filepath.Join(rpath, filename)
+
+		//Ok. Download the file
+		resp, err := http.Get(decodedURL)
+		if err != nil {
+			return otto.FalseValue()
+		}
+		defer resp.Body.Close()
+
+		// Create the file
+		err = fsh.FileSystemAbstraction.WriteStream(downloadDest, resp.Body, 0775)
+		if err != nil {
+			return otto.FalseValue()
+		}
+		return otto.TrueValue()
+	})
+
+	vm.Set("_http_getb64", func(call otto.FunctionCall) otto.Value {
+		//Get URL from function variable and return bytes as base64
+		url, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		//Get respond of the url
+		res, err := http.Get(url)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		bodyContent, err := ioutil.ReadAll(res.Body)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		sEnc := base64.StdEncoding.EncodeToString(bodyContent)
+
+		r, err := otto.ToValue(string(sEnc))
+		if err != nil {
+			log.Println(err.Error())
+			return otto.NullValue()
+		}
+		return r
+	})
+
+	//Wrap all the native code function into an imagelib class
+	vm.Run(`
+		var http = {};
+		http.get = _http_get;
+		http.post = _http_post;
+		http.head = _http_head;
+		http.download = _http_download;
+		http.getb64 = _http_getb64;
+		http.getCode = _http_code;
+	`)
+
+}

+ 407 - 406
mod/agi/agi.image.go

@@ -1,406 +1,407 @@
-package agi
-
-import (
-	"bytes"
-	"errors"
-	"fmt"
-	"image"
-	"image/jpeg"
-	_ "image/jpeg"
-	"image/png"
-	_ "image/png"
-	"log"
-	"os"
-	"path/filepath"
-	"strings"
-
-	"github.com/disintegration/imaging"
-	"github.com/oliamb/cutter"
-	"github.com/robertkrimen/otto"
-
-	"imuslab.com/arozos/mod/neuralnet"
-	user "imuslab.com/arozos/mod/user"
-)
-
-/*
-	AJGI Image Processing Library
-
-	This is a library for handling image related functionalities in agi scripts.
-
-*/
-
-func (g *Gateway) ImageLibRegister() {
-	err := g.RegisterLib("imagelib", g.injectImageLibFunctions)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func (g *Gateway) injectImageLibFunctions(vm *otto.Otto, u *user.User) {
-	//Get image dimension, requires filepath (virtual)
-	vm.Set("_imagelib_getImageDimension", func(call otto.FunctionCall) otto.Value {
-		imageFileVpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		fsh, imagePath, err := virtualPathToRealPath(imageFileVpath, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		if !fsh.FileSystemAbstraction.FileExists(imagePath) {
-			g.raiseError(errors.New("File not exists! Given " + imagePath))
-			return otto.FalseValue()
-		}
-
-		openingPath := imagePath
-		var closerFunc func()
-		if fsh.RequireBuffer {
-			bufferPath, cf := g.getUserSpecificTempFilePath(u, imagePath)
-			closerFunc = cf
-			defer closerFunc()
-			c, err := fsh.FileSystemAbstraction.ReadFile(imagePath)
-			if err != nil {
-				g.raiseError(errors.New("Read from file system failed: " + err.Error()))
-				return otto.FalseValue()
-			}
-			os.WriteFile(bufferPath, c, 0775)
-			openingPath = bufferPath
-		}
-
-		file, err := os.Open(openingPath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		image, _, err := image.DecodeConfig(file)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		file.Close()
-		rawResults := []int{image.Width, image.Height}
-		result, _ := vm.ToValue(rawResults)
-		return result
-	})
-
-	//Resize image, require (filepath, outputpath, width, height)
-	vm.Set("_imagelib_resizeImage", func(call otto.FunctionCall) otto.Value {
-		vsrc, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		vdest, err := call.Argument(1).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		width, err := call.Argument(2).ToInteger()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		height, err := call.Argument(3).ToInteger()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Convert the virtual paths to real paths
-		srcfsh, rsrc, err := virtualPathToRealPath(vsrc, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		destfsh, rdest, err := virtualPathToRealPath(vdest, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		ext := strings.ToLower(filepath.Ext(rdest))
-		if !inArray([]string{".jpg", ".jpeg", ".png"}, ext) {
-			g.raiseError(errors.New("File extension not supported. Only support .jpg and .png"))
-			return otto.FalseValue()
-		}
-
-		if destfsh.FileSystemAbstraction.FileExists(rdest) {
-			err := destfsh.FileSystemAbstraction.Remove(rdest)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-		}
-
-		resizeOpeningFile := rsrc
-		resizeWritingFile := rdest
-		var srcCloser func()
-		var destCloser func()
-		if srcfsh.RequireBuffer {
-			resizeOpeningFile, srcCloser, err = g.bufferRemoteResourcesToLocal(srcfsh, u, rsrc)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-			defer srcCloser()
-		}
-
-		if destfsh.RequireBuffer {
-			resizeWritingFile, destCloser, err = g.bufferRemoteResourcesToLocal(destfsh, u, rdest)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-			defer destCloser()
-		}
-
-		//Resize the image
-		src, err := imaging.Open(resizeOpeningFile)
-		if err != nil {
-			//Opening failed
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		src = imaging.Resize(src, int(width), int(height), imaging.Lanczos)
-		err = imaging.Save(src, resizeWritingFile)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		if destfsh.RequireBuffer {
-			c, _ := os.ReadFile(resizeWritingFile)
-			destfsh.FileSystemAbstraction.WriteFile(rdest, c, 0775)
-		}
-
-		return otto.TrueValue()
-	})
-
-	//Crop the given image, require (input, output, posx, posy, width, height)
-	vm.Set("_imagelib_cropImage", func(call otto.FunctionCall) otto.Value {
-		vsrc, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		vdest, err := call.Argument(1).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		posx, err := call.Argument(2).ToInteger()
-		if err != nil {
-			posx = 0
-		}
-
-		posy, err := call.Argument(3).ToInteger()
-		if err != nil {
-			posy = 0
-		}
-
-		width, err := call.Argument(4).ToInteger()
-		if err != nil {
-			g.raiseError(errors.New("Image width not defined"))
-			return otto.FalseValue()
-		}
-
-		height, err := call.Argument(5).ToInteger()
-		if err != nil {
-			g.raiseError(errors.New("Image height not defined"))
-			return otto.FalseValue()
-		}
-
-		//Convert the virtual paths to realpaths
-
-		srcFsh, rsrc, err := virtualPathToRealPath(vsrc, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		srcFshAbs := srcFsh.FileSystemAbstraction
-		destFsh, rdest, err := virtualPathToRealPath(vdest, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		destWritePath := rdest
-		var destCloserFunction func()
-		if destFsh.RequireBuffer {
-			destWritePath, destCloserFunction = g.getUserSpecificTempFilePath(u, rdest)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-			defer destCloserFunction()
-		}
-
-		//Try to read the source image
-		imageBytes, err := srcFshAbs.ReadFile(rsrc)
-		if err != nil {
-			fmt.Println(err)
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		img, _, err := image.Decode(bytes.NewReader(imageBytes))
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Crop the image
-		croppedImg, _ := cutter.Crop(img, cutter.Config{
-			Width:  int(width),
-			Height: int(height),
-			Anchor: image.Point{int(posx), int(posy)},
-			Mode:   cutter.TopLeft,
-		})
-
-		//Create the new image
-		out, err := os.Create(destWritePath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		if strings.ToLower(filepath.Ext(destWritePath)) == ".png" {
-			png.Encode(out, croppedImg)
-		} else if strings.ToLower(filepath.Ext(destWritePath)) == ".jpg" {
-			jpeg.Encode(out, croppedImg, nil)
-		} else {
-			g.raiseError(errors.New("Not supported format: Only support jpg or png"))
-			return otto.FalseValue()
-		}
-		out.Close()
-
-		if destFsh.RequireBuffer {
-			c, _ := os.ReadFile(destWritePath)
-			err := destFsh.FileSystemAbstraction.WriteFile(rdest, c, 0775)
-			if err != nil {
-				fmt.Println(">", err.Error())
-			}
-		}
-
-		return otto.TrueValue()
-	})
-
-	//Get the given file's thumbnail in base64
-	vm.Set("_imagelib_loadThumbString", func(call otto.FunctionCall) otto.Value {
-		vsrc, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vsrc)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		rpath, _ := fsh.FileSystemAbstraction.VirtualPathToRealPath(vsrc, u.Username)
-
-		//Get the files' thumb base64 string
-		base64String, err := g.Option.FileSystemRender.LoadCache(fsh, rpath, false)
-		if err != nil {
-			return otto.FalseValue()
-		} else {
-			value, _ := vm.ToValue(base64String)
-			return value
-		}
-	})
-
-	vm.Set("_imagelib_classify", func(call otto.FunctionCall) otto.Value {
-		vsrc, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		classifier, err := call.Argument(1).ToString()
-		if err != nil {
-			classifier = "default"
-		}
-
-		if classifier == "" || classifier == "undefined" {
-			classifier = "default"
-		}
-
-		//Convert the vsrc to real path
-		fsh, rsrc, err := virtualPathToRealPath(vsrc, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		analysisSrc := rsrc
-		var closerFunc func()
-		if fsh.RequireBuffer {
-			analysisSrc, closerFunc, err = g.bufferRemoteResourcesToLocal(fsh, u, rsrc)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-			defer closerFunc()
-		}
-
-		if classifier == "default" || classifier == "darknet19" {
-			//Use darknet19 for classification
-			r, err := neuralnet.AnalysisPhotoDarknet19(analysisSrc)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			result, err := vm.ToValue(r)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			return result
-
-		} else if classifier == "yolo3" {
-			//Use yolo3 for classification, return positions of object as well
-			r, err := neuralnet.AnalysisPhotoYOLO3(analysisSrc)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			result, err := vm.ToValue(r)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			return result
-
-		} else {
-			//Unsupported classifier
-			log.Println("[AGI] Unsupported image classifier name: " + classifier)
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-	})
-
-	//Wrap all the native code function into an imagelib class
-	vm.Run(`
-		var imagelib = {};
-		imagelib.getImageDimension = _imagelib_getImageDimension;
-		imagelib.resizeImage = _imagelib_resizeImage;
-		imagelib.cropImage = _imagelib_cropImage;
-		imagelib.loadThumbString = _imagelib_loadThumbString;
-		imagelib.classify = _imagelib_classify;
-	`)
-}
+package agi
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"image"
+	"image/jpeg"
+	_ "image/jpeg"
+	"image/png"
+	_ "image/png"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/disintegration/imaging"
+	"github.com/oliamb/cutter"
+	"github.com/robertkrimen/otto"
+
+	"imuslab.com/arozos/mod/filesystem"
+	"imuslab.com/arozos/mod/neuralnet"
+	user "imuslab.com/arozos/mod/user"
+)
+
+/*
+	AJGI Image Processing Library
+
+	This is a library for handling image related functionalities in agi scripts.
+
+*/
+
+func (g *Gateway) ImageLibRegister() {
+	err := g.RegisterLib("imagelib", g.injectImageLibFunctions)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (g *Gateway) injectImageLibFunctions(vm *otto.Otto, u *user.User, scriptFsh *filesystem.FileSystemHandler, scriptPath string) {
+	//Get image dimension, requires filepath (virtual)
+	vm.Set("_imagelib_getImageDimension", func(call otto.FunctionCall) otto.Value {
+		imageFileVpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		fsh, imagePath, err := virtualPathToRealPath(imageFileVpath, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if !fsh.FileSystemAbstraction.FileExists(imagePath) {
+			g.raiseError(errors.New("File not exists! Given " + imagePath))
+			return otto.FalseValue()
+		}
+
+		openingPath := imagePath
+		var closerFunc func()
+		if fsh.RequireBuffer {
+			bufferPath, cf := g.getUserSpecificTempFilePath(u, imagePath)
+			closerFunc = cf
+			defer closerFunc()
+			c, err := fsh.FileSystemAbstraction.ReadFile(imagePath)
+			if err != nil {
+				g.raiseError(errors.New("Read from file system failed: " + err.Error()))
+				return otto.FalseValue()
+			}
+			os.WriteFile(bufferPath, c, 0775)
+			openingPath = bufferPath
+		}
+
+		file, err := os.Open(openingPath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		image, _, err := image.DecodeConfig(file)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		file.Close()
+		rawResults := []int{image.Width, image.Height}
+		result, _ := vm.ToValue(rawResults)
+		return result
+	})
+
+	//Resize image, require (filepath, outputpath, width, height)
+	vm.Set("_imagelib_resizeImage", func(call otto.FunctionCall) otto.Value {
+		vsrc, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		vdest, err := call.Argument(1).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		width, err := call.Argument(2).ToInteger()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		height, err := call.Argument(3).ToInteger()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Convert the virtual paths to real paths
+		srcfsh, rsrc, err := virtualPathToRealPath(vsrc, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		destfsh, rdest, err := virtualPathToRealPath(vdest, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		ext := strings.ToLower(filepath.Ext(rdest))
+		if !inArray([]string{".jpg", ".jpeg", ".png"}, ext) {
+			g.raiseError(errors.New("File extension not supported. Only support .jpg and .png"))
+			return otto.FalseValue()
+		}
+
+		if destfsh.FileSystemAbstraction.FileExists(rdest) {
+			err := destfsh.FileSystemAbstraction.Remove(rdest)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+		}
+
+		resizeOpeningFile := rsrc
+		resizeWritingFile := rdest
+		var srcCloser func()
+		var destCloser func()
+		if srcfsh.RequireBuffer {
+			resizeOpeningFile, srcCloser, err = g.bufferRemoteResourcesToLocal(srcfsh, u, rsrc)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+			defer srcCloser()
+		}
+
+		if destfsh.RequireBuffer {
+			resizeWritingFile, destCloser, err = g.bufferRemoteResourcesToLocal(destfsh, u, rdest)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+			defer destCloser()
+		}
+
+		//Resize the image
+		src, err := imaging.Open(resizeOpeningFile)
+		if err != nil {
+			//Opening failed
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		src = imaging.Resize(src, int(width), int(height), imaging.Lanczos)
+		err = imaging.Save(src, resizeWritingFile)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if destfsh.RequireBuffer {
+			c, _ := os.ReadFile(resizeWritingFile)
+			destfsh.FileSystemAbstraction.WriteFile(rdest, c, 0775)
+		}
+
+		return otto.TrueValue()
+	})
+
+	//Crop the given image, require (input, output, posx, posy, width, height)
+	vm.Set("_imagelib_cropImage", func(call otto.FunctionCall) otto.Value {
+		vsrc, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		vdest, err := call.Argument(1).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		posx, err := call.Argument(2).ToInteger()
+		if err != nil {
+			posx = 0
+		}
+
+		posy, err := call.Argument(3).ToInteger()
+		if err != nil {
+			posy = 0
+		}
+
+		width, err := call.Argument(4).ToInteger()
+		if err != nil {
+			g.raiseError(errors.New("Image width not defined"))
+			return otto.FalseValue()
+		}
+
+		height, err := call.Argument(5).ToInteger()
+		if err != nil {
+			g.raiseError(errors.New("Image height not defined"))
+			return otto.FalseValue()
+		}
+
+		//Convert the virtual paths to realpaths
+
+		srcFsh, rsrc, err := virtualPathToRealPath(vsrc, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		srcFshAbs := srcFsh.FileSystemAbstraction
+		destFsh, rdest, err := virtualPathToRealPath(vdest, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		destWritePath := rdest
+		var destCloserFunction func()
+		if destFsh.RequireBuffer {
+			destWritePath, destCloserFunction = g.getUserSpecificTempFilePath(u, rdest)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+			defer destCloserFunction()
+		}
+
+		//Try to read the source image
+		imageBytes, err := srcFshAbs.ReadFile(rsrc)
+		if err != nil {
+			fmt.Println(err)
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		img, _, err := image.Decode(bytes.NewReader(imageBytes))
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Crop the image
+		croppedImg, _ := cutter.Crop(img, cutter.Config{
+			Width:  int(width),
+			Height: int(height),
+			Anchor: image.Point{int(posx), int(posy)},
+			Mode:   cutter.TopLeft,
+		})
+
+		//Create the new image
+		out, err := os.Create(destWritePath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if strings.ToLower(filepath.Ext(destWritePath)) == ".png" {
+			png.Encode(out, croppedImg)
+		} else if strings.ToLower(filepath.Ext(destWritePath)) == ".jpg" {
+			jpeg.Encode(out, croppedImg, nil)
+		} else {
+			g.raiseError(errors.New("Not supported format: Only support jpg or png"))
+			return otto.FalseValue()
+		}
+		out.Close()
+
+		if destFsh.RequireBuffer {
+			c, _ := os.ReadFile(destWritePath)
+			err := destFsh.FileSystemAbstraction.WriteFile(rdest, c, 0775)
+			if err != nil {
+				fmt.Println(">", err.Error())
+			}
+		}
+
+		return otto.TrueValue()
+	})
+
+	//Get the given file's thumbnail in base64
+	vm.Set("_imagelib_loadThumbString", func(call otto.FunctionCall) otto.Value {
+		vsrc, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vsrc)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		rpath, _ := fsh.FileSystemAbstraction.VirtualPathToRealPath(vsrc, u.Username)
+
+		//Get the files' thumb base64 string
+		base64String, err := g.Option.FileSystemRender.LoadCache(fsh, rpath, false)
+		if err != nil {
+			return otto.FalseValue()
+		} else {
+			value, _ := vm.ToValue(base64String)
+			return value
+		}
+	})
+
+	vm.Set("_imagelib_classify", func(call otto.FunctionCall) otto.Value {
+		vsrc, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		classifier, err := call.Argument(1).ToString()
+		if err != nil {
+			classifier = "default"
+		}
+
+		if classifier == "" || classifier == "undefined" {
+			classifier = "default"
+		}
+
+		//Convert the vsrc to real path
+		fsh, rsrc, err := virtualPathToRealPath(vsrc, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		analysisSrc := rsrc
+		var closerFunc func()
+		if fsh.RequireBuffer {
+			analysisSrc, closerFunc, err = g.bufferRemoteResourcesToLocal(fsh, u, rsrc)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+			defer closerFunc()
+		}
+
+		if classifier == "default" || classifier == "darknet19" {
+			//Use darknet19 for classification
+			r, err := neuralnet.AnalysisPhotoDarknet19(analysisSrc)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			result, err := vm.ToValue(r)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			return result
+
+		} else if classifier == "yolo3" {
+			//Use yolo3 for classification, return positions of object as well
+			r, err := neuralnet.AnalysisPhotoYOLO3(analysisSrc)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			result, err := vm.ToValue(r)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			return result
+
+		} else {
+			//Unsupported classifier
+			log.Println("[AGI] Unsupported image classifier name: " + classifier)
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+	})
+
+	//Wrap all the native code function into an imagelib class
+	vm.Run(`
+		var imagelib = {};
+		imagelib.getImageDimension = _imagelib_getImageDimension;
+		imagelib.resizeImage = _imagelib_resizeImage;
+		imagelib.cropImage = _imagelib_cropImage;
+		imagelib.loadThumbString = _imagelib_loadThumbString;
+		imagelib.classify = _imagelib_classify;
+	`)
+}

+ 289 - 288
mod/agi/agi.iot.go

@@ -1,288 +1,289 @@
-package agi
-
-import (
-	"encoding/json"
-	"log"
-
-	"github.com/robertkrimen/otto"
-	"imuslab.com/arozos/mod/iot"
-	user "imuslab.com/arozos/mod/user"
-)
-
-/*
-	AGI IoT Control Protocols
-
-	This is a library for allowing AGI script to control / send commands to IoT devices
-	Use with caution and prepare to handle errors. IoT devices are not always online / connectabe.
-
-	Author: tobychui
-*/
-
-func (g *Gateway) IoTLibRegister() {
-	err := g.RegisterLib("iot", g.injectIoTFunctions)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func (g *Gateway) injectIoTFunctions(vm *otto.Otto, u *user.User) {
-	//Scan and return the latest iot device list
-	vm.Set("_iot_scan", func(call otto.FunctionCall) otto.Value {
-		scannedDevices := g.Option.IotManager.ScanDevices()
-		js, _ := json.Marshal(scannedDevices)
-		devList, err := vm.ToValue(string(js))
-		if err != nil {
-			return otto.FalseValue()
-		}
-		return devList
-	})
-
-	//List the current scanned device list from cache
-	vm.Set("_iot_list", func(call otto.FunctionCall) otto.Value {
-		devices := g.Option.IotManager.GetCachedDeviceList()
-		js, _ := json.Marshal(devices)
-		devList, err := vm.ToValue(string(js))
-		if err != nil {
-			return otto.FalseValue()
-		}
-		return devList
-	})
-
-	//Conenct an iot device. Return true if the device is connected or the device do not require connection before command exec
-	vm.Set("_iot_connect", func(call otto.FunctionCall) otto.Value {
-		//Get device ID from paratmer
-		devID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		//Get the auth info from paramters
-		username, err := call.Argument(1).ToString()
-		if err != nil {
-			username = ""
-		}
-
-		password, err := call.Argument(2).ToString()
-		if err != nil {
-			password = ""
-		}
-
-		token, err := call.Argument(3).ToString()
-		if err != nil {
-			token = ""
-		}
-
-		//Get device by id
-		dev := g.Option.IotManager.GetDeviceByID(devID)
-		if dev == nil {
-			//No device with that ID found
-			return otto.FalseValue()
-		}
-
-		if dev.RequireConnect == true {
-			//Build the auto info
-			autoInfo := iot.AuthInfo{
-				Username: username,
-				Password: password,
-				Token:    token,
-			}
-
-			//Connect the device
-			dev.Handler.Connect(dev, &autoInfo)
-		}
-
-		//Return true
-		return otto.TrueValue()
-	})
-
-	//Get the status of the given device
-	vm.Set("_iot_status", func(call otto.FunctionCall) otto.Value {
-		//Get device ID from paratmer
-		devID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		dev := g.Option.IotManager.GetDeviceByID(devID)
-
-		if dev == nil {
-			return otto.FalseValue()
-		}
-
-		//We have no idea what is the structure of the dev status.
-		//Just leave it to the front end to handle :P
-		devStatus, err := dev.Handler.Status(dev)
-		if err != nil {
-			log.Println("*AGI IoT* " + err.Error())
-			return otto.FalseValue()
-		}
-
-		js, _ := json.Marshal(devStatus)
-		results, _ := vm.ToValue(string(js))
-		return results
-	})
-
-	vm.Set("_iot_exec", func(call otto.FunctionCall) otto.Value {
-		//Get device ID from paratmer
-		devID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		//Get endpoint name
-		epname, err := call.Argument(1).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		//Get payload if any
-		payload, err := call.Argument(2).ToString()
-		if err != nil {
-			payload = ""
-		}
-
-		//Get device by id
-		dev := g.Option.IotManager.GetDeviceByID(devID)
-		if dev == nil {
-			//Device not found
-			log.Println("*AGI IoT* Given device ID do not match any IoT devices")
-			return otto.FalseValue()
-		}
-
-		//Get the endpoint from name
-		var targetEp *iot.Endpoint
-		for _, ep := range dev.ControlEndpoints {
-			if ep.Name == epname {
-				//This is the target endpoint
-				thisEp := ep
-				targetEp = thisEp
-			}
-
-		}
-
-		if targetEp == nil {
-			//Endpoint not found
-			log.Println("*AGI IoT* Failed to get endpoint by name in this device")
-			return otto.FalseValue()
-		}
-
-		var results interface{}
-
-		//Try to convert it into a string map
-		if payload != "" {
-			payloadMap := map[string]interface{}{}
-
-			err = json.Unmarshal([]byte(payload), &payloadMap)
-			if err != nil {
-				log.Println("*AGI IoT* Failed to parse input payload: " + err.Error())
-				return otto.FalseValue()
-			}
-
-			//Execute the request
-			results, err = dev.Handler.Execute(dev, targetEp, payloadMap)
-
-		} else {
-			//Execute the request without payload
-			results, err = dev.Handler.Execute(dev, targetEp, nil)
-		}
-
-		if err != nil {
-			log.Println("*AGI IoT* Failed to execute request to device: " + err.Error())
-			return otto.FalseValue()
-		}
-
-		js, _ := json.Marshal(results)
-		reply, _ := vm.ToValue(string(js))
-		return reply
-	})
-
-	//Disconnect a given iot device using the device UUID
-	vm.Set("_iot_disconnect", func(call otto.FunctionCall) otto.Value {
-		//Get device ID from paratmer
-		devID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		dev := g.Option.IotManager.GetDeviceByID(devID)
-
-		if dev == nil {
-			return otto.FalseValue()
-		}
-
-		if dev.RequireConnect == true {
-			err = dev.Handler.Disconnect(dev)
-			if err != nil {
-				return otto.FalseValue()
-			}
-		}
-
-		return otto.TrueValue()
-	})
-
-	//Return the icon tag for this device
-	vm.Set("_iot_iconTag", func(call otto.FunctionCall) otto.Value {
-		//Get device ID from paratmer
-		devID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		dev := g.Option.IotManager.GetDeviceByID(devID)
-		if dev == nil {
-			//device not found
-			return otto.NullValue()
-		}
-
-		deviceIconTag := dev.Handler.Icon(dev)
-		it, _ := vm.ToValue(deviceIconTag)
-
-		return it
-	})
-
-	vm.Set("_iot_ready", func(call otto.FunctionCall) otto.Value {
-		if g.Option.IotManager == nil {
-			return otto.FalseValue()
-		} else {
-			return otto.TrueValue()
-		}
-	})
-
-	//Wrap all the native code function into an imagelib class
-	_, err := vm.Run(`
-		var iot = {
-			"scan": function(){
-				var devList = _iot_scan();
-				return JSON.parse(devList);
-			},
-			"list": function(){
-				var devList = _iot_list();
-				return JSON.parse(devList);
-			},
-			"status": function(devid){
-				var devStatus = _iot_status(devid);
-				return JSON.parse(devStatus);
-			},
-			"exec": function(devid, epname, payload){
-				payload = payload || "";
-				payload = JSON.stringify(payload);
-				var resp = _iot_exec(devid, epname, payload);
-				if (resp == false){
-					return false;
-				}else{
-					return JSON.parse(resp);
-				}
-			}
-		};
-
-		iot.ready = _iot_ready;
-		iot.connect = _iot_connect;
-		iot.disconnect = _iot_disconnect;
-		iot.iconTag = _iot_iconTag;
-		
-	`)
-
-	if err != nil {
-		log.Println("*AGI* IoT Functions Injection Error", err.Error())
-	}
-}
+package agi
+
+import (
+	"encoding/json"
+	"log"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	"imuslab.com/arozos/mod/iot"
+	user "imuslab.com/arozos/mod/user"
+)
+
+/*
+	AGI IoT Control Protocols
+
+	This is a library for allowing AGI script to control / send commands to IoT devices
+	Use with caution and prepare to handle errors. IoT devices are not always online / connectabe.
+
+	Author: tobychui
+*/
+
+func (g *Gateway) IoTLibRegister() {
+	err := g.RegisterLib("iot", g.injectIoTFunctions)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (g *Gateway) injectIoTFunctions(vm *otto.Otto, u *user.User, scriptFsh *filesystem.FileSystemHandler, scriptPath string) {
+	//Scan and return the latest iot device list
+	vm.Set("_iot_scan", func(call otto.FunctionCall) otto.Value {
+		scannedDevices := g.Option.IotManager.ScanDevices()
+		js, _ := json.Marshal(scannedDevices)
+		devList, err := vm.ToValue(string(js))
+		if err != nil {
+			return otto.FalseValue()
+		}
+		return devList
+	})
+
+	//List the current scanned device list from cache
+	vm.Set("_iot_list", func(call otto.FunctionCall) otto.Value {
+		devices := g.Option.IotManager.GetCachedDeviceList()
+		js, _ := json.Marshal(devices)
+		devList, err := vm.ToValue(string(js))
+		if err != nil {
+			return otto.FalseValue()
+		}
+		return devList
+	})
+
+	//Conenct an iot device. Return true if the device is connected or the device do not require connection before command exec
+	vm.Set("_iot_connect", func(call otto.FunctionCall) otto.Value {
+		//Get device ID from paratmer
+		devID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		//Get the auth info from paramters
+		username, err := call.Argument(1).ToString()
+		if err != nil {
+			username = ""
+		}
+
+		password, err := call.Argument(2).ToString()
+		if err != nil {
+			password = ""
+		}
+
+		token, err := call.Argument(3).ToString()
+		if err != nil {
+			token = ""
+		}
+
+		//Get device by id
+		dev := g.Option.IotManager.GetDeviceByID(devID)
+		if dev == nil {
+			//No device with that ID found
+			return otto.FalseValue()
+		}
+
+		if dev.RequireConnect == true {
+			//Build the auto info
+			autoInfo := iot.AuthInfo{
+				Username: username,
+				Password: password,
+				Token:    token,
+			}
+
+			//Connect the device
+			dev.Handler.Connect(dev, &autoInfo)
+		}
+
+		//Return true
+		return otto.TrueValue()
+	})
+
+	//Get the status of the given device
+	vm.Set("_iot_status", func(call otto.FunctionCall) otto.Value {
+		//Get device ID from paratmer
+		devID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		dev := g.Option.IotManager.GetDeviceByID(devID)
+
+		if dev == nil {
+			return otto.FalseValue()
+		}
+
+		//We have no idea what is the structure of the dev status.
+		//Just leave it to the front end to handle :P
+		devStatus, err := dev.Handler.Status(dev)
+		if err != nil {
+			log.Println("*AGI IoT* " + err.Error())
+			return otto.FalseValue()
+		}
+
+		js, _ := json.Marshal(devStatus)
+		results, _ := vm.ToValue(string(js))
+		return results
+	})
+
+	vm.Set("_iot_exec", func(call otto.FunctionCall) otto.Value {
+		//Get device ID from paratmer
+		devID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		//Get endpoint name
+		epname, err := call.Argument(1).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		//Get payload if any
+		payload, err := call.Argument(2).ToString()
+		if err != nil {
+			payload = ""
+		}
+
+		//Get device by id
+		dev := g.Option.IotManager.GetDeviceByID(devID)
+		if dev == nil {
+			//Device not found
+			log.Println("*AGI IoT* Given device ID do not match any IoT devices")
+			return otto.FalseValue()
+		}
+
+		//Get the endpoint from name
+		var targetEp *iot.Endpoint
+		for _, ep := range dev.ControlEndpoints {
+			if ep.Name == epname {
+				//This is the target endpoint
+				thisEp := ep
+				targetEp = thisEp
+			}
+
+		}
+
+		if targetEp == nil {
+			//Endpoint not found
+			log.Println("*AGI IoT* Failed to get endpoint by name in this device")
+			return otto.FalseValue()
+		}
+
+		var results interface{}
+
+		//Try to convert it into a string map
+		if payload != "" {
+			payloadMap := map[string]interface{}{}
+
+			err = json.Unmarshal([]byte(payload), &payloadMap)
+			if err != nil {
+				log.Println("*AGI IoT* Failed to parse input payload: " + err.Error())
+				return otto.FalseValue()
+			}
+
+			//Execute the request
+			results, err = dev.Handler.Execute(dev, targetEp, payloadMap)
+
+		} else {
+			//Execute the request without payload
+			results, err = dev.Handler.Execute(dev, targetEp, nil)
+		}
+
+		if err != nil {
+			log.Println("*AGI IoT* Failed to execute request to device: " + err.Error())
+			return otto.FalseValue()
+		}
+
+		js, _ := json.Marshal(results)
+		reply, _ := vm.ToValue(string(js))
+		return reply
+	})
+
+	//Disconnect a given iot device using the device UUID
+	vm.Set("_iot_disconnect", func(call otto.FunctionCall) otto.Value {
+		//Get device ID from paratmer
+		devID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		dev := g.Option.IotManager.GetDeviceByID(devID)
+
+		if dev == nil {
+			return otto.FalseValue()
+		}
+
+		if dev.RequireConnect == true {
+			err = dev.Handler.Disconnect(dev)
+			if err != nil {
+				return otto.FalseValue()
+			}
+		}
+
+		return otto.TrueValue()
+	})
+
+	//Return the icon tag for this device
+	vm.Set("_iot_iconTag", func(call otto.FunctionCall) otto.Value {
+		//Get device ID from paratmer
+		devID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		dev := g.Option.IotManager.GetDeviceByID(devID)
+		if dev == nil {
+			//device not found
+			return otto.NullValue()
+		}
+
+		deviceIconTag := dev.Handler.Icon(dev)
+		it, _ := vm.ToValue(deviceIconTag)
+
+		return it
+	})
+
+	vm.Set("_iot_ready", func(call otto.FunctionCall) otto.Value {
+		if g.Option.IotManager == nil {
+			return otto.FalseValue()
+		} else {
+			return otto.TrueValue()
+		}
+	})
+
+	//Wrap all the native code function into an imagelib class
+	_, err := vm.Run(`
+		var iot = {
+			"scan": function(){
+				var devList = _iot_scan();
+				return JSON.parse(devList);
+			},
+			"list": function(){
+				var devList = _iot_list();
+				return JSON.parse(devList);
+			},
+			"status": function(devid){
+				var devStatus = _iot_status(devid);
+				return JSON.parse(devStatus);
+			},
+			"exec": function(devid, epname, payload){
+				payload = payload || "";
+				payload = JSON.stringify(payload);
+				var resp = _iot_exec(devid, epname, payload);
+				if (resp == false){
+					return false;
+				}else{
+					return JSON.parse(resp);
+				}
+			}
+		};
+
+		iot.ready = _iot_ready;
+		iot.connect = _iot_connect;
+		iot.disconnect = _iot_disconnect;
+		iot.iconTag = _iot_iconTag;
+		
+	`)
+
+	if err != nil {
+		log.Println("*AGI* IoT Functions Injection Error", err.Error())
+	}
+}

+ 132 - 131
mod/agi/agi.share.go

@@ -1,131 +1,132 @@
-package agi
-
-import (
-	"log"
-	"time"
-
-	"github.com/robertkrimen/otto"
-	user "imuslab.com/arozos/mod/user"
-)
-
-func (g *Gateway) ShareLibRegister() {
-	err := g.RegisterLib("share", g.injectShareFunctions)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func (g *Gateway) injectShareFunctions(vm *otto.Otto, u *user.User) {
-	vm.Set("_share_file", func(call otto.FunctionCall) otto.Value {
-		//Get the vpath of file to share
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.New().MakeCustomError("Unable to decode filepath", "No given filepath for sharing")
-		}
-
-		//Get the timeout from the 2nd parameter for how long this share will exists
-		timeout, err := call.Argument(1).ToInteger()
-		if err != nil {
-			//Not defined -> Do not expire
-			timeout = 0
-		}
-
-		//Create a share object for this request
-		vpathSourceFsh := u.GetRootFSHFromVpathInUserScope(vpath)
-		shareID, err := g.Option.ShareManager.CreateNewShare(u, vpathSourceFsh, vpath)
-		if err != nil {
-			log.Println("[AGI] Create Share Failed: " + err.Error())
-			return otto.New().MakeCustomError("Share failed", err.Error())
-		}
-
-		if timeout > 0 {
-			go func(timeout int) {
-				time.Sleep(time.Duration(timeout) * time.Second)
-				g.Option.ShareManager.RemoveShareByUUID(u, shareID.UUID)
-				log.Println("[AGI] Share auto-removed: " + shareID.UUID)
-			}(int(timeout))
-		}
-
-		r, _ := otto.ToValue(shareID.UUID)
-		return r
-	})
-
-	vm.Set("_share_removeShare", func(call otto.FunctionCall) otto.Value {
-		shareUUID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.New().MakeCustomError("Failed to remove share", "No share UUID given")
-		}
-		err = g.Option.ShareManager.RemoveShareByUUID(u, shareUUID)
-		if err != nil {
-			log.Println("[AGI] Share remove failed: " + err.Error())
-			return otto.New().MakeCustomError("Failed to remove share", err.Error())
-		}
-
-		return otto.TrueValue()
-	})
-
-	vm.Set("_share_getShareUUID", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			log.Println("[AGI] Failed to get share UUID: filepath not given")
-			return otto.NullValue()
-		}
-
-		shareObject := g.Option.ShareManager.GetShareObjectFromUserAndVpath(u, vpath)
-		if shareObject == nil {
-			log.Println("[AGI] Failed to get share UUID: File not shared")
-			return otto.NullValue()
-		}
-
-		shareUUID := shareObject.UUID
-		val, _ := otto.ToValue(shareUUID)
-		return val
-	})
-
-	vm.Set("_share_checkShareExists", func(call otto.FunctionCall) otto.Value {
-		shareUUID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.New().MakeCustomError("Failed to check share exists", "No share UUID given")
-		}
-
-		shareObject := g.Option.ShareManager.GetShareObjectFromUUID(shareUUID)
-		r, _ := otto.ToValue(!(shareObject == nil))
-		return r
-	})
-
-	vm.Set("_share_checkSharePermission", func(call otto.FunctionCall) otto.Value {
-		shareUUID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.New().MakeCustomError("Failed to check share permission", "No share UUID given")
-		}
-
-		shareObject := g.Option.ShareManager.GetShareObjectFromUUID(shareUUID)
-		if shareObject == nil {
-			return otto.NullValue()
-		}
-		r, _ := otto.ToValue(shareObject.Permission)
-		return r
-	})
-
-	vm.Set("_share_fileIsShared", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.New().MakeCustomError("Failed to check share exists", "No filepath given")
-		}
-
-		isShared := g.Option.ShareManager.FileIsShared(u, vpath)
-		r, _ := otto.ToValue(isShared)
-		return r
-	})
-
-	//Wrap all the native code function into an imagelib class
-	vm.Run(`
-		var share = {};
-		share.shareFile = _share_file;
-		share.removeShare = _share_removeShare;
-		share.checkShareExists = _share_checkShareExists;
-		share.fileIsShared = _share_fileIsShared;
-		share.getFileShareUUID = _share_getShareUUID;
-		share.checkSharePermission = _share_checkSharePermission;
-	`)
-}
+package agi
+
+import (
+	"log"
+	"time"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	user "imuslab.com/arozos/mod/user"
+)
+
+func (g *Gateway) ShareLibRegister() {
+	err := g.RegisterLib("share", g.injectShareFunctions)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (g *Gateway) injectShareFunctions(vm *otto.Otto, u *user.User, scriptFsh *filesystem.FileSystemHandler, scriptPath string) {
+	vm.Set("_share_file", func(call otto.FunctionCall) otto.Value {
+		//Get the vpath of file to share
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.New().MakeCustomError("Unable to decode filepath", "No given filepath for sharing")
+		}
+
+		//Get the timeout from the 2nd parameter for how long this share will exists
+		timeout, err := call.Argument(1).ToInteger()
+		if err != nil {
+			//Not defined -> Do not expire
+			timeout = 0
+		}
+
+		//Create a share object for this request
+		vpathSourceFsh := u.GetRootFSHFromVpathInUserScope(vpath)
+		shareID, err := g.Option.ShareManager.CreateNewShare(u, vpathSourceFsh, vpath)
+		if err != nil {
+			log.Println("[AGI] Create Share Failed: " + err.Error())
+			return otto.New().MakeCustomError("Share failed", err.Error())
+		}
+
+		if timeout > 0 {
+			go func(timeout int) {
+				time.Sleep(time.Duration(timeout) * time.Second)
+				g.Option.ShareManager.RemoveShareByUUID(u, shareID.UUID)
+				log.Println("[AGI] Share auto-removed: " + shareID.UUID)
+			}(int(timeout))
+		}
+
+		r, _ := otto.ToValue(shareID.UUID)
+		return r
+	})
+
+	vm.Set("_share_removeShare", func(call otto.FunctionCall) otto.Value {
+		shareUUID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.New().MakeCustomError("Failed to remove share", "No share UUID given")
+		}
+		err = g.Option.ShareManager.RemoveShareByUUID(u, shareUUID)
+		if err != nil {
+			log.Println("[AGI] Share remove failed: " + err.Error())
+			return otto.New().MakeCustomError("Failed to remove share", err.Error())
+		}
+
+		return otto.TrueValue()
+	})
+
+	vm.Set("_share_getShareUUID", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			log.Println("[AGI] Failed to get share UUID: filepath not given")
+			return otto.NullValue()
+		}
+
+		shareObject := g.Option.ShareManager.GetShareObjectFromUserAndVpath(u, vpath)
+		if shareObject == nil {
+			log.Println("[AGI] Failed to get share UUID: File not shared")
+			return otto.NullValue()
+		}
+
+		shareUUID := shareObject.UUID
+		val, _ := otto.ToValue(shareUUID)
+		return val
+	})
+
+	vm.Set("_share_checkShareExists", func(call otto.FunctionCall) otto.Value {
+		shareUUID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.New().MakeCustomError("Failed to check share exists", "No share UUID given")
+		}
+
+		shareObject := g.Option.ShareManager.GetShareObjectFromUUID(shareUUID)
+		r, _ := otto.ToValue(!(shareObject == nil))
+		return r
+	})
+
+	vm.Set("_share_checkSharePermission", func(call otto.FunctionCall) otto.Value {
+		shareUUID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.New().MakeCustomError("Failed to check share permission", "No share UUID given")
+		}
+
+		shareObject := g.Option.ShareManager.GetShareObjectFromUUID(shareUUID)
+		if shareObject == nil {
+			return otto.NullValue()
+		}
+		r, _ := otto.ToValue(shareObject.Permission)
+		return r
+	})
+
+	vm.Set("_share_fileIsShared", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.New().MakeCustomError("Failed to check share exists", "No filepath given")
+		}
+
+		isShared := g.Option.ShareManager.FileIsShared(u, vpath)
+		r, _ := otto.ToValue(isShared)
+		return r
+	})
+
+	//Wrap all the native code function into an imagelib class
+	vm.Run(`
+		var share = {};
+		share.shareFile = _share_file;
+		share.removeShare = _share_removeShare;
+		share.checkShareExists = _share_checkShareExists;
+		share.fileIsShared = _share_fileIsShared;
+		share.getFileShareUUID = _share_getShareUUID;
+		share.checkSharePermission = _share_checkSharePermission;
+	`)
+}

+ 61 - 61
mod/agi/handler.go

@@ -1,61 +1,61 @@
-package agi
-
-import (
-	"io/ioutil"
-	"net/http"
-	"path/filepath"
-)
-
-//Handle AGI Exectuion Request with token, design for letting other web scripting language like php to interface with AGI
-func (g *Gateway) HandleAgiExecutionRequestWithToken(w http.ResponseWriter, r *http.Request) {
-	token, err := mv(r, "token", false)
-	if err != nil {
-		//Username not defined
-		sendErrorResponse(w, "Token not defined or empty.")
-		return
-	}
-
-	script, err := mv(r, "script", false)
-	if err != nil {
-		//Username not defined
-		sendErrorResponse(w, "Script path not defined or empty.")
-		return
-	}
-
-	//Try to get the username from token
-	username, err := g.Option.UserHandler.GetAuthAgent().GetUsernameFromToken(token)
-	if err != nil {
-		//This token is not valid
-		w.WriteHeader(http.StatusUnauthorized)
-		w.Write([]byte("401 - Unauthorized (Token not valid)"))
-		return
-	}
-
-	//Check if user exists and have access to the script
-	targetUser, err := g.Option.UserHandler.GetUserInfoFromUsername(username)
-	if err != nil {
-		//This user not exists
-		w.WriteHeader(http.StatusUnauthorized)
-		w.Write([]byte("401 - Unauthorized (User not exists)"))
-		return
-	}
-
-	scriptScope := ""
-	allowAccess := checkUserAccessToScript(targetUser, script, scriptScope)
-	if !allowAccess {
-		w.WriteHeader(http.StatusUnauthorized)
-		w.Write([]byte("401 - Unauthorized (Permission Denied)"))
-		return
-	}
-
-	//Get the content of the script
-	scriptContentByte, err := ioutil.ReadFile(filepath.Join("./web/", script))
-	if err != nil {
-		w.WriteHeader(http.StatusNotFound)
-		w.Write([]byte("404 - Script Not Found"))
-		return
-	}
-	scriptContent := string(scriptContentByte)
-
-	g.ExecuteAGIScript(scriptContent, script, scriptScope, w, r, targetUser)
-}
+package agi
+
+import (
+	"io/ioutil"
+	"net/http"
+	"path/filepath"
+)
+
+//Handle AGI Exectuion Request with token, design for letting other web scripting language like php to interface with AGI
+func (g *Gateway) HandleAgiExecutionRequestWithToken(w http.ResponseWriter, r *http.Request) {
+	token, err := mv(r, "token", false)
+	if err != nil {
+		//Username not defined
+		sendErrorResponse(w, "Token not defined or empty.")
+		return
+	}
+
+	script, err := mv(r, "script", false)
+	if err != nil {
+		//Username not defined
+		sendErrorResponse(w, "Script path not defined or empty.")
+		return
+	}
+
+	//Try to get the username from token
+	username, err := g.Option.UserHandler.GetAuthAgent().GetUsernameFromToken(token)
+	if err != nil {
+		//This token is not valid
+		w.WriteHeader(http.StatusUnauthorized)
+		w.Write([]byte("401 - Unauthorized (Token not valid)"))
+		return
+	}
+
+	//Check if user exists and have access to the script
+	targetUser, err := g.Option.UserHandler.GetUserInfoFromUsername(username)
+	if err != nil {
+		//This user not exists
+		w.WriteHeader(http.StatusUnauthorized)
+		w.Write([]byte("401 - Unauthorized (User not exists)"))
+		return
+	}
+
+	scriptScope := ""
+	allowAccess := checkUserAccessToScript(targetUser, script, scriptScope)
+	if !allowAccess {
+		w.WriteHeader(http.StatusUnauthorized)
+		w.Write([]byte("401 - Unauthorized (Permission Denied)"))
+		return
+	}
+
+	//Get the content of the script
+	scriptContentByte, err := ioutil.ReadFile(filepath.Join("./web/", script))
+	if err != nil {
+		w.WriteHeader(http.StatusNotFound)
+		w.Write([]byte("404 - Script Not Found"))
+		return
+	}
+	scriptContent := string(scriptContentByte)
+
+	g.ExecuteAGIScript(scriptContent, nil, script, scriptScope, w, r, targetUser)
+}

+ 81 - 72
mod/agi/static.go

@@ -1,72 +1,81 @@
-package agi
-
-import (
-	"net/url"
-	"path/filepath"
-	"strings"
-
-	user "imuslab.com/arozos/mod/user"
-)
-
-//Check if the user can access this script file
-func checkUserAccessToScript(thisuser *user.User, scriptFile string, scriptScope string) bool {
-	moduleName := getScriptRoot(scriptFile, scriptScope)
-	if !thisuser.GetModuleAccessPermission(moduleName) {
-		return false
-	}
-	return true
-}
-
-//validate the given path is a script from webroot
-func isValidAGIScript(scriptPath string) bool {
-	return fileExists(filepath.Join("./web", scriptPath)) && (filepath.Ext(scriptPath) == ".js" || filepath.Ext(scriptPath) == ".agi")
-}
-
-//Return the script root of the current executing script
-func getScriptRoot(scriptFile string, scriptScope string) string {
-	//Get the script root from the script path
-	webRootAbs, _ := filepath.Abs(scriptScope)
-	webRootAbs = filepath.ToSlash(filepath.Clean(webRootAbs) + "/")
-	scriptFileAbs, _ := filepath.Abs(scriptFile)
-	scriptFileAbs = filepath.ToSlash(filepath.Clean(scriptFileAbs))
-	scriptRoot := strings.Replace(scriptFileAbs, webRootAbs, "", 1)
-	scriptRoot = strings.Split(scriptRoot, "/")[0]
-	return scriptRoot
-}
-
-//For handling special url decode in the request
-func specialURIDecode(inputPath string) string {
-	inputPath = strings.ReplaceAll(inputPath, "+", "{{plus_sign}}")
-	inputPath, _ = url.QueryUnescape(inputPath)
-	inputPath = strings.ReplaceAll(inputPath, "{{plus_sign}}", "+")
-	return inputPath
-}
-
-func specialGlob(path string) ([]string, error) {
-	files, err := filepath.Glob(path)
-	if err != nil {
-		return []string{}, err
-	}
-
-	if strings.Contains(path, "[") == true || strings.Contains(path, "]") == true {
-		if len(files) == 0 {
-			//Handle reverse check. Replace all [ and ] with *
-			newSearchPath := strings.ReplaceAll(path, "[", "?")
-			newSearchPath = strings.ReplaceAll(newSearchPath, "]", "?")
-			newSearchPath = strings.ReplaceAll(newSearchPath, ":", "?")
-			//Scan with all the similar structure except [ and ]
-			tmpFilelist, _ := filepath.Glob(newSearchPath)
-			for _, file := range tmpFilelist {
-				file = filepath.ToSlash(file)
-				if strings.Contains(file, filepath.ToSlash(filepath.Dir(path))) {
-					files = append(files, file)
-				}
-			}
-		}
-	}
-	//Convert all filepaths to slash
-	for i := 0; i < len(files); i++ {
-		files[i] = filepath.ToSlash(files[i])
-	}
-	return files, nil
-}
+package agi
+
+import (
+	"net/url"
+	"path/filepath"
+	"strings"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	"imuslab.com/arozos/mod/filesystem/arozfs"
+	user "imuslab.com/arozos/mod/user"
+)
+
+//Get the full vpath if the passing value is a relative path
+//Return the original vpath if any error occured
+func relativeVpathRewrite(fsh *filesystem.FileSystemHandler, vpath string, vm *otto.Otto, u *user.User) string {
+	//Check if the vpath contain a UUID
+	if strings.Contains(vpath, ":/") || (len(vpath) > 0 && vpath[len(vpath)-1:] == ":") {
+		//This vpath contain root uuid.
+		return vpath
+	}
+
+	//We have no idea where the script is from. Trust its vpath is always full path
+	if fsh == nil {
+		return vpath
+	}
+
+	//Get the script execution root path
+	rootPath, err := vm.Get("__FILE__")
+	if err != nil {
+		return vpath
+	}
+
+	rootPathString, err := rootPath.ToString()
+	if err != nil {
+		return vpath
+	}
+
+	//Convert the root path to vpath
+	rootVpath, err := fsh.FileSystemAbstraction.RealPathToVirtualPath(rootPathString, u.Username)
+	if err != nil {
+		return vpath
+	}
+
+	rootScriptDir := filepath.Dir(rootVpath)
+	return arozfs.ToSlash(filepath.Clean(filepath.Join(rootScriptDir, vpath)))
+}
+
+//Check if the user can access this script file
+func checkUserAccessToScript(thisuser *user.User, scriptFile string, scriptScope string) bool {
+	moduleName := getScriptRoot(scriptFile, scriptScope)
+	if !thisuser.GetModuleAccessPermission(moduleName) {
+		return false
+	}
+	return true
+}
+
+//validate the given path is a script from webroot
+func isValidAGIScript(scriptPath string) bool {
+	return fileExists(filepath.Join("./web", scriptPath)) && (filepath.Ext(scriptPath) == ".js" || filepath.Ext(scriptPath) == ".agi")
+}
+
+//Return the script root of the current executing script
+func getScriptRoot(scriptFile string, scriptScope string) string {
+	//Get the script root from the script path
+	webRootAbs, _ := filepath.Abs(scriptScope)
+	webRootAbs = filepath.ToSlash(filepath.Clean(webRootAbs) + "/")
+	scriptFileAbs, _ := filepath.Abs(scriptFile)
+	scriptFileAbs = filepath.ToSlash(filepath.Clean(scriptFileAbs))
+	scriptRoot := strings.Replace(scriptFileAbs, webRootAbs, "", 1)
+	scriptRoot = strings.Split(scriptRoot, "/")[0]
+	return scriptRoot
+}
+
+//For handling special url decode in the request
+func specialURIDecode(inputPath string) string {
+	inputPath = strings.ReplaceAll(inputPath, "+", "{{plus_sign}}")
+	inputPath, _ = url.QueryUnescape(inputPath)
+	inputPath = strings.ReplaceAll(inputPath, "{{plus_sign}}", "+")
+	return inputPath
+}

+ 1 - 0
mod/agi/systemFunc.go

@@ -24,6 +24,7 @@ func (g *Gateway) injectStandardLibs(vm *otto.Otto, scriptFile string, scriptSco
 	vm.Set("INTERNAL_VERSION", g.Option.InternalVersion)
 	vm.Set("INTERNAL_VERSION", g.Option.InternalVersion)
 	vm.Set("LOADED_MODULES", g.Option.LoadedModule)
 	vm.Set("LOADED_MODULES", g.Option.LoadedModule)
 	vm.Set("LOADED_STORAGES", g.Option.UserHandler.GetStoragePool())
 	vm.Set("LOADED_STORAGES", g.Option.UserHandler.GetStoragePool())
+	vm.Set("__FILE__", scriptFile)
 	vm.Set("HTTP_RESP", "")
 	vm.Set("HTTP_RESP", "")
 	vm.Set("HTTP_HEADER", "text/plain")
 	vm.Set("HTTP_HEADER", "text/plain")
 
 

+ 307 - 300
mod/agi/userFunc.go

@@ -1,300 +1,307 @@
-package agi
-
-import (
-	"encoding/json"
-	"errors"
-	"io/ioutil"
-	"log"
-	"net/http"
-	"path/filepath"
-
-	"github.com/robertkrimen/otto"
-	"imuslab.com/arozos/mod/filesystem"
-	user "imuslab.com/arozos/mod/user"
-)
-
-//Define path translation function
-func virtualPathToRealPath(vpath string, u *user.User) (*filesystem.FileSystemHandler, string, error) {
-	fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
-	if err != nil {
-		return nil, "", err
-	}
-	rpath, err := fsh.FileSystemAbstraction.VirtualPathToRealPath(vpath, u.Username)
-	if err != nil {
-		return nil, "", err
-	}
-	return fsh, rpath, nil
-}
-
-func realpathToVirtualpath(fsh *filesystem.FileSystemHandler, path string, u *user.User) (string, error) {
-	return fsh.FileSystemAbstraction.RealPathToVirtualPath(path, u.Username)
-}
-
-//Inject user based functions into the virtual machine
-func (g *Gateway) injectUserFunctions(vm *otto.Otto, scriptFile string, scriptScope string, u *user.User, w http.ResponseWriter, r *http.Request) {
-	username := u.Username
-	vm.Set("USERNAME", username)
-	vm.Set("USERICON", u.GetUserIcon())
-	vm.Set("USERQUOTA_TOTAL", u.StorageQuota.TotalStorageQuota)
-	vm.Set("USERQUOTA_USED", u.StorageQuota.UsedStorageQuota)
-	vm.Set("USER_VROOTS", u.GetAllAccessibleFileSystemHandler())
-	vm.Set("USER_MODULES", u.GetUserAccessibleModules())
-
-	//File system and path related
-	vm.Set("decodeVirtualPath", func(call otto.FunctionCall) otto.Value {
-		log.Println("Call to deprecated function decodeVirtualPath")
-		return otto.FalseValue()
-	})
-
-	vm.Set("decodeAbsoluteVirtualPath", func(call otto.FunctionCall) otto.Value {
-		log.Println("Call to deprecated function decodeAbsoluteVirtualPath")
-		return otto.FalseValue()
-	})
-
-	vm.Set("encodeRealPath", func(call otto.FunctionCall) otto.Value {
-		log.Println("Call to deprecated function encodeRealPath")
-		return otto.FalseValue()
-	})
-
-	//Check if a given virtual path is readonly
-	vm.Set("pathCanWrite", func(call otto.FunctionCall) otto.Value {
-		vpath, _ := call.Argument(0).ToString()
-		if u.CanWrite(vpath) {
-			return otto.TrueValue()
-		} else {
-			return otto.FalseValue()
-		}
-	})
-
-	//Permission related
-	vm.Set("getUserPermissionGroup", func(call otto.FunctionCall) otto.Value {
-		groupinfo := u.GetUserPermissionGroup()
-		jsonString, _ := json.Marshal(groupinfo)
-		reply, _ := vm.ToValue(string(jsonString))
-		return reply
-	})
-
-	vm.Set("userIsAdmin", func(call otto.FunctionCall) otto.Value {
-		reply, _ := vm.ToValue(u.IsAdmin())
-		return reply
-	})
-
-	//User Account Related
-	/*
-		userExists(username);
-	*/
-	vm.Set("userExists", func(call otto.FunctionCall) otto.Value {
-		if u.IsAdmin() {
-			//Get username from function paramter
-			username, err := call.Argument(0).ToString()
-			if err != nil || username == "undefined" {
-				g.raiseError(errors.New("username is undefined"))
-				reply, _ := vm.ToValue(nil)
-				return reply
-			}
-
-			//Check if user exists
-			userExists := u.Parent().GetAuthAgent().UserExists(username)
-			if userExists {
-				return otto.TrueValue()
-			} else {
-				return otto.FalseValue()
-			}
-
-		} else {
-			g.raiseError(errors.New("Permission Denied: userExists require admin permission"))
-			return otto.FalseValue()
-		}
-
-	})
-
-	/*
-		createUser(username, password, defaultGroup);
-	*/
-	vm.Set("createUser", func(call otto.FunctionCall) otto.Value {
-		if u.IsAdmin() {
-			//Ok. Create user base on given information
-			username, err := call.Argument(0).ToString()
-			if err != nil || username == "undefined" {
-				g.raiseError(errors.New("username is undefined"))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			password, err := call.Argument(1).ToString()
-			if err != nil || password == "undefined" {
-				g.raiseError(errors.New("password is undefined"))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			defaultGroup, err := call.Argument(2).ToString()
-			if err != nil || defaultGroup == "undefined" {
-				g.raiseError(errors.New("defaultGroup is undefined"))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			//Check if username already used
-			userExists := u.Parent().GetAuthAgent().UserExists(username)
-			if userExists {
-				g.raiseError(errors.New("Username already exists"))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			//Check if the given permission group exists
-			groupExists := u.Parent().GetPermissionHandler().GroupExists(defaultGroup)
-			if !groupExists {
-				g.raiseError(errors.New(defaultGroup + " user-group not exists"))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			//Create the user
-			err = u.Parent().GetAuthAgent().CreateUserAccount(username, password, []string{defaultGroup})
-
-			if err != nil {
-				g.raiseError(errors.New("User creation failed: " + err.Error()))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			return otto.TrueValue()
-		} else {
-			g.raiseError(errors.New("Permission Denied: createUser require admin permission"))
-			return otto.FalseValue()
-		}
-
-	})
-
-	vm.Set("editUser", func(call otto.FunctionCall) otto.Value {
-		if u.IsAdmin() {
-
-		} else {
-			g.raiseError(errors.New("Permission Denied: editUser require admin permission"))
-			return otto.FalseValue()
-		}
-		//libname, err := call.Argument(0).ToString()
-		return otto.FalseValue()
-	})
-
-	/*
-		removeUser(username)
-	*/
-	vm.Set("removeUser", func(call otto.FunctionCall) otto.Value {
-		if u.IsAdmin() {
-			//Get username from function paramters
-			username, err := call.Argument(0).ToString()
-			if err != nil || username == "undefined" {
-				g.raiseError(errors.New("username is undefined"))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			//Check if the user exists
-			userExists := u.Parent().GetAuthAgent().UserExists(username)
-			if !userExists {
-				g.raiseError(errors.New(username + " not exists"))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			//User exists. Remove it from the system
-			err = u.Parent().GetAuthAgent().UnregisterUser(username)
-			if err != nil {
-				g.raiseError(errors.New("User removal failed: " + err.Error()))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			return otto.TrueValue()
-		} else {
-			g.raiseError(errors.New("Permission Denied: removeUser require admin permission"))
-			return otto.FalseValue()
-		}
-	})
-
-	vm.Set("getUserInfoByName", func(call otto.FunctionCall) otto.Value {
-		//libname, err := call.Argument(0).ToString()
-		if u.IsAdmin() {
-
-		} else {
-
-			g.raiseError(errors.New("Permission Denied: getUserInfoByName require admin permission"))
-			return otto.FalseValue()
-		}
-		return otto.TrueValue()
-	})
-
-	//Allow real time library includsion into the virtual machine
-	vm.Set("requirelib", func(call otto.FunctionCall) otto.Value {
-		libname, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			reply, _ := vm.ToValue(nil)
-			return reply
-		}
-
-		//Handle special case on high level libraries
-		if libname == "websocket" && w != nil && r != nil {
-			g.injectWebSocketFunctions(vm, u, w, r)
-			return otto.TrueValue()
-		} else {
-			//Check if the library name exists. If yes, run the initiation script on the vm
-			if entryPoint, ok := g.LoadedAGILibrary[libname]; ok {
-				entryPoint(vm, u)
-				return otto.TrueValue()
-			} else {
-				//Lib not exists
-				log.Println("Lib not found: " + libname)
-				return otto.FalseValue()
-			}
-		}
-
-		//Unknown status
-		return otto.FalseValue()
-	})
-
-	//Execd (Execute & detach) run another script and detach the execution
-	vm.Set("execd", func(call otto.FunctionCall) otto.Value {
-		//Check if the pkg is already registered
-		scriptName, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Carry the payload to the forked process if there are any
-		payload, _ := call.Argument(1).ToString()
-
-		//Check if the script file exists
-		targetScriptPath := filepath.ToSlash(filepath.Join(filepath.Dir(scriptFile), scriptName))
-		if !fileExists(targetScriptPath) {
-			g.raiseError(errors.New("*AGI* Target path not exists!"))
-			return otto.FalseValue()
-		}
-
-		//Run the script
-		scriptContent, _ := ioutil.ReadFile(targetScriptPath)
-		go func() {
-			//Create a new VM to execute the script (also for isolation)
-			vm := otto.New()
-			//Inject standard libs into the vm
-			g.injectStandardLibs(vm, scriptFile, scriptScope)
-			g.injectUserFunctions(vm, scriptFile, scriptScope, u, w, r)
-
-			vm.Set("PARENT_DETACHED", true)
-			vm.Set("PARENT_PAYLOAD", payload)
-			_, err = vm.Run(string(scriptContent))
-			if err != nil {
-				//Script execution failed
-				log.Println("Script Execution Failed: ", err.Error())
-				g.raiseError(err)
-			}
-		}()
-
-		return otto.TrueValue()
-	})
-
-}
+package agi
+
+import (
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"path/filepath"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	"imuslab.com/arozos/mod/filesystem/arozfs"
+	user "imuslab.com/arozos/mod/user"
+)
+
+//Define path translation function
+func virtualPathToRealPath(vpath string, u *user.User) (*filesystem.FileSystemHandler, string, error) {
+	fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
+	if err != nil {
+		return nil, "", err
+	}
+	rpath, err := fsh.FileSystemAbstraction.VirtualPathToRealPath(vpath, u.Username)
+	if err != nil {
+		return nil, "", err
+	}
+	return fsh, rpath, nil
+}
+
+func realpathToVirtualpath(fsh *filesystem.FileSystemHandler, path string, u *user.User) (string, error) {
+	return fsh.FileSystemAbstraction.RealPathToVirtualPath(path, u.Username)
+}
+
+//Inject user based functions into the virtual machine
+//Note that the fsh might be nil and scriptPath must be real path of script being executed
+//**Use local file system check if fsh == nil**
+func (g *Gateway) injectUserFunctions(vm *otto.Otto, fsh *filesystem.FileSystemHandler, scriptPath string, scriptScope string, u *user.User, w http.ResponseWriter, r *http.Request) {
+	username := u.Username
+	vm.Set("USERNAME", username)
+	vm.Set("USERICON", u.GetUserIcon())
+	vm.Set("USERQUOTA_TOTAL", u.StorageQuota.TotalStorageQuota)
+	vm.Set("USERQUOTA_USED", u.StorageQuota.UsedStorageQuota)
+	vm.Set("USER_VROOTS", u.GetAllAccessibleFileSystemHandler())
+	vm.Set("USER_MODULES", u.GetUserAccessibleModules())
+
+	//File system and path related
+	vm.Set("decodeVirtualPath", func(call otto.FunctionCall) otto.Value {
+		log.Println("Call to deprecated function decodeVirtualPath")
+		return otto.FalseValue()
+	})
+
+	vm.Set("decodeAbsoluteVirtualPath", func(call otto.FunctionCall) otto.Value {
+		log.Println("Call to deprecated function decodeAbsoluteVirtualPath")
+		return otto.FalseValue()
+	})
+
+	vm.Set("encodeRealPath", func(call otto.FunctionCall) otto.Value {
+		log.Println("Call to deprecated function encodeRealPath")
+		return otto.FalseValue()
+	})
+
+	//Check if a given virtual path is readonly
+	vm.Set("pathCanWrite", func(call otto.FunctionCall) otto.Value {
+		vpath, _ := call.Argument(0).ToString()
+		if u.CanWrite(vpath) {
+			return otto.TrueValue()
+		} else {
+			return otto.FalseValue()
+		}
+	})
+
+	//Permission related
+	vm.Set("getUserPermissionGroup", func(call otto.FunctionCall) otto.Value {
+		groupinfo := u.GetUserPermissionGroup()
+		jsonString, _ := json.Marshal(groupinfo)
+		reply, _ := vm.ToValue(string(jsonString))
+		return reply
+	})
+
+	vm.Set("userIsAdmin", func(call otto.FunctionCall) otto.Value {
+		reply, _ := vm.ToValue(u.IsAdmin())
+		return reply
+	})
+
+	//User Account Related
+	/*
+		userExists(username);
+	*/
+	vm.Set("userExists", func(call otto.FunctionCall) otto.Value {
+		if u.IsAdmin() {
+			//Get username from function paramter
+			username, err := call.Argument(0).ToString()
+			if err != nil || username == "undefined" {
+				g.raiseError(errors.New("username is undefined"))
+				reply, _ := vm.ToValue(nil)
+				return reply
+			}
+
+			//Check if user exists
+			userExists := u.Parent().GetAuthAgent().UserExists(username)
+			if userExists {
+				return otto.TrueValue()
+			} else {
+				return otto.FalseValue()
+			}
+
+		} else {
+			g.raiseError(errors.New("Permission Denied: userExists require admin permission"))
+			return otto.FalseValue()
+		}
+
+	})
+
+	/*
+		createUser(username, password, defaultGroup);
+	*/
+	vm.Set("createUser", func(call otto.FunctionCall) otto.Value {
+		if u.IsAdmin() {
+			//Ok. Create user base on given information
+			username, err := call.Argument(0).ToString()
+			if err != nil || username == "undefined" {
+				g.raiseError(errors.New("username is undefined"))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			password, err := call.Argument(1).ToString()
+			if err != nil || password == "undefined" {
+				g.raiseError(errors.New("password is undefined"))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			defaultGroup, err := call.Argument(2).ToString()
+			if err != nil || defaultGroup == "undefined" {
+				g.raiseError(errors.New("defaultGroup is undefined"))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			//Check if username already used
+			userExists := u.Parent().GetAuthAgent().UserExists(username)
+			if userExists {
+				g.raiseError(errors.New("Username already exists"))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			//Check if the given permission group exists
+			groupExists := u.Parent().GetPermissionHandler().GroupExists(defaultGroup)
+			if !groupExists {
+				g.raiseError(errors.New(defaultGroup + " user-group not exists"))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			//Create the user
+			err = u.Parent().GetAuthAgent().CreateUserAccount(username, password, []string{defaultGroup})
+
+			if err != nil {
+				g.raiseError(errors.New("User creation failed: " + err.Error()))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			return otto.TrueValue()
+		} else {
+			g.raiseError(errors.New("Permission Denied: createUser require admin permission"))
+			return otto.FalseValue()
+		}
+
+	})
+
+	vm.Set("editUser", func(call otto.FunctionCall) otto.Value {
+		if u.IsAdmin() {
+
+		} else {
+			g.raiseError(errors.New("Permission Denied: editUser require admin permission"))
+			return otto.FalseValue()
+		}
+		//libname, err := call.Argument(0).ToString()
+		return otto.FalseValue()
+	})
+
+	/*
+		removeUser(username)
+	*/
+	vm.Set("removeUser", func(call otto.FunctionCall) otto.Value {
+		if u.IsAdmin() {
+			//Get username from function paramters
+			username, err := call.Argument(0).ToString()
+			if err != nil || username == "undefined" {
+				g.raiseError(errors.New("username is undefined"))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			//Check if the user exists
+			userExists := u.Parent().GetAuthAgent().UserExists(username)
+			if !userExists {
+				g.raiseError(errors.New(username + " not exists"))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			//User exists. Remove it from the system
+			err = u.Parent().GetAuthAgent().UnregisterUser(username)
+			if err != nil {
+				g.raiseError(errors.New("User removal failed: " + err.Error()))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			return otto.TrueValue()
+		} else {
+			g.raiseError(errors.New("Permission Denied: removeUser require admin permission"))
+			return otto.FalseValue()
+		}
+	})
+
+	vm.Set("getUserInfoByName", func(call otto.FunctionCall) otto.Value {
+		//libname, err := call.Argument(0).ToString()
+		if u.IsAdmin() {
+
+		} else {
+
+			g.raiseError(errors.New("Permission Denied: getUserInfoByName require admin permission"))
+			return otto.FalseValue()
+		}
+		return otto.TrueValue()
+	})
+
+	//Allow real time library includsion into the virtual machine
+	vm.Set("requirelib", func(call otto.FunctionCall) otto.Value {
+		libname, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			reply, _ := vm.ToValue(nil)
+			return reply
+		}
+
+		//Handle special case on high level libraries
+		if libname == "websocket" && w != nil && r != nil {
+			g.injectWebSocketFunctions(vm, u, w, r)
+			return otto.TrueValue()
+		} else {
+			//Check if the library name exists. If yes, run the initiation script on the vm
+			if entryPoint, ok := g.LoadedAGILibrary[libname]; ok {
+				entryPoint(vm, u, fsh, scriptPath)
+				return otto.TrueValue()
+			} else {
+				//Lib not exists
+				log.Println("Lib not found: " + libname)
+				return otto.FalseValue()
+			}
+		}
+	})
+
+	//Execd (Execute & detach) run another script and detach the execution
+	vm.Set("execd", func(call otto.FunctionCall) otto.Value {
+		//Check if the pkg is already registered
+		scriptName, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Carry the payload to the forked process if there are any
+		payload, _ := call.Argument(1).ToString()
+
+		//Check if the script file exists
+		targetScriptPath := arozfs.ToSlash(filepath.Join(filepath.Dir(scriptPath), scriptName))
+		if fsh != nil {
+			if !fsh.FileSystemAbstraction.FileExists(targetScriptPath) {
+				g.raiseError(errors.New("[AGI] Target path not exists!"))
+				return otto.FalseValue()
+			}
+		} else {
+			if !filesystem.FileExists(targetScriptPath) {
+				g.raiseError(errors.New("[AGI] Target path not exists!"))
+				return otto.FalseValue()
+			}
+		}
+
+		//Run the script
+		scriptContent, _ := ioutil.ReadFile(targetScriptPath)
+		go func() {
+			//Create a new VM to execute the script (also for isolation)
+			vm := otto.New()
+			//Inject standard libs into the vm
+			g.injectStandardLibs(vm, scriptPath, scriptScope)
+			g.injectUserFunctions(vm, fsh, scriptPath, scriptScope, u, w, r)
+
+			vm.Set("PARENT_DETACHED", true)
+			vm.Set("PARENT_PAYLOAD", payload)
+			_, err = vm.Run(string(scriptContent))
+			if err != nil {
+				//Script execution failed
+				log.Println("Script Execution Failed: ", err.Error())
+				g.raiseError(err)
+			}
+		}()
+
+		return otto.TrueValue()
+	})
+
+}