chore(deps): update all dependencies
Fixes GO-2026-6629 (golang.org/x/text precis panic). Bumps bun and bun/dialect/pgdialect together to v1.3.0; go directive now 1.26.0.
This commit is contained in:
+1
-1
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "1.10.0"
|
||||
".": "1.11.2"
|
||||
}
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ This file provides guidance for AI coding assistants (OpenAI Codex, ChatGPT, Git
|
||||
- **Package**: `github.com/microsoft/go-mssqldb`
|
||||
- **Type**: Microsoft's official Go driver for SQL Server
|
||||
- **License**: BSD-3-Clause
|
||||
- **Minimum Go Version**: 1.24+
|
||||
- **Minimum Go Version**: 1.25+
|
||||
|
||||
## When to Recommend This Driver
|
||||
|
||||
|
||||
+48
@@ -1,4 +1,52 @@
|
||||
# Changelog
|
||||
## [1.11.2](https://github.com/microsoft/go-mssqldb/compare/v1.11.1...v1.11.2) (2026-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* accept yes/no for boolean connection string keywords ([#468](https://github.com/microsoft/go-mssqldb/issues/468)) ([0ad75cd](https://github.com/microsoft/go-mssqldb/commit/0ad75cd1f92cbfdd4c148b2369332ace4f208160))
|
||||
|
||||
## [1.11.1](https://github.com/microsoft/go-mssqldb/compare/v1.11.0...v1.11.1) (2026-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* drain response tokens on query error to prevent Tx.Query hang ([#407](https://github.com/microsoft/go-mssqldb/issues/407)) ([#410](https://github.com/microsoft/go-mssqldb/issues/410)) ([1f52296](https://github.com/microsoft/go-mssqldb/commit/1f5229662c8006ab3f2ff8bee49d24faa95287b8))
|
||||
* preserve strict encryption in DSN URLs ([#452](https://github.com/microsoft/go-mssqldb/issues/452)) ([ae21d6a](https://github.com/microsoft/go-mssqldb/commit/ae21d6a9a7b0d88e22a0ddb957ad792205c4cfdc))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* undo response-cleanup changes from [#410](https://github.com/microsoft/go-mssqldb/issues/410) ([#469](https://github.com/microsoft/go-mssqldb/issues/469)) ([46dbf85](https://github.com/microsoft/go-mssqldb/commit/46dbf85941c5f2bba0cbde962fc0b113834cac9a))
|
||||
|
||||
## [1.11.0](https://github.com/microsoft/go-mssqldb/compare/v1.10.0...v1.11.0) (2026-08-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ADO.Net connection string synonyms ([#374](https://github.com/microsoft/go-mssqldb/issues/374)) ([d72f750](https://github.com/microsoft/go-mssqldb/commit/d72f750358c4370852fc73cdda9f7f83b53903ff))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* clarify NTLM URL encoding for URL DSNs ([#378](https://github.com/microsoft/go-mssqldb/issues/378)) ([551d622](https://github.com/microsoft/go-mssqldb/commit/551d62231613700aca651cb5b778cdc1db584893))
|
||||
* close TCP connection on all connect() error paths ([#367](https://github.com/microsoft/go-mssqldb/issues/367)) ([0e13fee](https://github.com/microsoft/go-mssqldb/commit/0e13fee2c0bf0efd38803976171cb30d1bc961ad))
|
||||
* **isProc:** add `REVERT` builtin command ([#393](https://github.com/microsoft/go-mssqldb/issues/393)) ([31a9659](https://github.com/microsoft/go-mssqldb/commit/31a9659ac4763e622fc9fbae6fcff44f30760164))
|
||||
* prevent NTLM challenge out-of-bounds panic (DoS) ([#411](https://github.com/microsoft/go-mssqldb/issues/411)) ([81df983](https://github.com/microsoft/go-mssqldb/commit/81df9834c92c7709cdc180c92f8d9a06b9710981))
|
||||
* prevent panic on overflowing PRELOGIN option offset and length ([#415](https://github.com/microsoft/go-mssqldb/issues/415)) ([802d9f1](https://github.com/microsoft/go-mssqldb/commit/802d9f154ab83d7fb88d56f135f20787f5871f15))
|
||||
* prevent SQL Browser DAC response parsing panic (DoS) ([#414](https://github.com/microsoft/go-mssqldb/issues/414)) ([bc4cbf2](https://github.com/microsoft/go-mssqldb/commit/bc4cbf28590fba2f19fb0dfe7cf77891dddb1e1d))
|
||||
* quote table name and ORDER columns in bulk copy ([#416](https://github.com/microsoft/go-mssqldb/issues/416)) ([f925c53](https://github.com/microsoft/go-mssqldb/commit/f925c5343a0fda33276785b765e5571dead35e15))
|
||||
* replace deprecated reflect.SliceHeader with unsafe.Slice ([#366](https://github.com/microsoft/go-mssqldb/issues/366)) ([836048a](https://github.com/microsoft/go-mssqldb/commit/836048ac320da5b92d1c0f7038ba5ac562768f10)), closes [#272](https://github.com/microsoft/go-mssqldb/issues/272)
|
||||
* return float64 for REAL columns in readFixedType ([#381](https://github.com/microsoft/go-mssqldb/issues/381)) ([abddba8](https://github.com/microsoft/go-mssqldb/commit/abddba82884f2b672b88088a1849cb547194a27d))
|
||||
* strip port from AKV URLs for AllowedLocations and endpoint ([#369](https://github.com/microsoft/go-mssqldb/issues/369)) ([7fab98a](https://github.com/microsoft/go-mssqldb/commit/7fab98a2b5b3d57b933a554b6abe15114b5857b3))
|
||||
* **types:** cap initial PLP buffer allocation ([#409](https://github.com/microsoft/go-mssqldb/issues/409)) ([f088291](https://github.com/microsoft/go-mssqldb/commit/f0882915fbf71953d3251bd7482325dd62924050))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* add comprehensive benchmark suite with CI regression detection ([#376](https://github.com/microsoft/go-mssqldb/issues/376)) ([b935441](https://github.com/microsoft/go-mssqldb/commit/b9354415013929850e1a48856661d1109be661a9))
|
||||
* fast path for pure ASCII decoding in CharsetToUTF8 ([#431](https://github.com/microsoft/go-mssqldb/issues/431)) ([db51796](https://github.com/microsoft/go-mssqldb/commit/db5179626672ada427888117b399963d2c3a642b))
|
||||
|
||||
## [1.10.0](https://github.com/microsoft/go-mssqldb/compare/v1.9.8...v1.10.0) (2026-04-25)
|
||||
|
||||
|
||||
|
||||
+20
-4
@@ -11,9 +11,9 @@ A pure Go database/sql driver for Microsoft SQL Server and Azure SQL Database. T
|
||||
|
||||
## Install
|
||||
|
||||
Requires Go 1.17 or above.
|
||||
Requires Go 1.25 or above.
|
||||
|
||||
Install with `go install github.com/microsoft/go-mssqldb@latest`.
|
||||
Install with `go get github.com/microsoft/go-mssqldb@latest`.
|
||||
|
||||
## Connection Parameters and DSN
|
||||
|
||||
@@ -23,6 +23,8 @@ Other supported formats are listed below.
|
||||
|
||||
All connection string parameters are case-insensitive. Providing the same parameter more than once with different casing (e.g., `TrustServerCertificate` and `trustservercertificate`) will result in a parse error.
|
||||
|
||||
Boolean parameters accept `1`, `t`, `T`, `TRUE`, `true`, `True`, `0`, `f`, `F`, `FALSE`, `false`, `False`, and additionally `yes` and `no` in any casing.
|
||||
|
||||
### Common parameters
|
||||
|
||||
* `user id` - enter the SQL Server Authentication user id or the Windows Authentication user id in the DOMAIN\User format. On Windows, if user id is empty or missing Single-Sign-On is used. The user domain sensitive to the case which is defined in the connection string.
|
||||
@@ -180,6 +182,8 @@ For further information on usage:
|
||||
* `sqlserver://username@host/instance?krb5-configfile=path/to/file&krb5-credcachefile=/path/to/cache`
|
||||
* `sqlserver://username@host/instance?krb5-configfile=path/to/file&krb5-realm=domain.com&krb5-keytabfile=/path/to/keytabfile`
|
||||
|
||||
For URL connection strings, percent-encode special characters in the username or password. Windows authentication usernames use `%5C` for the domain separator, for example `sqlserver://DOMAIN%5Cusername:password@host`. In ADO and ODBC connection strings, use the literal `DOMAIN\username` form.
|
||||
|
||||
2. ADO: `key=value` pairs separated by `;`. Values can contain `;` and other special characters by enclosing them in double quotes. Leading and trailing whitespace is ignored.
|
||||
Examples:
|
||||
|
||||
@@ -191,11 +195,23 @@ For further information on usage:
|
||||
* `server=localhost;user id=sa;database=master;app name=MyAppName;krb5-configfile=path/to/file;krb5-realm=domain.com;krb5-keytabfile=path/to/keytabfile;authenticator=krb5`
|
||||
|
||||
|
||||
ADO strings support synonyms for database, app name, user id, and server
|
||||
ADO strings support synonyms for common connection parameters:
|
||||
* server <= addr, address, network address, data source
|
||||
* user id <= user, uid
|
||||
* password <= pwd
|
||||
* database <= initial catalog
|
||||
* app name <= application name
|
||||
* app name <= application name, app
|
||||
* connection timeout <= connect timeout, timeout
|
||||
* failoverpartner <= failover partner
|
||||
* failoverpartnerspn <= failover partner spn
|
||||
* applicationintent <= application intent
|
||||
* trustservercertificate <= trust server certificate
|
||||
* multisubnetfailover <= multi subnet failover
|
||||
* hostnameincertificate <= host name in certificate
|
||||
* serverspn <= server spn
|
||||
* servercertificate <= server certificate
|
||||
* workstation id <= wsid
|
||||
* columnencryption <= column encryption setting
|
||||
|
||||
3. ODBC: Prefix with `odbc`, `key=value` pairs separated by `;`. Allow `;` by wrapping
|
||||
values in `{}`. Examples:
|
||||
|
||||
+20
-10
@@ -1,7 +1,11 @@
|
||||
version: 1.0.{build}
|
||||
|
||||
image:
|
||||
- Visual Studio 2015
|
||||
# SQL Server 2022 and 2025 are only available on the Visual Studio 2026 image.
|
||||
image: Visual Studio 2026
|
||||
|
||||
# Every push to a branch with an open PR otherwise queues two builds, and the
|
||||
# project runs one build at a time. Only the PR build is a required check.
|
||||
skip_branch_with_pr: true
|
||||
|
||||
clone_folder: c:\gopath\src\github.com\microsoft\go-mssqldb
|
||||
|
||||
@@ -12,30 +16,36 @@ environment:
|
||||
SQLPASSWORD: Password12!
|
||||
DATABASE: test
|
||||
GOVERSION: 125
|
||||
SQLINSTANCE: SQL2025
|
||||
COLUMNENCRYPTION:
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
|
||||
TAGS:
|
||||
matrix:
|
||||
- SQLINSTANCE: SQL2017
|
||||
# Default protocol negotiation, 64bit, SQL auth
|
||||
- GOVERSION: 125
|
||||
SQLINSTANCE: SQL2017
|
||||
# Always Encrypted
|
||||
- GOVERSION: 125
|
||||
SQLINSTANCE: SQL2019
|
||||
COLUMNENCRYPTION: 1
|
||||
# Cover 32bit and named pipes protocol
|
||||
# 32bit and named pipes protocol
|
||||
- GOVERSION: 125-x86
|
||||
SQLINSTANCE: SQL2017
|
||||
GOARCH: 386
|
||||
PROTOCOL: np
|
||||
TAGS: -tags np
|
||||
# Cover SSPI and lpc protocol
|
||||
# SSPI and lpc protocol
|
||||
- GOVERSION: 125
|
||||
SQLINSTANCE: SQL2019
|
||||
PROTOCOL: lpc
|
||||
TAGS: -tags sm
|
||||
SQLUSER:
|
||||
SQLPASSWORD:
|
||||
|
||||
init:
|
||||
# AppVeyor does not document instance names past SQL2019; list what is installed.
|
||||
- ps: |
|
||||
$key = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL'
|
||||
(Get-ItemProperty $key -ErrorAction SilentlyContinue).PSObject.Properties |
|
||||
Where-Object Name -notlike 'PS*' |
|
||||
ForEach-Object { "instance: $($_.Name) -> $($_.Value)" }
|
||||
|
||||
install:
|
||||
- set GOROOT=c:\go%GOVERSION%
|
||||
- set PATH=%GOPATH%\bin;%GOROOT%\bin;%PATH%
|
||||
|
||||
+18
-4
@@ -133,7 +133,11 @@ func (b *Bulk) sendBulkCommand(ctx context.Context) (err error) {
|
||||
with_opts = append(with_opts, fmt.Sprintf("ROWS_PER_BATCH = %d", b.Options.RowsPerBatch))
|
||||
}
|
||||
if len(b.Options.Order) > 0 {
|
||||
with_opts = append(with_opts, fmt.Sprintf("ORDER(%s)", strings.Join(b.Options.Order, ",")))
|
||||
order := make([]string, len(b.Options.Order))
|
||||
for i, entry := range b.Options.Order {
|
||||
order[i] = quoteBulkOrder(entry)
|
||||
}
|
||||
with_opts = append(with_opts, fmt.Sprintf("ORDER(%s)", strings.Join(order, ",")))
|
||||
}
|
||||
if b.Options.Tablock {
|
||||
with_opts = append(with_opts, "TABLOCK")
|
||||
@@ -143,7 +147,11 @@ func (b *Bulk) sendBulkCommand(ctx context.Context) (err error) {
|
||||
with_part = fmt.Sprintf("WITH (%s)", strings.Join(with_opts, ","))
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("INSERT BULK %s (%s) %s", b.tablename, col_defs.String(), with_part)
|
||||
quotedTable, err := quoteObjectName(b.tablename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
query := fmt.Sprintf("INSERT BULK %s (%s) %s", quotedTable, col_defs.String(), with_part)
|
||||
|
||||
stmt, err := b.cn.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
@@ -292,6 +300,12 @@ func (b *Bulk) createColMetadata() []byte {
|
||||
}
|
||||
|
||||
func (b *Bulk) getMetadata(ctx context.Context) (err error) {
|
||||
// Check the destination before touching the session's FMTONLY setting.
|
||||
quotedTable, err := quoteObjectName(b.tablename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stmt, err := b.cn.prepareContext(ctx, "SET FMTONLY ON")
|
||||
if err != nil {
|
||||
return
|
||||
@@ -327,13 +341,13 @@ func (b *Bulk) getMetadata(ctx context.Context) (err error) {
|
||||
}()
|
||||
|
||||
// Get columns info.
|
||||
stmt, err = b.cn.prepareContext(ctx, fmt.Sprintf("select * from %s SET FMTONLY OFF", b.tablename))
|
||||
stmt, err = b.cn.prepareContext(ctx, fmt.Sprintf("select * from %s SET FMTONLY OFF", quotedTable))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rows, err := stmt.QueryContext(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get columns info failed: %v", err)
|
||||
return fmt.Errorf("get columns info failed: %w", err)
|
||||
}
|
||||
resetFmtonly = false
|
||||
b.metadata = rows.(*Rows).cols
|
||||
|
||||
+24
@@ -43,6 +43,30 @@ func (c *Conn) prepareCopyIn(ctx context.Context, query string) (_ driver.Stmt,
|
||||
return ci, nil
|
||||
}
|
||||
|
||||
// CopyIn creates a bulk import statement that can be passed to Prepare.
|
||||
//
|
||||
// table is the destination object name. It is treated as an object name and is
|
||||
// quoted before it is sent to the server, so a name that is not a regular
|
||||
// identifier does not need to be delimited by the caller. It may be qualified,
|
||||
// as in "schema.table" or "database.schema.table", and individual parts may be
|
||||
// delimited, as in "[my schema].[my table]".
|
||||
//
|
||||
// Because the destination is always an object name, a value that relied on
|
||||
// being spliced into the statement as raw text no longer resolves. Pass
|
||||
// "table" and options.Tablock rather than "table WITH (TABLOCK)". A name that
|
||||
// cannot identify an object, because it is empty or has more than four parts,
|
||||
// is reported before any statement is sent.
|
||||
//
|
||||
// columns are the destination column names, in the order their values are
|
||||
// passed to Exec.
|
||||
//
|
||||
// Each options.Order entry names one or more columns to declare the data is
|
||||
// already sorted by, as in "id" or "id ASC, name DESC". Column names are quoted
|
||||
// the same way table is. A column name is separated from an optional trailing
|
||||
// ASC or DESC sort direction by whitespace, and from the next column by a
|
||||
// comma, so a column whose name ends in "asc" or "desc" or contains a comma has
|
||||
// to be delimited by the caller to be told apart from a direction or a
|
||||
// separator, as in "[sort desc]" or "[order, id]".
|
||||
func CopyIn(table string, options BulkOptions, columns ...string) string {
|
||||
bulkconfig := &serializableBulkConfig{TableName: table, Options: options, ColumnsName: columns}
|
||||
|
||||
|
||||
+13
-1
@@ -3,6 +3,7 @@ package mssql
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Error represents an SQL Server error. This
|
||||
@@ -24,7 +25,18 @@ type Error struct {
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return "mssql: " + e.Message
|
||||
errChain := e.All
|
||||
if len(errChain) == 0 {
|
||||
errChain = []Error{e}
|
||||
}
|
||||
var msg strings.Builder
|
||||
for i, err := range errChain {
|
||||
if i > 0 {
|
||||
msg.WriteByte('\n')
|
||||
}
|
||||
fmt.Fprintf(&msg, "mssql: %s (%d)", err.Message, err.Number)
|
||||
}
|
||||
return msg.String()
|
||||
}
|
||||
|
||||
func (e Error) String() string {
|
||||
|
||||
+34
-4
@@ -304,15 +304,32 @@ func getNTLMv2TargetInfoFields(type2Message []byte) (info []byte, err error) {
|
||||
|
||||
targetNameAllocated := binary.LittleEndian.Uint16(type2Message[14:16])
|
||||
targetNameOffset := binary.LittleEndian.Uint32(type2Message[16:20])
|
||||
endOfOffset := int(targetNameOffset + uint32(targetNameAllocated))
|
||||
if type2MessageLength < endOfOffset {
|
||||
// Use 64-bit arithmetic so a server-controlled offset near the uint32
|
||||
// maximum cannot overflow and bypass the bounds check below.
|
||||
endOfOffset := int64(targetNameOffset) + int64(targetNameAllocated)
|
||||
if int64(type2MessageLength) < endOfOffset {
|
||||
return nil, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
|
||||
}
|
||||
|
||||
// The target information header fields live at offsets 42-48. A malformed
|
||||
// or truncated challenge message may be long enough to pass the checks
|
||||
// above but too short to contain these fields, so validate before reading
|
||||
// to avoid an out-of-bounds slice read.
|
||||
if type2MessageLength < 48 {
|
||||
return nil, fmt.Errorf(type2MessageError, type2MessageLength, 48)
|
||||
}
|
||||
|
||||
targetInformationAllocated := binary.LittleEndian.Uint16(type2Message[42:44])
|
||||
// A well-formed target information block is an AV_PAIR list terminated by
|
||||
// the 4-byte MsvAvEOL pair. The channel-binding path assumes those final
|
||||
// 4 bytes exist (it slices targetInfoFields[:len-4]), so a block shorter
|
||||
// than 4 bytes is malformed and would cause a negative slice index panic.
|
||||
if targetInformationAllocated < 4 {
|
||||
return nil, fmt.Errorf("mssql: NTLMv2 type 2 message target information length %d too small, minimum 4", targetInformationAllocated)
|
||||
}
|
||||
targetInformationDataOffset := binary.LittleEndian.Uint32(type2Message[44:48])
|
||||
endOfOffset = int(targetInformationDataOffset + uint32(targetInformationAllocated))
|
||||
if type2MessageLength < endOfOffset {
|
||||
endOfOffset = int64(targetInformationDataOffset) + int64(targetInformationAllocated)
|
||||
if int64(type2MessageLength) < endOfOffset {
|
||||
return nil, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
|
||||
}
|
||||
|
||||
@@ -388,7 +405,20 @@ func buildNTLMResponsePayload(lm, nt []byte, flags uint32, domain, workstation,
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// minChallengeMessageSize is the minimum size in bytes of a well-formed
|
||||
// NTLM Type 2 (CHALLENGE) message. NextBytes reads the signature (0:8),
|
||||
// message type (8:12), flags (20:24) and server challenge (24:32) fields
|
||||
// directly, so a shorter buffer would cause an out-of-bounds slice panic.
|
||||
const minChallengeMessageSize = 32
|
||||
|
||||
func (auth *Auth) NextBytes(bytes []byte) ([]byte, error) {
|
||||
// A malicious or on-path server can send a truncated challenge message.
|
||||
// Validate the length before slicing to avoid an out-of-bounds panic
|
||||
// (Denial of Service).
|
||||
if len(bytes) < minChallengeMessageSize {
|
||||
return nil, errorNTLM
|
||||
}
|
||||
|
||||
signature := string(bytes[0:8])
|
||||
if signature != "NTLMSSP\x00" {
|
||||
return nil, errorNTLM
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
//go:build !386 && !arm && !mips && !mipsle
|
||||
|
||||
package cp
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// asciiMask64 has the high bit set in each byte. When AND-ed with an 8 byte
|
||||
// chunk it reports whether any byte in that chunk has its high bit set, i.e.
|
||||
// is >= 0x80.
|
||||
const asciiMask64 uint64 = 0x8080808080808080
|
||||
|
||||
// isASCII reports whether s contains only bytes in the ASCII range (0x00-0x7F).
|
||||
// It scans the input in 8 byte chunks, the same pattern used by ucs22str in the
|
||||
// parent package.
|
||||
func isASCII(s []byte) bool {
|
||||
// how many 8 byte chunks are in the input buffer
|
||||
nlen8 := len(s) &^ 7
|
||||
for i := 0; i < nlen8; i += 8 {
|
||||
// read the 8 byte chunk with an alignment-safe load
|
||||
ui64 := binary.NativeEndian.Uint64(s[i : i+8])
|
||||
|
||||
// mask the entire 64 bit region and check for any byte with the high bit set
|
||||
if ui64&asciiMask64 > 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// deal with the at most 7 remaining bytes
|
||||
for i := nlen8; i < len(s); i++ {
|
||||
if s[i] >= 0x80 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
//go:build arm || 386 || mips || mipsle
|
||||
|
||||
package cp
|
||||
|
||||
// isASCII reports whether s contains only bytes in the ASCII range (0x00-0x7F).
|
||||
func isASCII(s []byte) bool {
|
||||
for _, b := range s {
|
||||
if b >= 0x80 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+7
@@ -96,6 +96,13 @@ func CharsetToUTF8(col Collation, s []byte) string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// Fast path: every code page maps the ASCII range (0x00-0x7F) to the
|
||||
// identical Unicode code points, so a pure ASCII input can be returned
|
||||
// without per-character decoding.
|
||||
if isASCII(s) {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
buf := strings.Builder{}
|
||||
buf.Grow(len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
|
||||
+37
-11
@@ -290,6 +290,17 @@ func setupTLSServerCertificateOnly(config *tls.Config, pemData []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseBoolParam also accepts the ODBC "yes"/"no" spelling that Encrypt already allows.
|
||||
func parseBoolParam(value string) (bool, error) {
|
||||
if strings.EqualFold(value, "yes") {
|
||||
return true, nil
|
||||
}
|
||||
if strings.EqualFold(value, "no") {
|
||||
return false, nil
|
||||
}
|
||||
return strconv.ParseBool(value)
|
||||
}
|
||||
|
||||
// parseTLS parses encryption parameters and returns the TLS configuration and trustServerCertificate value.
|
||||
func parseTLS(params map[string]string, host string) (Encryption, *tls.Config, bool, error) {
|
||||
trustServerCert := false
|
||||
@@ -317,7 +328,7 @@ func parseTLS(params map[string]string, host string) (Encryption, *tls.Config, b
|
||||
trust, ok := params[TrustServerCertificate]
|
||||
if ok {
|
||||
var err error
|
||||
trustServerCert, err = strconv.ParseBool(trust)
|
||||
trustServerCert, err = parseBoolParam(trust)
|
||||
if err != nil {
|
||||
f := "invalid trust server certificate '%s': %s"
|
||||
return encryption, nil, false, fmt.Errorf(f, trust, err.Error())
|
||||
@@ -440,7 +451,7 @@ func Parse(dsn string) (Config, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-network-packet-size-server-configuration-option\
|
||||
// https://docs.microsoft.com/sql/database-engine/configure-windows/configure-the-network-packet-size-server-configuration-option
|
||||
strpsize, ok := params[PacketSize]
|
||||
if ok {
|
||||
var err error
|
||||
@@ -462,7 +473,7 @@ func Parse(dsn string) (Config, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/dd341108.aspx
|
||||
// https://msdn.microsoft.com/library/dd341108.aspx
|
||||
//
|
||||
// Do not set a connection timeout. Use Context to manage such things.
|
||||
// Default to zero, but still allow it to be set.
|
||||
@@ -476,7 +487,7 @@ func Parse(dsn string) (Config, error) {
|
||||
}
|
||||
|
||||
// default keep alive should be 30 seconds according to spec:
|
||||
// https://msdn.microsoft.com/en-us/library/dd341108.aspx
|
||||
// https://msdn.microsoft.com/library/dd341108.aspx
|
||||
p.KeepAlive = 30 * time.Second
|
||||
if keepAlive, ok := params[KeepAlive]; ok {
|
||||
timeout, err := strconv.ParseUint(keepAlive, 10, 64)
|
||||
@@ -541,7 +552,7 @@ func Parse(dsn string) (Config, error) {
|
||||
disableRetry, ok := params[DisableRetry]
|
||||
if ok {
|
||||
var err error
|
||||
p.DisableRetry, err = strconv.ParseBool(disableRetry)
|
||||
p.DisableRetry, err = parseBoolParam(disableRetry)
|
||||
if err != nil {
|
||||
f := "invalid disableRetry '%s': %s"
|
||||
return p, fmt.Errorf(f, disableRetry, err.Error())
|
||||
@@ -601,7 +612,7 @@ func Parse(dsn string) (Config, error) {
|
||||
}
|
||||
|
||||
if c, ok := params["columnencryption"]; ok {
|
||||
columnEncryption, err := strconv.ParseBool(c)
|
||||
columnEncryption, err := parseBoolParam(c)
|
||||
if err != nil {
|
||||
if strings.EqualFold(c, "Enabled") {
|
||||
columnEncryption = true
|
||||
@@ -616,7 +627,7 @@ func Parse(dsn string) (Config, error) {
|
||||
|
||||
msf, ok := params[MultiSubnetFailover]
|
||||
if ok {
|
||||
multiSubnetFailover, err := strconv.ParseBool(msf)
|
||||
multiSubnetFailover, err := parseBoolParam(msf)
|
||||
if err != nil {
|
||||
if strings.EqualFold(msf, "Enabled") {
|
||||
multiSubnetFailover = true
|
||||
@@ -633,7 +644,7 @@ func Parse(dsn string) (Config, error) {
|
||||
}
|
||||
nti, ok := params[NoTraceID]
|
||||
if ok {
|
||||
notraceid, err := strconv.ParseBool(nti)
|
||||
notraceid, err := parseBoolParam(nti)
|
||||
if err == nil {
|
||||
p.NoTraceID = notraceid
|
||||
}
|
||||
@@ -642,7 +653,7 @@ func Parse(dsn string) (Config, error) {
|
||||
guidConversion, ok := params[GuidConversion]
|
||||
if ok {
|
||||
var err error
|
||||
p.Encoding.GuidConversion, err = strconv.ParseBool(guidConversion)
|
||||
p.Encoding.GuidConversion, err = parseBoolParam(guidConversion)
|
||||
if err != nil {
|
||||
f := "invalid guid conversion '%s': %s"
|
||||
return p, fmt.Errorf(f, guidConversion, err.Error())
|
||||
@@ -658,7 +669,7 @@ func Parse(dsn string) (Config, error) {
|
||||
epaString = os.Getenv("MSSQL_USE_EPA")
|
||||
}
|
||||
if epaString != "" {
|
||||
epaEnabled, err := strconv.ParseBool(epaString)
|
||||
epaEnabled, err := parseBoolParam(epaString)
|
||||
if err != nil {
|
||||
return p, fmt.Errorf("invalid epa enabled value '%s': %v", epaString, err)
|
||||
}
|
||||
@@ -722,6 +733,8 @@ func (p Config) URL() *url.URL {
|
||||
q.Add(Encrypt, "DISABLE")
|
||||
case EncryptionRequired:
|
||||
q.Add(Encrypt, "true")
|
||||
case EncryptionStrict:
|
||||
q.Add(Encrypt, "strict")
|
||||
}
|
||||
// Only include TrustServerCertificate if it was explicitly set in the original connection string
|
||||
if _, ok := p.Parameters[TrustServerCertificate]; ok {
|
||||
@@ -759,8 +772,10 @@ func (p Config) URL() *url.URL {
|
||||
return &res
|
||||
}
|
||||
|
||||
// ADO connection string keywords at https://github.com/dotnet/SqlClient/blob/main/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
|
||||
// adoSynonyms maps ADO.Net alternate keyword forms to this driver's canonical keys.
|
||||
// See https://learn.microsoft.com/dotnet/api/microsoft.data.sqlclient.sqlconnection.connectionstring
|
||||
var adoSynonyms = map[string]string{
|
||||
"app": AppName,
|
||||
"application name": AppName,
|
||||
"data source": Server,
|
||||
"address": Server,
|
||||
@@ -770,6 +785,17 @@ var adoSynonyms = map[string]string{
|
||||
"uid": UserID,
|
||||
"pwd": Password,
|
||||
"initial catalog": Database,
|
||||
"connect timeout": ConnectionTimeout,
|
||||
"timeout": ConnectionTimeout,
|
||||
"failover partner": FailoverPartner,
|
||||
"failover partner spn": FailoverPartnerSpn,
|
||||
"application intent": ApplicationIntent,
|
||||
"trust server certificate": TrustServerCertificate,
|
||||
"multi subnet failover": MultiSubnetFailover,
|
||||
"host name in certificate": HostNameInCertificate,
|
||||
"server spn": ServerSpn,
|
||||
"server certificate": ServerCertificate,
|
||||
"wsid": WorkstationID,
|
||||
"column encryption setting": "columnencryption",
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -620,7 +620,7 @@ func (s *Stmt) sendQuery(ctx context.Context, args []namedValue) (err error) {
|
||||
// those commands can be invoke without extra options
|
||||
var builtinCommands = []string{
|
||||
"RECONFIGURE", "SHUTDOWN", "CHECKPOINT",
|
||||
"COMMIT", "ROLLBACK",
|
||||
"COMMIT", "ROLLBACK", "REVERT",
|
||||
}
|
||||
|
||||
// isProc takes the query text in s and determines if it is a stored proc name
|
||||
|
||||
+189
@@ -1,9 +1,14 @@
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// maxIDParts is the number of parts SQL Server allows in an object name:
|
||||
// server.database.schema.object.
|
||||
const maxIDParts = 4
|
||||
|
||||
// TSQLQuoter implements sqlexp.Quoter
|
||||
type TSQLQuoter struct {
|
||||
}
|
||||
@@ -14,6 +19,190 @@ func (TSQLQuoter) ID(name string) string {
|
||||
return "[" + strings.Replace(name, "]", "]]", -1) + "]"
|
||||
}
|
||||
|
||||
// splitMultiPartID splits a possibly multi-part name such as "db.schema.table"
|
||||
// into its individual parts. Dots inside a part delimited with [] or "" are not
|
||||
// treated as separators.
|
||||
func splitMultiPartID(name string) []string {
|
||||
return splitTopLevel(name, '.')
|
||||
}
|
||||
|
||||
// splitTopLevel splits s on sep, ignoring any separator that appears inside a
|
||||
// delimited part such as [my.part] or "my.part".
|
||||
//
|
||||
// A delimiter only opens a delimited part when it starts that part and has a
|
||||
// matching closer. A delimiter anywhere else is an ordinary character of an
|
||||
// undelimited name, so later separators keep splitting and a name such as
|
||||
// "sch[ema.table" still names the object "table" in the schema "sch[ema".
|
||||
func splitTopLevel(s string, sep byte) []string {
|
||||
parts := make([]string, 0, 4)
|
||||
var part strings.Builder
|
||||
// blank reports whether the part being read is still empty or holds only
|
||||
// leading whitespace, so " [my.part] " is recognised as delimited too.
|
||||
blank := true
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if blank {
|
||||
if n := delimitedLen(s[i:]); n > 0 {
|
||||
part.WriteString(s[i : i+n])
|
||||
i += n - 1
|
||||
blank = false
|
||||
continue
|
||||
}
|
||||
}
|
||||
if c == sep {
|
||||
parts = append(parts, part.String())
|
||||
part.Reset()
|
||||
blank = true
|
||||
continue
|
||||
}
|
||||
part.WriteByte(c)
|
||||
if c != ' ' && c != '\t' && c != '\r' && c != '\n' {
|
||||
blank = false
|
||||
}
|
||||
}
|
||||
return append(parts, part.String())
|
||||
}
|
||||
|
||||
// delimitedLen returns the length of the well-formed delimited identifier that
|
||||
// starts s, such as [my part] or "my part", or 0 when s does not start one.
|
||||
// A doubled closing delimiter is an escaped literal rather than the end of the
|
||||
// identifier.
|
||||
func delimitedLen(s string) int {
|
||||
if len(s) < 2 {
|
||||
return 0
|
||||
}
|
||||
var closer byte
|
||||
switch s[0] {
|
||||
case '[':
|
||||
closer = ']'
|
||||
case '"':
|
||||
closer = '"'
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
for i := 1; i < len(s); i++ {
|
||||
if s[i] != closer {
|
||||
continue
|
||||
}
|
||||
if i+1 < len(s) && s[i+1] == closer {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
return i + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// unquoteID reports whether part is a single well-formed delimited identifier
|
||||
// and, if so, returns the identifier without its delimiters. A part such as
|
||||
// `[a] SELECT 1 --[b]` starts and ends with brackets but is not a single
|
||||
// identifier, so it is rejected. It recognises exactly the parts splitTopLevel
|
||||
// keeps together, because both decide what a delimited part is with
|
||||
// delimitedLen.
|
||||
func unquoteID(part string) (string, bool) {
|
||||
// delimitedLen returns 0 for anything that does not start a delimited
|
||||
// identifier, which the length comparison alone would accept for an empty
|
||||
// part.
|
||||
if n := delimitedLen(part); n == 0 || n != len(part) {
|
||||
return "", false
|
||||
}
|
||||
closer := part[len(part)-1]
|
||||
body := part[1 : len(part)-1]
|
||||
var name strings.Builder
|
||||
for i := 0; i < len(body); i++ {
|
||||
// delimitedLen already established that every closer in body is
|
||||
// doubled, so the second byte of the pair is the literal one.
|
||||
if body[i] == closer {
|
||||
i++
|
||||
}
|
||||
name.WriteByte(body[i])
|
||||
}
|
||||
return name.String(), true
|
||||
}
|
||||
|
||||
// idPartName returns the identifier one part of a multi-part name holds, with
|
||||
// any delimiters removed. It is empty when the part names nothing, whether it
|
||||
// was written as "", " " or "[]".
|
||||
func idPartName(part string) string {
|
||||
part = strings.TrimSpace(part)
|
||||
if name, ok := unquoteID(part); ok {
|
||||
return name
|
||||
}
|
||||
return part
|
||||
}
|
||||
|
||||
// quoteIDPart quotes one part of a multi-part name. A part the caller already
|
||||
// delimited with [] or "" keeps its identifier, everything else is escaped.
|
||||
// An empty part is preserved so a name such as "db..table" keeps deferring to
|
||||
// the server's default schema.
|
||||
func quoteIDPart(part string) string {
|
||||
name := idPartName(part)
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
return TSQLQuoter{}.ID(name)
|
||||
}
|
||||
|
||||
// quoteMultiPartID quotes a possibly multi-part object name such as "table",
|
||||
// "schema.table" or "db.schema.table" so it is safe to embed in query text.
|
||||
func quoteMultiPartID(name string) string {
|
||||
parts := splitMultiPartID(name)
|
||||
for i, part := range parts {
|
||||
parts[i] = quoteIDPart(part)
|
||||
}
|
||||
return strings.Join(parts, ".")
|
||||
}
|
||||
|
||||
// quoteObjectName quotes a possibly multi-part object name the same way
|
||||
// quoteMultiPartID does, but rejects a name that cannot identify an object so
|
||||
// the caller reports the problem itself instead of leaving the server to fail
|
||||
// on the query text it produced.
|
||||
func quoteObjectName(name string) (string, error) {
|
||||
parts := splitMultiPartID(name)
|
||||
if len(parts) > maxIDParts {
|
||||
return "", fmt.Errorf("object name %q has %d parts, at most %d are allowed", name, len(parts), maxIDParts)
|
||||
}
|
||||
// The last part names the object itself. An empty one means there is no
|
||||
// object to name, as in "", "dbo." or "dbo.[]", while an earlier empty
|
||||
// part is a qualifier left out on purpose, as in "db..table".
|
||||
if idPartName(parts[len(parts)-1]) == "" {
|
||||
return "", fmt.Errorf("object name %q does not name an object", name)
|
||||
}
|
||||
for i, part := range parts {
|
||||
parts[i] = quoteIDPart(part)
|
||||
}
|
||||
return strings.Join(parts, "."), nil
|
||||
}
|
||||
|
||||
// quoteBulkOrder quotes the column names in a BulkOptions.Order entry so they
|
||||
// are safe to embed in the ORDER hint of an INSERT BULK statement. An entry may
|
||||
// name a single column or several columns separated by commas. An optional
|
||||
// trailing ASC or DESC sort direction is preserved. A column name that itself
|
||||
// ends in "asc" or "desc", or that contains a comma, has to be delimited by the
|
||||
// caller to be told apart from a sort direction or a separator.
|
||||
func quoteBulkOrder(entry string) string {
|
||||
columns := splitTopLevel(entry, ',')
|
||||
for i, column := range columns {
|
||||
columns[i] = quoteBulkOrderColumn(column)
|
||||
}
|
||||
return strings.Join(columns, ",")
|
||||
}
|
||||
|
||||
// quoteBulkOrderColumn quotes a single column of an ORDER hint, keeping any
|
||||
// trailing sort direction.
|
||||
func quoteBulkOrderColumn(column string) string {
|
||||
name := strings.TrimSpace(column)
|
||||
direction := ""
|
||||
if i := strings.LastIndexAny(name, " \t\r\n"); i >= 0 {
|
||||
switch suffix := strings.ToUpper(strings.TrimSpace(name[i:])); suffix {
|
||||
case "ASC", "DESC":
|
||||
direction = " " + suffix
|
||||
name = name[:i]
|
||||
}
|
||||
}
|
||||
return quoteIDPart(name) + direction
|
||||
}
|
||||
|
||||
// Value quotes database values such as string or []byte types as strings
|
||||
// that are suitable and safe to embed in SQL text. The returned value
|
||||
// of a string will include all surrounding quotes.
|
||||
|
||||
+49
-9
@@ -20,10 +20,29 @@ import (
|
||||
"github.com/microsoft/go-mssqldb/msdsn"
|
||||
)
|
||||
|
||||
// parseDAC parses a SQL Server Browser SVR_RESP (DAC) reply, MC-SQLR 2.2.6.
|
||||
// The response is exactly 6 bytes: SVR_RESP (0x05), RESP_SIZE (0x0006, the
|
||||
// length of the whole packet), PROTOCOLVERSION (0x01), then the little-endian
|
||||
// TCP_DAC_PORT at offset 4. Every field except the port is fixed by the
|
||||
// protocol, so a reply that does not match is discarded instead of being used
|
||||
// to choose a TCP port. The message comes from an unauthenticated UDP source,
|
||||
// so this must never panic.
|
||||
func parseDAC(msg []byte, instance string) msdsn.BrowserData {
|
||||
results := msdsn.BrowserData{}
|
||||
if len(msg) == 6 && msg[0] == 5 {
|
||||
results[strings.ToUpper(instance)]["tcp"] = fmt.Sprint(binary.LittleEndian.Uint16(msg[5:]))
|
||||
if len(msg) == 6 && msg[0] == 5 && binary.LittleEndian.Uint16(msg[1:3]) == 6 && msg[3] == 1 {
|
||||
port := binary.LittleEndian.Uint16(msg[4:6])
|
||||
// Port 0 is never a listening endpoint. Accepting it would leave
|
||||
// Config.Port at zero, which resolveServerPort silently rewrites to the
|
||||
// default TDS port, connecting to the regular endpoint while the caller
|
||||
// believes it reached the DAC one.
|
||||
if port == 0 {
|
||||
return results
|
||||
}
|
||||
name := strings.ToUpper(instance)
|
||||
results[name] = map[string]string{
|
||||
"InstanceName": name,
|
||||
"tcp": fmt.Sprint(port),
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
@@ -62,11 +81,18 @@ func getInstances(ctx context.Context, d Dialer, address string, browserMsg msds
|
||||
var bmsg []byte
|
||||
var resp []byte
|
||||
if browserMsg == msdsn.BrowserDAC {
|
||||
// CLNT_UCAST_DAC (MC-SQLR 2.2.4) is 0x0F, PROTOCOLVERSION 0x01, then a
|
||||
// null-terminated instance name starting at offset 2. The buffer is
|
||||
// sized so its zero-initialized last byte is the terminator.
|
||||
bmsg = make([]byte, 3+len(instance))
|
||||
bmsg[0] = byte(msdsn.BrowserDAC)
|
||||
bmsg[1] = 1
|
||||
_ = copy(bmsg[3:], instance)
|
||||
resp = make([]byte, 6)
|
||||
_ = copy(bmsg[2:], instance)
|
||||
// A DAC reply is exactly 6 bytes (MC-SQLR 2.2.6). Read into a larger
|
||||
// buffer so an oversized datagram is reported as such rather than
|
||||
// being silently truncated to a well-formed looking 6 bytes, which
|
||||
// would defeat the length check in parseDAC.
|
||||
resp = make([]byte, 7)
|
||||
} else { // default to AllInstances
|
||||
bmsg = []byte{byte(msdsn.BrowserAllInstances)}
|
||||
resp = make([]byte, 16*1024-1)
|
||||
@@ -348,10 +374,17 @@ func readPreloginOption(buffer []byte, offset int) (*preloginOption, error) {
|
||||
}
|
||||
|
||||
func readPreloginOptionData(plOption *preloginOption, buffer []byte) ([]byte, error) {
|
||||
if plOption == nil {
|
||||
return nil, fmt.Errorf("invalid buffer, invalid prelogin option")
|
||||
}
|
||||
|
||||
buffer_length := len(buffer)
|
||||
// widen to int before adding so the sum cannot wrap around and bypass the bounds check
|
||||
optionOffset := int(plOption.offset)
|
||||
optionLength := int(plOption.length)
|
||||
|
||||
// check if prelogin option data exists in buffer
|
||||
if plOption == nil || int(plOption.length+plOption.offset) > buffer_length ||
|
||||
int(plOption.offset) >= buffer_length {
|
||||
if optionOffset+optionLength > buffer_length || optionOffset >= buffer_length {
|
||||
return nil, fmt.Errorf("invalid buffer, invalid prelogin option")
|
||||
}
|
||||
|
||||
@@ -359,7 +392,7 @@ func readPreloginOptionData(plOption *preloginOption, buffer []byte) ([]byte, er
|
||||
return nil, fmt.Errorf("cannot read data for prelogin terminator record")
|
||||
}
|
||||
|
||||
value := buffer[plOption.offset : plOption.length+plOption.offset]
|
||||
value := buffer[optionOffset : optionOffset+optionLength]
|
||||
return value, nil
|
||||
}
|
||||
|
||||
@@ -1156,6 +1189,13 @@ func connect(ctx context.Context, c *Connector, logger ContextLogger, p msdsn.Co
|
||||
packetSize = 32767
|
||||
}
|
||||
|
||||
var toconn *timeoutConn
|
||||
defer func() {
|
||||
if err != nil && toconn != nil {
|
||||
toconn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
initiate_connection:
|
||||
dialCtx := ctx
|
||||
if p.DialTimeout >= 0 {
|
||||
@@ -1172,7 +1212,7 @@ initiate_connection:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
toconn := newTimeoutConn(conn, p.ConnTimeout)
|
||||
toconn = newTimeoutConn(conn, p.ConnTimeout)
|
||||
outbuf := newTdsBuffer(packetSize, toconn)
|
||||
|
||||
if p.Encryption == msdsn.EncryptionStrict {
|
||||
@@ -1379,7 +1419,6 @@ initiate_connection:
|
||||
if token.isError() {
|
||||
tokenErr := token.getError()
|
||||
tokenErr.Message = "login error: " + tokenErr.Message
|
||||
conn.Close()
|
||||
return nil, tokenErr
|
||||
}
|
||||
case error:
|
||||
@@ -1390,6 +1429,7 @@ initiate_connection:
|
||||
|
||||
if sess.routedServer != "" {
|
||||
toconn.Close()
|
||||
toconn = nil // avoid double-close: if dialConnection fails after goto, the defer would see the old (closed) conn
|
||||
// Need to handle case when routedServer is in "host\instance" format.
|
||||
routedParts := strings.SplitN(sess.routedServer, "\\", 2)
|
||||
p.Host = routedParts[0]
|
||||
|
||||
+11
-1
@@ -77,6 +77,11 @@ const _PLP_NULL = 0xFFFFFFFFFFFFFFFF
|
||||
const _UNKNOWN_PLP_LEN = 0xFFFFFFFFFFFFFFFE
|
||||
const _PLP_TERMINATOR = 0x00000000
|
||||
|
||||
// _MAX_PLP_LEN is the largest length a PLP value can legitimately advertise.
|
||||
// The (max) LOB types top out at 2 GB - 1 byte, so any larger size is a
|
||||
// malformed stream rather than a value we should try to read.
|
||||
const _MAX_PLP_LEN = 0x7FFFFFFF
|
||||
|
||||
// TVP COLUMN FLAGS
|
||||
const _TVP_END_TOKEN = 0x00
|
||||
const _TVP_ROW_TOKEN = 0x01
|
||||
@@ -361,7 +366,7 @@ func readFixedType(ti *typeInfo, r *tdsBuffer, c *cryptoMetadata, encoding msdsn
|
||||
case typeDateTim4:
|
||||
return decodeDateTim4(buf, loc)
|
||||
case typeFlt4:
|
||||
return math.Float32frombits(binary.LittleEndian.Uint32(buf))
|
||||
return float64(math.Float32frombits(binary.LittleEndian.Uint32(buf)))
|
||||
case typeMoney4:
|
||||
return decodeMoney4(buf)
|
||||
case typeMoney:
|
||||
@@ -748,6 +753,11 @@ func readPLPType(ti *typeInfo, r *tdsBuffer, c *cryptoMetadata, encoding msdsn.E
|
||||
// size unknown
|
||||
buf = bytes.NewBuffer(make([]byte, 0, 1000))
|
||||
default:
|
||||
// The advertised size is untrusted, so reject anything a real server
|
||||
// cannot produce before using it as an allocation size.
|
||||
if size > _MAX_PLP_LEN {
|
||||
badStreamPanicf("PLP length %d exceeds the maximum LOB size of %d bytes", size, uint64(_MAX_PLP_LEN))
|
||||
}
|
||||
buf = bytes.NewBuffer(make([]byte, 0, size))
|
||||
}
|
||||
for {
|
||||
|
||||
+1
-16
@@ -5,7 +5,6 @@ package mssql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
)
|
||||
@@ -129,21 +128,7 @@ func ucs22str(s []byte) (string, error) {
|
||||
|
||||
// we can reuse the underlying array and create our own uint16 slice here
|
||||
// because utf16.Decode allocates a new buffer and only reads its input.
|
||||
|
||||
// declare a real uint16 slice so that the compiler can keep track of
|
||||
// the underlying memory as we transfer & convert it.
|
||||
// This is to ensure that the GC does not prematurely collect our data.
|
||||
var uint16slice []uint16
|
||||
|
||||
uint16Header := (*reflect.SliceHeader)(unsafe.Pointer(&uint16slice))
|
||||
sourceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&s))
|
||||
|
||||
uint16Header.Data = sourceHeader.Data
|
||||
// it is important to reference s after the assignment of the Data
|
||||
// pointer to make sure that s is not garbage collected before
|
||||
// we have another reference to the data.
|
||||
uint16Header.Len = len(s) / 2 // the output is half the length in bytes
|
||||
uint16Header.Cap = uint16Header.Len // the capacity is also half the number of bytes
|
||||
uint16slice := unsafe.Slice((*uint16)(unsafe.Pointer(&s[0])), len(s)/2)
|
||||
|
||||
// decode the uint16s as utf-16 and return a string.
|
||||
// After this point both s and uint16slice can be garbage collected.
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
// This value is automatically updated by Release Please during the release process.
|
||||
// It is written to the prelogin and login7 packets during a new connection.
|
||||
const driverVersion = "v1.10.0" // x-release-please-version
|
||||
const driverVersion = "v1.11.2" // x-release-please-version
|
||||
|
||||
func getDriverVersion(ver string) uint32 {
|
||||
var majorVersion uint32
|
||||
|
||||
Reference in New Issue
Block a user