summaryrefslogtreecommitdiffstats
path: root/golib
diff options
context:
space:
mode:
authorSebastien Douheret <sebastien.douheret@iot.bzh>2019-04-04 23:45:56 +0200
committerSebastien Douheret <sebastien.douheret@iot.bzh>2019-04-04 23:45:56 +0200
commit89ea6ebd3671e6ebbf6101525a5416427806f318 (patch)
tree5db52146365a9c2c439b77485f938cc8c2e3a727 /golib
parentee147062c3bebed83e34bf5ce71019c95f62b96f (diff)
Fixed package tree and go mod filev0.5.0
Change-Id: I1047094d5b80d0622e2c2ce674979f18207b8c0f Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
Diffstat (limited to 'golib')
-rw-r--r--golib/eows/eows-in.go59
-rw-r--r--golib/eows/eows-out.go122
-rw-r--r--golib/eows/eows-signal.go48
-rw-r--r--golib/eows/eows-signal_windows.go43
-rw-r--r--golib/eows/eows.go287
-rw-r--r--golib/error.go16
-rw-r--r--golib/filepath.go129
-rw-r--r--golib/httpclient.go411
8 files changed, 0 insertions, 1115 deletions
diff --git a/golib/eows/eows-in.go b/golib/eows/eows-in.go
deleted file mode 100644
index 5e74c76..0000000
--- a/golib/eows/eows-in.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package eows
-
-import (
- "fmt"
- "os"
- "syscall"
- "time"
-)
-
-// DoneChan Channel used to propagate status+error on command exit
-type DoneChan struct {
- status int
- err error
-}
-
-// pumpStdin is in charge of receive characters and send them to stdin
-func (e *ExecOverWS) pumpStdin(inw *os.File) {
-
- done := make(chan DoneChan, 1)
-
- if e.InputEvent != "" && e.InputCB != nil {
-
- err := (*e.SocketIO).On(e.InputEvent, func(stdin []byte) {
- in, err := e.InputCB(e, stdin)
- if err != nil {
- e.logDebug("Error stdin: %s", err.Error())
- inw.Close()
- return
- }
- if _, err := inw.Write(in); err != nil {
- e.logError("Error while writing to stdin: %s", err.Error())
- }
- })
- if err != nil {
- e.logError("Error stdin on event: %s", err.Error())
- }
- }
-
- // Monitor process exit
- go func() {
- status := 0
- sts, err := e.proc.Wait()
- if !sts.Success() {
- s := sts.Sys().(syscall.WaitStatus)
- status = s.ExitStatus()
- }
- e.procExited = true
-
- done <- DoneChan{status, err}
- }()
-
- // Wait cmd complete
- select {
- case dC := <-done:
- e.ExitCB(e, dC.status, dC.err)
- case <-time.After(time.Duration(e.CmdExecTimeout) * time.Second):
- e.ExitCB(e, -999, fmt.Errorf("Exit Timeout for command ID %v", e.CmdID))
- }
-}
diff --git a/golib/eows/eows-out.go b/golib/eows/eows-out.go
deleted file mode 100644
index 3163b2f..0000000
--- a/golib/eows/eows-out.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package eows
-
-import (
- "bufio"
- "io"
- "strings"
- "time"
-)
-
-// scanChars - gain character by character (or as soon as one or more characters are available)
-func scanChars(data []byte, atEOF bool) (advance int, token []byte, err error) {
- if atEOF && len(data) == 0 {
- return 0, nil, nil
- }
- return len(data), data, nil
-}
-
-// _pumper is in charge to collect
-func (e *ExecOverWS) _pumper(sc *bufio.Scanner, fctCB func(s string)) {
-
- // Select split function (default sc.ScanLines)
- if e.OutSplit == SplitChar || e.OutSplit == SplitLineTime || e.OutSplit == SplitTime {
- sc.Split(scanChars)
- }
-
- // Scan method according to split type
- if e.OutSplit == SplitLineTime || e.OutSplit == SplitTime {
- t0 := time.Now()
- buf := ""
- for sc.Scan() {
- buf += sc.Text()
- if time.Since(t0).Nanoseconds() > e.LineTimeSpan ||
- (e.OutSplit == SplitLineTime && strings.Contains(buf, "\n")) {
- fctCB(buf)
- buf = ""
- t0 = time.Now()
- }
- if e.procExited {
- break
- }
- }
- // Send remaining characters
- if len(buf) > 0 {
- fctCB(buf)
- }
-
- } else {
-
- for sc.Scan() {
- fctCB(sc.Text())
- if e.procExited {
- break
- }
- }
- }
-
-}
-
-// pipePumpStdout is in charge to forward stdout in websocket
-func (e *ExecOverWS) pipePumpStdout(r io.Reader, done chan struct{}) {
-
- sc := bufio.NewScanner(r)
-
- e._pumper(sc, func(b string) {
- if e.OutputCB != nil {
- e.OutputCB(e, []byte(b), []byte{})
- }
- })
-
- e.logDebug("STDOUT pump exit")
-
- if sc.Err() != nil && !strings.Contains(sc.Err().Error(), "file already closed") {
- e.logError("stdout scan: %v", sc.Err())
- }
-
- close(done)
-}
-
-// pipePumpStderr is in charge to forward stderr in websocket
-func (e *ExecOverWS) pipePumpStderr(r io.Reader) {
-
- sc := bufio.NewScanner(r)
-
- e._pumper(sc, func(b string) {
- if e.OutputCB != nil {
- e.OutputCB(e, []byte{}, []byte(b))
- }
- })
-
- e.logDebug("STDERR pump exit")
-
- if sc.Err() != nil && !strings.Contains(sc.Err().Error(), "file already closed") {
- e.logError("stderr scan: %v", sc.Err())
- }
-}
-
-// ptsPumpStdout is in charge to forward stdout in websocket
-// (only used when PtyMode is set)
-func (e *ExecOverWS) ptsPumpStdout(r io.Reader, done chan struct{}) {
-
- buffer := make([]byte, 1024)
- for {
- n, err := r.Read(buffer)
- if err != nil {
- if err != io.EOF &&
- !strings.Contains(err.Error(), "file already closed") {
- e.logError("Error stdout read: %v", err)
- }
- break
- }
- if n == 0 {
- continue
- }
- if e.OutputCB != nil {
- e.OutputCB(e, buffer[:n], []byte{})
- }
- }
-
- close(done)
-
- e.logDebug("Eows stdout pump exited")
-}
diff --git a/golib/eows/eows-signal.go b/golib/eows/eows-signal.go
deleted file mode 100644
index f48279a..0000000
--- a/golib/eows/eows-signal.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// +build !windows
-
-// Package eows is used to Execute commands Over WebSocket
-package eows
-
-import (
- "fmt"
- "os"
- "syscall"
-)
-
-// Signal sends a signal to the running command / process
-func (e *ExecOverWS) Signal(signal string) error {
- var sig os.Signal
- switch signal {
- case "quit", "SIGQUIT":
- sig = syscall.SIGQUIT
- case "terminated", "SIGTERM":
- sig = syscall.SIGTERM
- case "interrupt", "SIGINT":
- sig = syscall.SIGINT
- case "aborted", "SIGABRT":
- sig = syscall.SIGABRT
- case "continued", "SIGCONT":
- sig = syscall.SIGCONT
- case "hangup", "SIGHUP":
- sig = syscall.SIGHUP
- case "killed", "SIGKILL":
- sig = syscall.SIGKILL
- case "stopped (signal)", "SIGSTOP":
- sig = syscall.SIGSTOP
- case "stopped", "SIGTSTP":
- sig = syscall.SIGTSTP
- case "user defined signal 1", "SIGUSR1":
- sig = syscall.SIGUSR1
- case "user defined signal 2", "SIGUSR2":
- sig = syscall.SIGUSR2
- default:
- return fmt.Errorf("Unsupported signal")
- }
-
- if e.proc == nil {
- return fmt.Errorf("Cannot retrieve process")
- }
-
- e.logDebug("SEND signal %v to proc %v", sig, e.proc.Pid)
- return e.proc.Signal(sig)
-}
diff --git a/golib/eows/eows-signal_windows.go b/golib/eows/eows-signal_windows.go
deleted file mode 100644
index 23ad924..0000000
--- a/golib/eows/eows-signal_windows.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package eows
-
-// +build windows
-
-// Signal sends a signal to the running command / process
-func (e *ExecOverWS) Signal(signal string) error {
- panic("FIXME: Not implemented")
- /*
- var sig os.Signal
- switch signal {
- case "quit", "SIGQUIT":
- sig = syscall.SIGQUIT
- case "terminated", "SIGTERM":
- sig = syscall.SIGTERM
- case "interrupt", "SIGINT":
- sig = syscall.SIGINT
- case "aborted", "SIGABRT":
- sig = syscall.SIGABRT
- case "continued", "SIGCONT":
- sig = syscall.SIGCONT
- case "hangup", "SIGHUP":
- sig = syscall.SIGHUP
- case "killed", "SIGKILL":
- sig = syscall.SIGKILL
- case "stopped (signal)", "SIGSTOP":
- sig = syscall.SIGSTOP
- case "stopped", "SIGTSTP":
- sig = syscall.SIGTSTP
- case "user defined signal 1", "SIGUSR1":
- sig = syscall.SIGUSR1
- case "user defined signal 2", "SIGUSR2":
- sig = syscall.SIGUSR2
- default:
- return fmt.Errorf("Unsupported signal")
- }
-
- if e.proc == nil {
- return fmt.Errorf("Cannot retrieve process")
- }
- fmt.Printf("SEND signal %v to proc %v\n", sig, e.proc.Pid)
- return e.proc.Signal(sig)
- */
-}
diff --git a/golib/eows/eows.go b/golib/eows/eows.go
deleted file mode 100644
index 9d0b520..0000000
--- a/golib/eows/eows.go
+++ /dev/null
@@ -1,287 +0,0 @@
-// Package eows is used to Execute commands Over WebSocket
-package eows
-
-import (
- "fmt"
- "os"
- "os/exec"
- "strings"
- "syscall"
- "time"
- "unsafe"
-
- "github.com/Sirupsen/logrus"
- "github.com/googollee/go-socket.io"
- "github.com/kr/pty"
-)
-
-// OnInputCB is the function callback used to receive data
-type OnInputCB func(e *ExecOverWS, stdin []byte) ([]byte, error)
-
-// EmitOutputCB is the function callback used to emit data
-type EmitOutputCB func(e *ExecOverWS, stdout, stderr []byte)
-
-// EmitExitCB is the function callback used to emit exit proc code
-type EmitExitCB func(e *ExecOverWS, code int, err error)
-
-// SplitType Type of spliting method to tokenize stdout/stderr
-type SplitType uint8
-
-const (
- // SplitLine Split line by line
- SplitLine SplitType = iota
- // SplitChar Split character by character
- SplitChar
- // SplitLineTime Split by line or until a timeout has passed
- SplitLineTime
- // SplitTime Split until a timeout has passed
- SplitTime
-)
-
-// Inspired by :
-// https://github.com/gorilla/websocket/blob/master/examples/command/main.go
-
-// ExecOverWS .
-type ExecOverWS struct {
- Cmd string // command name to execute
- Args []string // command arguments
- SocketIO *socketio.Socket // websocket
- Sid string // websocket ID
- CmdID string // command ID
-
- // Optional fields
- Env []string // command environment variables
- CmdExecTimeout int // command execution time timeout
- Log *logrus.Logger // logger (nil if disabled)
- InputEvent string // websocket input event name
- InputCB OnInputCB // stdin callback
- OutputCB EmitOutputCB // stdout/stderr callback
- ExitCB EmitExitCB // exit proc callback
- UserData *map[string]interface{} // user data passed to callbacks
- OutSplit SplitType // split method to tokenize stdout/stderr
- LineTimeSpan int64 // time span (only used with SplitTime or SplitLineTime)
- PtyMode bool // Allocate a pseudo-terminal (allow to execute screen-based program)
- PtyTermEcho bool // Turn on/off terminal echo
-
- // Private fields
-
- proc *os.Process
- command *exec.Cmd
- ptmx *os.File
- procExited bool
-}
-
-var cmdIDMap = make(map[string]*ExecOverWS)
-
-// New creates a new instace of eows
-func New(cmd string, args []string, so *socketio.Socket, soID, cmdID string) *ExecOverWS {
-
- e := &ExecOverWS{
- Cmd: cmd,
- Args: args,
- SocketIO: so,
- Sid: soID,
- CmdID: cmdID,
- CmdExecTimeout: -1, // default no timeout
- OutSplit: SplitLineTime, // default split by line with time
- LineTimeSpan: 500 * time.Millisecond.Nanoseconds(),
- PtyMode: false,
- PtyTermEcho: true,
- }
-
- cmdIDMap[cmdID] = e
-
- return e
-}
-
-// GetEows gets ExecOverWS object from command ID
-func GetEows(cmdID string) *ExecOverWS {
- if _, ok := cmdIDMap[cmdID]; !ok {
- return nil
- }
- return cmdIDMap[cmdID]
-}
-
-// Start executes the command and redirect stdout/stderr into a WebSocket
-func (e *ExecOverWS) Start() error {
- var err error
- var outr, outw, errr, errw, inr, inw *os.File
-
- bashArgs := []string{"/bin/bash", "-c", e.Cmd + " " + strings.Join(e.Args, " ")}
-
- // no timeout == 1 year
- if e.CmdExecTimeout == -1 {
- e.CmdExecTimeout = 365 * 24 * 60 * 60
- }
-
- e.procExited = false
-
- if e.PtyMode {
-
- e.command = exec.Command(bashArgs[0], bashArgs[1:]...)
- e.command.Env = append(os.Environ(), e.Env...)
- e.ptmx, err = pty.Start(e.command)
- if err != nil {
- err = fmt.Errorf("Process start error: " + err.Error())
- goto exitErr
- }
- e.proc = e.command.Process
-
- // Turn off terminal echo
- if !e.PtyTermEcho {
- e.terminalEcho(e.ptmx, false)
- }
-
- } else {
-
- // Create pipes
- outr, outw, err = os.Pipe()
- if err != nil {
- err = fmt.Errorf("Pipe stdout error: " + err.Error())
- goto exitErr
- }
-
- errr, errw, err = os.Pipe()
- if err != nil {
- err = fmt.Errorf("Pipe stderr error: " + err.Error())
- goto exitErr
- }
-
- inr, inw, err = os.Pipe()
- if err != nil {
- err = fmt.Errorf("Pipe stdin error: " + err.Error())
- goto exitErr
- }
-
- e.proc, err = os.StartProcess(bashArgs[0], bashArgs, &os.ProcAttr{
- Files: []*os.File{inr, outw, errw},
- Env: append(os.Environ(), e.Env...),
- })
- if err != nil {
- err = fmt.Errorf("Process start error: " + err.Error())
- goto exitErr
- }
- }
-
- go func() {
- stdoutDone := make(chan struct{})
-
- if e.PtyMode {
- // Make sure to close the pty at the end.
- defer e.ptmx.Close()
-
- // Handle both stdout mixed with stderr
- go e.ptsPumpStdout(e.ptmx, stdoutDone)
-
- // Blocking function that poll input or wait for end of process
- e.pumpStdin(e.ptmx)
-
- } else {
- // Make sure to close all pipes
- defer outr.Close()
- defer outw.Close()
- defer errr.Close()
- defer errw.Close()
- defer inr.Close()
- defer inw.Close()
-
- // Handle stdout + stderr
- go e.pipePumpStdout(outr, stdoutDone)
- go e.pipePumpStderr(errr)
-
- // Blocking function that poll input or wait for end of process
- e.pumpStdin(inw)
- }
-
- if status, err := e.proc.Wait(); err == nil {
- // Other commands need a bonk on the head.
- if !status.Exited() {
- if err := e.proc.Signal(os.Interrupt); err != nil {
- e.logError("Proc interrupt:", err)
- }
-
- select {
- case <-stdoutDone:
- case <-time.After(time.Second):
- // A bigger bonk on the head.
- if err := e.proc.Signal(os.Kill); err != nil {
- e.logError("Proc term:", err)
- }
- <-stdoutDone
- }
- }
- }
-
- delete(cmdIDMap, e.CmdID)
- }()
-
- return nil
-
-exitErr:
- for _, pf := range []*os.File{outr, outw, errr, errw, inr, inw} {
- pf.Close()
- }
- return err
-}
-
-// TerminalSetSize Set terminal size
-func (e *ExecOverWS) TerminalSetSize(rows, cols uint16) error {
- if !e.PtyMode || e.ptmx == nil {
- return fmt.Errorf("PtyMode not set")
- }
- w, err := pty.GetsizeFull(e.ptmx)
- if err != nil {
- return err
- }
- return e.TerminalSetSizePos(rows, cols, w.X, w.Y)
-}
-
-// TerminalSetSizePos Set terminal size and position
-func (e *ExecOverWS) TerminalSetSizePos(rows, cols, x, y uint16) error {
- if !e.PtyMode || e.ptmx == nil {
- return fmt.Errorf("PtyMode not set")
- }
- winSz := pty.Winsize{Rows: rows, Cols: cols, X: x, Y: y}
- return pty.Setsize(e.ptmx, &winSz)
-}
-
-/**
- * Private functions
- **/
-
-// terminalEcho Enable or disable echoing terminal input.
-// This is useful specifically for when users enter passwords.
-func (e *ExecOverWS) terminalEcho(ff *os.File, show bool) {
- var termios = &syscall.Termios{}
-
- fd := ff.Fd()
-
- if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd,
- syscall.TCGETS, uintptr(unsafe.Pointer(termios))); err != 0 {
- return
- }
-
- if show {
- termios.Lflag |= syscall.ECHO
- } else {
- termios.Lflag &^= syscall.ECHO
- }
-
- if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd,
- uintptr(syscall.TCSETS),
- uintptr(unsafe.Pointer(termios))); err != 0 {
- return
- }
-}
-
-func (e *ExecOverWS) logDebug(format string, a ...interface{}) {
- if e.Log != nil {
- e.Log.Debugf(format, a...)
- }
-}
-
-func (e *ExecOverWS) logError(format string, a ...interface{}) {
- if e.Log != nil {
- e.Log.Errorf(format, a...)
- }
-}
diff --git a/golib/error.go b/golib/error.go
deleted file mode 100644
index 6873d82..0000000
--- a/golib/error.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package common
-
-import (
- "fmt"
-
- "github.com/gin-gonic/gin"
-)
-
-// APIError returns an uniform json formatted error
-func APIError(c *gin.Context, format string, args ...interface{}) {
- errMsg := fmt.Sprintf(format, args...)
- c.JSON(500, gin.H{
- "status": "error",
- "error": errMsg,
- })
-}
diff --git a/golib/filepath.go b/golib/filepath.go
deleted file mode 100644
index 1817f58..0000000
--- a/golib/filepath.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package common
-
-import (
- "fmt"
- "os"
- "os/user"
- "path"
- "path/filepath"
- "regexp"
- "runtime"
- "strings"
-)
-
-// Exists returns whether the given file or directory exists or not
-func Exists(path string) bool {
- _, err := os.Stat(path)
- if err == nil {
- return true
- }
- if os.IsNotExist(err) {
- return false
- }
- return true
-}
-
-// IsDir returns true when the given path is a directory
-func IsDir(path string) bool {
- fi, err := os.Stat(path)
- if err != nil {
- return false
- }
-
- return fi.Mode().IsDir()
-}
-
-// ResolveEnvVar Resolved environment variable regarding the syntax ${MYVAR}
-// or $MYVAR following by a slash or a backslash
-func ResolveEnvVar(s string) (string, error) {
- if s == "" {
- return s, nil
- }
-
- // Resolved tilde : ~/
- if len(s) > 2 && s[:2] == "~/" {
- if usr, err := user.Current(); err == nil {
- s = filepath.Join(usr.HomeDir, s[2:])
- }
- }
-
- // Resolved ${MYVAR}
- re := regexp.MustCompile("\\${([^}]+)}")
- vars := re.FindAllStringSubmatch(s, -1)
- res := s
- for _, v := range vars {
- val := ""
- if v[1] == "EXEPATH" {
- // Specific case to resolve $EXEPATH or ${EXEPATH} used as current executable path
- val = GetExePath()
-
- } else {
- // Get env var value
- val = os.Getenv(v[1])
- if val == "" {
- // Specific case to resolved $HOME or ${HOME} on Windows host
- if runtime.GOOS == "windows" && v[1] == "HOME" {
- if usr, err := user.Current(); err == nil {
- val = usr.HomeDir
- }
- } else {
- return res, fmt.Errorf("ERROR: %s env variable not defined", v[1])
- }
- }
- }
-
- rer := regexp.MustCompile("\\${" + v[1] + "}")
- res = rer.ReplaceAllString(res, val)
- }
-
- // Resolved $MYVAR following by a slash (or a backslash for Windows)
- // TODO
- //re := regexp.MustCompile("\\$([^\\/])+/")
-
- return path.Clean(res), nil
-}
-
-// PathNormalize normalizes a linux or windows like path
-func PathNormalize(p string) string {
- sep := string(filepath.Separator)
- if sep != "/" {
- return p
- }
- // Replace drive like C: by C/
- res := p
- if p[1:2] == ":" {
- res = p[0:1] + sep + p[2:]
- }
- res = strings.Replace(res, "\\", "/", -1)
- return filepath.Clean(res)
-}
-
-// GetUserHome returns the user's home directory or empty string on error
-func GetUserHome() string {
- if usr, err := user.Current(); err == nil && usr != nil && usr.HomeDir != "" {
- return usr.HomeDir
- }
- for _, p := range []string{"HOME", "HomePath"} {
- if h := os.Getenv(p); h != "" {
- return h
- }
- }
-
- return ""
-}
-
-// GetExePath returns the full path of the current executable
-func GetExePath() string {
- exePath := os.Args[0] // set fallback value
- ee, _ := os.Executable()
- exeAbsPath, err := filepath.Abs(ee)
- if err == nil {
- exePath, err = filepath.EvalSymlinks(exeAbsPath)
- if err == nil {
- exePath = filepath.Dir(ee)
- } else {
- exePath = filepath.Dir(exeAbsPath)
- }
- }
- return exePath
-}
diff --git a/golib/httpclient.go b/golib/httpclient.go
deleted file mode 100644
index f4880e6..0000000
--- a/golib/httpclient.go
+++ /dev/null
@@ -1,411 +0,0 @@
-package common
-
-import (
- "bytes"
- "crypto/tls"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "os"
- "strings"
-)
-
-// HTTPClient .
-type HTTPClient struct {
- LoggerOut io.Writer
- LoggerLevel int
- LoggerPrefix string
-
- httpClient http.Client
- initDone bool
- endpoint string
- apikey string
- username string
- password string
- id string
- csrf string
- conf HTTPClientConfig
-}
-
-// HTTPClientConfig is used to config HTTPClient
-type HTTPClientConfig struct {
- URLPrefix string
- ContentType string
- HeaderAPIKeyName string
- Apikey string
- HeaderClientKeyName string
- CsrfDisable bool
- LogOut io.Writer
- LogLevel int
- LogPrefix string
-}
-
-// Logger levels constants
-const (
- HTTPLogLevelPanic = 0
- HTTPLogLevelError = 1
- HTTPLogLevelWarning = 2
- HTTPLogLevelInfo = 3
- HTTPLogLevelDebug = 4
-)
-
-// Inspired by syncthing/cmd/cli
-
-const insecure = false
-
-// HTTPNewClient creates a new HTTP client to deal with Syncthing
-func HTTPNewClient(baseURL string, cfg HTTPClientConfig) (*HTTPClient, error) {
-
- // Create w new Http client
- httpClient := http.Client{
- Transport: &http.Transport{
- TLSClientConfig: &tls.Config{
- InsecureSkipVerify: insecure,
- },
- },
- }
-
- lOut := cfg.LogOut
- if cfg.LogOut == nil {
- lOut = os.Stdout
- }
- client := HTTPClient{
- LoggerOut: lOut,
- LoggerLevel: cfg.LogLevel,
- LoggerPrefix: cfg.LogPrefix,
-
- httpClient: httpClient,
- initDone: false,
- endpoint: baseURL,
- apikey: cfg.Apikey,
- conf: cfg,
- /* TODO - add user + pwd support
- username: c.GlobalString("username"),
- password: c.GlobalString("password"),
- */
- }
-
- // Default set Content-Type to json
- if client.conf.ContentType == "" {
- client.conf.ContentType = "application/json"
- }
-
- if err := client.getCidAndCsrf(); err != nil {
- client.log(HTTPLogLevelError, "Cannot retrieve Client ID and/or CSRF: %v", err)
- return &client, err
- }
-
- client.log(HTTPLogLevelDebug, "HTTP client url %s init Done", client.endpoint)
- client.initDone = true
- return &client, nil
-}
-
-// GetLogLevel Get a readable string representing the log level
-func (c *HTTPClient) GetLogLevel() string {
- return c.LogLevelToString(c.LoggerLevel)
-}
-
-// LogLevelToString Convert an integer log level to string
-func (c *HTTPClient) LogLevelToString(lvl int) string {
- switch lvl {
- case HTTPLogLevelPanic:
- return "panic"
- case HTTPLogLevelError:
- return "error"
- case HTTPLogLevelWarning:
- return "warning"
- case HTTPLogLevelInfo:
- return "info"
- case HTTPLogLevelDebug:
- return "debug"
- }
- return "Unknown"
-}
-
-// SetLogLevel set the log level from a readable string
-func (c *HTTPClient) SetLogLevel(lvl string) error {
- switch strings.ToLower(lvl) {
- case "panic":
- c.LoggerLevel = HTTPLogLevelPanic
- case "error":
- c.LoggerLevel = HTTPLogLevelError
- case "warn", "warning":
- c.LoggerLevel = HTTPLogLevelWarning
- case "info":
- c.LoggerLevel = HTTPLogLevelInfo
- case "debug":
- c.LoggerLevel = HTTPLogLevelDebug
- default:
- return fmt.Errorf("Unknown level")
- }
- return nil
-}
-
-// GetClientID returns the id
-func (c *HTTPClient) GetClientID() string {
- return c.id
-}
-
-/***
-** High level functions
-***/
-
-// Get Send a Get request to client and return directly data of body response
-func (c *HTTPClient) Get(url string, out interface{}) error {
- return c._Request("GET", url, nil, out)
-}
-
-// Post Send a Post request to client and return directly data of body response
-func (c *HTTPClient) Post(url string, in interface{}, out interface{}) error {
- return c._Request("POST", url, in, out)
-}
-
-// Put Send a Put request to client and return directly data of body response
-func (c *HTTPClient) Put(url string, in interface{}, out interface{}) error {
- return c._Request("PUT", url, in, out)
-}
-
-// Delete Send a Delete request to client and return directly data of body response
-func (c *HTTPClient) Delete(url string, out interface{}) error {
- return c._Request("DELETE", url, nil, out)
-}
-
-/***
-** Low level functions
-***/
-
-// HTTPGet Send a Get request to client and return an error object
-func (c *HTTPClient) HTTPGet(url string, data *[]byte) error {
- _, err := c._HTTPRequest("GET", url, nil, data)
- return err
-}
-
-// HTTPGetWithRes Send a Get request to client and return both response and error
-func (c *HTTPClient) HTTPGetWithRes(url string, data *[]byte) (*http.Response, error) {
- return c._HTTPRequest("GET", url, nil, data)
-}
-
-// HTTPPost Send a POST request to client and return an error object
-func (c *HTTPClient) HTTPPost(url string, body string) error {
- _, err := c._HTTPRequest("POST", url, &body, nil)
- return err
-}
-
-// HTTPPostWithRes Send a POST request to client and return both response and error
-func (c *HTTPClient) HTTPPostWithRes(url string, body string) (*http.Response, error) {
- return c._HTTPRequest("POST", url, &body, nil)
-}
-
-// HTTPPut Send a PUT request to client and return an error object
-func (c *HTTPClient) HTTPPut(url string, body string) error {
- _, err := c._HTTPRequest("PUT", url, &body, nil)
- return err
-}
-
-// HTTPPutWithRes Send a PUT request to client and return both response and error
-func (c *HTTPClient) HTTPPutWithRes(url string, body string) (*http.Response, error) {
- return c._HTTPRequest("PUT", url, &body, nil)
-}
-
-// HTTPDelete Send a DELETE request to client and return an error object
-func (c *HTTPClient) HTTPDelete(url string) error {
- _, err := c._HTTPRequest("DELETE", url, nil, nil)
- return err
-}
-
-// HTTPDeleteWithRes Send a DELETE request to client and return both response and error
-func (c *HTTPClient) HTTPDeleteWithRes(url string) (*http.Response, error) {
- return c._HTTPRequest("DELETE", url, nil, nil)
-}
-
-// ResponseToBArray converts an Http response to a byte array
-func (c *HTTPClient) ResponseToBArray(response *http.Response) []byte {
- defer response.Body.Close()
- bytes, err := ioutil.ReadAll(response.Body)
- if err != nil {
- c.log(HTTPLogLevelError, "ResponseToBArray failure: %v", err.Error())
- }
- return bytes
-}
-
-/***
-** Private functions
-***/
-
-// _HTTPRequest Generic function used by high level function to send requests
-func (c *HTTPClient) _Request(method string, url string, in interface{}, out interface{}) error {
- var err error
- var res *http.Response
- var body []byte
- if in != nil {
- body, err = json.Marshal(in)
- if err != nil {
- return err
- }
- sb := string(body)
- res, err = c._HTTPRequest(method, url, &sb, nil)
- } else {
- res, err = c._HTTPRequest(method, url, nil, nil)
- }
- if err != nil {
- return err
- }
- if res.StatusCode != 200 {
- return fmt.Errorf("HTTP status %s", res.Status)
- }
-
- // Don't decode response if no out data pointer is nil
- if out == nil {
- return nil
- }
- return json.Unmarshal(c.ResponseToBArray(res), out)
-}
-
-// _HTTPRequest Generic function that returns a new Request given a method, URL, and optional body and data.
-func (c *HTTPClient) _HTTPRequest(method, url string, body *string, data *[]byte) (*http.Response, error) {
- if !c.initDone {
- if err := c.getCidAndCsrf(); err == nil {
- c.initDone = true
- }
- }
-
- var err error
- var request *http.Request
- if body != nil {
- request, err = http.NewRequest(method, c.formatURL(url), bytes.NewBufferString(*body))
- } else {
- request, err = http.NewRequest(method, c.formatURL(url), nil)
- }
-
- if err != nil {
- return nil, err
- }
- res, err := c.handleRequest(request)
- if err != nil {
- return res, err
- }
- if res.StatusCode != 200 {
- return res, errors.New(res.Status)
- }
-
- if data != nil {
- *data = c.ResponseToBArray(res)
- }
-
- return res, nil
-}
-
-func (c *HTTPClient) handleRequest(request *http.Request) (*http.Response, error) {
- if c.conf.ContentType != "" {
- request.Header.Set("Content-Type", c.conf.ContentType)
- }
- if c.conf.HeaderAPIKeyName != "" && c.apikey != "" {
- request.Header.Set(c.conf.HeaderAPIKeyName, c.apikey)
- }
- if c.conf.HeaderClientKeyName != "" && c.id != "" {
- request.Header.Set(c.conf.HeaderClientKeyName, c.id)
- }
- if c.username != "" || c.password != "" {
- request.SetBasicAuth(c.username, c.password)
- }
- if c.csrf != "" {
- request.Header.Set("X-CSRF-Token-"+c.id[:5], c.csrf)
- }
-
- c.log(HTTPLogLevelDebug, "HTTP %s %v", request.Method, request.URL)
- response, err := c.httpClient.Do(request)
- c.log(HTTPLogLevelDebug, "HTTP RESPONSE: %v\n", response)
- if err != nil {
- c.log(HTTPLogLevelInfo, "%v", err)
- return nil, err
- }
-
- // Detect client ID change
- cid := response.Header.Get(c.conf.HeaderClientKeyName)
- if cid != "" && c.id != cid {
- c.id = cid
- }
-
- // Detect CSR token change
- for _, item := range response.Cookies() {
- if c.id != "" && item.Name == "CSRF-Token-"+c.id[:5] {
- c.csrf = item.Value
- goto csrffound
- }
- }
- // OK CSRF found
-csrffound:
-
- if response.StatusCode == 404 {
- return nil, errors.New("Invalid endpoint or API call")
- } else if response.StatusCode == 401 {
- return nil, errors.New("Invalid username or password")
- } else if response.StatusCode == 403 {
- if c.apikey == "" {
- // Request a new Csrf for next requests
- c.getCidAndCsrf()
- return nil, errors.New("Invalid CSRF token")
- }
- return nil, errors.New("Invalid API key")
- } else if response.StatusCode != 200 {
- data := make(map[string]interface{})
- // Try to decode error field of APIError struct
- json.Unmarshal(c.ResponseToBArray(response), &data)
- if err, found := data["error"]; found {
- return nil, fmt.Errorf(err.(string))
- }
- body := strings.TrimSpace(string(c.ResponseToBArray(response)))
- if body != "" {
- return nil, fmt.Errorf(body)
- }
- return nil, errors.New("Unknown HTTP status returned: " + response.Status)
- }
- return response, nil
-}
-
-// formatURL Build full url by concatenating all parts
-func (c *HTTPClient) formatURL(endURL string) string {
- url := c.endpoint
- if !strings.HasSuffix(url, "/") {
- url += "/"
- }
- url += strings.TrimLeft(c.conf.URLPrefix, "/")
- if !strings.HasSuffix(url, "/") {
- url += "/"
- }
- return url + strings.TrimLeft(endURL, "/")
-}
-
-// Send request to retrieve Client id and/or CSRF token
-func (c *HTTPClient) getCidAndCsrf() error {
- // Don't use cid + csrf when apikey is set
- if c.apikey != "" {
- return nil
- }
- request, err := http.NewRequest("GET", c.endpoint, nil)
- if err != nil {
- return err
- }
- if _, err := c.handleRequest(request); err != nil {
- return err
- }
- if c.id == "" {
- return errors.New("Failed to get device ID")
- }
- if !c.conf.CsrfDisable && c.csrf == "" {
- return errors.New("Failed to get CSRF token")
- }
- return nil
-}
-
-// log Internal logger function
-func (c *HTTPClient) log(level int, format string, args ...interface{}) {
- if level > c.LoggerLevel {
- return
- }
- sLvl := strings.ToUpper(c.LogLevelToString(level))
- fmt.Fprintf(c.LoggerOut, sLvl+": "+c.LoggerPrefix+format+"\n", args...)
-}