@@ -151,35 +151,192 @@ def _decode_datetime(raw):
151151 import datetime
152152 return "%s" % (datetime .datetime (1900 , 1 , 1 ) + datetime .timedelta (days = days , milliseconds = ticks * 10.0 / 3.0 ))
153153
154+ def _decode_money (raw ):
155+ if len (raw ) == 4 :
156+ v = struct .unpack ("<i" , raw )[0 ]
157+ else : # 8 bytes: signed high dword, unsigned low dword
158+ hi , lo = struct .unpack ("<iI" , raw )
159+ v = (hi << 32 ) | lo
160+ return "%.4f" % (v / 10000.0 )
161+
162+ def _decode_numeric (raw , scale ):
163+ sign = bytearray (raw )[0 ] # 1 == positive, 0 == negative; magnitude is little-endian
164+ magnitude = 0
165+ for b in reversed (bytearray (raw [1 :])):
166+ magnitude = (magnitude << 8 ) | b
167+ value = magnitude if sign else - magnitude
168+ if scale :
169+ s = "%0*d" % (scale + 1 , abs (value ))
170+ return ("-" if value < 0 else "" ) + s [:- scale ] + "." + s [- scale :]
171+ return str (value )
172+
173+ def _decode_guid (raw ):
174+ a , b , c = struct .unpack ("<IHH" , raw [:8 ]) # Data1/2/3 little-endian, Data4 big-endian (mixed-endian GUID)
175+ d = raw [8 :]
176+ return "%08X-%04X-%04X-%s-%s" % (a , b , c ,
177+ "" .join ("%02X" % x for x in bytearray (d [:2 ])), "" .join ("%02X" % x for x in bytearray (d [2 :])))
178+
179+ def _decode_temporal (t , scale , raw ):
180+ # DATE 0x28 / TIME 0x29 / DATETIME2 0x2a / DATETIMEOFFSET 0x2b; time is scaled 10^-scale second units,
181+ # rendered with the column's exact fractional precision (Python datetime only holds microseconds)
182+ import datetime
183+ offset = None
184+ if t == 0x2b : # trailing 2-byte signed offset (minutes from UTC); value bytes are stored as UTC
185+ offset = struct .unpack ("<h" , raw [- 2 :])[0 ]
186+ raw = raw [:- 2 ]
187+ has_date = t in (0x28 , 0x2a , 0x2b )
188+ has_time = t != 0x28
189+ date_bytes = raw [- 3 :] if has_date else b""
190+ time_bytes = (raw [:- 3 ] if has_date else raw ) if has_time else b""
191+ days = struct .unpack ("<I" , date_bytes + b"\x00 " )[0 ] if has_date else 0
192+ base = datetime .datetime (1 , 1 , 1 ) + datetime .timedelta (days = days )
193+ frac = ""
194+ if has_time :
195+ ticks = struct .unpack ("<Q" , time_bytes + b"\x00 " * (8 - len (time_bytes )))[0 ]
196+ base += datetime .timedelta (seconds = ticks // (10 ** scale ))
197+ if scale :
198+ frac = "." + ("%0*d" % (scale , ticks % (10 ** scale )))
199+ if offset is not None :
200+ base += datetime .timedelta (minutes = offset )
201+ if t == 0x28 :
202+ return "%s" % base .date ()
203+ if t == 0x29 :
204+ return "%s" % base .time () + frac
205+ s = "%s" % base + frac
206+ if offset is not None :
207+ sign , mins = ("+" , offset ) if offset >= 0 else ("-" , - offset )
208+ s += " %s%02d:%02d" % (sign , mins // 60 , mins % 60 )
209+ return s
210+
211+ # SQL Server COLLATION -> Python codec. The 5-byte collation is a little-endian uint32 (low 20 bits = LCID)
212+ # plus a 1-byte sort id: a non-zero sort id fixes the code page, else the LCID does. Only single-byte / DBCS
213+ # code pages need a codec (NVARCHAR is UTF-16, handled separately). Derived from pytds; default cp1252 (the
214+ # stock SQL_Latin1_General code page - NOT latin-1, whose 0x80-0x9F differ, corrupting e.g. the euro sign).
215+ _LCID_CP = {
216+ 0x405 : "cp1250" , 0x40e : "cp1250" , 0x415 : "cp1250" , 0x418 : "cp1250" , 0x41a : "cp1250" , 0x41b : "cp1250" ,
217+ 0x41c : "cp1250" , 0x424 : "cp1250" , 0x402 : "cp1251" , 0x419 : "cp1251" , 0x422 : "cp1251" , 0x423 : "cp1251" ,
218+ 0x42f : "cp1251" , 0x408 : "cp1253" , 0x41f : "cp1254" , 0x42c : "cp1254" , 0x443 : "cp1254" , 0x40d : "cp1255" ,
219+ 0x401 : "cp1256" , 0x420 : "cp1256" , 0x429 : "cp1256" , 0x425 : "cp1257" , 0x426 : "cp1257" , 0x427 : "cp1257" ,
220+ 0x42a : "cp1258" , 0x41e : "cp874" , 0x411 : "cp932" , 0x804 : "cp936" , 0x1004 : "cp936" , 0x412 : "cp949" ,
221+ 0x404 : "cp950" , 0xc04 : "cp950" , 0x1404 : "cp950" ,
222+ }
223+
224+ def _sortid_cp (sid ):
225+ if 30 <= sid <= 34 :
226+ return "cp437"
227+ if 40 <= sid <= 44 or sid == 49 or 55 <= sid <= 61 :
228+ return "cp850"
229+ if sid in (51 , 52 , 53 , 54 ) or 183 <= sid <= 186 :
230+ return "cp1252"
231+ if 80 <= sid <= 96 :
232+ return "cp1250"
233+ if 104 <= sid <= 108 :
234+ return "cp1251"
235+ if 112 <= sid <= 124 :
236+ return "cp1253"
237+ if 128 <= sid <= 130 :
238+ return "cp1254"
239+ if 136 <= sid <= 138 :
240+ return "cp1255"
241+ if 144 <= sid <= 146 :
242+ return "cp1256"
243+ if 152 <= sid <= 160 :
244+ return "cp1257"
245+ return None
246+
247+ def _collation_codec (collation ):
248+ if not collation or len (collation ) < 5 :
249+ return "cp1252"
250+ lump = struct .unpack ("<I" , collation [:4 ])[0 ]
251+ sid = bytearray (collation )[4 ]
252+ if sid :
253+ return _sortid_cp (sid ) or "cp1252"
254+ return _LCID_CP .get (lump & 0xfffff , "cp1252" )
255+
256+ def _decode_variant (body ):
257+ # SQL_VARIANT value: base type (1) | property-bytes count (1) | type-specific metadata | value bytes
258+ b = bytearray (body )
259+ base , propbytes = b [0 ], b [1 ]
260+ meta , val = body [2 :2 + propbytes ], body [2 + propbytes :]
261+ if base == 0x30 :
262+ return str (bytearray (val )[0 ])
263+ if base == 0x32 :
264+ return "1" if bytearray (val )[0 ] else "0"
265+ if base == 0x34 :
266+ return str (struct .unpack ("<h" , val )[0 ])
267+ if base == 0x38 :
268+ return str (struct .unpack ("<i" , val )[0 ])
269+ if base == 0x7f :
270+ return str (struct .unpack ("<q" , val )[0 ])
271+ if base == 0x3b :
272+ return repr (struct .unpack ("<f" , val )[0 ])
273+ if base == 0x3e :
274+ return repr (struct .unpack ("<d" , val )[0 ])
275+ if base in (0x3c , 0x7a ):
276+ return _decode_money (val )
277+ if base == 0x3d :
278+ return _decode_datetime (val )
279+ if base == 0x24 :
280+ return _decode_guid (val )
281+ if base in (0x6a , 0x6c ): # decimal/numeric: metadata = precision, scale
282+ return _decode_numeric (val , bytearray (meta )[1 ])
283+ if base in (0xe7 , 0xef ):
284+ return val .decode ("utf-16-le" , "replace" )
285+ if base in (0xa5 , 0xad ):
286+ return val # binary -> raw bytes
287+ if base in (0xa7 , 0xaf ): # (var)char: metadata = 5-byte collation + 2-byte max length
288+ return val .decode (_collation_codec (meta [:5 ]), "replace" )
289+ if base == 0x28 :
290+ return _decode_temporal (base , 0 , val )
291+ if base in (0x29 , 0x2a , 0x2b ): # metadata = scale
292+ return _decode_temporal (base , bytearray (meta )[0 ], val )
293+ return "" .join ("%02x" % x for x in bytearray (val )) # unknown base type -> hex (never desyncs)
294+
154295class _Column (object ):
155- __slots__ = ("name" , "type" , "size" , "scale" , "binary" )
296+ __slots__ = ("name" , "type" , "size" , "scale" , "binary" , "collation" )
156297
157298def _parse_type_info (data , off ):
158299 col = _Column ()
159300 col .type = _u8 (data , off ); off += 1
160- col .size , col .scale , col .binary = 0 , 0 , False
301+ col .size , col .scale , col .binary , col . collation = 0 , 0 , False , None
161302 t = col .type
162303 if t in (0x30 , 0x32 , 0x34 , 0x38 , 0x3a , 0x3b , 0x3c , 0x3d , 0x3e , 0x7a , 0x7f , 0x1f ):
163304 pass # fixed-length types, size implied by type
164- elif t in (0x26 , 0x68 , 0x6d , 0x6e , 0x6f , 0x24 , 0x2e , 0x37 ): # INTN/BITN/FLTN/MONEYN/DATETIMN/GUID
305+ elif t in (0x26 , 0x68 , 0x6d , 0x6e , 0x6f , 0x24 ): # INTN/BITN/FLTN/MONEYN/DATETIMN/GUID
165306 col .size = _u8 (data , off ); off += 1
166- elif t in (0x6a , 0x6c ): # DECIMALN / NUMERICN
307+ elif t in (0x6a , 0x6c , 0x37 , 0x3f ): # DECIMALN/NUMERICN + legacy DECIMAL/NUMERIC (size, precision, scale)
167308 col .size = _u8 (data , off ); off += 1
168309 off += 1 # precision
169310 col .scale = _u8 (data , off ); off += 1
170311 elif t in (0xa7 , 0xaf , 0xe7 , 0xef ): # (BIG)VARCHAR/CHAR, N(VAR)CHAR
171312 col .size = struct .unpack ("<H" , data [off :off + 2 ])[0 ]; off += 2
172- off += 5 # collation
313+ col . collation = data [ off : off + 5 ]; off += 5
173314 elif t in (0xa5 , 0xad ): # (BIG)VARBINARY / BINARY
174315 col .size = struct .unpack ("<H" , data [off :off + 2 ])[0 ]; off += 2
175316 col .binary = True
176317 elif t in (0x28 , 0x29 , 0x2a , 0x2b ): # DATE/TIME/DATETIME2/DATETIMEOFFSET
177318 if t != 0x28 :
178319 col .scale = _u8 (data , off ); off += 1
320+ elif t == 0xf0 : # UDT (CLR geometry/geography/hierarchyid) - value arrives as PLP raw bytes
321+ off += 2 # max byte size
322+ for _ in range (3 ): # db, schema, type name: B_VARCHAR
323+ off += 1 + _u8 (data , off ) * 2
324+ off += 2 + struct .unpack ("<H" , data [off :off + 2 ])[0 ] * 2 # assembly-qualified name: US_VARCHAR
325+ col .binary = True
326+ elif t == 0xf1 : # XML (value arrives PLP-encoded UTF-16, no size in TYPE_INFO)
327+ if _u8 (data , off ): # schema-present: B_VARCHAR dbname, B_VARCHAR owner, US_VARCHAR collection
328+ off += 1
329+ for _ in range (2 ):
330+ off += 1 + _u8 (data , off ) * 2
331+ off += 2 + struct .unpack ("<H" , data [off :off + 2 ])[0 ] * 2
332+ else :
333+ off += 1
334+ elif t == 0x62 : # SQL_VARIANT (4-byte max length; per-value base type carried in the body)
335+ col .size = struct .unpack ("<i" , data [off :off + 4 ])[0 ]; off += 4
179336 elif t in (0x23 , 0x63 , 0x22 ): # TEXT/NTEXT/IMAGE
180337 col .size = struct .unpack ("<i" , data [off :off + 4 ])[0 ]; off += 4
181338 if t in (0x23 , 0x63 ):
182- off += 5 # collation
339+ col . collation = data [ off : off + 5 ]; off += 5
183340 col .binary = (t == 0x22 )
184341 # table name (num parts + parts) follows in COLMETADATA for these; handled by caller via name read
185342 else :
@@ -219,10 +376,9 @@ def _decode_value(col, data, off):
219376 if t == 0x3e :
220377 return repr (struct .unpack ("<d" , data [off :off + 8 ])[0 ]), off + 8
221378 if t == 0x7a : # MONEY4 (4 bytes)
222- return "%.4f" % (struct .unpack ("<i" , data [off :off + 4 ])[0 ] / 10000.0 ), off + 4
223- if t == 0x3c : # MONEY (8 bytes: high 4 then low 4)
224- hi , lo = struct .unpack ("<iI" , data [off :off + 8 ])
225- return "%.4f" % (((hi << 32 ) | lo ) / 10000.0 ), off + 8
379+ return _decode_money (data [off :off + 4 ]), off + 4
380+ if t == 0x3c : # MONEY (8 bytes: high dword then low dword)
381+ return _decode_money (data [off :off + 8 ]), off + 8
226382 if t == 0x3d : # DATETIME (8 bytes)
227383 return _decode_datetime (data [off :off + 8 ]), off + 8
228384 if t == 0x3a : # DATETIM4 / smalldatetime (2-byte days since 1900 + 2-byte minutes)
@@ -245,7 +401,7 @@ def _decode_value(col, data, off):
245401 return raw , off # binary -> raw bytes
246402 if t in (0xe7 , 0xef ):
247403 return raw .decode ("utf-16-le" , "replace" ), off
248- return raw .decode ("latin-1 " ), off # (var)char is the collation's single-byte codepage; latin-1 round-trips every byte losslessly
404+ return raw .decode (_collation_codec ( col . collation ), "replace " ), off # (var)char: the collation's code page
249405
250406 if t in (0x23 , 0x63 , 0x22 ): # TEXT/NTEXT/IMAGE: 1-byte textptr len (0 = NULL) then textptr+timestamp then 4-byte len
251407 ptr_len = _u8 (data , off ); off += 1
@@ -258,7 +414,21 @@ def _decode_value(col, data, off):
258414 return raw , off
259415 if t == 0x63 :
260416 return raw .decode ("utf-16-le" , "replace" ), off
261- return raw .decode ("latin-1" ), off # TEXT is single-byte codepage; latin-1 is lossless
417+ return raw .decode (_collation_codec (col .collation ), "replace" ), off # TEXT: the collation's code page
418+
419+ if t == 0xf1 : # XML: PLP-encoded UTF-16-LE
420+ raw , off = _read_plp (data , off )
421+ return (raw .decode ("utf-16-le" , "replace" ) if raw is not None else None ), off
422+
423+ if t == 0xf0 : # UDT (geometry/geography/hierarchyid): PLP raw bytes -> sqlmap hex-encodes them
424+ return _read_plp (data , off )
425+
426+ if t == 0x62 : # SQL_VARIANT: 4-byte total length (0 = NULL) then a self-describing value body
427+ (total ,) = struct .unpack ("<i" , data [off :off + 4 ]); off += 4
428+ if total <= 0 :
429+ return None , off
430+ body , off = data [off :off + total ], off + total
431+ return _decode_variant (body ), off
262432
263433 # nullable / length-prefixed numeric & misc
264434 (n ,) = struct .unpack ("<B" , data [off :off + 1 ]); off += 1
@@ -272,47 +442,20 @@ def _decode_value(col, data, off):
272442 if t == 0x6d : # FLTN
273443 return repr (struct .unpack ("<f" if n == 4 else "<d" , raw )[0 ]), off
274444 if t in (0x6e , 0x3d , 0x7a ): # MONEYN / MONEY / MONEY4
275- if n == 4 :
276- v = struct .unpack ("<i" , raw )[0 ]
277- else :
278- hi , lo = struct .unpack ("<ii" , raw )
279- v = (hi << 32 ) | (lo & 0xffffffff )
280- return "%.4f" % (v / 10000.0 ), off
281- if t in (0x6a , 0x6c ): # DECIMALN / NUMERICN
282- sign = bytearray (raw )[0 ]
283- magnitude = 0
284- for b in reversed (bytearray (raw [1 :])):
285- magnitude = (magnitude << 8 ) | b
286- value = magnitude if sign else - magnitude
287- if col .scale :
288- s = "%0*d" % (col .scale + 1 , value if value >= 0 else - value )
289- s = ("-" if value < 0 else "" ) + s [:- col .scale ] + "." + s [- col .scale :]
290- return s , off
291- return str (value ), off
445+ return _decode_money (raw ), off
446+ if t in (0x6a , 0x6c , 0x37 , 0x3f ): # DECIMALN / NUMERICN (+ legacy DECIMAL / NUMERIC)
447+ return _decode_numeric (raw , col .scale ), off
292448 if t == 0x24 : # GUID
293- a , b , c = struct .unpack ("<IHH" , raw [:8 ])
294- d = raw [8 :]
295- return ("%08X-%04X-%04X-%s-%s" % (a , b , c , "" .join ("%02X" % x for x in bytearray (d [:2 ])), "" .join ("%02X" % x for x in bytearray (d [2 :])))), off
449+ return _decode_guid (raw ), off
296450 if t == 0x6f : # DATETIMN (n=8 datetime, n=4 smalldatetime)
297451 if n == 8 :
298452 return _decode_datetime (raw ), off
299453 import datetime
300454 days , mins = struct .unpack ("<HH" , raw )
301455 return "%s" % (datetime .datetime (1900 , 1 , 1 ) + datetime .timedelta (days = days , minutes = mins )), off
302- if t == 0x28 : # DATE (3 bytes: days since 0001-01-01)
303- import datetime
304- days = struct .unpack ("<I" , raw [:3 ] + b"\x00 " )[0 ]
305- return "%s" % (datetime .date (1 , 1 , 1 ) + datetime .timedelta (days = days )), off
306- if t in (0x2a , 0x29 ): # DATETIME2 (time + 3-byte date) / TIME (time only), time = scaled 10^-scale s units
307- import datetime
308- date_bytes = raw [- 3 :] if t == 0x2a else b"\x00 \x00 \x00 "
309- time_bytes = raw [:- 3 ] if t == 0x2a else raw
310- days = struct .unpack ("<I" , date_bytes + b"\x00 " )[0 ]
311- ticks = struct .unpack ("<Q" , time_bytes + b"\x00 " * (8 - len (time_bytes )))[0 ]
312- seconds = ticks / (10.0 ** (col .scale or 7 ))
313- base = datetime .datetime (1 , 1 , 1 ) + datetime .timedelta (days = days , seconds = seconds )
314- return ("%s" % base if t == 0x2a else "%s" % base .time ()), off
315- # unknown date/time layout: return the raw hex as a last resort (never desyncs)
456+ if t in (0x28 , 0x29 , 0x2a , 0x2b ): # DATE / TIME / DATETIME2 / DATETIMEOFFSET
457+ return _decode_temporal (t , col .scale , raw ), off
458+ # unknown layout: return the raw hex as a last resort (never desyncs)
316459 return "" .join ("%02x" % x for x in bytearray (raw )), off
317460
318461def _parse_tokens (sock , login = False ):
0 commit comments