package main import ( "bufio" "encoding/hex" "fmt" "io" "log" "os" "os/exec" "strings" "sync" ) // Middleware represents a middleware object type Middleware struct { command string data chan []byte mu sync.Mutex Stdin io.Writer Stdout io.Reader stop chan bool // Channel used only to indicate goroutine should shutdown } // NewMiddleware returns new middleware func NewMiddleware(command string) *Middleware { m := new(Middleware) m.command = command m.data = make(chan []byte, 1000) m.stop = make(chan bool) commands := strings.Split(command, " ") cmd := exec.Command(commands[0], commands[1:]...) m.Stdout, _ = cmd.StdoutPipe() m.Stdin, _ = cmd.StdinPipe() cmd.Stderr = os.Stderr go m.read(m.Stdout) go func() { err := cmd.Start() if err != nil { log.Fatal(err) } err = cmd.Wait() if err != nil { log.Fatal(err) } }() return m } // ReadFrom start a worker to read from this plugin func (m *Middleware) ReadFrom(plugin io.Reader) { Debug(2, "[MIDDLEWARE-MASTER] Starting reading from", plugin) go m.copy(m.Stdin, plugin) } func (m *Middleware) copy(to io.Writer, from io.Reader) { buf := make([]byte, 5*1024*1024) dst := make([]byte, len(buf)*4) for { nr, _ := from.Read(buf) if nr == 0 || nr > len(buf) { continue } payload := buf[0:nr] if Settings.PrettifyHTTP { payload = prettifyHTTP(payload) nr = len(payload) if nr*2 > len(dst) { continue } } if Settings.PrettifyHTTP { payload = prettifyHTTP(payload) nr = len(payload) } hex.Encode(dst, payload) dst[nr*2] = '\n' m.mu.Lock() to.Write(dst[0 : nr*2+1]) m.mu.Unlock() Debug(3, "[MIDDLEWARE-MASTER] Sending:", string(buf[0:nr]), "From:", from) } } func (m *Middleware) read(from io.Reader) { reader := bufio.NewReader(from) var line []byte var e error for { if line, e = reader.ReadBytes('\n'); e != nil { if e == io.EOF { continue } else { break } } buf := make([]byte, len(line)/2) if _, err := hex.Decode(buf, line[:len(line)-1]); err != nil { fmt.Fprintln(os.Stderr, "Failed to decode input payload", err, len(line), string(line[:len(line)-1])) } Debug(3, "[MIDDLEWARE-MASTER] Received:", string(buf)) select { case <-m.stop: return case m.data <- buf: } } return } func (m *Middleware) Read(data []byte) (int, error) { var buf []byte select { case <-m.stop: return 0, ErrorStopped case buf = <-m.data: } n := copy(data, buf) return n, nil } func (m *Middleware) String() string { return fmt.Sprintf("Modifying traffic using '%s' command", m.command) } // Close closes this plugin func (m *Middleware) Close() error { close(m.stop) return nil }