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")