1 // TarHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../Common/ComTry.h"
6 #include "../../../Common/IntToString.h"
7 #include "../../../Common/StringConvert.h"
8 #include "../../../Common/UTFConvert.h"
9 
10 #include "../../../Windows/TimeUtils.h"
11 
12 #include "../../Common/LimitedStreams.h"
13 #include "../../Common/MethodProps.h"
14 #include "../../Common/ProgressUtils.h"
15 #include "../../Common/StreamObjects.h"
16 #include "../../Common/StreamUtils.h"
17 
18 #include "../Common/ItemNameUtils.h"
19 
20 #include "TarHandler.h"
21 
22 using namespace NWindows;
23 
24 namespace NArchive {
25 namespace NTar {
26 
27 // 21.02: we use UTF8 code page by default, even if some files show error
28 // before 21.02 : CP_OEMCP;
29 // static const UINT k_DefaultCodePage = CP_UTF8;
30 
31 
32 static const Byte kProps[] =
33 {
34   kpidPath,
35   kpidIsDir,
36   kpidSize,
37   kpidPackSize,
38   kpidMTime,
39   kpidPosixAttrib,
40   kpidUser,
41   kpidGroup,
42   kpidSymLink,
43   kpidHardLink,
44   kpidCharacts
45   // kpidLinkType
46 };
47 
48 static const Byte kArcProps[] =
49 {
50   kpidHeadersSize,
51   kpidCodePage,
52   kpidCharacts
53 };
54 
55 IMP_IInArchive_Props
56 IMP_IInArchive_ArcProps
57 
GetArchiveProperty(PROPID propID,PROPVARIANT * value)58 STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
59 {
60   NCOM::CPropVariant prop;
61   switch (propID)
62   {
63     case kpidPhySize: if (_phySizeDefined) prop = _phySize; break;
64     case kpidHeadersSize: if (_phySizeDefined) prop = _headersSize; break;
65     case kpidErrorFlags:
66     {
67       UInt32 flags = 0;
68       if (!_isArc)
69         flags |= kpv_ErrorFlags_IsNotArc;
70       else switch (_error)
71       {
72         case k_ErrorType_UnexpectedEnd: flags = kpv_ErrorFlags_UnexpectedEnd; break;
73         case k_ErrorType_Corrupted: flags = kpv_ErrorFlags_HeadersError; break;
74         // case k_ErrorType_OK: break;
75         // case k_ErrorType_Warning: break;
76         default: break;
77       }
78       if (flags != 0)
79         prop = flags;
80       break;
81     }
82 
83     case kpidWarningFlags:
84     {
85       if (_warning)
86         prop = kpv_ErrorFlags_HeadersError;
87       break;
88     }
89 
90     case kpidCodePage:
91     {
92       char sz[16];
93       const char *name = NULL;
94       switch (_openCodePage)
95       {
96         case CP_OEMCP: name = "OEM"; break;
97         case CP_UTF8: name = "UTF-8"; break;
98       }
99       if (!name)
100       {
101         ConvertUInt32ToString(_openCodePage, sz);
102         name = sz;
103       }
104       prop = name;
105       break;
106     }
107 
108     case kpidCharacts:
109     {
110       prop = _encodingCharacts.GetCharactsString();
111       break;
112     }
113   }
114   prop.Detach(value);
115   return S_OK;
116 }
117 
ReadItem2(ISequentialInStream * stream,bool & filled,CItemEx & item)118 HRESULT CHandler::ReadItem2(ISequentialInStream *stream, bool &filled, CItemEx &item)
119 {
120   item.HeaderPos = _phySize;
121   EErrorType error;
122   HRESULT res = ReadItem(stream, filled, item, error);
123   if (error == k_ErrorType_Warning)
124     _warning = true;
125   else if (error != k_ErrorType_OK)
126     _error = error;
127   RINOK(res);
128   if (filled)
129   {
130     /*
131     if (item.IsSparse())
132       _isSparse = true;
133     */
134     if (item.IsPaxExtendedHeader())
135       _thereIsPaxExtendedHeader = true;
136     if (item.IsThereWarning())
137       _warning = true;
138   }
139   _phySize += item.HeaderSize;
140   _headersSize += item.HeaderSize;
141   return S_OK;
142 }
143 
144 
Check(const AString & s)145 void CEncodingCharacts::Check(const AString &s)
146 {
147   IsAscii = s.IsAscii();
148   if (!IsAscii)
149   {
150     /*
151     {
152       Oem_Checked = true;
153       UString u;
154       MultiByteToUnicodeString2(u, s, CP_OEMCP);
155       Oem_Ok = (u.Find((wchar_t)0xfffd) <= 0);
156     }
157     Utf_Checked = true;
158     */
159     UtfCheck.Check_AString(s);
160   }
161 }
162 
163 
GetCharactsString() const164 AString CEncodingCharacts::GetCharactsString() const
165 {
166   AString s;
167   if (IsAscii)
168   {
169     s += "ASCII";
170   }
171   /*
172   if (Oem_Checked)
173   {
174     s.Add_Space_if_NotEmpty();
175     s += (Oem_Ok ? "oem-ok" : "oem-error");
176   }
177   if (Utf_Checked)
178   */
179   else
180   {
181     s.Add_Space_if_NotEmpty();
182     s += (UtfCheck.IsOK() ? "UTF8" : "UTF8-ERROR"); // "UTF8-error"
183     {
184       AString s2;
185       UtfCheck.PrintStatus(s2);
186       s.Add_Space_if_NotEmpty();
187       s += s2;
188     }
189   }
190   return s;
191 }
192 
193 
Open2(IInStream * stream,IArchiveOpenCallback * callback)194 HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback)
195 {
196   UInt64 endPos = 0;
197   {
198     RINOK(stream->Seek(0, STREAM_SEEK_END, &endPos));
199     RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL));
200   }
201 
202   _phySizeDefined = true;
203 
204   // bool utf8_OK = true;
205 
206   for (;;)
207   {
208     CItemEx item;
209     bool filled;
210     RINOK(ReadItem2(stream, filled, item));
211     if (!filled)
212       break;
213 
214     _isArc = true;
215 
216     /*
217     if (!_forceCodePage)
218     {
219       if (utf8_OK) utf8_OK = CheckUTF8(item.Name, item.NameCouldBeReduced);
220       if (utf8_OK) utf8_OK = CheckUTF8(item.LinkName, item.LinkNameCouldBeReduced);
221       if (utf8_OK) utf8_OK = CheckUTF8(item.User);
222       if (utf8_OK) utf8_OK = CheckUTF8(item.Group);
223     }
224     */
225 
226     item.EncodingCharacts.Check(item.Name);
227     _encodingCharacts.Update(item.EncodingCharacts);
228 
229     _items.Add(item);
230 
231     RINOK(stream->Seek((Int64)item.GetPackSizeAligned(), STREAM_SEEK_CUR, &_phySize));
232     if (_phySize > endPos)
233     {
234       _error = k_ErrorType_UnexpectedEnd;
235       break;
236     }
237     /*
238     if (_phySize == endPos)
239     {
240       _errorMessage = "There are no trailing zero-filled records";
241       break;
242     }
243     */
244     if (callback)
245     {
246       if (_items.Size() == 1)
247       {
248         RINOK(callback->SetTotal(NULL, &endPos));
249       }
250       if ((_items.Size() & 0x3FF) == 0)
251       {
252         UInt64 numFiles = _items.Size();
253         RINOK(callback->SetCompleted(&numFiles, &_phySize));
254       }
255     }
256   }
257 
258   /*
259   if (!_forceCodePage)
260   {
261     if (!utf8_OK)
262       _curCodePage = k_DefaultCodePage;
263   }
264   */
265   _openCodePage = _curCodePage;
266 
267   if (_items.Size() == 0)
268   {
269     if (_error != k_ErrorType_OK)
270     {
271       _isArc = false;
272       return S_FALSE;
273     }
274     CMyComPtr<IArchiveOpenVolumeCallback> openVolumeCallback;
275     if (!callback)
276       return S_FALSE;
277     callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback);
278     if (!openVolumeCallback)
279       return S_FALSE;
280     NCOM::CPropVariant prop;
281     if (openVolumeCallback->GetProperty(kpidName, &prop) != S_OK)
282       return S_FALSE;
283     if (prop.vt != VT_BSTR)
284       return S_FALSE;
285     unsigned len = MyStringLen(prop.bstrVal);
286     if (len < 4 || MyStringCompareNoCase(prop.bstrVal + len - 4, L".tar") != 0)
287       return S_FALSE;
288   }
289 
290   _isArc = true;
291   return S_OK;
292 }
293 
Open(IInStream * stream,const UInt64 *,IArchiveOpenCallback * openArchiveCallback)294 STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *openArchiveCallback)
295 {
296   COM_TRY_BEGIN
297   {
298     Close();
299     RINOK(Open2(stream, openArchiveCallback));
300     _stream = stream;
301   }
302   return S_OK;
303   COM_TRY_END
304 }
305 
OpenSeq(ISequentialInStream * stream)306 STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream)
307 {
308   Close();
309   _seqStream = stream;
310   _isArc = true;
311   return S_OK;
312 }
313 
Close()314 STDMETHODIMP CHandler::Close()
315 {
316   _isArc = false;
317   _warning = false;
318   _error = k_ErrorType_OK;
319 
320   _phySizeDefined = false;
321   _phySize = 0;
322   _headersSize = 0;
323   _curIndex = 0;
324   _latestIsRead = false;
325   // _isSparse = false;
326   _thereIsPaxExtendedHeader = false;
327   _encodingCharacts.Clear();
328   _items.Clear();
329   _seqStream.Release();
330   _stream.Release();
331   return S_OK;
332 }
333 
GetNumberOfItems(UInt32 * numItems)334 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
335 {
336   *numItems = (_stream ? _items.Size() : (UInt32)(Int32)-1);
337   return S_OK;
338 }
339 
CHandler()340 CHandler::CHandler()
341 {
342   copyCoderSpec = new NCompress::CCopyCoder();
343   copyCoder = copyCoderSpec;
344   _openCodePage = CP_UTF8;
345   Init();
346 }
347 
SkipTo(UInt32 index)348 HRESULT CHandler::SkipTo(UInt32 index)
349 {
350   while (_curIndex < index || !_latestIsRead)
351   {
352     if (_latestIsRead)
353     {
354       UInt64 packSize = _latestItem.GetPackSizeAligned();
355       RINOK(copyCoderSpec->Code(_seqStream, NULL, &packSize, &packSize, NULL));
356       _phySize += copyCoderSpec->TotalSize;
357       if (copyCoderSpec->TotalSize != packSize)
358       {
359         _error = k_ErrorType_UnexpectedEnd;
360         return S_FALSE;
361       }
362       _latestIsRead = false;
363       _curIndex++;
364     }
365     else
366     {
367       bool filled;
368       RINOK(ReadItem2(_seqStream, filled, _latestItem));
369       if (!filled)
370       {
371         _phySizeDefined = true;
372         return E_INVALIDARG;
373       }
374       _latestIsRead = true;
375     }
376   }
377   return S_OK;
378 }
379 
TarStringToUnicode(const AString & s,NWindows::NCOM::CPropVariant & prop,bool toOs) const380 void CHandler::TarStringToUnicode(const AString &s, NWindows::NCOM::CPropVariant &prop, bool toOs) const
381 {
382   UString dest;
383   if (_curCodePage == CP_UTF8)
384     ConvertUTF8ToUnicode(s, dest);
385   else
386     MultiByteToUnicodeString2(dest, s, _curCodePage);
387   if (toOs)
388     NItemName::ReplaceToOsSlashes_Remove_TailSlash(dest,
389         true); // useBackslashReplacement
390   prop = dest;
391 }
392 
GetProperty(UInt32 index,PROPID propID,PROPVARIANT * value)393 STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
394 {
395   COM_TRY_BEGIN
396   NCOM::CPropVariant prop;
397 
398   const CItemEx *item;
399   if (_stream)
400     item = &_items[index];
401   else
402   {
403     if (index < _curIndex)
404       return E_INVALIDARG;
405     else
406     {
407       RINOK(SkipTo(index));
408       item = &_latestItem;
409     }
410   }
411 
412   switch (propID)
413   {
414     case kpidPath: TarStringToUnicode(item->Name, prop, true); break;
415     case kpidIsDir: prop = item->IsDir(); break;
416     case kpidSize: prop = item->GetUnpackSize(); break;
417     case kpidPackSize: prop = item->GetPackSizeAligned(); break;
418     case kpidMTime:
419       if (item->MTime != 0)
420       {
421         FILETIME ft;
422         if (NTime::UnixTime64ToFileTime(item->MTime, ft))
423           prop = ft;
424       }
425       break;
426     case kpidPosixAttrib: prop = item->Get_Combined_Mode(); break;
427     case kpidUser:  TarStringToUnicode(item->User, prop); break;
428     case kpidGroup: TarStringToUnicode(item->Group, prop); break;
429     case kpidSymLink:  if (item->LinkFlag == NFileHeader::NLinkFlag::kSymLink  && !item->LinkName.IsEmpty()) TarStringToUnicode(item->LinkName, prop); break;
430     case kpidHardLink: if (item->LinkFlag == NFileHeader::NLinkFlag::kHardLink && !item->LinkName.IsEmpty()) TarStringToUnicode(item->LinkName, prop); break;
431     // case kpidLinkType: prop = (int)item->LinkFlag; break;
432     case kpidCharacts:
433     {
434       AString s = item->EncodingCharacts.GetCharactsString();
435       if (item->IsThereWarning())
436       {
437         s.Add_Space_if_NotEmpty();
438         s += "HEADER_ERROR";
439       }
440       prop = s;
441       break;
442     }
443   }
444   prop.Detach(value);
445   return S_OK;
446   COM_TRY_END
447 }
448 
Extract(const UInt32 * indices,UInt32 numItems,Int32 testMode,IArchiveExtractCallback * extractCallback)449 HRESULT CHandler::Extract(const UInt32 *indices, UInt32 numItems,
450     Int32 testMode, IArchiveExtractCallback *extractCallback)
451 {
452   COM_TRY_BEGIN
453   ISequentialInStream *stream = _seqStream;
454   bool seqMode = (_stream == NULL);
455   if (!seqMode)
456     stream = _stream;
457 
458   bool allFilesMode = (numItems == (UInt32)(Int32)-1);
459   if (allFilesMode)
460     numItems = _items.Size();
461   if (_stream && numItems == 0)
462     return S_OK;
463   UInt64 totalSize = 0;
464   UInt32 i;
465   for (i = 0; i < numItems; i++)
466     totalSize += _items[allFilesMode ? i : indices[i]].GetUnpackSize();
467   extractCallback->SetTotal(totalSize);
468 
469   UInt64 totalPackSize;
470   totalSize = totalPackSize = 0;
471 
472   CLocalProgress *lps = new CLocalProgress;
473   CMyComPtr<ICompressProgressInfo> progress = lps;
474   lps->Init(extractCallback, false);
475 
476   CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream;
477   CMyComPtr<ISequentialInStream> inStream(streamSpec);
478   streamSpec->SetStream(stream);
479 
480   CLimitedSequentialOutStream *outStreamSpec = new CLimitedSequentialOutStream;
481   CMyComPtr<ISequentialOutStream> outStream(outStreamSpec);
482 
483   for (i = 0; i < numItems || seqMode; i++)
484   {
485     lps->InSize = totalPackSize;
486     lps->OutSize = totalSize;
487     RINOK(lps->SetCur());
488     CMyComPtr<ISequentialOutStream> realOutStream;
489     Int32 askMode = testMode ?
490         NExtract::NAskMode::kTest :
491         NExtract::NAskMode::kExtract;
492     const UInt32 index = allFilesMode ? i : indices[i];
493     const CItemEx *item;
494     if (seqMode)
495     {
496       HRESULT res = SkipTo(index);
497       if (res == E_INVALIDARG)
498         break;
499       RINOK(res);
500       item = &_latestItem;
501     }
502     else
503       item = &_items[index];
504 
505     RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
506     UInt64 unpackSize = item->GetUnpackSize();
507     totalSize += unpackSize;
508     totalPackSize += item->GetPackSizeAligned();
509     if (item->IsDir())
510     {
511       RINOK(extractCallback->PrepareOperation(askMode));
512       RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
513       continue;
514     }
515     bool skipMode = false;
516     if (!testMode && !realOutStream)
517     {
518       if (!seqMode)
519       {
520         /*
521         // probably we must show extracting info it callback handler instead
522         if (item->IsHardLink() ||
523             item->IsSymLink())
524         {
525           RINOK(extractCallback->PrepareOperation(askMode));
526           RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
527         }
528         */
529         continue;
530       }
531       skipMode = true;
532       askMode = NExtract::NAskMode::kSkip;
533     }
534     RINOK(extractCallback->PrepareOperation(askMode));
535 
536     outStreamSpec->SetStream(realOutStream);
537     realOutStream.Release();
538     outStreamSpec->Init(skipMode ? 0 : unpackSize, true);
539 
540     Int32 opRes = NExtract::NOperationResult::kOK;
541     CMyComPtr<ISequentialInStream> inStream2;
542     if (!item->IsSparse())
543       inStream2 = inStream;
544     else
545     {
546       GetStream(index, &inStream2);
547       if (!inStream2)
548         return E_FAIL;
549     }
550 
551     {
552       if (item->IsSymLink())
553       {
554         RINOK(WriteStream(outStreamSpec, (const char *)item->LinkName, item->LinkName.Len()));
555       }
556       else
557       {
558         if (!seqMode)
559         {
560           RINOK(_stream->Seek((Int64)item->GetDataPosition(), STREAM_SEEK_SET, NULL));
561         }
562         streamSpec->Init(item->GetPackSizeAligned());
563         RINOK(copyCoder->Code(inStream2, outStream, NULL, NULL, progress));
564       }
565       if (outStreamSpec->GetRem() != 0)
566         opRes = NExtract::NOperationResult::kDataError;
567     }
568     if (seqMode)
569     {
570       _latestIsRead = false;
571       _curIndex++;
572     }
573     outStreamSpec->ReleaseStream();
574     RINOK(extractCallback->SetOperationResult(opRes));
575   }
576   return S_OK;
577   COM_TRY_END
578 }
579 
580 class CSparseStream:
581   public IInStream,
582   public CMyUnknownImp
583 {
584   UInt64 _phyPos;
585   UInt64 _virtPos;
586   bool _needStartSeek;
587 
588 public:
589   CHandler *Handler;
590   CMyComPtr<IUnknown> HandlerRef;
591   unsigned ItemIndex;
592   CRecordVector<UInt64> PhyOffsets;
593 
594   MY_UNKNOWN_IMP2(ISequentialInStream, IInStream)
595   STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
596   STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition);
597 
Init()598   void Init()
599   {
600     _virtPos = 0;
601     _phyPos = 0;
602     _needStartSeek = true;
603   }
604 };
605 
606 
Read(void * data,UInt32 size,UInt32 * processedSize)607 STDMETHODIMP CSparseStream::Read(void *data, UInt32 size, UInt32 *processedSize)
608 {
609   if (processedSize)
610     *processedSize = 0;
611   if (size == 0)
612     return S_OK;
613   const CItemEx &item = Handler->_items[ItemIndex];
614   if (_virtPos >= item.Size)
615     return S_OK;
616   {
617     UInt64 rem = item.Size - _virtPos;
618     if (size > rem)
619       size = (UInt32)rem;
620   }
621 
622   HRESULT res = S_OK;
623 
624   if (item.SparseBlocks.IsEmpty())
625     memset(data, 0, size);
626   else
627   {
628     unsigned left = 0, right = item.SparseBlocks.Size();
629     for (;;)
630     {
631       unsigned mid = (left + right) / 2;
632       if (mid == left)
633         break;
634       if (_virtPos < item.SparseBlocks[mid].Offset)
635         right = mid;
636       else
637         left = mid;
638     }
639 
640     const CSparseBlock &sb = item.SparseBlocks[left];
641     UInt64 relat = _virtPos - sb.Offset;
642 
643     if (_virtPos >= sb.Offset && relat < sb.Size)
644     {
645       UInt64 rem = sb.Size - relat;
646       if (size > rem)
647         size = (UInt32)rem;
648       UInt64 phyPos = PhyOffsets[left] + relat;
649       if (_needStartSeek || _phyPos != phyPos)
650       {
651         RINOK(Handler->_stream->Seek((Int64)(item.GetDataPosition() + phyPos), STREAM_SEEK_SET, NULL));
652         _needStartSeek = false;
653         _phyPos = phyPos;
654       }
655       res = Handler->_stream->Read(data, size, &size);
656       _phyPos += size;
657     }
658     else
659     {
660       UInt64 next = item.Size;
661       if (_virtPos < sb.Offset)
662         next = sb.Offset;
663       else if (left + 1 < item.SparseBlocks.Size())
664         next = item.SparseBlocks[left + 1].Offset;
665       UInt64 rem = next - _virtPos;
666       if (size > rem)
667         size = (UInt32)rem;
668       memset(data, 0, size);
669     }
670   }
671 
672   _virtPos += size;
673   if (processedSize)
674     *processedSize = size;
675   return res;
676 }
677 
Seek(Int64 offset,UInt32 seekOrigin,UInt64 * newPosition)678 STDMETHODIMP CSparseStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)
679 {
680   switch (seekOrigin)
681   {
682     case STREAM_SEEK_SET: break;
683     case STREAM_SEEK_CUR: offset += _virtPos; break;
684     case STREAM_SEEK_END: offset += Handler->_items[ItemIndex].Size; break;
685     default: return STG_E_INVALIDFUNCTION;
686   }
687   if (offset < 0)
688     return HRESULT_WIN32_ERROR_NEGATIVE_SEEK;
689   _virtPos = (UInt64)offset;
690   if (newPosition)
691     *newPosition = _virtPos;
692   return S_OK;
693 }
694 
GetStream(UInt32 index,ISequentialInStream ** stream)695 STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream)
696 {
697   COM_TRY_BEGIN
698 
699   const CItemEx &item = _items[index];
700 
701   if (item.IsSparse())
702   {
703     CSparseStream *streamSpec = new CSparseStream;
704     CMyComPtr<IInStream> streamTemp = streamSpec;
705     streamSpec->Init();
706     streamSpec->Handler = this;
707     streamSpec->HandlerRef = (IInArchive *)this;
708     streamSpec->ItemIndex = index;
709     streamSpec->PhyOffsets.Reserve(item.SparseBlocks.Size());
710     UInt64 offs = 0;
711     FOR_VECTOR(i, item.SparseBlocks)
712     {
713       const CSparseBlock &sb = item.SparseBlocks[i];
714       streamSpec->PhyOffsets.AddInReserved(offs);
715       offs += sb.Size;
716     }
717     *stream = streamTemp.Detach();
718     return S_OK;
719   }
720 
721   if (item.IsSymLink())
722   {
723     Create_BufInStream_WithReference((const Byte *)(const char *)item.LinkName, item.LinkName.Len(), (IInArchive *)this, stream);
724     return S_OK;
725   }
726 
727   return CreateLimitedInStream(_stream, item.GetDataPosition(), item.PackSize, stream);
728 
729   COM_TRY_END
730 }
731 
Init()732 void CHandler::Init()
733 {
734   _forceCodePage = false;
735   _curCodePage = _specifiedCodePage = CP_UTF8;  // CP_OEMCP;
736   _thereIsPaxExtendedHeader = false;
737 }
738 
SetProperties(const wchar_t * const * names,const PROPVARIANT * values,UInt32 numProps)739 STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)
740 {
741   Init();
742 
743   for (UInt32 i = 0; i < numProps; i++)
744   {
745     UString name = names[i];
746     name.MakeLower_Ascii();
747     if (name.IsEmpty())
748       return E_INVALIDARG;
749 
750     const PROPVARIANT &prop = values[i];
751 
752     if (name[0] == L'x')
753     {
754       // some clients write 'x' property. So we support it
755       UInt32 level = 0;
756       RINOK(ParsePropToUInt32(name.Ptr(1), prop, level));
757     }
758     else if (name.IsEqualTo("cp"))
759     {
760       UInt32 cp = CP_OEMCP;
761       RINOK(ParsePropToUInt32(L"", prop, cp));
762       _forceCodePage = true;
763       _curCodePage = _specifiedCodePage = cp;
764     }
765     else if (name.IsPrefixedBy_Ascii_NoCase("mt"))
766     {
767     }
768     else if (name.IsPrefixedBy_Ascii_NoCase("memuse"))
769     {
770     }
771     else
772       return E_INVALIDARG;
773   }
774   return S_OK;
775 }
776 
777 }}
778