Example #1
0
// Serve all files in the current directory, or only a few select filetypes (html, css, js, png and txt)
func registerHandlers(mux *http.ServeMux, handlePath, servedir string, perm pinterface.IPermissions, luapool *lStatePool, cache *FileCache, addDomain bool) {

	// Handle all requests with this function
	allRequests := func(w http.ResponseWriter, req *http.Request) {
		if perm.Rejected(w, req) {
			// Get and call the Permission Denied function
			perm.DenyFunction()(w, req)
			// Reject the request by returning
			return
		}

		// Local to this function
		servedir := servedir

		// Look for the directory that is named the same as the host
		if addDomain {
			servedir = filepath.Join(servedir, getDomain(req))
		}

		urlpath := req.URL.Path
		filename := url2filename(servedir, urlpath)
		// Remove the trailing slash from the filename, if any
		noslash := filename
		if strings.HasSuffix(filename, pathsep) {
			noslash = filename[:len(filename)-1]
		}
		hasdir := fs.exists(filename) && fs.isDir(filename)
		dirname := filename
		hasfile := fs.exists(noslash)

		// Set the server header.
		serverHeaders(w)

		// Share the directory or file
		if hasdir {
			dirPage(w, req, servedir, dirname, perm, luapool, cache)
			return
		} else if !hasdir && hasfile {
			// Share a single file instead of a directory
			filePage(w, req, noslash, perm, luapool, cache)
			return
		}
		// Not found
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprint(w, noPage(filename))
	}

	// Handle requests differently depending on if rate limiting is enabled or not
	if disableRateLimiting {
		mux.HandleFunc(handlePath, allRequests)
	} else {
		limiter := tollbooth.NewLimiter(limitRequests, time.Second)
		limiter.MessageContentType = "text/html; charset=utf-8"
		limiter.Message = easyPage("Rate-limit exceeded", "<div style='color:red'>You have reached the maximum request limit.</div>")
		mux.Handle(handlePath, tollbooth.LimitFuncHandler(limiter, allRequests))
	}
}
Example #2
0
// Run a Lua file as a configuration script. Also has access to the userstate and permissions.
// Returns an error if there was a problem with running the lua script, otherwise nil.
func runConfiguration(filename string, perm pinterface.IPermissions, luapool *lStatePool, cache *FileCache, mux *http.ServeMux, singleFileMode bool) error {

	// Retrieve a Lua state
	L := luapool.Get()

	// Retrieve the userstate
	userstate := perm.UserState()

	// Server configuration functions
	exportServerConfigFunctions(L, perm, filename, luapool)

	// Other basic system functions, like log()
	exportBasicSystemFunctions(L)

	// Simpleredis data structures (could be used for storing server stats)
	exportList(L, userstate)
	exportSet(L, userstate)
	exportHash(L, userstate)
	exportKeyValue(L, userstate)

	// For handling JSON data
	exportJSONFunctions(L)
	exportJFile(L, filepath.Dir(filename))

	// For saving and loading Lua functions
	exportCodeLibrary(L, userstate)

	// Plugins
	exportPluginFunctions(L, nil)

	// Cache
	exportCacheFunctions(L, cache)

	if singleFileMode {
		// Lua HTTP handlers
		exportLuaHandlerFunctions(L, filename, perm, luapool, cache, mux, false)
	}

	// Run the script
	if err := L.DoFile(filename); err != nil {
		// Close the Lua state
		L.Close()

		// Logging and/or HTTP response is handled elsewhere
		return err
	}

	// Only put the Lua state back if there were no errors
	luapool.Put(L)

	return nil
}
Example #3
0
// Return a *lua.LState object that contains several exposed functions
func exportCommonFunctions(w http.ResponseWriter, req *http.Request, filename string, perm pinterface.IPermissions, L *lua.LState, luapool *lStatePool, flushFunc func(), cache *FileCache) {

	// Retrieve the userstate
	userstate := perm.UserState()

	// Make basic functions, like print, available to the Lua script.
	// Only exports functions that can relate to HTTP responses or requests.
	exportBasicWeb(w, req, L, filename, flushFunc)

	// Functions for serving files in the same directory as a script
	exportServeFile(w, req, L, filename, perm, luapool, cache)

	// Make other basic functions available
	exportBasicSystemFunctions(L)

	// Functions for rendering markdown or amber
	exportRenderFunctions(w, req, L)

	// Make the functions related to userstate available to the Lua script
	exportUserstate(w, req, L, userstate)

	// Simpleredis data structures
	exportList(L, userstate)
	exportSet(L, userstate)
	exportHash(L, userstate)
	exportKeyValue(L, userstate)

	// For handling JSON data
	exportJSONFunctions(L)
	exportJFile(L, filepath.Dir(filename))

	// For saving and loading Lua functions
	exportCodeLibrary(L, userstate)

	// pprint
	//exportREPL(L)

	// Plugins
	exportPluginFunctions(L, nil)

	// Cache
	exportCacheFunctions(L, cache)

	// File uploads
	exportUploadedFile(L, w, req, filepath.Dir(filename))
}
Example #4
0
// REPL provides a "Read Eveal Print" loop for interacting with Lua.
// A variety of functions are exposed to the Lua state.
func REPL(perm pinterface.IPermissions, luapool *lStatePool, cache *FileCache, ready, done chan bool) error {
	var (
		historyFilename string
		err             error
	)

	historydir, err := homedir.Dir()
	if err != nil {
		log.Error("Could not find a user directory to store the REPL history.")
		historydir = "."
	}
	if runtime.GOOS == "windows" {
		historyFilename = filepath.Join(historydir, "algernon", "repl.txt")
	} else {
		historyFilename = filepath.Join(historydir, ".algernon_history")
	}

	// Retrieve the userstate
	userstate := perm.UserState()

	// Retrieve a Lua state
	L := luapool.Get()
	// Don't re-use the Lua state
	defer L.Close()

	// Server configuration functions
	exportServerConfigFunctions(L, perm, "", luapool)

	// Other basic system functions, like log()
	exportBasicSystemFunctions(L)

	// Simpleredis data structures
	exportList(L, userstate)
	exportSet(L, userstate)
	exportHash(L, userstate)
	exportKeyValue(L, userstate)

	// For handling JSON data
	exportJSONFunctions(L)
	exportJFile(L, serverDir)

	// For saving and loading Lua functions
	exportCodeLibrary(L, userstate)

	// Pretty printing
	exportREPL(L)

	// Colors and input
	enableColors := runtime.GOOS != "windows"
	o := term.NewTextOutput(enableColors, true)

	// Plugin functionality
	exportPluginFunctions(L, o)

	// Cache
	exportCacheFunctions(L, cache)

	// Getting ready
	o.Println(o.LightBlue(versionString))

	<-ready // Wait for the server to be ready

	// Tell the user that the server is ready
	o.Println(o.LightGreen("Ready"))

	// Start the read, eval, print loop
	var (
		line     string
		prompt   = o.LightGreen("lua> ")
		EOF      bool
		EOFcount int
	)
	if loadHistory(historyFilename) != nil && fs.exists(historyFilename) {
		log.Error("Could not load REPL history:", historyFilename)
	}
	// To be run at server shutdown
	atShutdown(func() {
		// Verbose mode has different log output at shutdown
		if !verboseMode {
			o.Println(o.LightBlue(exitMessage))
		}
	})
	for {
		// Retrieve user input
		EOF = false
		if line, err = getInput(prompt); err != nil {
			if err.Error() == "EOF" {
				if debugMode {
					o.Println(o.LightPurple(err.Error()))
				}
				EOF = true
			} else {
				log.Error("Error reading line(" + err.Error() + ").")
				continue
			}
		} else {
			addHistory(line)
			// Save the REPL history at every line.
			// This proved to be safer than only saving the history at shutdown
			// (due to how ctrl-c was handled on some systems)
			// and it's hard to imagine performance issues with this.
			saveHistory(historyFilename)
		}

		if EOF {
			switch EOFcount {
			case 0:
				o.Err("Press ctrl-d again to exit.")
				EOFcount++
				continue
			default:
				done <- true
				return nil
			}
		}

		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}

		switch line {
		case "help":
			outputHelp(o, generalHelpText)
			continue
		case "webhelp":
			outputHelp(o, webHelpText)
			continue
		case "confighelp":
			outputHelp(o, configHelpText)
			continue
		case "quit", "exit", "shutdown", "halt":
			done <- true
			return nil
		case "zalgo":
			// Easter egg
			o.ErrExit("Ḫ̷̲̫̰̯̭̀̂̑̈ͅĚ̥̖̩̘̱͔͈͈ͬ̚ ̦̦͖̲̀ͦ͂C̜͓̲̹͐̔ͭ̏Oͭ͛͂̋ͭͬͬ͆͏̺͓̰͚͠ͅM̢͉̼̖͍̊̕Ḛ̭̭͗̉̀̆ͬ̐ͪ̒S͉̪͂͌̄")
		}

		// If the line starts with print, don't touch it
		if strings.HasPrefix(line, "print(") {
			if err = L.DoString(line); err != nil {
				// Output the error message
				o.Err(err.Error())
			}
		} else {
			// Wrap the line in "pprint"
			if err = L.DoString("pprint(" + line + ")"); err != nil {
				// If there was a syntax error, try again without pprint
				if strings.Contains(err.Error(), "syntax error") {
					if err = L.DoString(line); err != nil {
						// Output the error message
						o.Err(err.Error())
					}
					// For other kinds of errors, output the error
				} else {
					// Output the error message
					o.Err(err.Error())
				}
			}
		}
	}
}
Example #5
0
// Make functions related to server configuration and permissions available
func exportServerConfigFunctions(L *lua.LState, perm pinterface.IPermissions, filename string, luapool *lStatePool) {

	// Set a default host and port. Maybe useful for alg applications.
	L.SetGlobal("SetAddr", L.NewFunction(func(L *lua.LState) int {
		serverAddrLua = L.ToString(1)
		return 0 // number of results
	}))

	// Clear the default path prefixes. This makes everything public.
	L.SetGlobal("ClearPermissions", L.NewFunction(func(L *lua.LState) int {
		perm.Clear()
		return 0 // number of results
	}))

	// Registers a path prefix, for instance "/secret",
	// as having *user* rights.
	L.SetGlobal("AddUserPrefix", L.NewFunction(func(L *lua.LState) int {
		path := L.ToString(1)
		perm.AddUserPath(path)
		return 0 // number of results
	}))

	// Registers a path prefix, for instance "/secret",
	// as having *admin* rights.
	L.SetGlobal("AddAdminPrefix", L.NewFunction(func(L *lua.LState) int {
		path := L.ToString(1)
		perm.AddAdminPath(path)
		return 0 // number of results
	}))

	// Sets a Lua function as a custom "permissions denied" page handler.
	L.SetGlobal("DenyHandler", L.NewFunction(func(L *lua.LState) int {
		luaDenyFunc := L.ToFunction(1)

		// Custom handler for when permissions are denied
		perm.SetDenyFunction(func(w http.ResponseWriter, req *http.Request) {
			// Set up a new Lua state with the current http.ResponseWriter and *http.Request, without caching
			exportCommonFunctions(w, req, filename, perm, L, luapool, nil, nil)

			// Then run the given Lua function
			L.Push(luaDenyFunc)
			if err := L.PCall(0, lua.MultRet, nil); err != nil {
				// Non-fatal error
				log.Error("Permission denied handler failed:", err)
				// Use the default permission handler from now on if the lua function fails
				perm.SetDenyFunction(permissions.PermissionDenied)
				perm.DenyFunction()(w, req)
			}
		})
		return 0 // number of results
	}))

	// Sets a Lua function to be run once the server is done parsing configuration and arguments.
	L.SetGlobal("OnReady", L.NewFunction(func(L *lua.LState) int {
		luaReadyFunc := L.ToFunction(1)

		// Custom handler for when permissions are denied.
		// Put the *lua.LState in a closure.
		serverReadyFunctionLua = func() {
			// Run the given Lua function
			L.Push(luaReadyFunc)
			if err := L.PCall(0, lua.MultRet, nil); err != nil {
				// Non-fatal error
				log.Error("The OnReady function failed:", err)
			}
		}
		return 0 // number of results
	}))

	// Set a access log filename. If blank, the log will go to the console (or browser, if debug mode is set).
	L.SetGlobal("LogTo", L.NewFunction(func(L *lua.LState) int {
		filename := L.ToString(1)
		serverLogFile = filename
		// Log as JSON by default
		log.SetFormatter(&log.JSONFormatter{})
		// Log to stderr if an empty filename is given
		if filename == "" {
			log.SetOutput(os.Stderr)
			L.Push(lua.LBool(true))
			return 1 // number of results
		}
		// Try opening/creating the given filename, for appending
		f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, defaultPermissions)
		if err != nil {
			log.Error(err)
			L.Push(lua.LBool(false))
			return 1 // number of results
		}
		// Set the file to log to and return
		log.SetOutput(f)
		L.Push(lua.LBool(true))
		return 1 // number of results
	}))

	// Use a single Lua file as the server, instead of directory structure
	L.SetGlobal("ServerFile", L.NewFunction(func(L *lua.LState) int {
		givenFilename := L.ToString(1)
		serverfilename := filepath.Join(filepath.Dir(filename), givenFilename)
		if !fs.exists(filename) {
			log.Error("Could not find", serverfilename)
			L.Push(lua.LBool(false))
			return 1 // number of results
		}
		luaServerFilename = serverfilename
		L.Push(lua.LBool(true))
		return 1 // number of results
	}))

	L.SetGlobal("ServerInfo", L.NewFunction(func(L *lua.LState) int {
		// Return the string, but drop the final newline
		L.Push(lua.LString(serverInfo()))
		return 1 // number of results
	}))

}