232 lines
6.1 KiB
Go
232 lines
6.1 KiB
Go
// Copyright 2015 PingCAP, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
// The MIT License (MIT)
|
|
//
|
|
// Copyright (c) 2014 wandoulabs
|
|
// Copyright (c) 2014 siddontang
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
// this software and associated documentation files (the "Software"), to deal in
|
|
// the Software without restriction, including without limitation the rights to
|
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
|
// subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
|
|
package util
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"math"
|
|
"net/http"
|
|
"slices"
|
|
"strconv"
|
|
|
|
"github.com/pingcap/tidb/pkg/config"
|
|
"github.com/pingcap/tidb/pkg/parser/charset"
|
|
)
|
|
|
|
// ParseNullTermString parses a null terminated string.
|
|
func ParseNullTermString(b []byte) (str []byte, remain []byte) {
|
|
off := bytes.IndexByte(b, 0)
|
|
if off == -1 {
|
|
return nil, b
|
|
}
|
|
return b[:off], b[off+1:]
|
|
}
|
|
|
|
// ParseLengthEncodedInt parses a length encoded integer.
|
|
func ParseLengthEncodedInt(b []byte) (num uint64, isNull bool, n int) {
|
|
switch b[0] {
|
|
// 251: NULL
|
|
case 0xfb:
|
|
n = 1
|
|
isNull = true
|
|
return
|
|
|
|
// 252: value of following 2
|
|
case 0xfc:
|
|
num = uint64(b[1]) | uint64(b[2])<<8
|
|
n = 3
|
|
return
|
|
|
|
// 253: value of following 3
|
|
case 0xfd:
|
|
num = uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16
|
|
n = 4
|
|
return
|
|
|
|
// 254: value of following 8
|
|
case 0xfe:
|
|
num = uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |
|
|
uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 |
|
|
uint64(b[7])<<48 | uint64(b[8])<<56
|
|
n = 9
|
|
return
|
|
}
|
|
|
|
// https://dev.mysql.com/doc/internals/en/integer.html#length-encoded-integer: If the first byte of a packet is a length-encoded integer and its byte value is 0xfe, you must check the length of the packet to verify that it has enough space for a 8-byte integer.
|
|
// TODO: 0xff is undefined
|
|
|
|
// 0-250: value of first byte
|
|
num = uint64(b[0])
|
|
n = 1
|
|
return
|
|
}
|
|
|
|
// ParseLengthEncodedBytes parses a length encoded byte slice.
|
|
func ParseLengthEncodedBytes(b []byte) ([]byte, bool, int, error) {
|
|
// Get length
|
|
num, isNull, n := ParseLengthEncodedInt(b)
|
|
if num < 1 {
|
|
return nil, isNull, n, nil
|
|
}
|
|
|
|
n += int(num)
|
|
|
|
// Check data length
|
|
if len(b) >= n {
|
|
return b[n-int(num) : n], false, n, nil
|
|
}
|
|
|
|
return nil, false, n, io.EOF
|
|
}
|
|
|
|
// InputDecoder is used to decode input.
|
|
type InputDecoder struct {
|
|
encoding charset.Encoding
|
|
}
|
|
|
|
// NewInputDecoder creates a new InputDecoder.
|
|
func NewInputDecoder(chs string) *InputDecoder {
|
|
return &InputDecoder{
|
|
encoding: charset.FindEncodingTakeUTF8AsNoop(chs),
|
|
}
|
|
}
|
|
|
|
// DecodeInput decodes input.
|
|
func (i *InputDecoder) DecodeInput(src []byte) []byte {
|
|
result, err := i.encoding.Transform(nil, src, charset.OpDecode)
|
|
if err != nil {
|
|
return src
|
|
}
|
|
return result
|
|
}
|
|
|
|
// LengthEncodedIntSize returns the size of length encoded integer.
|
|
func LengthEncodedIntSize(n uint64) int {
|
|
switch {
|
|
case n <= 250:
|
|
return 1
|
|
|
|
case n <= 0xffff:
|
|
return 3
|
|
|
|
case n <= 0xffffff:
|
|
return 4
|
|
}
|
|
|
|
return 9
|
|
}
|
|
|
|
const (
|
|
expFormatBig = 1e15
|
|
expFormatSmall = 1e-15
|
|
defaultMySQLPrec = 5
|
|
)
|
|
|
|
// AppendFormatFloat appends a float64 to dst in MySQL format.
|
|
func AppendFormatFloat(in []byte, fVal float64, prec, bitSize int) []byte {
|
|
absVal := math.Abs(fVal)
|
|
if absVal > math.MaxFloat64 || math.IsNaN(absVal) {
|
|
return []byte{'0'}
|
|
}
|
|
isEFormat := false
|
|
if bitSize == 32 {
|
|
isEFormat = float32(absVal) >= expFormatBig || (float32(absVal) != 0 && float32(absVal) < expFormatSmall)
|
|
} else {
|
|
isEFormat = absVal >= expFormatBig || (absVal != 0 && absVal < expFormatSmall)
|
|
}
|
|
var out []byte
|
|
if isEFormat {
|
|
if bitSize == 32 {
|
|
prec = defaultMySQLPrec
|
|
}
|
|
out = strconv.AppendFloat(in, fVal, 'e', prec, bitSize)
|
|
valStr := out[len(in):]
|
|
// remove the '+' from the string for compatibility.
|
|
plusPos := bytes.IndexByte(valStr, '+')
|
|
if plusPos > 0 {
|
|
plusPosInOut := len(in) + plusPos
|
|
out = slices.Delete(out, plusPosInOut, plusPosInOut+1)
|
|
}
|
|
// remove extra '0'
|
|
ePos := bytes.IndexByte(valStr, 'e')
|
|
pointPos := bytes.IndexByte(valStr, '.')
|
|
ePosInOut := len(in) + ePos
|
|
pointPosInOut := len(in) + pointPos
|
|
validPos := ePosInOut
|
|
for i := ePosInOut - 1; i >= pointPosInOut; i-- {
|
|
if !(out[i] == '0' || out[i] == '.') {
|
|
break
|
|
}
|
|
validPos = i
|
|
}
|
|
out = append(out[:validPos], out[ePosInOut:]...)
|
|
} else {
|
|
out = strconv.AppendFloat(in, fVal, 'f', prec, bitSize)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// CorsHandler adds Cors Header if `cors` config is set.
|
|
type CorsHandler struct {
|
|
handler http.Handler
|
|
cfg *config.Config
|
|
}
|
|
|
|
// NewCorsHandler creates a new CorsHandler.
|
|
func NewCorsHandler(handler http.Handler, cfg *config.Config) http.Handler {
|
|
return CorsHandler{handler: handler, cfg: cfg}
|
|
}
|
|
|
|
// ServeHTTP implements http.Handler interface.
|
|
func (h CorsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
if h.cfg.Cors != "" {
|
|
w.Header().Set("Access-Control-Allow-Origin", h.cfg.Cors)
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET")
|
|
}
|
|
h.handler.ServeHTTP(w, req)
|
|
}
|
|
|
|
// NewTestConfig creates a new config for test.
|
|
func NewTestConfig() *config.Config {
|
|
cfg := config.NewConfig()
|
|
cfg.Host = "127.0.0.1"
|
|
cfg.Status.StatusHost = "127.0.0.1"
|
|
cfg.Security.AutoTLS = false
|
|
cfg.Socket = ""
|
|
return cfg
|
|
}
|