mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-07-09 21:58:42 +02:00
f5e6f53d75
Signed-off-by: Ronni Skansing <rskansing@gmail.com>
380 lines
7.5 KiB
Go
380 lines
7.5 KiB
Go
// Package utils ...
|
|
package utils
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"image"
|
|
"image/jpeg"
|
|
"image/png"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/ysmood/gson"
|
|
)
|
|
|
|
// TestEnvs for testing.
|
|
var TestEnvs = map[string]string{
|
|
"GODEBUG": "tracebackancestors=100",
|
|
}
|
|
|
|
// InContainer will be true if is inside container environment, such as docker.
|
|
var InContainer = FileExists("/.dockerenv") || FileExists("/.containerenv") ||
|
|
os.Getenv("KUBERNETES_SERVICE_HOST") != ""
|
|
|
|
// Noop does nothing.
|
|
func Noop() {}
|
|
|
|
// Logger interface.
|
|
type Logger interface {
|
|
// Same as fmt.Printf
|
|
Println(vs ...interface{})
|
|
}
|
|
|
|
// Log type for Println.
|
|
type Log func(msg ...interface{})
|
|
|
|
// Println interface.
|
|
func (l Log) Println(msg ...interface{}) {
|
|
l(msg...)
|
|
}
|
|
|
|
// LoggerQuiet does nothing.
|
|
var LoggerQuiet Logger = Log(func(_ ...interface{}) {})
|
|
|
|
// MultiLogger is similar to https://golang.org/pkg/io/#MultiWriter
|
|
func MultiLogger(list ...Logger) Log {
|
|
return Log(func(msg ...interface{}) {
|
|
for _, lg := range list {
|
|
lg.Println(msg...)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Panic is the same as the built-in panic.
|
|
var Panic = func(v interface{}) { panic(v) }
|
|
|
|
// E if the last arg is error, panic it.
|
|
func E(args ...interface{}) []interface{} {
|
|
err, ok := args[len(args)-1].(error)
|
|
if ok {
|
|
Panic(err)
|
|
}
|
|
return args
|
|
}
|
|
|
|
// S Template render, the params is key-value pairs.
|
|
func S(tpl string, params ...interface{}) string {
|
|
var out bytes.Buffer
|
|
|
|
dict := map[string]interface{}{}
|
|
fnDict := template.FuncMap{}
|
|
|
|
l := len(params)
|
|
for i := 0; i < l-1; i += 2 {
|
|
k := params[i].(string) //nolint: forcetypeassert
|
|
v := params[i+1]
|
|
if reflect.TypeOf(v).Kind() == reflect.Func {
|
|
fnDict[k] = v
|
|
} else {
|
|
dict[k] = v
|
|
}
|
|
}
|
|
|
|
t := template.Must(template.New("").Funcs(fnDict).Parse(tpl))
|
|
E(t.Execute(&out, dict))
|
|
|
|
return out.String()
|
|
}
|
|
|
|
// RandString generate random string with specified string length.
|
|
func RandString(l int) string {
|
|
b := make([]byte, l)
|
|
_, _ = rand.Read(b)
|
|
return hex.EncodeToString(b)
|
|
}
|
|
|
|
// Mkdir makes dir recursively.
|
|
func Mkdir(path string) error {
|
|
return os.MkdirAll(path, 0o775)
|
|
}
|
|
|
|
// AbsolutePaths returns absolute paths of files in current working directory.
|
|
func AbsolutePaths(paths []string) []string {
|
|
absPaths := []string{}
|
|
for _, p := range paths {
|
|
absPath, err := filepath.Abs(p)
|
|
E(err)
|
|
absPaths = append(absPaths, absPath)
|
|
}
|
|
return absPaths
|
|
}
|
|
|
|
// OutputFile auto creates file if not exists, it will try to detect the data type and
|
|
// auto output binary, string or json.
|
|
func OutputFile(p string, data interface{}) error {
|
|
dir := filepath.Dir(p)
|
|
_ = Mkdir(dir)
|
|
|
|
var bin []byte
|
|
|
|
switch t := data.(type) {
|
|
case []byte:
|
|
bin = t
|
|
case string:
|
|
bin = []byte(t)
|
|
case io.Reader:
|
|
f, _ := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o664)
|
|
_, err := io.Copy(f, t)
|
|
return err
|
|
default:
|
|
bin = MustToJSONBytes(data)
|
|
}
|
|
|
|
return os.WriteFile(p, bin, 0o664)
|
|
}
|
|
|
|
// ReadString reads file as string.
|
|
func ReadString(p string) (string, error) {
|
|
bin, err := os.ReadFile(p)
|
|
return string(bin), err
|
|
}
|
|
|
|
// All runs all actions concurrently, returns the wait function for all actions.
|
|
func All(actions ...func()) func() {
|
|
wg := &sync.WaitGroup{}
|
|
|
|
wg.Add(len(actions))
|
|
|
|
runner := func(action func()) {
|
|
defer wg.Done()
|
|
action()
|
|
}
|
|
|
|
for _, action := range actions {
|
|
go runner(action)
|
|
}
|
|
|
|
return wg.Wait
|
|
}
|
|
|
|
// IdleCounter is similar to sync.WaitGroup but it only resolves if no jobs for specified duration.
|
|
type IdleCounter struct {
|
|
lock *sync.Mutex
|
|
job int
|
|
duration time.Duration
|
|
tmr *time.Timer
|
|
}
|
|
|
|
// NewIdleCounter ...
|
|
func NewIdleCounter(d time.Duration) *IdleCounter {
|
|
tmr := time.NewTimer(time.Hour)
|
|
tmr.Stop()
|
|
|
|
return &IdleCounter{
|
|
lock: &sync.Mutex{},
|
|
duration: d,
|
|
tmr: tmr,
|
|
}
|
|
}
|
|
|
|
// Add ...
|
|
func (de *IdleCounter) Add() {
|
|
de.lock.Lock()
|
|
defer de.lock.Unlock()
|
|
|
|
de.tmr.Stop()
|
|
de.job++
|
|
}
|
|
|
|
// Done ...
|
|
func (de *IdleCounter) Done() {
|
|
de.lock.Lock()
|
|
defer de.lock.Unlock()
|
|
|
|
de.job--
|
|
if de.job == 0 {
|
|
de.reset(de.duration)
|
|
}
|
|
if de.job < 0 {
|
|
panic("all jobs are already done")
|
|
}
|
|
}
|
|
|
|
// Wait ...
|
|
func (de *IdleCounter) Wait(ctx context.Context) {
|
|
de.lock.Lock()
|
|
if de.job == 0 {
|
|
de.reset(de.duration)
|
|
}
|
|
de.lock.Unlock()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
de.tmr.Stop()
|
|
case <-de.tmr.C:
|
|
}
|
|
}
|
|
|
|
func (de *IdleCounter) reset(d time.Duration) {
|
|
if !de.tmr.Stop() {
|
|
select {
|
|
case <-de.tmr.C:
|
|
default:
|
|
}
|
|
}
|
|
de.tmr.Reset(d)
|
|
}
|
|
|
|
var chPause = make(chan struct{})
|
|
|
|
// Pause the goroutine forever.
|
|
func Pause() {
|
|
<-chPause
|
|
}
|
|
|
|
// Dump values for debugging.
|
|
func Dump(list ...interface{}) string {
|
|
out := []string{}
|
|
for _, el := range list {
|
|
out = append(out, gson.New(el).JSON("", " "))
|
|
}
|
|
return strings.Join(out, " ")
|
|
}
|
|
|
|
// MustToJSONBytes encode data to json bytes.
|
|
func MustToJSONBytes(data interface{}) []byte {
|
|
buf := bytes.NewBuffer(nil)
|
|
enc := json.NewEncoder(buf)
|
|
enc.SetEscapeHTML(false)
|
|
E(enc.Encode(data))
|
|
b := buf.Bytes()
|
|
return b[:len(b)-1]
|
|
}
|
|
|
|
// MustToJSON encode data to json string.
|
|
func MustToJSON(data interface{}) string {
|
|
return string(MustToJSONBytes(data))
|
|
}
|
|
|
|
// FileExists checks if file exists, only for file, not for dir.
|
|
func FileExists(path string) bool {
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if info.IsDir() {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
var regSpace = regexp.MustCompile(`\s`)
|
|
|
|
// Exec command.
|
|
func Exec(line string, rest ...string) string {
|
|
return ExecLine(true, line, rest...)
|
|
}
|
|
|
|
var execLogger = log.New(os.Stdout, "[exec] ", 0)
|
|
|
|
// ExecLine of command.
|
|
func ExecLine(std bool, line string, rest ...string) string {
|
|
args := rest
|
|
if line != "" {
|
|
args = append(regSpace.Split(line, -1), rest...)
|
|
}
|
|
|
|
execLogger.Println(FormatCLIArgs(args))
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
|
|
cmd := exec.Command(args[0], args[1:]...)
|
|
cmd.Stderr = buf
|
|
cmd.Stdout = buf
|
|
|
|
if std {
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Stderr = io.MultiWriter(buf, os.Stderr)
|
|
cmd.Stdout = io.MultiWriter(buf, os.Stdout)
|
|
}
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
if std {
|
|
panic(err)
|
|
}
|
|
panic(fmt.Sprintf("%v\n%v", err, buf.String()))
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// UseNode installs Node.js and set the bin path to PATH env var.
|
|
func UseNode(std bool) {
|
|
binPath := strings.TrimSpace(ExecLine(std, "go run github.com/ysmood/use-node@latest -p v20"))
|
|
E(os.Setenv("PATH", binPath+string(os.PathListSeparator)+os.Getenv("PATH")))
|
|
}
|
|
|
|
// FormatCLIArgs into one line string.
|
|
func FormatCLIArgs(args []string) string {
|
|
list := []string{}
|
|
for _, arg := range args {
|
|
if regSpace.MatchString(arg) {
|
|
list = append(list, fmt.Sprintf("%#v", arg))
|
|
} else {
|
|
list = append(list, arg)
|
|
}
|
|
}
|
|
return strings.Join(list, " ")
|
|
}
|
|
|
|
// EscapeGoString not using encoding like base64 or gzip because of they will
|
|
// make git diff every large for small change.
|
|
func EscapeGoString(s string) string {
|
|
return "`" + strings.ReplaceAll(s, "`", "` + \"`\" + `") + "`"
|
|
}
|
|
|
|
// CropImage by the specified box, quality is only for jpeg bin.
|
|
func CropImage(bin []byte, quality, x, y, width, height int) ([]byte, error) {
|
|
img, typ, err := image.Decode(bytes.NewBuffer(bin))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cropped := bytes.NewBuffer(nil)
|
|
|
|
img = img.(interface { //nolint: forcetypeassert
|
|
SubImage(r image.Rectangle) image.Image
|
|
}).SubImage(image.Rect(
|
|
x, y, x+width, y+height,
|
|
))
|
|
|
|
switch typ {
|
|
case "png":
|
|
err = png.Encode(cropped, img)
|
|
case "jpeg":
|
|
if quality == 0 {
|
|
quality = 80
|
|
}
|
|
|
|
err = jpeg.Encode(cropped, img, &jpeg.Options{Quality: quality})
|
|
}
|
|
|
|
return cropped.Bytes(), err
|
|
}
|