1 // Base64Handler.cpp
2
3 #include "StdAfx.h"
4
5 #include "../../../C/CpuArch.h"
6
7 #include "../../Common/ComTry.h"
8 #include "../../Common/MyBuffer.h"
9 #include "../../Common/IntToString.h"
10 #include "../../Common/MyVector.h"
11
12 #include "../../Windows/PropVariant.h"
13
14 #include "../Common/ProgressUtils.h"
15 #include "../Common/RegisterArc.h"
16 #include "../Common/StreamUtils.h"
17 #include "../Common/InBuffer.h"
18
19 /*
20 spaces:
21 9(TAB),10(LF),13(CR),32(SPACE)
22 Non-breaking space:
23 0xa0 : Unicode, Windows code pages 1250-1258
24 0xff (unused): DOS code pages
25
26 end of stream markers: '=' (0x3d):
27 "=" , if numBytes (% 3 == 2)
28 "==" , if numBytes (% 3 == 1)
29 */
30
31
32 static const Byte k_Base64Table[256] =
33 {
34 66,77,77,77,77,77,77,77,77,65,65,77,77,65,77,77,
35 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
36 65,77,77,77,77,77,77,77,77,77,77,62,77,77,77,63,
37 52,53,54,55,56,57,58,59,60,61,77,77,77,64,77,77,
38 77, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
39 15,16,17,18,19,20,21,22,23,24,25,77,77,77,77,77,
40 77,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
41 41,42,43,44,45,46,47,48,49,50,51,77,77,77,77,77,
42 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
43 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
44 65,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
45 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
46 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
47 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
48 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
49 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77
50 };
51
52 static const unsigned k_Code_Equals = 64;
53 static const unsigned k_Code_Space = 65;
54 static const unsigned k_Code_Zero = 66;
55
IsArc_Base64(const Byte * p,size_t size)56 API_FUNC_static_IsArc IsArc_Base64(const Byte *p, size_t size)
57 {
58 size_t num = 0;
59 size_t firstSpace = 0;
60
61 for (;;)
62 {
63 if (size == 0)
64 return k_IsArc_Res_NEED_MORE;
65 UInt32 c = k_Base64Table[(Byte)(*p++)];
66 size--;
67 if (c < 64)
68 {
69 num++;
70 continue;
71 }
72
73 if (c == k_Code_Space)
74 {
75 if (p[-1] == ' ' && firstSpace == 0)
76 firstSpace = num;
77 continue;
78 }
79
80 if (c != k_Code_Equals)
81 return k_IsArc_Res_NO;
82 break;
83 }
84
85 {
86 // we try to redece false positive detection here.
87 // we don't expect space character in starting base64 line
88 const unsigned kNumExpectedNonSpaceSyms = 20;
89 if (firstSpace != 0 && firstSpace < num && firstSpace < kNumExpectedNonSpaceSyms)
90 return k_IsArc_Res_NO;
91 }
92
93 num &= 3;
94
95 if (num <= 1)
96 return k_IsArc_Res_NO;
97 if (num != 3)
98 {
99 if (size == 0)
100 return k_IsArc_Res_NEED_MORE;
101 UInt32 c = k_Base64Table[(Byte)(*p++)];
102 size--;
103 if (c != k_Code_Equals)
104 return k_IsArc_Res_NO;
105 }
106
107 for (;;)
108 {
109 if (size == 0)
110 return k_IsArc_Res_YES;
111 UInt32 c = k_Base64Table[(Byte)(*p++)];
112 size--;
113 if (c == k_Code_Space)
114 continue;
115 return k_IsArc_Res_NO;
116 }
117 }
118 }
119
120
121 enum EBase64Res
122 {
123 k_Base64_RES_MaybeFinished,
124 k_Base64_RES_Finished,
125 k_Base64_RES_NeedMoreInput,
126 k_Base64_RES_UnexpectedChar
127 };
128
129
130 static EBase64Res Base64ToBin(Byte *p, size_t size, const Byte **srcEnd, Byte **destEnd)
131 {
132 Byte *dest = p;
133 UInt32 val = 1;
134 EBase64Res res = k_Base64_RES_NeedMoreInput;
135
136 for (;;)
137 {
138 if (size == 0)
139 {
140 if (val == 1)
141 res = k_Base64_RES_MaybeFinished;
142 break;
143 }
144 UInt32 c = k_Base64Table[(Byte)(*p++)];
145 size--;
146 if (c < 64)
147 {
148 val = (val << 6) | c;
149 if ((val & ((UInt32)1 << 24)) == 0)
150 continue;
151 dest[0] = (Byte)(val >> 16);
152 dest[1] = (Byte)(val >> 8);
153 dest[2] = (Byte)(val);
154 dest += 3;
155 val = 1;
156 continue;
157 }
158
159 if (c == k_Code_Space)
160 continue;
161
162 if (c == k_Code_Equals)
163 {
164 if (val >= (1 << 12))
165 {
166 if (val & (1 << 18))
167 {
168 res = k_Base64_RES_Finished;
169 break;
170 }
171 if (size == 0)
172 break;
173 c = k_Base64Table[(Byte)(*p++)];
174 size--;
175 if (c == k_Code_Equals)
176 {
177 res = k_Base64_RES_Finished;
178 break;
179 }
180 }
181 }
182
183 p--;
184 res = k_Base64_RES_UnexpectedChar;
185 break;
186 }
187
188 if (val >= ((UInt32)1 << 12))
189 {
190 if (val & (1 << 18))
191 {
192 *dest++ = (Byte)(val >> 10);
193 val <<= 2;
194 }
195 *dest++ = (Byte)(val >> 4);
196 }
197
198 *srcEnd = p;
199 *destEnd = dest;
200 return res;
201 }
202
203
204 static const Byte *Base64_SkipSpaces(const Byte *p, size_t size)
205 {
206 for (;;)
207 {
208 if (size == 0)
209 return p;
210 const UInt32 c = k_Base64Table[(Byte)(*p++)];
211 size--;
212 if (c == k_Code_Space)
213 continue;
214 return p - 1;
215 }
216 }
217
218
219 // the following function is used by DmgHandler.cpp
220
221 Byte *Base64ToBin(Byte *dest, const char *src);
222 Byte *Base64ToBin(Byte *dest, const char *src)
223 {
224 UInt32 val = 1;
225
226 for (;;)
227 {
228 const UInt32 c = k_Base64Table[(Byte)(*src++)];
229
230 if (c < 64)
231 {
232 val = (val << 6) | c;
233 if ((val & ((UInt32)1 << 24)) == 0)
234 continue;
235 dest[0] = (Byte)(val >> 16);
236 dest[1] = (Byte)(val >> 8);
237 dest[2] = (Byte)(val);
238 dest += 3;
239 val = 1;
240 continue;
241 }
242
243 if (c == k_Code_Space)
244 continue;
245
246 if (c == k_Code_Equals)
247 break;
248
249 if (c == k_Code_Zero && val == 1) // end of string
250 return dest;
251
252 return NULL;
253 }
254
255 if (val < (1 << 12))
256 return NULL;
257
258 if (val & (1 << 18))
259 {
260 *dest++ = (Byte)(val >> 10);
261 val <<= 2;
262 }
263 else if (k_Base64Table[(Byte)(*src++)] != k_Code_Equals)
264 return NULL;
265 *dest++ = (Byte)(val >> 4);
266
267 for (;;)
268 {
269 const Byte c = k_Base64Table[(Byte)(*src++)];
270 if (c == k_Code_Space)
271 continue;
272 if (c == k_Code_Zero)
273 return dest;
274 return NULL;
275 }
276 }
277
278
279 namespace NArchive {
280 namespace NBase64 {
281
282 Z7_CLASS_IMP_CHandler_IInArchive_0
283
284 bool _isArc;
285 UInt64 _phySize;
286 size_t _size;
287 EBase64Res _sres;
288 CByteBuffer _data;
289 };
290
291 static const Byte kProps[] =
292 {
293 kpidSize,
294 kpidPackSize,
295 };
296
297 IMP_IInArchive_Props
298 IMP_IInArchive_ArcProps_NO_Table
299
300 Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
301 {
302 *numItems = 1;
303 return S_OK;
304 }
305
306 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
307 {
308 NWindows::NCOM::CPropVariant prop;
309 switch (propID)
310 {
311 case kpidPhySize: if (_phySize != 0) prop = _phySize; break;
312 case kpidErrorFlags:
313 {
314 UInt32 v = 0;
315 if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;
316 if (_sres == k_Base64_RES_NeedMoreInput) v |= kpv_ErrorFlags_UnexpectedEnd;
317 if (v != 0)
318 prop = v;
319 break;
320 }
321 }
322 prop.Detach(value);
323 return S_OK;
324 }
325
326 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value))
327 {
328 // COM_TRY_BEGIN
329 NWindows::NCOM::CPropVariant prop;
330 switch (propID)
331 {
332 case kpidSize: prop = (UInt64)_size; break;
333 case kpidPackSize: prop = _phySize; break;
334 }
335 prop.Detach(value);
336 return S_OK;
337 // COM_TRY_END
338 }
339
340
341 static HRESULT ReadStream_OpenProgress(ISequentialInStream *stream, void *data, size_t size, IArchiveOpenCallback *openCallback) throw()
342 {
343 UInt64 bytes = 0;
344 while (size != 0)
345 {
346 const UInt32 kBlockSize = ((UInt32)1 << 24);
347 const UInt32 curSize = (size < kBlockSize) ? (UInt32)size : kBlockSize;
348 UInt32 processedSizeLoc;
349 RINOK(stream->Read(data, curSize, &processedSizeLoc))
350 if (processedSizeLoc == 0)
351 return E_FAIL;
352 data = (void *)((Byte *)data + processedSizeLoc);
353 size -= processedSizeLoc;
354 bytes += processedSizeLoc;
355 const UInt64 files = 1;
356 RINOK(openCallback->SetCompleted(&files, &bytes))
357 }
358 return S_OK;
359 }
360
361
362 Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *openCallback))
363 {
364 COM_TRY_BEGIN
365 {
366 Close();
367 {
368 const unsigned kStartSize = 1 << 12;
369 _data.Alloc(kStartSize);
370 size_t size = kStartSize;
371 RINOK(ReadStream(stream, _data, &size))
372 if (IsArc_Base64(_data, size) == k_IsArc_Res_NO)
373 return S_FALSE;
374 }
375 _isArc = true;
376
377 UInt64 packSize64;
378 RINOK(InStream_GetSize_SeekToEnd(stream, packSize64))
379 if (packSize64 == 0)
380 return S_FALSE;
381 size_t curSize = 1 << 16;
382 if (curSize > packSize64)
383 curSize = (size_t)packSize64;
384 const unsigned kLogStep = 4;
385
386 for (;;)
387 {
388 RINOK(InStream_SeekSet(stream, 0))
389
390 _data.Alloc(curSize);
391 RINOK(ReadStream_OpenProgress(stream, _data, curSize, openCallback))
392
393 const Byte *srcEnd;
394 Byte *dest;
395 _sres = Base64ToBin(_data, curSize, &srcEnd, &dest);
396 _size = (size_t)(dest - _data);
397 const size_t mainSize = (size_t)(srcEnd - _data);
398 _phySize = mainSize;
399 if (_sres == k_Base64_RES_UnexpectedChar)
400 break;
401 if (curSize != mainSize)
402 {
403 const Byte *end2 = Base64_SkipSpaces(srcEnd, curSize - mainSize);
404 if ((size_t)(end2 - _data) != curSize)
405 break;
406 _phySize = curSize;
407 }
408
409 if (curSize == packSize64)
410 break;
411
412 UInt64 curSize64 = packSize64;
413 if (curSize < (packSize64 >> kLogStep))
414 curSize64 = (UInt64)curSize << kLogStep;
415 curSize = (size_t)curSize64;
416 if (curSize != curSize64)
417 return E_OUTOFMEMORY;
418 }
419 if (_size == 0)
420 return S_FALSE;
421 return S_OK;
422 }
423 COM_TRY_END
424 }
425
426 Z7_COM7F_IMF(CHandler::Close())
427 {
428 _phySize = 0;
429 _size = 0;
430 _isArc = false;
431 _sres = k_Base64_RES_MaybeFinished;
432 _data.Free();
433 return S_OK;
434 }
435
436
437 Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
438 Int32 testMode, IArchiveExtractCallback *extractCallback))
439 {
440 COM_TRY_BEGIN
441 if (numItems == 0)
442 return S_OK;
443 if (numItems != (UInt32)(Int32)-1 && (numItems != 1 || indices[0] != 0))
444 return E_INVALIDARG;
445
446 RINOK(extractCallback->SetTotal(_size))
447 CMyComPtr2_Create<ICompressProgressInfo, CLocalProgress> lps;
448 lps->Init(extractCallback, false);
449 // RINOK(lps->SetCur())
450 Int32 opRes;
451 {
452 CMyComPtr<ISequentialOutStream> realOutStream;
453 const Int32 askMode = testMode ?
454 NExtract::NAskMode::kTest :
455 NExtract::NAskMode::kExtract;
456 RINOK(extractCallback->GetStream(0, &realOutStream, askMode))
457 if (!testMode && !realOutStream)
458 return S_OK;
459 RINOK(extractCallback->PrepareOperation(askMode))
460 if (realOutStream)
461 {
462 RINOK(WriteStream(realOutStream, (const Byte *)_data, _size))
463 }
464 opRes = NExtract::NOperationResult::kOK;
465 if (_sres != k_Base64_RES_Finished)
466 {
467 if (_sres == k_Base64_RES_NeedMoreInput)
468 opRes = NExtract::NOperationResult::kUnexpectedEnd;
469 else if (_sres == k_Base64_RES_UnexpectedChar)
470 opRes = NExtract::NOperationResult::kDataError;
471 }
472 }
473 RINOK(extractCallback->SetOperationResult(opRes))
474 lps->InSize = _phySize;
475 lps->OutSize = _size;
476 return lps->SetCur();
477 COM_TRY_END
478 }
479
480 REGISTER_ARC_I_NO_SIG(
481 "Base64", "b64", NULL, 0xC5,
482 0,
483 NArcInfoFlags::kKeepName
484 | NArcInfoFlags::kStartOpen
485 | NArcInfoFlags::kByExtOnlyOpen,
486 IsArc_Base64)
487
488 }}
489