app-init
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
package navicat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Header is EchoHeader from ntunnel_mysql.php (16 bytes).
|
||||
type Header struct {
|
||||
Magic uint32
|
||||
Version uint16
|
||||
Errno uint32
|
||||
Message string
|
||||
UncertainVersion bool
|
||||
}
|
||||
|
||||
func (h Header) OK() bool { return h.Errno == 0 }
|
||||
|
||||
// ConnInfo is actn=C payload after a successful header.
|
||||
type ConnInfo struct {
|
||||
HostInfo string
|
||||
ProtoInfo string
|
||||
ServerInfo string
|
||||
}
|
||||
|
||||
// Field is one EchoFieldsHeader column. Type/flags/length are mysqli numeric values.
|
||||
type Field struct {
|
||||
Name string
|
||||
Table string
|
||||
Type uint32
|
||||
Flags uint32
|
||||
Length uint32
|
||||
}
|
||||
|
||||
// Cell is one row field. Bytes are kept as the PHP string; never parsed as int64/float64.
|
||||
type Cell struct {
|
||||
Null bool
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
func (c Cell) Text() string {
|
||||
if c.Null {
|
||||
return ""
|
||||
}
|
||||
return string(c.Bytes)
|
||||
}
|
||||
|
||||
// Result is one query in an actn=Q response.
|
||||
type Result struct {
|
||||
Errno uint32
|
||||
Message string
|
||||
AffectedRows uint32
|
||||
InsertID uint32 // protocol is 32-bit; do not invent high bits
|
||||
NumFields uint32
|
||||
NumRows uint32
|
||||
Info string
|
||||
Fields []Field
|
||||
Rows [][]Cell
|
||||
}
|
||||
|
||||
func (r Result) IsError() bool { return r.Errno > 0 }
|
||||
func (r Result) IsResultSet() bool {
|
||||
return r.Errno == 0 && r.NumFields > 0
|
||||
}
|
||||
|
||||
// ConnectResponse is a parsed actn=C reply.
|
||||
type ConnectResponse struct {
|
||||
Header Header
|
||||
Conn ConnInfo
|
||||
}
|
||||
|
||||
// QueryResponse is a parsed actn=Q reply.
|
||||
type QueryResponse struct {
|
||||
Header Header
|
||||
Results []Result
|
||||
}
|
||||
|
||||
func ParseHeader(body []byte) (Header, *reader, error) {
|
||||
var h Header
|
||||
if len(body) < HeaderSize {
|
||||
if looksHTML(body) {
|
||||
return h, nil, &Error{Kind: "http", Message: "tunnel returned HTML, not Navicat binary"}
|
||||
}
|
||||
return h, nil, fmt.Errorf("response too short (%d bytes)", len(body))
|
||||
}
|
||||
r := newReader(body)
|
||||
magic, err := r.u32()
|
||||
if err != nil {
|
||||
return h, nil, err
|
||||
}
|
||||
if magic != Magic {
|
||||
if looksHTML(body) {
|
||||
return h, nil, &Error{Kind: "http", Message: "tunnel returned HTML, not Navicat binary"}
|
||||
}
|
||||
return h, nil, fmt.Errorf("invalid Navicat magic %d (want %d)", magic, Magic)
|
||||
}
|
||||
ver, err := r.u16()
|
||||
if err != nil {
|
||||
return h, nil, err
|
||||
}
|
||||
errno, err := r.u32()
|
||||
if err != nil {
|
||||
return h, nil, err
|
||||
}
|
||||
if _, err := r.readN(6); err != nil {
|
||||
return h, nil, err
|
||||
}
|
||||
h.Magic = magic
|
||||
h.Version = ver
|
||||
h.Errno = errno
|
||||
h.UncertainVersion = !knownVersion(ver)
|
||||
if errno != 0 {
|
||||
msg, err := r.blockString()
|
||||
if err != nil {
|
||||
return h, nil, fmt.Errorf("read error message: %w", err)
|
||||
}
|
||||
h.Message = msg
|
||||
}
|
||||
return h, r, nil
|
||||
}
|
||||
|
||||
func ParseConnect(body []byte) (*ConnectResponse, error) {
|
||||
h, r, err := ParseHeader(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &ConnectResponse{Header: h}
|
||||
if !h.OK() {
|
||||
return out, nil
|
||||
}
|
||||
host, err := r.blockString()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("conn host_info: %w", err)
|
||||
}
|
||||
proto, err := r.blockString()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("conn proto_info: %w", err)
|
||||
}
|
||||
server, err := r.blockString()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("conn server_info: %w", err)
|
||||
}
|
||||
out.Conn = ConnInfo{HostInfo: host, ProtoInfo: proto, ServerInfo: server}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func ParseQuery(body []byte) (*QueryResponse, error) {
|
||||
h, r, err := ParseHeader(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &QueryResponse{Header: h}
|
||||
if !h.OK() {
|
||||
return out, nil
|
||||
}
|
||||
for {
|
||||
if r.remaining() == 0 {
|
||||
break
|
||||
}
|
||||
res, err := parseOneResult(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Results = append(out.Results, res)
|
||||
if r.remaining() == 0 {
|
||||
break
|
||||
}
|
||||
sep, err := r.readByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sep == sepLast {
|
||||
break
|
||||
}
|
||||
if sep != sepMore {
|
||||
return nil, fmt.Errorf("invalid query separator 0x%02x", sep)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func parseOneResult(r *reader) (Result, error) {
|
||||
var res Result
|
||||
var err error
|
||||
if res.Errno, err = r.u32(); err != nil {
|
||||
return res, err
|
||||
}
|
||||
if res.AffectedRows, err = r.u32(); err != nil {
|
||||
return res, err
|
||||
}
|
||||
if res.InsertID, err = r.u32(); err != nil {
|
||||
return res, err
|
||||
}
|
||||
if res.NumFields, err = r.u32(); err != nil {
|
||||
return res, err
|
||||
}
|
||||
if res.NumRows, err = r.u32(); err != nil {
|
||||
return res, err
|
||||
}
|
||||
if _, err = r.readN(12); err != nil {
|
||||
return res, err
|
||||
}
|
||||
if res.Errno > 0 {
|
||||
res.Message, err = r.blockString()
|
||||
return res, err
|
||||
}
|
||||
if res.NumFields == 0 {
|
||||
res.Info, err = r.blockString()
|
||||
return res, err
|
||||
}
|
||||
res.Fields = make([]Field, 0, res.NumFields)
|
||||
for i := uint32(0); i < res.NumFields; i++ {
|
||||
f, err := parseField(r)
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("field %d: %w", i, err)
|
||||
}
|
||||
res.Fields = append(res.Fields, f)
|
||||
}
|
||||
res.Rows = make([][]Cell, 0, res.NumRows)
|
||||
for i := uint32(0); i < res.NumRows; i++ {
|
||||
row := make([]Cell, res.NumFields)
|
||||
for j := uint32(0); j < res.NumFields; j++ {
|
||||
val, isNull, err := r.block(true)
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("row %d col %d: %w", i, j, err)
|
||||
}
|
||||
row[j] = Cell{Null: isNull, Bytes: val}
|
||||
}
|
||||
res.Rows = append(res.Rows, row)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func parseField(r *reader) (Field, error) {
|
||||
var f Field
|
||||
var err error
|
||||
if f.Name, err = r.blockString(); err != nil {
|
||||
return f, err
|
||||
}
|
||||
if f.Table, err = r.blockString(); err != nil {
|
||||
return f, err
|
||||
}
|
||||
if f.Type, err = r.u32(); err != nil {
|
||||
return f, err
|
||||
}
|
||||
if f.Flags, err = r.u32(); err != nil {
|
||||
return f, err
|
||||
}
|
||||
if f.Length, err = r.u32(); err != nil {
|
||||
return f, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func looksHTML(body []byte) bool {
|
||||
s := strings.TrimSpace(string(body))
|
||||
if len(s) < 5 {
|
||||
return false
|
||||
}
|
||||
n := 64
|
||||
if len(s) < n {
|
||||
n = len(s)
|
||||
}
|
||||
ls := strings.ToLower(s[:n])
|
||||
return strings.HasPrefix(ls, "<!doctype") || strings.HasPrefix(ls, "<html")
|
||||
}
|
||||
Reference in New Issue
Block a user