github.com/teknogeek/dnscontrol/v2@v2.10.1-0.20200227202244-ae299b55ba42/pkg/js/js.go (about) 1 package js 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "path/filepath" 8 "strings" 9 10 "github.com/robertkrimen/otto" // load underscore js into vm by default 11 _ "github.com/robertkrimen/otto/underscore" // required by otto 12 13 "github.com/StackExchange/dnscontrol/v2/models" 14 "github.com/StackExchange/dnscontrol/v2/pkg/printer" 15 "github.com/StackExchange/dnscontrol/v2/pkg/transform" 16 ) 17 18 // currentDirectory is the current directory as used by require(). 19 // This is used to emulate nodejs-style require() directory handling. 20 // If require("a/b/c.js") is called, any require() statement in c.js 21 // needs to be accessed relative to "a/b". Therefore we 22 // track the currentDirectory (which is the current directory as 23 // far as require() is concerned, not the actual os.Getwd(). 24 var currentDirectory string 25 26 // ExecuteJavascript accepts a javascript string and runs it, returning the resulting dnsConfig. 27 func ExecuteJavascript(file string, devMode bool) (*models.DNSConfig, error) { 28 script, err := ioutil.ReadFile(file) 29 if err != nil { 30 return nil, fmt.Errorf("Reading js file %s: %s", file, err) 31 } 32 33 // Record the directory path leading up to this file. 34 currentDirectory = filepath.Clean(filepath.Dir(file)) 35 36 vm := otto.New() 37 38 vm.Set("require", require) 39 vm.Set("REV", reverse) 40 41 helperJs := GetHelpers(devMode) 42 // run helper script to prime vm and initialize variables 43 if _, err := vm.Run(helperJs); err != nil { 44 return nil, err 45 } 46 47 // run user script 48 if _, err := vm.Run(script); err != nil { 49 return nil, err 50 } 51 52 // export conf as string and unmarshal 53 value, err := vm.Run(`JSON.stringify(conf)`) 54 if err != nil { 55 return nil, err 56 } 57 str, err := value.ToString() 58 if err != nil { 59 return nil, err 60 } 61 conf := &models.DNSConfig{} 62 if err = json.Unmarshal([]byte(str), conf); err != nil { 63 return nil, err 64 } 65 return conf, nil 66 } 67 68 // GetHelpers returns the filename of helpers.js, or the esc'ed version. 69 func GetHelpers(devMode bool) string { 70 return _escFSMustString(devMode, "/helpers.js") 71 } 72 73 func require(call otto.FunctionCall) otto.Value { 74 if len(call.ArgumentList) != 1 { 75 throw(call.Otto, "require takes exactly one argument") 76 } 77 file := call.Argument(0).String() // The filename as given by the user 78 79 // relFile is the file we're actually going to pass to ReadFile(). 80 // It defaults to the user-provided name unless it is relative. 81 relFile := file 82 cleanFile := filepath.Clean(filepath.Join(currentDirectory, file)) 83 if strings.HasPrefix(file, ".") { 84 relFile = cleanFile 85 } 86 87 // Record the old currentDirectory so that we can return there. 88 currentDirectoryOld := currentDirectory 89 // Record the directory path leading up to the file we're about to require. 90 currentDirectory = filepath.Clean(filepath.Dir(cleanFile)) 91 92 printer.Debugf("requiring: %s (%s)\n", file, relFile) 93 data, err := ioutil.ReadFile(relFile) 94 95 if err != nil { 96 throw(call.Otto, err.Error()) 97 } 98 99 var value otto.Value = otto.TrueValue() 100 101 // If its a json file return the json value, else default to true 102 if strings.HasSuffix(filepath.Ext(relFile), "json") { 103 cmd := fmt.Sprintf(`JSON.parse(JSON.stringify(%s))`, string(data)) 104 value, err = call.Otto.Run(cmd) 105 } else { 106 _, err = call.Otto.Run(string(data)) 107 } 108 109 if err != nil { 110 throw(call.Otto, err.Error()) 111 } 112 113 // Pop back to the old directory. 114 currentDirectory = currentDirectoryOld 115 116 return value 117 } 118 119 func throw(vm *otto.Otto, str string) { 120 panic(vm.MakeCustomError("Error", str)) 121 } 122 123 func reverse(call otto.FunctionCall) otto.Value { 124 if len(call.ArgumentList) != 1 { 125 throw(call.Otto, "REV takes exactly one argument") 126 } 127 dom := call.Argument(0).String() 128 rev, err := transform.ReverseDomainName(dom) 129 if err != nil { 130 throw(call.Otto, err.Error()) 131 } 132 v, _ := otto.ToValue(rev) 133 return v 134 }