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