Add garble support (#1793)

Co-authored-by: AlbinoDrought <sean@albinodrought.com>
Co-authored-by: stffabi <stffabi@users.noreply.github.com>
This commit is contained in:
Lea Anthony
2022-09-13 10:05:37 +10:00
committed by GitHub
co-authored by AlbinoDrought stffabi
parent eef99ee577
commit 052b9222c1
37 changed files with 866 additions and 293 deletions
+37 -17
View File
@@ -2,6 +2,7 @@ package build
import (
"fmt"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"io"
"os"
"os/exec"
@@ -75,7 +76,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
// tags to pass to `go`
tags := ""
command.StringFlag("tags", "tags to pass to Go compiler (quoted and space separated)", &tags)
command.StringFlag("tags", "Build tags to pass to Go compiler. Must be quoted. Space or comma (but not both) separated", &tags)
outputFilename := ""
command.StringFlag("o", "Output filename", &outputFilename)
@@ -111,9 +112,18 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
windowsConsole := false
command.BoolFlag("windowsconsole", "Keep the console when building for Windows", &windowsConsole)
obfuscated := false
command.BoolFlag("obfuscated", "Code obfuscation of bound Wails methods", &obfuscated)
garbleargs := "-literals -tiny -seed=random"
command.StringFlag("garbleargs", "Arguments to pass to garble", &garbleargs)
dryRun := false
command.BoolFlag("dryrun", "Dry run, prints the config for the command that would be executed", &dryRun)
skipBindings := false
command.BoolFlag("skipbindings", "Skips generation of bindings", &skipBindings)
command.Action(func() error {
quiet := verbosity == 0
@@ -137,13 +147,10 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
return fmt.Errorf("unable to find compiler: %s", compilerCommand)
}
// Tags
userTags := []string{}
for _, tag := range strings.Split(tags, " ") {
thisTag := strings.TrimSpace(tag)
if thisTag != "" {
userTags = append(userTags, thisTag)
}
// Process User Tags
userTags, err := buildtags.Parse(tags)
if err != nil {
return err
}
// Webview2 installer strategy (download by default)
@@ -176,6 +183,15 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
targets.AddSlice(strings.Split(platform, ","))
targets.Deduplicate()
cwd, err := os.Getwd()
if err != nil {
return err
}
projectOptions, err := project.Load(cwd)
if err != nil {
return err
}
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
@@ -197,6 +213,9 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
TrimPath: trimpath,
RaceDetector: raceDetector,
WindowsConsole: windowsConsole,
Obfuscated: obfuscated,
GarbleArgs: garbleargs,
SkipBindings: skipBindings,
}
// Start a new tabwriter
@@ -208,7 +227,12 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
_, _ = fmt.Fprintf(w, "App Type: \t%s\n", buildOptions.OutputType)
_, _ = fmt.Fprintf(w, "Platforms: \t%s\n", platform)
_, _ = fmt.Fprintf(w, "Compiler: \t%s\n", compilerPath)
_, _ = fmt.Fprintf(w, "Skip Bindings: \t%t\n", skipBindings)
_, _ = fmt.Fprintf(w, "Build Mode: \t%s\n", modeString)
_, _ = fmt.Fprintf(w, "Obfuscated: \t%t\n", buildOptions.Obfuscated)
if buildOptions.Obfuscated {
_, _ = fmt.Fprintf(w, "Garble Args: \t%s\n", buildOptions.GarbleArgs)
}
_, _ = fmt.Fprintf(w, "Skip Frontend: \t%t\n", skipFrontend)
_, _ = fmt.Fprintf(w, "Compress: \t%t\n", buildOptions.Compress)
_, _ = fmt.Fprintf(w, "Package: \t%t\n", buildOptions.Pack)
@@ -230,15 +254,6 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
return err
}
cwd, err := os.Getwd()
if err != nil {
return err
}
projectOptions, err := project.Load(cwd)
if err != nil {
return err
}
// Check platform
validPlatformArch := slicer.String([]string{
"darwin",
@@ -329,6 +344,11 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
buildOptions.OutputFile = outputFilename
}
if obfuscated && skipBindings {
logger.Println("Warning: obfuscated flag overrides skipbindings flag.")
buildOptions.SkipBindings = false
}
if !dryRun {
// Start Time
start := time.Now()
+28 -25
View File
@@ -4,6 +4,8 @@ import (
"context"
"errors"
"fmt"
"github.com/wailsapp/wails/v2/pkg/commands/bindings"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"io"
"net"
"net/http"
@@ -77,7 +79,7 @@ type devFlags struct {
reloadDirs string
openBrowser bool
noReload bool
noGen bool
skipBindings bool
wailsjsdir string
tags string
verbosity int
@@ -106,9 +108,9 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
command.StringFlag("reloaddirs", "Additional directories to trigger reloads (comma separated)", &flags.reloadDirs)
command.BoolFlag("browser", "Open application in browser", &flags.openBrowser)
command.BoolFlag("noreload", "Disable reload on asset change", &flags.noReload)
command.BoolFlag("nogen", "Disable generate module", &flags.noGen)
command.BoolFlag("skipbindings", "Skip bindings generation", &flags.skipBindings)
command.StringFlag("wailsjsdir", "Directory to generate the Wails JS modules", &flags.wailsjsdir)
command.StringFlag("tags", "tags to pass to Go compiler (quoted and space separated)", &flags.tags)
command.StringFlag("tags", "Build tags to pass to Go compiler. Must be quoted. Space or comma (but not both) separated", &flags.tags)
command.IntFlag("v", "Verbosity level (0 - silent, 1 - standard, 2 - verbose)", &flags.verbosity)
command.StringFlag("loglevel", "Loglevel to use - Trace, Debug, Info, Warning, Error", &flags.loglevel)
command.BoolFlag("f", "Force build application", &flags.forceBuild)
@@ -125,14 +127,6 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
logger := clilogger.New(w)
app.PrintBanner()
userTags := []string{}
for _, tag := range strings.Split(flags.tags, " ") {
thisTag := strings.TrimSpace(tag)
if thisTag != "" {
userTags = append(userTags, thisTag)
}
}
cwd, err := os.Getwd()
if err != nil {
return err
@@ -159,28 +153,37 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
return err
}
// Run go mod tidy to ensure we're up to date
err = runCommand(cwd, false, "go", "mod", "tidy", "-compat=1.17")
// Run go mod tidy to ensure we're up-to-date
err = runCommand(cwd, false, "go", "mod", "tidy")
if err != nil {
return err
}
if !flags.noGen {
self := os.Args[0]
if flags.tags != "" {
err = runCommand(cwd, true, self, "generate", "module", "-tags", flags.tags)
} else {
err = runCommand(cwd, true, self, "generate", "module")
buildOptions := generateBuildOptions(flags)
buildOptions.Logger = logger
userTags, err := buildtags.Parse(flags.tags)
if err != nil {
return err
}
buildOptions.UserTags = userTags
if !flags.skipBindings {
if flags.verbosity == build.VERBOSE {
LogGreen("Generating Bindings...")
}
stdout, err := bindings.GenerateBindings(bindings.Options{
Tags: buildOptions.UserTags,
})
if err != nil {
return err
}
if flags.verbosity == build.VERBOSE {
LogGreen(stdout)
}
}
buildOptions := generateBuildOptions(flags)
buildOptions.Logger = logger
buildOptions.UserTags = internal.ParseUserTags(flags.tags)
// Setup signal handler
quitChannel := make(chan os.Signal, 1)
signal.Notify(quitChannel, os.Interrupt, os.Kill, syscall.SIGTERM)
@@ -269,7 +272,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
return err
}
// Reset the process and the binary so the defer knows about it and is a nop.
// Reset the process and the binary so defer knows about it and is a nop.
debugBinaryProcess = nil
appBinary = ""
@@ -654,7 +657,7 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
}
if flags.frontendDevServerURL != "" {
// If we are using an external dev server all the reload of the frontend part can be skipped
// If we are using an external dev server, the reloading of the frontend part can be skipped
continue
}
if len(changedPaths) != 0 {
@@ -1,16 +1,10 @@
package generate
import (
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/cmd/wails/internal"
"github.com/wailsapp/wails/v2/internal/shell"
"github.com/wailsapp/wails/v2/pkg/commands/bindings"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"io"
)
// AddModuleCommand adds the `module` subcommand for the `generate` command
@@ -22,37 +16,18 @@ func AddModuleCommand(app *clir.Cli, parent *clir.Command, w io.Writer) error {
command.Action(func() error {
filename := "wailsbindings"
if runtime.GOOS == "windows" {
filename += ".exe"
}
// go build -tags bindings -o bindings.exe
tempDir := os.TempDir()
filename = filepath.Join(tempDir, filename)
cwd, err := os.Getwd()
buildTags, err := buildtags.Parse(tags)
if err != nil {
return err
}
tagList := internal.ParseUserTags(tags)
tagList = append(tagList, "bindings")
stdout, stderr, err := shell.RunCommand(cwd, "go", "build", "-tags", strings.Join(tagList, ","), "-o", filename)
_, err = bindings.GenerateBindings(bindings.Options{
Tags: buildTags,
})
if err != nil {
return fmt.Errorf("%s\n%s\n%s", stdout, stderr, err)
return err
}
stdout, stderr, err = shell.RunCommand(cwd, filename)
println(stdout)
println(stderr)
if err != nil {
return fmt.Errorf("%s\n%s\n%s", stdout, stderr, err)
}
// Best effort removal of temp file
_ = os.Remove(filename)
return nil
})
return nil
@@ -186,7 +186,7 @@ func Install(options *Options) (bool, *Template, error) {
}
} else {
// Get the absolute path of the given directory
targetDir, err := filepath.Abs(filepath.Join(cwd, options.TargetDir))
targetDir, err := filepath.Abs(options.TargetDir)
if err != nil {
return false, nil, err
}
-15
View File
@@ -1,15 +0,0 @@
package internal
import "strings"
// ParseUserTags takes the string form of tags and converts to a slice of strings
func ParseUserTags(tagString string) []string {
userTags := make([]string, 0)
for _, tag := range strings.Split(tagString, " ") {
thisTag := strings.TrimSpace(tag)
if thisTag != "" {
userTags = append(userTags, thisTag)
}
}
return userTags
}
+2 -1
View File
@@ -31,7 +31,8 @@ func (a *App) Run() error {
a.appoptions.OnDomReady,
a.appoptions.OnBeforeClose,
}
appBindings := binding.NewBindings(a.logger, a.appoptions.Bind, bindingExemptions)
appBindings := binding.NewBindings(a.logger, a.appoptions.Bind, bindingExemptions, IsObfuscated())
err := generateBindings(appBindings)
if err != nil {
+1 -1
View File
@@ -191,7 +191,7 @@ func CreateApp(appoptions *options.App) (*App, error) {
appoptions.OnDomReady,
appoptions.OnBeforeClose,
}
appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions)
appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, false)
err = generateBindings(appBindings)
if err != nil {
+8
View File
@@ -0,0 +1,8 @@
//go:build !obfuscated
package app
// IsObfuscated returns false if the obfuscated build tag is not set
func IsObfuscated() bool {
return false
}
+8
View File
@@ -0,0 +1,8 @@
//go:build obfuscated
package app
// IsObfuscated returns true if the obfuscated build tag is set
func IsObfuscated() bool {
return true
}
+2 -2
View File
@@ -1,5 +1,4 @@
//go:build production
// +build production
package app
@@ -65,6 +64,7 @@ func CreateApp(appoptions *options.App) (*App, error) {
myLogger.SetLogLevel(appoptions.LogLevelProduction)
}
ctx = context.WithValue(ctx, "logger", myLogger)
ctx = context.WithValue(ctx, "obfuscated", IsObfuscated())
// Preflight Checks
err = PreflightChecks(appoptions, myLogger)
@@ -90,7 +90,7 @@ func CreateApp(appoptions *options.App) (*App, error) {
appoptions.OnDomReady,
appoptions.OnBeforeClose,
}
appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions)
appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, IsObfuscated())
eventHandler := runtime.NewEvents(myLogger)
ctx = context.WithValue(ctx, "events", eventHandler)
// Attach logger to context
+3 -1
View File
@@ -22,14 +22,16 @@ type Bindings struct {
exemptions slicer.StringSlicer
structsToGenerateTS map[string]map[string]interface{}
obfuscate bool
}
// NewBindings returns a new Bindings object
func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}) *Bindings {
func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}, obfuscate bool) *Bindings {
result := &Bindings{
db: newDB(),
logger: logger.CustomLogger("Bindings"),
structsToGenerateTS: make(map[string]map[string]interface{}),
obfuscate: obfuscate,
}
for _, exemption := range exemptions {
+40 -5
View File
@@ -2,6 +2,7 @@ package binding
import (
"encoding/json"
"sort"
"sync"
"unsafe"
)
@@ -15,14 +16,18 @@ type DB struct {
// It used for performance gains at runtime
methodMap map[string]*BoundMethod
// This uses ids to reference bound methods at runtime
obfuscatedMethodMap map[int]*BoundMethod
// Lock to ensure sync access to the data
lock sync.RWMutex
}
func newDB() *DB {
return &DB{
store: make(map[string]map[string]map[string]*BoundMethod),
methodMap: make(map[string]*BoundMethod),
store: make(map[string]map[string]map[string]*BoundMethod),
methodMap: make(map[string]*BoundMethod),
obfuscatedMethodMap: make(map[int]*BoundMethod),
}
}
@@ -56,11 +61,18 @@ func (d *DB) GetMethod(qualifiedMethodName string) *BoundMethod {
return d.methodMap[qualifiedMethodName]
}
// GetObfuscatedMethod returns the method for the given ID
func (d *DB) GetObfuscatedMethod(id int) *BoundMethod {
// Lock the db whilst processing and unlock on return
d.lock.RLock()
defer d.lock.RUnlock()
return d.obfuscatedMethodMap[id]
}
// AddMethod adds the given method definition to the db using the given qualified path: packageName.structName.methodName
func (d *DB) AddMethod(packageName string, structName string, methodName string, methodDefinition *BoundMethod) {
// TODO: Validate inputs?
// Lock the db whilst processing and unlock on return
d.lock.Lock()
defer d.lock.Unlock()
@@ -97,8 +109,31 @@ func (d *DB) ToJSON() (string, error) {
d.lock.RLock()
defer d.lock.RUnlock()
d.UpdateObfuscatedCallMap()
bytes, err := json.Marshal(&d.store)
// Return zero copy string as this string will be read only
return *(*string)(unsafe.Pointer(&bytes)), err
result := *(*string)(unsafe.Pointer(&bytes))
return result, err
}
// UpdateObfuscatedCallMap sets up the secure call mappings
func (d *DB) UpdateObfuscatedCallMap() map[string]int {
var mappings = make(map[string]int)
// Iterate map keys and sort them
keys := make([]string, 0, len(d.methodMap))
for k := range d.methodMap {
keys = append(keys, k)
}
sort.Strings(keys)
// Iterate sorted keys and add to obfuscated method map
for id, k := range keys {
mappings[k] = id
d.obfuscatedMethodMap[id] = d.methodMap[k]
}
return mappings
}
+10 -1
View File
@@ -16,6 +16,10 @@ import (
func (b *Bindings) GenerateGoBindings(baseDir string) error {
store := b.db.store
var obfuscatedBindings map[string]int
if b.obfuscate {
obfuscatedBindings = b.db.UpdateObfuscatedCallMap()
}
for packageName, structs := range store {
packageDir := filepath.Join(baseDir, packageName)
err := fs.Mkdir(packageDir)
@@ -54,7 +58,12 @@ func (b *Bindings) GenerateGoBindings(baseDir string) error {
argsString := args.Join(", ")
jsoutput.WriteString(fmt.Sprintf("\nexport function %s(%s) {", methodName, argsString))
jsoutput.WriteString("\n")
jsoutput.WriteString(fmt.Sprintf(" return window['go']['%s']['%s']['%s'](%s);", packageName, structName, methodName, argsString))
if b.obfuscate {
id := obfuscatedBindings[strings.Join([]string{packageName, structName, methodName}, ".")]
jsoutput.WriteString(fmt.Sprintf(" return ObfuscatedCall(%d, [%s]);", id, argsString))
} else {
jsoutput.WriteString(fmt.Sprintf(" return window['go']['%s']['%s']['%s'](%s);", packageName, structName, methodName, argsString))
}
jsoutput.WriteString("\n")
jsoutput.WriteString(fmt.Sprintf("}"))
jsoutput.WriteString("\n")
+1 -1
View File
@@ -25,7 +25,7 @@ type B struct {
func TestNestedStruct(t *testing.T) {
bind := &BindForTest{}
testBindings := NewBindings(logger.New(nil), []interface{}{bind}, []interface{}{})
testBindings := NewBindings(logger.New(nil), []interface{}{bind}, []interface{}{}, false)
namesStrSlicer := testBindings.getAllStructNames()
names := []string{}
@@ -42,7 +42,9 @@ func NewAssetServer(ctx context.Context, options *options.App, bindingsJSON stri
func NewAssetServerWithHandler(ctx context.Context, handler http.Handler, bindingsJSON string) (*AssetServer, error) {
var buffer bytes.Buffer
buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n")
if bindingsJSON != "" {
buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n")
}
buffer.Write(runtime.RuntimeDesktopJS)
result := &AssetServer{
@@ -72,12 +72,17 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil {
result.startURL = _starturl
} else {
bindingsJSON, err := appBindings.ToJSON()
if err != nil {
log.Fatal(err)
var bindings string
var err error
if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated {
bindings, err = appBindings.ToJSON()
if err != nil {
log.Fatal(err)
}
} else {
appBindings.DB().UpdateObfuscatedCallMap()
}
assets, err := assetserver.NewAssetServer(ctx, appoptions, bindingsJSON)
assets, err := assetserver.NewAssetServer(ctx, appoptions, bindings)
if err != nil {
log.Fatal(err)
}
+10 -5
View File
@@ -75,12 +75,17 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil {
result.startURL = _starturl
} else {
bindingsJSON, err := appBindings.ToJSON()
if err != nil {
log.Fatal(err)
var bindings string
var err error
if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated {
bindings, err = appBindings.ToJSON()
if err != nil {
log.Fatal(err)
}
} else {
appBindings.DB().UpdateObfuscatedCallMap()
}
assets, err := assetserver.NewAssetServer(ctx, appoptions, bindingsJSON)
assets, err := assetserver.NewAssetServer(ctx, appoptions, bindings)
if err != nil {
log.Fatal(err)
}
@@ -91,12 +91,18 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
return result
}
bindingsJSON, err := appBindings.ToJSON()
if err != nil {
log.Fatal(err)
var bindings string
var err error
if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated {
bindings, err = appBindings.ToJSON()
if err != nil {
log.Fatal(err)
}
} else {
appBindings.DB().UpdateObfuscatedCallMap()
}
assets, err := assetserver.NewAssetServer(ctx, appoptions, bindingsJSON)
assets, err := assetserver.NewAssetServer(ctx, appoptions, bindings)
if err != nil {
log.Fatal(err)
}
@@ -38,6 +38,8 @@ func (d *Dispatcher) ProcessMessage(message string, sender frontend.Frontend) (s
return d.processEventMessage(message, sender)
case 'C':
return d.processCallMessage(message, sender)
case 'c':
return d.processSecureCallMessage(message, sender)
case 'W':
return d.processWindowMessage(message, sender)
case 'B':
@@ -0,0 +1,57 @@
package dispatcher
import (
"encoding/json"
"fmt"
"github.com/wailsapp/wails/v2/internal/frontend"
)
type secureCallMessage struct {
ID int `json:"id"`
Args []json.RawMessage `json:"args"`
CallbackID string `json:"callbackID"`
}
func (d *Dispatcher) processSecureCallMessage(message string, sender frontend.Frontend) (string, error) {
var payload secureCallMessage
err := json.Unmarshal([]byte(message[1:]), &payload)
if err != nil {
return "", err
}
var result interface{}
// Lookup method
registeredMethod := d.bindingsDB.GetObfuscatedMethod(payload.ID)
// Check we have it
if registeredMethod == nil {
return "", fmt.Errorf("method '%d' not registered", payload.ID)
}
args, err2 := registeredMethod.ParseArgs(payload.Args)
if err2 != nil {
errmsg := fmt.Errorf("error parsing arguments: %s", err2.Error())
result, _ := d.NewErrorCallback(errmsg.Error(), payload.CallbackID)
return result, errmsg
}
result, err = registeredMethod.Call(args)
callbackMessage := &CallbackMessage{
CallbackID: payload.CallbackID,
}
if err != nil {
callbackMessage.Err = err.Error()
} else {
callbackMessage.Result = result
}
messageData, err := json.Marshal(callbackMessage)
d.log.Trace("json call result data: %+v\n", string(messageData))
if err != nil {
// what now?
d.log.Fatal(err.Error())
}
return "c" + string(messageData), nil
}

Some files were not shown because too many files have changed in this diff Show More