• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // PropIDUtils.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../../C/CpuArch.h"
6 
7 #include "../../../Common/IntToString.h"
8 #include "../../../Common/StringConvert.h"
9 
10 #include "../../../Windows/FileIO.h"
11 #include "../../../Windows/PropVariantConv.h"
12 
13 #include "../../PropID.h"
14 
15 #include "PropIDUtils.h"
16 
17 #ifndef Z7_SFX
18 #define Get16(x) GetUi16(x)
19 #define Get32(x) GetUi32(x)
20 #endif
21 
22 using namespace NWindows;
23 
24 static const unsigned kNumWinAtrribFlags = 21;
25 static const char g_WinAttribChars[kNumWinAtrribFlags + 1] = "RHS8DAdNTsLCOIEV.X.PU";
26 
27 /*
28 FILE_ATTRIBUTE_
29 
30 0 READONLY
31 1 HIDDEN
32 2 SYSTEM
33 3 (Volume label - obsolete)
34 4 DIRECTORY
35 5 ARCHIVE
36 6 DEVICE
37 7 NORMAL
38 8 TEMPORARY
39 9 SPARSE_FILE
40 10 REPARSE_POINT
41 11 COMPRESSED
42 12 OFFLINE
43 13 NOT_CONTENT_INDEXED (I - Win10 attrib/Explorer)
44 14 ENCRYPTED
45 15 INTEGRITY_STREAM (V - ReFS Win8/Win2012)
46 16 VIRTUAL (reserved)
47 17 NO_SCRUB_DATA (X - ReFS Win8/Win2012 attrib)
48 18 RECALL_ON_OPEN or EA
49 19 PINNED
50 20 UNPINNED
51 21 STRICTLY_SEQUENTIAL
52 22 RECALL_ON_DATA_ACCESS
53 */
54 
55 
56 static const char kPosixTypes[16] = { '0', 'p', 'c', '3', 'd', '5', 'b', '7', '-', '9', 'l', 'B', 's', 'D', 'E', 'F' };
57 #define MY_ATTR_CHAR(a, n, c) (((a) & (1 << (n))) ? c : '-')
58 
ConvertPosixAttribToString(char * s,UInt32 a)59 static void ConvertPosixAttribToString(char *s, UInt32 a) throw()
60 {
61   s[0] = kPosixTypes[(a >> 12) & 0xF];
62   for (int i = 6; i >= 0; i -= 3)
63   {
64     s[7 - i] = MY_ATTR_CHAR(a, i + 2, 'r');
65     s[8 - i] = MY_ATTR_CHAR(a, i + 1, 'w');
66     s[9 - i] = MY_ATTR_CHAR(a, i + 0, 'x');
67   }
68   if ((a & 0x800) != 0) s[3] = ((a & (1 << 6)) ? 's' : 'S'); // S_ISUID
69   if ((a & 0x400) != 0) s[6] = ((a & (1 << 3)) ? 's' : 'S'); // S_ISGID
70   if ((a & 0x200) != 0) s[9] = ((a & (1 << 0)) ? 't' : 'T'); // S_ISVTX
71   s[10] = 0;
72 
73   a &= ~(UInt32)0xFFFF;
74   if (a != 0)
75   {
76     s[10] = ' ';
77     ConvertUInt32ToHex8Digits(a, s + 11);
78   }
79 }
80 
81 
ConvertWinAttribToString(char * s,UInt32 wa)82 void ConvertWinAttribToString(char *s, UInt32 wa) throw()
83 {
84   /*
85   some programs store posix attributes in high 16 bits.
86     p7zip - stores additional 0x8000 flag marker.
87     macos - stores additional 0x4000 flag marker.
88     info-zip - no additional marker.
89   But this code works with Attrib from internal 7zip code.
90   So we expect that 0x8000 marker is set, if there are posix attributes.
91   (DT_UNKNOWN == 0) type in high bits is possible in some case for linux files.
92   0x8000 flag is possible also in ReFS (Windows)?
93   */
94 
95   const bool isPosix = (
96       (wa & 0x8000) != 0 // FILE_ATTRIBUTE_UNIX_EXTENSION;
97       // && (wa & 0xFFFF0000u) != 0
98       );
99 
100   UInt32 posix = 0;
101   if (isPosix)
102   {
103     posix = wa >> 16;
104     if ((wa & 0xF0000000u) != 0)
105       wa &= (UInt32)0x3FFF;
106   }
107 
108   for (unsigned i = 0; i < kNumWinAtrribFlags; i++)
109   {
110     UInt32 flag = (1 << i);
111     if ((wa & flag) != 0)
112     {
113       char c = g_WinAttribChars[i];
114       if (c != '.')
115       {
116         wa &= ~flag;
117         // if (i != 7) // we can disable N (NORMAL) printing
118         *s++ = c;
119       }
120     }
121   }
122 
123   if (wa != 0)
124   {
125     *s++ = ' ';
126     ConvertUInt32ToHex8Digits(wa, s);
127     s += strlen(s);
128   }
129 
130   *s = 0;
131 
132   if (isPosix)
133   {
134     *s++ = ' ';
135     ConvertPosixAttribToString(s, posix);
136   }
137 }
138 
139 
ConvertPropertyToShortString2(char * dest,const PROPVARIANT & prop,PROPID propID,int level)140 void ConvertPropertyToShortString2(char *dest, const PROPVARIANT &prop, PROPID propID, int level) throw()
141 {
142   *dest = 0;
143 
144   if (prop.vt == VT_FILETIME)
145   {
146     const FILETIME &ft = prop.filetime;
147     unsigned ns100 = 0;
148     int numDigits = kTimestampPrintLevel_NTFS;
149     const unsigned prec = prop.wReserved1;
150     const unsigned ns100_Temp = prop.wReserved2;
151     if (prec != 0
152         && prec <= k_PropVar_TimePrec_1ns
153         && ns100_Temp < 100
154         && prop.wReserved3 == 0)
155     {
156       ns100 = ns100_Temp;
157       if (prec == k_PropVar_TimePrec_Unix ||
158           prec == k_PropVar_TimePrec_DOS)
159         numDigits = 0;
160       else if (prec == k_PropVar_TimePrec_HighPrec)
161         numDigits = 9;
162       else
163       {
164         numDigits = (int)prec - (int)k_PropVar_TimePrec_Base;
165         if (
166             // numDigits < kTimestampPrintLevel_DAY // for debuf
167             numDigits < kTimestampPrintLevel_SEC
168             )
169 
170           numDigits = kTimestampPrintLevel_NTFS;
171       }
172     }
173     if (ft.dwHighDateTime == 0 && ft.dwLowDateTime == 0 && ns100 == 0)
174       return;
175     if (level > numDigits)
176       level = numDigits;
177     ConvertUtcFileTimeToString2(ft, ns100, dest, level);
178     return;
179   }
180 
181   switch (propID)
182   {
183     case kpidCRC:
184     {
185       if (prop.vt != VT_UI4)
186         break;
187       ConvertUInt32ToHex8Digits(prop.ulVal, dest);
188       return;
189     }
190     case kpidAttrib:
191     {
192       if (prop.vt != VT_UI4)
193         break;
194       const UInt32 a = prop.ulVal;
195 
196       /*
197       if ((a & 0x8000) && (a & 0x7FFF) == 0)
198         ConvertPosixAttribToString(dest, a >> 16);
199       else
200       */
201       ConvertWinAttribToString(dest, a);
202       return;
203     }
204     case kpidPosixAttrib:
205     {
206       if (prop.vt != VT_UI4)
207         break;
208       ConvertPosixAttribToString(dest, prop.ulVal);
209       return;
210     }
211     case kpidINode:
212     {
213       if (prop.vt != VT_UI8)
214         break;
215       ConvertUInt32ToString((UInt32)(prop.uhVal.QuadPart >> 48), dest);
216       dest += strlen(dest);
217       *dest++ = '-';
218       const UInt64 low = prop.uhVal.QuadPart & (((UInt64)1 << 48) - 1);
219       ConvertUInt64ToString(low, dest);
220       return;
221     }
222     case kpidVa:
223     {
224       UInt64 v = 0;
225       if (prop.vt == VT_UI4)
226         v = prop.ulVal;
227       else if (prop.vt == VT_UI8)
228         v = (UInt64)prop.uhVal.QuadPart;
229       else
230         break;
231       dest[0] = '0';
232       dest[1] = 'x';
233       ConvertUInt64ToHex(v, dest + 2);
234       return;
235     }
236 
237     /*
238     case kpidDevice:
239     {
240       UInt64 v = 0;
241       if (prop.vt == VT_UI4)
242         v = prop.ulVal;
243       else if (prop.vt == VT_UI8)
244         v = (UInt64)prop.uhVal.QuadPart;
245       else
246         break;
247       ConvertUInt32ToString(MY_dev_major(v), dest);
248       dest += strlen(dest);
249       *dest++ = ',';
250       ConvertUInt32ToString(MY_dev_minor(v), dest);
251       return;
252     }
253     */
254     default: break;
255   }
256 
257   ConvertPropVariantToShortString(prop, dest);
258 }
259 
ConvertPropertyToString2(UString & dest,const PROPVARIANT & prop,PROPID propID,int level)260 void ConvertPropertyToString2(UString &dest, const PROPVARIANT &prop, PROPID propID, int level)
261 {
262   if (prop.vt == VT_BSTR)
263   {
264     dest.SetFromBstr(prop.bstrVal);
265     return;
266   }
267   char temp[64];
268   ConvertPropertyToShortString2(temp, prop, propID, level);
269   dest = temp;
270 }
271 
272 #ifndef Z7_SFX
273 
AddHexToString(AString & res,unsigned v)274 static inline void AddHexToString(AString &res, unsigned v)
275 {
276   res.Add_Char((char)GET_HEX_CHAR_UPPER(v >> 4));
277   res.Add_Char((char)GET_HEX_CHAR_UPPER(v & 15));
278 }
279 
280 /*
281 static AString Data_To_Hex(const Byte *data, size_t size)
282 {
283   AString s;
284   for (size_t i = 0; i < size; i++)
285     AddHexToString(s, data[i]);
286   return s;
287 }
288 */
289 
290 static const char * const sidNames[] =
291 {
292     "0"
293   , "Dialup"
294   , "Network"
295   , "Batch"
296   , "Interactive"
297   , "Logon"  // S-1-5-5-X-Y
298   , "Service"
299   , "Anonymous"
300   , "Proxy"
301   , "EnterpriseDC"
302   , "Self"
303   , "AuthenticatedUsers"
304   , "RestrictedCode"
305   , "TerminalServer"
306   , "RemoteInteractiveLogon"
307   , "ThisOrganization"
308   , "16"
309   , "IUserIIS"
310   , "LocalSystem"
311   , "LocalService"
312   , "NetworkService"
313   , "Domains"
314 };
315 
316 struct CSecID2Name
317 {
318   UInt32 n;
319   const char *sz;
320 };
321 
FindPairIndex(const CSecID2Name * pairs,unsigned num,UInt32 id)322 static int FindPairIndex(const CSecID2Name * pairs, unsigned num, UInt32 id)
323 {
324   for (unsigned i = 0; i < num; i++)
325     if (pairs[i].n == id)
326       return (int)i;
327   return -1;
328 }
329 
330 static const CSecID2Name sid_32_Names[] =
331 {
332   { 544, "Administrators" },
333   { 545, "Users" },
334   { 546, "Guests" },
335   { 547, "PowerUsers" },
336   { 548, "AccountOperators" },
337   { 549, "ServerOperators" },
338   { 550, "PrintOperators" },
339   { 551, "BackupOperators" },
340   { 552, "Replicators" },
341   { 553, "Backup Operators" },
342   { 554, "PreWindows2000CompatibleAccess" },
343   { 555, "RemoteDesktopUsers" },
344   { 556, "NetworkConfigurationOperators" },
345   { 557, "IncomingForestTrustBuilders" },
346   { 558, "PerformanceMonitorUsers" },
347   { 559, "PerformanceLogUsers" },
348   { 560, "WindowsAuthorizationAccessGroup" },
349   { 561, "TerminalServerLicenseServers" },
350   { 562, "DistributedCOMUsers" },
351   { 569, "CryptographicOperators" },
352   { 573, "EventLogReaders" },
353   { 574, "CertificateServiceDCOMAccess" }
354 };
355 
356 static const CSecID2Name sid_21_Names[] =
357 {
358   { 500, "Administrator" },
359   { 501, "Guest" },
360   { 502, "KRBTGT" },
361   { 512, "DomainAdmins" },
362   { 513, "DomainUsers" },
363   { 515, "DomainComputers" },
364   { 516, "DomainControllers" },
365   { 517, "CertPublishers" },
366   { 518, "SchemaAdmins" },
367   { 519, "EnterpriseAdmins" },
368   { 520, "GroupPolicyCreatorOwners" },
369   { 553, "RASandIASServers" },
370   { 553, "RASandIASServers" },
371   { 571, "AllowedRODCPasswordReplicationGroup" },
372   { 572, "DeniedRODCPasswordReplicationGroup" }
373 };
374 
375 struct CServicesToName
376 {
377   UInt32 n[5];
378   const char *sz;
379 };
380 
381 static const CServicesToName services_to_name[] =
382 {
383   { { 0x38FB89B5, 0xCBC28419, 0x6D236C5C, 0x6E770057, 0x876402C0 } , "TrustedInstaller" }
384 };
385 
ParseSid(AString & s,const Byte * p,size_t lim)386 static void ParseSid(AString &s, const Byte *p, size_t lim /* , unsigned &sidSize */)
387 {
388   // sidSize = 0;
389   if (lim < 8)
390   {
391     s += "ERROR";
392     return;
393   }
394   if (p[0] != 1) // rev
395   {
396     s += "UNSUPPORTED";
397     return;
398   }
399   const unsigned num = p[1];
400   const unsigned sidSize_Loc = 8 + num * 4;
401   if (sidSize_Loc > lim)
402   {
403     s += "ERROR";
404     return;
405   }
406   // sidSize = sidSize_Loc;
407   const UInt32 authority = GetBe32(p + 4);
408 
409   if (p[2] == 0 && p[3] == 0 && authority == 5 && num >= 1)
410   {
411     const UInt32 v0 = Get32(p + 8);
412     if (v0 < Z7_ARRAY_SIZE(sidNames))
413     {
414       s += sidNames[v0];
415       return;
416     }
417     if (v0 == 32 && num == 2)
418     {
419       const UInt32 v1 = Get32(p + 12);
420       const int index = FindPairIndex(sid_32_Names, Z7_ARRAY_SIZE(sid_32_Names), v1);
421       if (index >= 0)
422       {
423         s += sid_32_Names[(unsigned)index].sz;
424         return;
425       }
426     }
427     if (v0 == 21 && num == 5)
428     {
429       UInt32 v4 = Get32(p + 8 + 4 * 4);
430       const int index = FindPairIndex(sid_21_Names, Z7_ARRAY_SIZE(sid_21_Names), v4);
431       if (index >= 0)
432       {
433         s += sid_21_Names[(unsigned)index].sz;
434         return;
435       }
436     }
437     if (v0 == 80 && num == 6)
438     {
439       for (unsigned i = 0; i < Z7_ARRAY_SIZE(services_to_name); i++)
440       {
441         const CServicesToName &sn = services_to_name[i];
442         int j;
443         for (j = 0; j < 5 && sn.n[j] == Get32(p + 8 + 4 + j * 4); j++);
444         if (j == 5)
445         {
446           s += sn.sz;
447           return;
448         }
449       }
450     }
451   }
452 
453   s += "S-1-";
454   if (p[2] == 0 && p[3] == 0)
455     s.Add_UInt32(authority);
456   else
457   {
458     s += "0x";
459     for (int i = 2; i < 8; i++)
460       AddHexToString(s, p[i]);
461   }
462   for (UInt32 i = 0; i < num; i++)
463   {
464     s.Add_Minus();
465     s.Add_UInt32(Get32(p + 8 + i * 4));
466   }
467 }
468 
ParseOwner(AString & s,const Byte * p,size_t size,UInt32 pos)469 static void ParseOwner(AString &s, const Byte *p, size_t size, UInt32 pos)
470 {
471   if (pos > size)
472   {
473     s += "ERROR";
474     return;
475   }
476   // unsigned sidSize = 0;
477   ParseSid(s, p + pos, size - pos /* , sidSize */);
478 }
479 
ParseAcl(AString & s,const Byte * p,size_t size,const char * strName,UInt32 flags,UInt32 offset)480 static void ParseAcl(AString &s, const Byte *p, size_t size, const char *strName, UInt32 flags, UInt32 offset)
481 {
482   const unsigned control = Get16(p + 2);
483   if ((flags & control) == 0)
484     return;
485   const UInt32 pos = Get32(p + offset);
486   s.Add_Space();
487   s += strName;
488   if (pos >= size)
489     return;
490   p += pos;
491   size -= (size_t)pos;
492   if (size < 8)
493     return;
494   if (Get16(p) != 2) // revision
495     return;
496   const UInt32 num = Get32(p + 4);
497   s.Add_UInt32(num);
498 
499   /*
500   UInt32 aclSize = Get16(p + 2);
501   if (num >= (1 << 16))
502     return;
503   if (aclSize > size)
504     return;
505   size = aclSize;
506   size -= 8;
507   p += 8;
508   for (UInt32 i = 0 ; i < num; i++)
509   {
510     if (size <= 8)
511       return;
512     // Byte type = p[0];
513     // Byte flags = p[1];
514     // UInt32 aceSize = Get16(p + 2);
515     // UInt32 mask = Get32(p + 4);
516     p += 8;
517     size -= 8;
518 
519     UInt32 sidSize = 0;
520     s.Add_Space();
521     ParseSid(s, p, size, sidSize);
522     if (sidSize == 0)
523       return;
524     p += sidSize;
525     size -= sidSize;
526   }
527 
528   // the tail can contain zeros. So (size != 0) is not ERROR
529   // if (size != 0) s += " ERROR";
530   */
531 }
532 
533 /*
534 #define MY_SE_OWNER_DEFAULTED       (0x0001)
535 #define MY_SE_GROUP_DEFAULTED       (0x0002)
536 */
537 #define MY_SE_DACL_PRESENT          (0x0004)
538 /*
539 #define MY_SE_DACL_DEFAULTED        (0x0008)
540 */
541 #define MY_SE_SACL_PRESENT          (0x0010)
542 /*
543 #define MY_SE_SACL_DEFAULTED        (0x0020)
544 #define MY_SE_DACL_AUTO_INHERIT_REQ (0x0100)
545 #define MY_SE_SACL_AUTO_INHERIT_REQ (0x0200)
546 #define MY_SE_DACL_AUTO_INHERITED   (0x0400)
547 #define MY_SE_SACL_AUTO_INHERITED   (0x0800)
548 #define MY_SE_DACL_PROTECTED        (0x1000)
549 #define MY_SE_SACL_PROTECTED        (0x2000)
550 #define MY_SE_RM_CONTROL_VALID      (0x4000)
551 #define MY_SE_SELF_RELATIVE         (0x8000)
552 */
553 
ConvertNtSecureToString(const Byte * data,size_t size,AString & s)554 void ConvertNtSecureToString(const Byte *data, size_t size, AString &s)
555 {
556   s.Empty();
557   if (size < 20 || size > (1 << 18))
558   {
559     s += "ERROR";
560     return;
561   }
562   if (Get16(data) != 1) // revision
563   {
564     s += "UNSUPPORTED";
565     return;
566   }
567   ParseOwner(s, data, size, Get32(data + 4));
568   s.Add_Space();
569   ParseOwner(s, data, size, Get32(data + 8));
570   ParseAcl(s, data, size, "s:", MY_SE_SACL_PRESENT, 12);
571   ParseAcl(s, data, size, "d:", MY_SE_DACL_PRESENT, 16);
572   s.Add_Space();
573   s.Add_UInt32((UInt32)size);
574   // s.Add_LF();
575   // s += Data_To_Hex(data, size);
576 }
577 
578 #ifdef _WIN32
579 
CheckSid(const Byte * data,size_t size,UInt32 pos)580 static bool CheckSid(const Byte *data, size_t size, UInt32 pos) throw()
581 {
582   if (pos >= size)
583     return false;
584   size -= pos;
585   if (size < 8)
586     return false;
587   if (data[pos] != 1) // rev
588     return false;
589   const unsigned num = data[pos + 1];
590   return (8 + num * 4 <= size);
591 }
592 
CheckAcl(const Byte * p,size_t size,UInt32 flags,size_t offset)593 static bool CheckAcl(const Byte *p, size_t size, UInt32 flags, size_t offset) throw()
594 {
595   const unsigned control = Get16(p + 2);
596   if ((flags & control) == 0)
597     return true;
598   const UInt32 pos = Get32(p + offset);
599   if (pos >= size)
600     return false;
601   p += pos;
602   size -= pos;
603   if (size < 8)
604     return false;
605   const unsigned aclSize = Get16(p + 2);
606   return (aclSize <= size);
607 }
608 
CheckNtSecure(const Byte * data,size_t size)609 bool CheckNtSecure(const Byte *data, size_t size) throw()
610 {
611   if (size < 20)
612     return false;
613   if (Get16(data) != 1) // revision
614     return true; // windows function can handle such error, so we allow it
615   if (size > (1 << 18))
616     return false;
617   if (!CheckSid(data, size, Get32(data + 4))) return false;
618   if (!CheckSid(data, size, Get32(data + 8))) return false;
619   if (!CheckAcl(data, size, MY_SE_SACL_PRESENT, 12)) return false;
620   if (!CheckAcl(data, size, MY_SE_DACL_PRESENT, 16)) return false;
621   return true;
622 }
623 
624 #endif
625 
626 
627 
628 // IO_REPARSE_TAG_*
629 
630 static const CSecID2Name k_ReparseTags[] =
631 {
632   { 0xA0000003, "MOUNT_POINT" },
633   { 0xC0000004, "HSM" },
634   { 0x80000005, "DRIVE_EXTENDER" },
635   { 0x80000006, "HSM2" },
636   { 0x80000007, "SIS" },
637   { 0x80000008, "WIM" },
638   { 0x80000009, "CSV" },
639   { 0x8000000A, "DFS" },
640   { 0x8000000B, "FILTER_MANAGER" },
641   { 0xA000000C, "SYMLINK" },
642   { 0xA0000010, "IIS_CACHE" },
643   { 0x80000012, "DFSR" },
644   { 0x80000013, "DEDUP" },
645   { 0xC0000014, "APPXSTRM" },
646   { 0x80000014, "NFS" },
647   { 0x80000015, "FILE_PLACEHOLDER" },
648   { 0x80000016, "DFM" },
649   { 0x80000017, "WOF" },
650   { 0x80000018, "WCI" },
651   { 0x8000001B, "APPEXECLINK" },
652   { 0xA000001D, "LX_SYMLINK" },
653   { 0x80000023, "AF_UNIX" },
654   { 0x80000024, "LX_FIFO" },
655   { 0x80000025, "LX_CHR" },
656   { 0x80000026, "LX_BLK" }
657 };
658 
ConvertNtReparseToString(const Byte * data,size_t size,UString & s)659 bool ConvertNtReparseToString(const Byte *data, size_t size, UString &s)
660 {
661   s.Empty();
662   NFile::CReparseAttr attr;
663 
664   if (attr.Parse(data, size))
665   {
666     if (attr.IsSymLink_WSL())
667     {
668       s += "WSL: ";
669       s += attr.GetPath();
670     }
671     else
672     {
673       if (!attr.IsSymLink_Win())
674         s += "Junction: ";
675       s += attr.GetPath();
676       if (s.IsEmpty())
677         s += "Link: ";
678       if (!attr.IsOkNamePair())
679       {
680         s += " : ";
681         s += attr.PrintName;
682       }
683     }
684     if (attr.MinorError)
685       s += " : MINOR_ERROR";
686     return true;
687     // s.Add_Space(); // for debug
688   }
689 
690   if (size < 8)
691     return false;
692   const UInt32 tag = Get32(data);
693   const UInt32 len = Get16(data + 4);
694   if (len + 8 > size)
695     return false;
696   if (Get16(data + 6) != 0) // padding
697     return false;
698 
699   /*
700   #define my_IO_REPARSE_TAG_DEDUP        (0x80000013L)
701   if (tag == my_IO_REPARSE_TAG_DEDUP)
702   {
703   }
704   */
705 
706   {
707     const int index = FindPairIndex(k_ReparseTags, Z7_ARRAY_SIZE(k_ReparseTags), tag);
708     if (index >= 0)
709       s += k_ReparseTags[(unsigned)index].sz;
710     else
711     {
712       s += "REPARSE:";
713       char hex[16];
714       ConvertUInt32ToHex8Digits(tag, hex);
715       s += hex;
716     }
717   }
718 
719   s.Add_Colon();
720   s.Add_UInt32(len);
721 
722   if (len != 0)
723   {
724     s.Add_Space();
725 
726     data += 8;
727 
728     for (UInt32 i = 0; i < len; i++)
729     {
730       if (i >= 16)
731       {
732         s += "...";
733         break;
734       }
735       const unsigned b = data[i];
736       s.Add_Char((char)GET_HEX_CHAR_UPPER(b >> 4));
737       s.Add_Char((char)GET_HEX_CHAR_UPPER(b & 15));
738     }
739   }
740 
741   return true;
742 }
743 
744 #endif
745