mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-14 12:48:28 +10:00
Add Surge MITM and scripts
This commit is contained in:
231
script/modules/require/module.go
Normal file
231
script/modules/require/module.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package require
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"text/template"
|
||||
|
||||
js "github.com/dop251/goja"
|
||||
"github.com/dop251/goja/parser"
|
||||
)
|
||||
|
||||
type ModuleLoader func(*js.Runtime, *js.Object)
|
||||
|
||||
// SourceLoader represents a function that returns a file data at a given path.
|
||||
// The function should return ModuleFileDoesNotExistError if the file either doesn't exist or is a directory.
|
||||
// This error will be ignored by the resolver and the search will continue. Any other errors will be propagated.
|
||||
type SourceLoader func(path string) ([]byte, error)
|
||||
|
||||
var (
|
||||
InvalidModuleError = errors.New("Invalid module")
|
||||
IllegalModuleNameError = errors.New("Illegal module name")
|
||||
NoSuchBuiltInModuleError = errors.New("No such built-in module")
|
||||
ModuleFileDoesNotExistError = errors.New("module file does not exist")
|
||||
)
|
||||
|
||||
// Registry contains a cache of compiled modules which can be used by multiple Runtimes
|
||||
type Registry struct {
|
||||
sync.Mutex
|
||||
native map[string]ModuleLoader
|
||||
builtin map[string]ModuleLoader
|
||||
compiled map[string]*js.Program
|
||||
|
||||
srcLoader SourceLoader
|
||||
globalFolders []string
|
||||
fsEnabled bool
|
||||
}
|
||||
|
||||
type RequireModule struct {
|
||||
r *Registry
|
||||
runtime *js.Runtime
|
||||
modules map[string]*js.Object
|
||||
nodeModules map[string]*js.Object
|
||||
}
|
||||
|
||||
func NewRegistry(opts ...Option) *Registry {
|
||||
r := &Registry{}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(r)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
type Option func(*Registry)
|
||||
|
||||
// WithLoader sets a function which will be called by the require() function in order to get a source code for a
|
||||
// module at the given path. The same function will be used to get external source maps.
|
||||
// Note, this only affects the modules loaded by the require() function. If you need to use it as a source map
|
||||
// loader for code parsed in a different way (such as runtime.RunString() or eval()), use (*Runtime).SetParserOptions()
|
||||
func WithLoader(srcLoader SourceLoader) Option {
|
||||
return func(r *Registry) {
|
||||
r.srcLoader = srcLoader
|
||||
}
|
||||
}
|
||||
|
||||
// WithGlobalFolders appends the given paths to the registry's list of
|
||||
// global folders to search if the requested module is not found
|
||||
// elsewhere. By default, a registry's global folders list is empty.
|
||||
// In the reference Node.js implementation, the default global folders
|
||||
// list is $NODE_PATH, $HOME/.node_modules, $HOME/.node_libraries and
|
||||
// $PREFIX/lib/node, see
|
||||
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders.
|
||||
func WithGlobalFolders(globalFolders ...string) Option {
|
||||
return func(r *Registry) {
|
||||
r.globalFolders = globalFolders
|
||||
}
|
||||
}
|
||||
|
||||
func WithFsEnable(enabled bool) Option {
|
||||
return func(r *Registry) {
|
||||
r.fsEnabled = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// Enable adds the require() function to the specified runtime.
|
||||
func (r *Registry) Enable(runtime *js.Runtime) *RequireModule {
|
||||
rrt := &RequireModule{
|
||||
r: r,
|
||||
runtime: runtime,
|
||||
modules: make(map[string]*js.Object),
|
||||
nodeModules: make(map[string]*js.Object),
|
||||
}
|
||||
|
||||
runtime.Set("require", rrt.require)
|
||||
return rrt
|
||||
}
|
||||
|
||||
func (r *Registry) RegisterNodeModule(name string, loader ModuleLoader) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if r.builtin == nil {
|
||||
r.builtin = make(map[string]ModuleLoader)
|
||||
}
|
||||
name = filepathClean(name)
|
||||
r.builtin[name] = loader
|
||||
}
|
||||
|
||||
func (r *Registry) RegisterNativeModule(name string, loader ModuleLoader) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if r.native == nil {
|
||||
r.native = make(map[string]ModuleLoader)
|
||||
}
|
||||
name = filepathClean(name)
|
||||
r.native[name] = loader
|
||||
}
|
||||
|
||||
// DefaultSourceLoader is used if none was set (see WithLoader()). It simply loads files from the host's filesystem.
|
||||
func DefaultSourceLoader(filename string) ([]byte, error) {
|
||||
fp := filepath.FromSlash(filename)
|
||||
f, err := os.Open(fp)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
err = ModuleFileDoesNotExistError
|
||||
} else if runtime.GOOS == "windows" {
|
||||
if errors.Is(err, syscall.Errno(0x7b)) { // ERROR_INVALID_NAME, The filename, directory name, or volume label syntax is incorrect.
|
||||
err = ModuleFileDoesNotExistError
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
// On some systems (e.g. plan9 and FreeBSD) it is possible to use the standard read() call on directories
|
||||
// which means we cannot rely on read() returning an error, we have to do stat() instead.
|
||||
if fi, err := f.Stat(); err == nil {
|
||||
if fi.IsDir() {
|
||||
return nil, ModuleFileDoesNotExistError
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
return io.ReadAll(f)
|
||||
}
|
||||
|
||||
func (r *Registry) getSource(p string) ([]byte, error) {
|
||||
srcLoader := r.srcLoader
|
||||
if srcLoader == nil {
|
||||
srcLoader = DefaultSourceLoader
|
||||
}
|
||||
return srcLoader(p)
|
||||
}
|
||||
|
||||
func (r *Registry) getCompiledSource(p string) (*js.Program, error) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
prg := r.compiled[p]
|
||||
if prg == nil {
|
||||
buf, err := r.getSource(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := string(buf)
|
||||
|
||||
if path.Ext(p) == ".json" {
|
||||
s = "module.exports = JSON.parse('" + template.JSEscapeString(s) + "')"
|
||||
}
|
||||
|
||||
source := "(function(exports, require, module) {" + s + "\n})"
|
||||
parsed, err := js.Parse(p, source, parser.WithSourceMapLoader(r.srcLoader))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prg, err = js.CompileAST(parsed, false)
|
||||
if err == nil {
|
||||
if r.compiled == nil {
|
||||
r.compiled = make(map[string]*js.Program)
|
||||
}
|
||||
r.compiled[p] = prg
|
||||
}
|
||||
return prg, err
|
||||
}
|
||||
return prg, nil
|
||||
}
|
||||
|
||||
func (r *RequireModule) require(call js.FunctionCall) js.Value {
|
||||
ret, err := r.Require(call.Argument(0).String())
|
||||
if err != nil {
|
||||
if _, ok := err.(*js.Exception); !ok {
|
||||
panic(r.runtime.NewGoError(err))
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func filepathClean(p string) string {
|
||||
return path.Clean(p)
|
||||
}
|
||||
|
||||
// Require can be used to import modules from Go source (similar to JS require() function).
|
||||
func (r *RequireModule) Require(p string) (ret js.Value, err error) {
|
||||
module, err := r.resolve(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ret = module.Get("exports")
|
||||
return
|
||||
}
|
||||
|
||||
func Require(runtime *js.Runtime, name string) js.Value {
|
||||
if r, ok := js.AssertFunction(runtime.Get("require")); ok {
|
||||
mod, err := r(js.Undefined(), runtime.ToValue(name))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return mod
|
||||
}
|
||||
panic(runtime.NewTypeError("Please enable require for this runtime using new(require.Registry).Enable(runtime)"))
|
||||
}
|
||||
277
script/modules/require/resolve.go
Normal file
277
script/modules/require/resolve.go
Normal file
@@ -0,0 +1,277 @@
|
||||
package require
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
js "github.com/dop251/goja"
|
||||
)
|
||||
|
||||
const NodePrefix = "node:"
|
||||
|
||||
// NodeJS module search algorithm described by
|
||||
// https://nodejs.org/api/modules.html#modules_all_together
|
||||
func (r *RequireModule) resolve(modpath string) (module *js.Object, err error) {
|
||||
origPath, modpath := modpath, filepathClean(modpath)
|
||||
if modpath == "" {
|
||||
return nil, IllegalModuleNameError
|
||||
}
|
||||
|
||||
var start string
|
||||
err = nil
|
||||
if path.IsAbs(origPath) {
|
||||
start = "/"
|
||||
} else {
|
||||
start = r.getCurrentModulePath()
|
||||
}
|
||||
|
||||
p := path.Join(start, modpath)
|
||||
if isFileOrDirectoryPath(origPath) && r.r.fsEnabled {
|
||||
if module = r.modules[p]; module != nil {
|
||||
return
|
||||
}
|
||||
module, err = r.loadAsFileOrDirectory(p)
|
||||
if err == nil && module != nil {
|
||||
r.modules[p] = module
|
||||
}
|
||||
} else {
|
||||
module, err = r.loadNative(origPath)
|
||||
if err == nil {
|
||||
return
|
||||
} else {
|
||||
if err == InvalidModuleError {
|
||||
err = nil
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
if module = r.nodeModules[p]; module != nil {
|
||||
return
|
||||
}
|
||||
if r.r.fsEnabled {
|
||||
module, err = r.loadNodeModules(modpath, start)
|
||||
if err == nil && module != nil {
|
||||
r.nodeModules[p] = module
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if module == nil && err == nil {
|
||||
err = InvalidModuleError
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadNative(path string) (*js.Object, error) {
|
||||
module := r.modules[path]
|
||||
if module != nil {
|
||||
return module, nil
|
||||
}
|
||||
|
||||
var ldr ModuleLoader
|
||||
if r.r.native != nil {
|
||||
ldr = r.r.native[path]
|
||||
}
|
||||
var isBuiltIn, withPrefix bool
|
||||
if ldr == nil {
|
||||
if r.r.builtin != nil {
|
||||
ldr = r.r.builtin[path]
|
||||
}
|
||||
if ldr == nil && strings.HasPrefix(path, NodePrefix) {
|
||||
ldr = r.r.builtin[path[len(NodePrefix):]]
|
||||
if ldr == nil {
|
||||
return nil, NoSuchBuiltInModuleError
|
||||
}
|
||||
withPrefix = true
|
||||
}
|
||||
isBuiltIn = true
|
||||
}
|
||||
|
||||
if ldr != nil {
|
||||
module = r.createModuleObject()
|
||||
r.modules[path] = module
|
||||
if isBuiltIn {
|
||||
if withPrefix {
|
||||
r.modules[path[len(NodePrefix):]] = module
|
||||
} else {
|
||||
if !strings.HasPrefix(path, NodePrefix) {
|
||||
r.modules[NodePrefix+path] = module
|
||||
}
|
||||
}
|
||||
}
|
||||
ldr(r.runtime, module)
|
||||
return module, nil
|
||||
}
|
||||
|
||||
return nil, InvalidModuleError
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadAsFileOrDirectory(path string) (module *js.Object, err error) {
|
||||
if module, err = r.loadAsFile(path); module != nil || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return r.loadAsDirectory(path)
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadAsFile(path string) (module *js.Object, err error) {
|
||||
if module, err = r.loadModule(path); module != nil || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p := path + ".js"
|
||||
if module, err = r.loadModule(p); module != nil || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p = path + ".json"
|
||||
return r.loadModule(p)
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadIndex(modpath string) (module *js.Object, err error) {
|
||||
p := path.Join(modpath, "index.js")
|
||||
if module, err = r.loadModule(p); module != nil || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p = path.Join(modpath, "index.json")
|
||||
return r.loadModule(p)
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadAsDirectory(modpath string) (module *js.Object, err error) {
|
||||
p := path.Join(modpath, "package.json")
|
||||
buf, err := r.r.getSource(p)
|
||||
if err != nil {
|
||||
return r.loadIndex(modpath)
|
||||
}
|
||||
var pkg struct {
|
||||
Main string
|
||||
}
|
||||
err = json.Unmarshal(buf, &pkg)
|
||||
if err != nil || len(pkg.Main) == 0 {
|
||||
return r.loadIndex(modpath)
|
||||
}
|
||||
|
||||
m := path.Join(modpath, pkg.Main)
|
||||
if module, err = r.loadAsFile(m); module != nil || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return r.loadIndex(m)
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadNodeModule(modpath, start string) (*js.Object, error) {
|
||||
return r.loadAsFileOrDirectory(path.Join(start, modpath))
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadNodeModules(modpath, start string) (module *js.Object, err error) {
|
||||
for _, dir := range r.r.globalFolders {
|
||||
if module, err = r.loadNodeModule(modpath, dir); module != nil || err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
for {
|
||||
var p string
|
||||
if path.Base(start) != "node_modules" {
|
||||
p = path.Join(start, "node_modules")
|
||||
} else {
|
||||
p = start
|
||||
}
|
||||
if module, err = r.loadNodeModule(modpath, p); module != nil || err != nil {
|
||||
return
|
||||
}
|
||||
if start == ".." { // Dir('..') is '.'
|
||||
break
|
||||
}
|
||||
parent := path.Dir(start)
|
||||
if parent == start {
|
||||
break
|
||||
}
|
||||
start = parent
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RequireModule) getCurrentModulePath() string {
|
||||
var buf [2]js.StackFrame
|
||||
frames := r.runtime.CaptureCallStack(2, buf[:0])
|
||||
if len(frames) < 2 {
|
||||
return "."
|
||||
}
|
||||
return path.Dir(frames[1].SrcName())
|
||||
}
|
||||
|
||||
func (r *RequireModule) createModuleObject() *js.Object {
|
||||
module := r.runtime.NewObject()
|
||||
module.Set("exports", r.runtime.NewObject())
|
||||
return module
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadModule(path string) (*js.Object, error) {
|
||||
module := r.modules[path]
|
||||
if module == nil {
|
||||
module = r.createModuleObject()
|
||||
r.modules[path] = module
|
||||
err := r.loadModuleFile(path, module)
|
||||
if err != nil {
|
||||
module = nil
|
||||
delete(r.modules, path)
|
||||
if errors.Is(err, ModuleFileDoesNotExistError) {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
return module, err
|
||||
}
|
||||
return module, nil
|
||||
}
|
||||
|
||||
func (r *RequireModule) loadModuleFile(path string, jsModule *js.Object) error {
|
||||
prg, err := r.r.getCompiledSource(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := r.runtime.RunProgram(prg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if call, ok := js.AssertFunction(f); ok {
|
||||
jsExports := jsModule.Get("exports")
|
||||
jsRequire := r.runtime.Get("require")
|
||||
|
||||
// Run the module source, with "jsExports" as "this",
|
||||
// "jsExports" as the "exports" variable, "jsRequire"
|
||||
// as the "require" variable and "jsModule" as the
|
||||
// "module" variable (Nodejs capable).
|
||||
_, err = call(jsExports, jsExports, jsRequire, jsModule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return InvalidModuleError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isFileOrDirectoryPath(path string) bool {
|
||||
result := path == "." || path == ".." ||
|
||||
strings.HasPrefix(path, "/") ||
|
||||
strings.HasPrefix(path, "./") ||
|
||||
strings.HasPrefix(path, "../")
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
result = result ||
|
||||
strings.HasPrefix(path, `.\`) ||
|
||||
strings.HasPrefix(path, `..\`) ||
|
||||
filepath.IsAbs(path)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user