You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
150 lines
3.5 KiB
Go
150 lines
3.5 KiB
Go
4 years ago
|
// Copyright (c) 2019, The Garble Authors.
|
||
|
// See LICENSE for licensing information.
|
||
|
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// commandReverse implements "garble reverse".
|
||
|
func commandReverse(args []string) error {
|
||
|
flags, args := splitFlagsFromArgs(args)
|
||
|
mainPkg := "."
|
||
|
if len(args) > 0 {
|
||
|
mainPkg = args[0]
|
||
|
args = args[1:]
|
||
|
}
|
||
|
|
||
|
listArgs := []string{
|
||
|
"-json",
|
||
|
"-deps",
|
||
|
"-export",
|
||
|
}
|
||
|
listArgs = append(listArgs, flags...)
|
||
|
listArgs = append(listArgs, mainPkg)
|
||
|
cmd, err := toolexecCmd("list", listArgs)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
stdout, err := cmd.StdoutPipe()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
var stderr bytes.Buffer
|
||
|
cmd.Stderr = &stderr
|
||
|
|
||
|
if err := cmd.Start(); err != nil {
|
||
|
return fmt.Errorf("go list error: %v", err)
|
||
|
}
|
||
|
mainPkgPath := ""
|
||
|
dec := json.NewDecoder(stdout)
|
||
|
var privatePkgPaths []string
|
||
|
for dec.More() {
|
||
|
var pkg listedPackage
|
||
|
if err := dec.Decode(&pkg); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if pkg.Export == "" {
|
||
|
continue
|
||
|
}
|
||
|
if pkg.Name == "main" {
|
||
|
if mainPkgPath != "" {
|
||
|
return fmt.Errorf("found two main packages: %s %s", mainPkgPath, pkg.ImportPath)
|
||
|
}
|
||
|
mainPkgPath = pkg.ImportPath
|
||
|
}
|
||
|
if isPrivate(pkg.ImportPath) {
|
||
|
privatePkgPaths = append(privatePkgPaths, pkg.ImportPath)
|
||
|
}
|
||
|
buildID, err := buildidOf(pkg.Export)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// Adding it to buildInfo.imports allows us to reuse the
|
||
|
// "if" branch below. Plus, if this edge case triggers
|
||
|
// multiple times in a single package compile, we can
|
||
|
// call "go list" once and cache its result.
|
||
|
buildInfo.imports[pkg.ImportPath] = importedPkg{
|
||
|
packagefile: pkg.Export,
|
||
|
actionID: decodeHash(splitActionID(buildID)),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if err := cmd.Wait(); err != nil {
|
||
|
return fmt.Errorf("go list error: %v: %s", err, stderr.Bytes())
|
||
|
}
|
||
|
|
||
|
var replaces []string
|
||
|
|
||
|
for _, pkgPath := range privatePkgPaths {
|
||
|
ipkg := buildInfo.imports[pkgPath]
|
||
|
|
||
|
// All original exported names names are hashed with the
|
||
|
// obfuscated package's action ID.
|
||
|
tpkg, err := origImporter.Import(pkgPath)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
pkgScope := tpkg.Scope()
|
||
|
for _, name := range pkgScope.Names() {
|
||
|
obj := pkgScope.Lookup(name)
|
||
|
if !obj.Exported() {
|
||
|
continue
|
||
|
}
|
||
|
replaces = append(replaces, hashWith(ipkg.actionID, name), name)
|
||
|
}
|
||
|
}
|
||
|
repl := strings.NewReplacer(replaces...)
|
||
|
|
||
|
// TODO: return a non-zero status code if we could not reverse any string.
|
||
|
if len(args) == 0 {
|
||
|
return reverseContent(os.Stdout, os.Stdin, repl)
|
||
|
}
|
||
|
for _, path := range args {
|
||
|
f, err := os.Open(path)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer f.Close()
|
||
|
if err := reverseContent(os.Stdout, f, repl); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
f.Close() // since we're in a loop
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func reverseContent(w io.Writer, r io.Reader, repl *strings.Replacer) error {
|
||
|
// Read line by line.
|
||
|
// Reading the entire content at once wouldn't be interactive,
|
||
|
// nor would it support large files well.
|
||
|
// Reading entire lines ensures we don't cut words in half.
|
||
|
// We use bufio.Reader instead of bufio.Scanner,
|
||
|
// to also obtain the newline characters themselves.
|
||
|
br := bufio.NewReader(r)
|
||
|
for {
|
||
|
// Note that ReadString can return a line as well as an error if
|
||
|
// we hit EOF without a newline.
|
||
|
// In that case, we still want to process the string.
|
||
|
line, readErr := br.ReadString('\n')
|
||
|
if _, err := repl.WriteString(w, line); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if readErr == io.EOF {
|
||
|
return nil
|
||
|
}
|
||
|
if readErr != nil {
|
||
|
return readErr
|
||
|
}
|
||
|
}
|
||
|
}
|