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