gitee.com/h79/goutils@v1.22.10/common/ssh/reply.go (about)

     1  package ssh
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"go.uber.org/zap"
     9  	"io"
    10  	"strings"
    11  	"time"
    12  )
    13  
    14  type Header struct {
    15  	Type ReplyType
    16  }
    17  
    18  type FileHeader struct {
    19  	FileInfo
    20  }
    21  
    22  type DirHeader struct {
    23  	FileInfo
    24  }
    25  
    26  type EndDirHeader struct {
    27  }
    28  
    29  type TimeHeader struct {
    30  	Mtime time.Time
    31  	Atime time.Time
    32  }
    33  
    34  func readReply(r io.Reader) error {
    35  	reply, err := ParseReply(r)
    36  	if err != nil {
    37  		return err
    38  	}
    39  
    40  	if reply.IsFailure() {
    41  		return reply
    42  	}
    43  	return nil
    44  }
    45  
    46  type ReplyType uint8
    47  
    48  func (rt ReplyType) String() string {
    49  	switch rt {
    50  	case hCopyFile:
    51  		return "copyfile"
    52  	case hStartDirectory:
    53  		return "startDirectory"
    54  	case hEndDirectory:
    55  		return "endDirectory"
    56  	case hTime:
    57  		return "time"
    58  	case Ok:
    59  		return "Ok"
    60  	case Warning:
    61  		return "Warning"
    62  	case Error:
    63  		return "Error"
    64  	}
    65  	return fmt.Sprintf("Type(%d)", rt)
    66  }
    67  
    68  const (
    69  	Ok      ReplyType = 0
    70  	Warning ReplyType = 1
    71  	Error   ReplyType = 2
    72  
    73  	hCopyFile       = ReplyType('C')
    74  	hStartDirectory = ReplyType('D')
    75  	hEndDirectory   = ReplyType('E')
    76  	hTime           = ReplyType('T')
    77  )
    78  
    79  type Reply struct {
    80  	Type    ReplyType
    81  	Message string
    82  }
    83  
    84  func ParseReply(reader io.Reader) (Reply, error) {
    85  	buff := bufio.NewReader(reader)
    86  	replyType, err := buff.ReadByte()
    87  	if err != nil {
    88  		return Reply{}, err
    89  	}
    90  	message := ""
    91  	if replyType > 0 {
    92  		message, err = buff.ReadString('\n')
    93  		if err != nil {
    94  			return Reply{}, err
    95  		}
    96  		message = strings.TrimSuffix(message, "\n")
    97  	}
    98  	zap.L().Debug("SSH:", zap.String("Type", ReplyType(replyType).String()), zap.String("Body", message))
    99  	return Reply{ReplyType(replyType), message}, nil
   100  }
   101  
   102  func (r Reply) Parse() (interface{}, error) {
   103  	switch r.Type {
   104  	case hCopyFile:
   105  		return r.parseFileInfo()
   106  
   107  	case hStartDirectory:
   108  		return r.parseDirInfo()
   109  
   110  	case hEndDirectory:
   111  		return &EndDirHeader{}, nil
   112  
   113  	case hTime:
   114  		return r.parseTimeInfo()
   115  
   116  	case Ok:
   117  		fallthrough
   118  	case Warning:
   119  		fallthrough
   120  	case Error:
   121  		return &Header{Type: r.Type}, nil
   122  
   123  	default:
   124  		return nil, fmt.Errorf("invalid scp message type: %v", r.Type)
   125  	}
   126  }
   127  
   128  func (r Reply) IsOk() bool {
   129  	return r.Type == Ok
   130  }
   131  
   132  func (r Reply) IsWarning() bool {
   133  	return r.Type == Warning
   134  }
   135  
   136  // IsError returns true when the remote responded with an error.
   137  func (r Reply) IsError() bool {
   138  	return r.Type == Error
   139  }
   140  
   141  // IsFailure returns true when the remote answered with a warning or an error.
   142  func (r Reply) IsFailure() bool {
   143  	return r.IsWarning() || r.IsError()
   144  }
   145  
   146  // GetMessage returns the message the remote sent back.
   147  func (r Reply) GetMessage() string {
   148  	return r.Message
   149  }
   150  
   151  func (r Reply) Error() string {
   152  	return r.Message
   153  }
   154  
   155  func (r Reply) parseFileInfo() (*FileHeader, error) {
   156  	var (
   157  		info = FileHeader{}
   158  		buf  = bytes.NewBuffer([]byte(r.Message))
   159  	)
   160  	n, err := fmt.Fscanf(buf, "%04o %d %s", &info.Mode, &info.Size, &info.Name)
   161  	if err != nil {
   162  		return nil, fmt.Errorf("failed to read scp file message header: err=%s", err)
   163  	}
   164  	if n != 3 {
   165  		return nil, fmt.Errorf("unexpected count in reading file message header: n=%d", 3)
   166  	}
   167  	return &info, nil
   168  }
   169  
   170  func (r Reply) parseDirInfo() (*DirHeader, error) {
   171  	var (
   172  		info = DirHeader{}
   173  		buf  = bytes.NewBuffer([]byte(r.Message))
   174  	)
   175  	n, err := fmt.Fscanf(buf, "%04o %d %s", &info.Mode, &info.Size, &info.Name)
   176  	if err != nil {
   177  		return nil, fmt.Errorf("failed to read scp file message header: err=%s", err)
   178  	}
   179  	if n != 3 {
   180  		return nil, fmt.Errorf("unexpected count in reading file message header: n=%d", 3)
   181  	}
   182  	return &info, nil
   183  }
   184  
   185  func (r Reply) parseTimeInfo() (*TimeHeader, error) {
   186  	var (
   187  		ms   int64
   188  		mus  int
   189  		as   int64
   190  		aus  int
   191  		info = TimeHeader{}
   192  		buf  = bytes.NewBuffer([]byte(r.Message))
   193  	)
   194  	n, err := fmt.Fscanf(buf, "%d %d %d %d", &ms, &mus, &as, &aus)
   195  	if err != nil {
   196  		return nil, fmt.Errorf("failed to read scp file message header: err=%s", err)
   197  	}
   198  	if n != 4 {
   199  		return nil, fmt.Errorf("unexpected count in reading file message header: n=%d", 3)
   200  	}
   201  	info.Mtime = fromSecondsAndMicroseconds(ms, mus)
   202  	info.Atime = fromSecondsAndMicroseconds(as, aus)
   203  	return &info, nil
   204  }
   205  
   206  func fromSecondsAndMicroseconds(seconds int64, microseconds int) time.Time {
   207  	return time.Unix(seconds, int64(microseconds)*(int64(time.Microsecond)/int64(time.Nanosecond)))
   208  }
   209  
   210  var fileErr = errors.New("unable to parse message as file infos")