VirtualBox

source: vbox/trunk/src/VBox/Main/src-all/ExtPackUtil.cpp@ 85233

Last change on this file since 85233 was 85233, checked in by vboxsync, 4 years ago

Main/ExtPackUtil.cpp: Fixed a error string construction bug in VBoxExtPackLoadDesc, simplified the others. bugref:9790

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 50.0 KB
Line 
1/* $Id: ExtPackUtil.cpp 85233 2020-07-11 16:30:55Z vboxsync $ */
2/** @file
3 * VirtualBox Main - Extension Pack Utilities and definitions, VBoxC, VBoxSVC, ++.
4 */
5
6/*
7 * Copyright (C) 2010-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#include "../include/ExtPackUtil.h"
23
24#include <iprt/ctype.h>
25#include <iprt/dir.h>
26#include <iprt/err.h>
27#include <iprt/file.h>
28#include <iprt/manifest.h>
29#include <iprt/param.h>
30#include <iprt/path.h>
31#include <iprt/sha.h>
32#include <iprt/string.h>
33#include <iprt/vfs.h>
34#include <iprt/tar.h>
35#include <iprt/zip.h>
36#include <iprt/cpp/xml.h>
37
38#include <VBox/log.h>
39
40
41/**
42 * Worker for VBoxExtPackLoadDesc that loads the plug-in descriptors.
43 *
44 * @returns Same as VBoxExtPackLoadDesc.
45 * @param pVBoxExtPackElm
46 * @param pcPlugIns Where to return the number of plug-ins in the
47 * array.
48 * @param paPlugIns Where to return the plug-in descriptor array.
49 * (RTMemFree it even on failure)
50 */
51static RTCString *
52vboxExtPackLoadPlugInDescs(const xml::ElementNode *pVBoxExtPackElm,
53 uint32_t *pcPlugIns, PVBOXEXTPACKPLUGINDESC *paPlugIns)
54{
55 *pcPlugIns = 0;
56 *paPlugIns = NULL;
57
58 /** @todo plug-ins */
59 NOREF(pVBoxExtPackElm);
60
61 return NULL;
62}
63
64/**
65 * Clears the extension pack descriptor.
66 *
67 * @param a_pExtPackDesc The descriptor to clear.
68 */
69static void vboxExtPackClearDesc(PVBOXEXTPACKDESC a_pExtPackDesc)
70{
71 a_pExtPackDesc->strName.setNull();
72 a_pExtPackDesc->strDescription.setNull();
73 a_pExtPackDesc->strVersion.setNull();
74 a_pExtPackDesc->strEdition.setNull();
75 a_pExtPackDesc->uRevision = 0;
76 a_pExtPackDesc->strMainModule.setNull();
77 a_pExtPackDesc->strMainVMModule.setNull();
78 a_pExtPackDesc->strVrdeModule.setNull();
79 a_pExtPackDesc->cPlugIns = 0;
80 a_pExtPackDesc->paPlugIns = NULL;
81 a_pExtPackDesc->fShowLicense = false;
82}
83
84/**
85 * Initializes an extension pack descriptor so that it's safe to call free on
86 * it whatever happens later on.
87 *
88 * @param a_pExtPackDesc The descirptor to initialize.
89 */
90void VBoxExtPackInitDesc(PVBOXEXTPACKDESC a_pExtPackDesc)
91{
92 vboxExtPackClearDesc(a_pExtPackDesc);
93}
94
95
96/**
97 * Load the extension pack descriptor from an XML document.
98 *
99 * @returns NULL on success, pointer to an error message on failure (caller
100 * deletes it).
101 * @param a_pDoc Pointer to the XML document.
102 * @param a_pExtPackDesc Where to store the extension pack descriptor.
103 */
104static RTCString *vboxExtPackLoadDescFromDoc(xml::Document *a_pDoc, PVBOXEXTPACKDESC a_pExtPackDesc)
105{
106 /*
107 * Get the main element and check its version.
108 */
109 const xml::ElementNode *pVBoxExtPackElm = a_pDoc->getRootElement();
110 if ( !pVBoxExtPackElm
111 || strcmp(pVBoxExtPackElm->getName(), "VirtualBoxExtensionPack") != 0)
112 return new RTCString("No VirtualBoxExtensionPack element");
113
114 RTCString strFormatVersion;
115 if (!pVBoxExtPackElm->getAttributeValueN("version", strFormatVersion, RT_XML_ATTR_TINY))
116 return new RTCString("Missing format version");
117 if (!strFormatVersion.equals("1.0"))
118 return &(new RTCString("Unsupported format version: "))->append(strFormatVersion);
119
120 /*
121 * Read and validate mandatory bits.
122 */
123 const xml::ElementNode *pNameElm = pVBoxExtPackElm->findChildElement("Name");
124 if (!pNameElm)
125 return new RTCString("The 'Name' element is missing");
126 const char *pszName = pNameElm->getValueN(RT_XML_CONTENT_SMALL);
127 if (!VBoxExtPackIsValidName(pszName))
128 return &(new RTCString("Invalid name: "))->append(pszName);
129
130 const xml::ElementNode *pDescElm = pVBoxExtPackElm->findChildElement("Description");
131 if (!pDescElm)
132 return new RTCString("The 'Description' element is missing");
133 const char *pszDesc = pDescElm->getValueN(RT_XML_CONTENT_LARGE);
134 if (!pszDesc || *pszDesc == '\0')
135 return new RTCString("The 'Description' element is empty");
136 if (strpbrk(pszDesc, "\n\r\t\v\b") != NULL)
137 return new RTCString("The 'Description' must not contain control characters");
138
139 const xml::ElementNode *pVersionElm = pVBoxExtPackElm->findChildElement("Version");
140 if (!pVersionElm)
141 return new RTCString("The 'Version' element is missing");
142 const char *pszVersion = pVersionElm->getValueN(RT_XML_CONTENT_SMALL);
143 if (!pszVersion || *pszVersion == '\0')
144 return new RTCString("The 'Version' element is empty");
145 if (!VBoxExtPackIsValidVersionString(pszVersion))
146 return &(new RTCString("Invalid version string: "))->append(pszVersion);
147
148 uint32_t uRevision;
149 if (!pVersionElm->getAttributeValue("revision", uRevision))
150 uRevision = 0;
151
152 const char *pszEdition;
153 if (!pVersionElm->getAttributeValueN("edition", pszEdition, RT_XML_ATTR_TINY))
154 pszEdition = "";
155 if (!VBoxExtPackIsValidEditionString(pszEdition))
156 return &(new RTCString("Invalid edition string: "))->append(pszEdition);
157
158 const xml::ElementNode *pMainModuleElm = pVBoxExtPackElm->findChildElement("MainModule");
159 if (!pMainModuleElm)
160 return new RTCString("The 'MainModule' element is missing");
161 const char *pszMainModule = pMainModuleElm->getValueN(RT_XML_CONTENT_SMALL);
162 if (!pszMainModule || *pszMainModule == '\0')
163 return new RTCString("The 'MainModule' element is empty");
164 if (!VBoxExtPackIsValidModuleString(pszMainModule))
165 return &(new RTCString("Invalid main module string: "))->append(pszMainModule);
166
167 /*
168 * The main VM module, optional.
169 * Accept both none and empty as tokens of no main VM module.
170 */
171 const char *pszMainVMModule = NULL;
172 const xml::ElementNode *pMainVMModuleElm = pVBoxExtPackElm->findChildElement("MainVMModule");
173 if (pMainVMModuleElm)
174 {
175 pszMainVMModule = pMainVMModuleElm->getValueN(RT_XML_CONTENT_SMALL);
176 if (!pszMainVMModule || *pszMainVMModule == '\0')
177 pszMainVMModule = NULL;
178 else if (!VBoxExtPackIsValidModuleString(pszMainVMModule))
179 return &(new RTCString("Invalid main VM module string: "))->append(pszMainVMModule);
180 }
181
182 /*
183 * The VRDE module, optional.
184 * Accept both none and empty as tokens of no VRDE module.
185 */
186 const char *pszVrdeModule = NULL;
187 const xml::ElementNode *pVrdeModuleElm = pVBoxExtPackElm->findChildElement("VRDEModule");
188 if (pVrdeModuleElm)
189 {
190 pszVrdeModule = pVrdeModuleElm->getValueN(RT_XML_CONTENT_SMALL);
191 if (!pszVrdeModule || *pszVrdeModule == '\0')
192 pszVrdeModule = NULL;
193 else if (!VBoxExtPackIsValidModuleString(pszVrdeModule))
194 return &(new RTCString("Invalid VRDE module string: "))->append(pszVrdeModule);
195 }
196
197 /*
198 * Whether to show the license, optional. (presense is enough here)
199 */
200 const xml::ElementNode *pShowLicenseElm = pVBoxExtPackElm->findChildElement("ShowLicense");
201 bool fShowLicense = pShowLicenseElm != NULL;
202
203 /*
204 * Parse plug-in descriptions (last because of the manual memory management).
205 */
206 uint32_t cPlugIns = 0;
207 PVBOXEXTPACKPLUGINDESC paPlugIns = NULL;
208 RTCString *pstrRet = vboxExtPackLoadPlugInDescs(pVBoxExtPackElm, &cPlugIns, &paPlugIns);
209 if (pstrRet)
210 {
211 RTMemFree(paPlugIns);
212 return pstrRet;
213 }
214
215 /*
216 * Everything seems fine, fill in the return values and return successfully.
217 */
218 a_pExtPackDesc->strName = pszName;
219 a_pExtPackDesc->strDescription = pszDesc;
220 a_pExtPackDesc->strVersion = pszVersion;
221 a_pExtPackDesc->strEdition = pszEdition;
222 a_pExtPackDesc->uRevision = uRevision;
223 a_pExtPackDesc->strMainModule = pszMainModule;
224 a_pExtPackDesc->strMainVMModule = pszMainVMModule;
225 a_pExtPackDesc->strVrdeModule = pszVrdeModule;
226 a_pExtPackDesc->cPlugIns = cPlugIns;
227 a_pExtPackDesc->paPlugIns = paPlugIns;
228 a_pExtPackDesc->fShowLicense = fShowLicense;
229
230 return NULL;
231}
232
233/**
234 * Reads the extension pack descriptor.
235 *
236 * @returns NULL on success, pointer to an error message on failure (caller
237 * deletes it).
238 * @param a_pszDir The directory containing the description file.
239 * @param a_pExtPackDesc Where to store the extension pack descriptor.
240 * @param a_pObjInfo Where to store the object info for the file (unix
241 * attribs). Optional.
242 */
243RTCString *VBoxExtPackLoadDesc(const char *a_pszDir, PVBOXEXTPACKDESC a_pExtPackDesc, PRTFSOBJINFO a_pObjInfo)
244{
245 vboxExtPackClearDesc(a_pExtPackDesc);
246
247 /*
248 * Validate, open and parse the XML file.
249 */
250 char szFilePath[RTPATH_MAX];
251 int vrc = RTPathJoin(szFilePath, sizeof(szFilePath), a_pszDir, VBOX_EXTPACK_DESCRIPTION_NAME);
252 if (RT_FAILURE(vrc))
253 return new RTCStringFmt("RTPathJoin failed with %Rrc", vrc);
254
255 RTFSOBJINFO ObjInfo;
256 vrc = RTPathQueryInfoEx(szFilePath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
257 if (RT_FAILURE(vrc))
258 return new RTCStringFmt("RTPathQueryInfoEx failed with %Rrc", vrc);
259 if (a_pObjInfo)
260 *a_pObjInfo = ObjInfo;
261 if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
262 {
263 if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode))
264 return new RTCString("The XML file is symlinked, that is not allowed");
265 return new RTCStringFmt("The XML file is not a file (fMode=%#x)", ObjInfo.Attr.fMode);
266 }
267
268 xml::Document Doc;
269 {
270 xml::XmlFileParser Parser;
271 try
272 {
273 Parser.read(szFilePath, Doc);
274 }
275 catch (xml::XmlError &rErr)
276 {
277 return new RTCString(rErr.what());
278 }
279 }
280
281 /*
282 * Hand the xml doc over to the common code.
283 */
284 try
285 {
286 return vboxExtPackLoadDescFromDoc(&Doc, a_pExtPackDesc);
287 }
288 catch (RTCError &rXcpt) // includes all XML exceptions
289 {
290 return new RTCString(rXcpt.what());
291 }
292}
293
294/**
295 * Reads the extension pack descriptor.
296 *
297 * @returns NULL on success, pointer to an error message on failure (caller
298 * deletes it).
299 * @param hVfsFile The file handle of the description file.
300 * @param a_pExtPackDesc Where to store the extension pack descriptor.
301 * @param a_pObjInfo Where to store the object info for the file (unix
302 * attribs). Optional.
303 */
304RTCString *VBoxExtPackLoadDescFromVfsFile(RTVFSFILE hVfsFile, PVBOXEXTPACKDESC a_pExtPackDesc, PRTFSOBJINFO a_pObjInfo)
305{
306 vboxExtPackClearDesc(a_pExtPackDesc);
307
308 /*
309 * Query the object info.
310 */
311 RTFSOBJINFO ObjInfo;
312 int rc = RTVfsFileQueryInfo(hVfsFile, &ObjInfo, RTFSOBJATTRADD_UNIX);
313 if (RT_FAILURE(rc))
314 return &(new RTCString)->printf("RTVfsFileQueryInfo failed: %Rrc", rc);
315 if (a_pObjInfo)
316 *a_pObjInfo = ObjInfo;
317
318 /*
319 * The simple approach, read the whole thing into memory and pass this to
320 * the XML parser.
321 */
322
323 /* Check the file size. */
324 if (ObjInfo.cbObject > _1M || ObjInfo.cbObject < 0)
325 return &(new RTCString)->printf("The XML file is too large (%'RU64 bytes)", ObjInfo.cbObject);
326 size_t const cbFile = (size_t)ObjInfo.cbObject;
327
328 /* Rewind to the start of the file. */
329 rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL);
330 if (RT_FAILURE(rc))
331 return &(new RTCString)->printf("RTVfsFileSeek(,0,BEGIN) failed: %Rrc", rc);
332
333 /* Allocate memory and read the file content into it. */
334 void *pvFile = RTMemTmpAlloc(cbFile);
335 if (!pvFile)
336 return &(new RTCString)->printf("RTMemTmpAlloc(%zu) failed", cbFile);
337
338 RTCString *pstrErr = NULL;
339 rc = RTVfsFileRead(hVfsFile, pvFile, cbFile, NULL);
340 if (RT_FAILURE(rc))
341 pstrErr = &(new RTCString)->printf("RTVfsFileRead failed: %Rrc", rc);
342
343 /*
344 * Parse the file.
345 */
346 xml::Document Doc;
347 if (RT_SUCCESS(rc))
348 {
349 xml::XmlMemParser Parser;
350 RTCString strFileName = VBOX_EXTPACK_DESCRIPTION_NAME;
351 try
352 {
353 Parser.read(pvFile, cbFile, strFileName, Doc);
354 }
355 catch (xml::XmlError &rErr)
356 {
357 pstrErr = new RTCString(rErr.what());
358 rc = VERR_PARSE_ERROR;
359 }
360 }
361 RTMemTmpFree(pvFile);
362
363 /*
364 * Hand the xml doc over to the common code.
365 */
366 if (RT_SUCCESS(rc))
367 try
368 {
369 pstrErr = vboxExtPackLoadDescFromDoc(&Doc, a_pExtPackDesc);
370 }
371 catch (RTCError &rXcpt) // includes all XML exceptions
372 {
373 return new RTCString(rXcpt.what());
374 }
375
376 return pstrErr;
377}
378
379/**
380 * Frees all resources associated with a extension pack descriptor.
381 *
382 * @param a_pExtPackDesc The extension pack descriptor which members
383 * should be freed.
384 */
385void VBoxExtPackFreeDesc(PVBOXEXTPACKDESC a_pExtPackDesc)
386{
387 if (!a_pExtPackDesc)
388 return;
389
390 a_pExtPackDesc->strName.setNull();
391 a_pExtPackDesc->strDescription.setNull();
392 a_pExtPackDesc->strVersion.setNull();
393 a_pExtPackDesc->strEdition.setNull();
394 a_pExtPackDesc->uRevision = 0;
395 a_pExtPackDesc->strMainModule.setNull();
396 a_pExtPackDesc->strMainVMModule.setNull();
397 a_pExtPackDesc->strVrdeModule.setNull();
398 a_pExtPackDesc->cPlugIns = 0;
399 RTMemFree(a_pExtPackDesc->paPlugIns);
400 a_pExtPackDesc->paPlugIns = NULL;
401 a_pExtPackDesc->fShowLicense = false;
402}
403
404/**
405 * Extract the extension pack name from the tarball path.
406 *
407 * @returns String containing the name on success, the caller must delete it.
408 * NULL if no valid name was found or if we ran out of memory.
409 * @param pszTarball The path to the tarball.
410 */
411RTCString *VBoxExtPackExtractNameFromTarballPath(const char *pszTarball)
412{
413 /*
414 * Skip ahead to the filename part and count the number of characters
415 * that matches the criteria for a mangled extension pack name.
416 */
417 const char *pszSrc = RTPathFilename(pszTarball);
418 if (!pszSrc)
419 return NULL;
420
421 size_t off = 0;
422 while (RT_C_IS_ALNUM(pszSrc[off]) || pszSrc[off] == '_')
423 off++;
424
425 /*
426 * Check min and max name limits.
427 */
428 if ( off > VBOX_EXTPACK_NAME_MAX_LEN
429 || off < VBOX_EXTPACK_NAME_MIN_LEN)
430 return NULL;
431
432 /*
433 * Return the unmangled name.
434 */
435 return VBoxExtPackUnmangleName(pszSrc, off);
436}
437
438/**
439 * Validates the extension pack name.
440 *
441 * @returns true if valid, false if not.
442 * @param pszName The name to validate.
443 * @sa VBoxExtPackExtractNameFromTarballPath
444 */
445bool VBoxExtPackIsValidName(const char *pszName)
446{
447 if (!pszName)
448 return false;
449
450 /*
451 * Check the characters making up the name, only english alphabet
452 * characters, decimal digits and spaces are allowed.
453 */
454 size_t off = 0;
455 while (pszName[off])
456 {
457 if (!RT_C_IS_ALNUM(pszName[off]) && pszName[off] != ' ')
458 return false;
459 off++;
460 }
461
462 /*
463 * Check min and max name limits.
464 */
465 if ( off > VBOX_EXTPACK_NAME_MAX_LEN
466 || off < VBOX_EXTPACK_NAME_MIN_LEN)
467 return false;
468
469 return true;
470}
471
472/**
473 * Checks if an alledged manged extension pack name.
474 *
475 * @returns true if valid, false if not.
476 * @param pszMangledName The mangled name to validate.
477 * @param cchMax The max number of chars to test.
478 * @sa VBoxExtPackMangleName
479 */
480bool VBoxExtPackIsValidMangledName(const char *pszMangledName, size_t cchMax /*= RTSTR_MAX*/)
481{
482 if (!pszMangledName)
483 return false;
484
485 /*
486 * Check the characters making up the name, only english alphabet
487 * characters, decimal digits and underscores (=space) are allowed.
488 */
489 size_t off = 0;
490 while (off < cchMax && pszMangledName[off])
491 {
492 if (!RT_C_IS_ALNUM(pszMangledName[off]) && pszMangledName[off] != '_')
493 return false;
494 off++;
495 }
496
497 /*
498 * Check min and max name limits.
499 */
500 if ( off > VBOX_EXTPACK_NAME_MAX_LEN
501 || off < VBOX_EXTPACK_NAME_MIN_LEN)
502 return false;
503
504 return true;
505}
506
507/**
508 * Mangle an extension pack name so it can be used by a directory or file name.
509 *
510 * @returns String containing the mangled name on success, the caller must
511 * delete it. NULL on failure.
512 * @param pszName The unmangled name.
513 * @sa VBoxExtPackUnmangleName, VBoxExtPackIsValidMangledName
514 */
515RTCString *VBoxExtPackMangleName(const char *pszName)
516{
517 AssertReturn(VBoxExtPackIsValidName(pszName), NULL);
518
519 char szTmp[VBOX_EXTPACK_NAME_MAX_LEN + 1];
520 size_t off = 0;
521 char ch;
522 while ((ch = pszName[off]) != '\0')
523 {
524 if (ch == ' ')
525 ch = '_';
526 szTmp[off++] = ch;
527 }
528 szTmp[off] = '\0';
529 Assert(VBoxExtPackIsValidMangledName(szTmp));
530
531 return new RTCString(szTmp, off);
532}
533
534/**
535 * Unmangle an extension pack name (reverses VBoxExtPackMangleName).
536 *
537 * @returns String containing the mangled name on success, the caller must
538 * delete it. NULL on failure.
539 * @param pszMangledName The mangled name.
540 * @param cchMax The max name length. RTSTR_MAX is fine.
541 * @sa VBoxExtPackMangleName, VBoxExtPackIsValidMangledName
542 */
543RTCString *VBoxExtPackUnmangleName(const char *pszMangledName, size_t cchMax)
544{
545 AssertReturn(VBoxExtPackIsValidMangledName(pszMangledName, cchMax), NULL);
546
547 char szTmp[VBOX_EXTPACK_NAME_MAX_LEN + 1];
548 size_t off = 0;
549 char ch;
550 while ( off < cchMax
551 && (ch = pszMangledName[off]) != '\0')
552 {
553 if (ch == '_')
554 ch = ' ';
555 else
556 AssertReturn(RT_C_IS_ALNUM(ch) || ch == ' ', NULL);
557 szTmp[off++] = ch;
558 }
559 szTmp[off] = '\0';
560 AssertReturn(VBoxExtPackIsValidName(szTmp), NULL);
561
562 return new RTCString(szTmp, off);
563}
564
565/**
566 * Constructs the extension pack directory path.
567 *
568 * A combination of RTPathJoin and VBoxExtPackMangleName.
569 *
570 * @returns IPRT status code like RTPathJoin.
571 * @param pszExtPackDir Where to return the directory path.
572 * @param cbExtPackDir The size of the return buffer.
573 * @param pszParentDir The parent directory (".../Extensions").
574 * @param pszName The extension pack name, unmangled.
575 */
576int VBoxExtPackCalcDir(char *pszExtPackDir, size_t cbExtPackDir, const char *pszParentDir, const char *pszName)
577{
578 AssertReturn(VBoxExtPackIsValidName(pszName), VERR_INTERNAL_ERROR_5);
579
580 RTCString *pstrMangledName = VBoxExtPackMangleName(pszName);
581 if (!pstrMangledName)
582 return VERR_INTERNAL_ERROR_4;
583
584 int vrc = RTPathJoin(pszExtPackDir, cbExtPackDir, pszParentDir, pstrMangledName->c_str());
585 delete pstrMangledName;
586
587 return vrc;
588}
589
590
591/**
592 * Validates the extension pack version string.
593 *
594 * @returns true if valid, false if not.
595 * @param pszVersion The version string to validate.
596 */
597bool VBoxExtPackIsValidVersionString(const char *pszVersion)
598{
599 if (!pszVersion || *pszVersion == '\0')
600 return false;
601
602 /* 1.x.y.z... */
603 for (;;)
604 {
605 if (!RT_C_IS_DIGIT(*pszVersion))
606 return false;
607 do
608 pszVersion++;
609 while (RT_C_IS_DIGIT(*pszVersion));
610 if (*pszVersion != '.')
611 break;
612 pszVersion++;
613 }
614
615 /* upper case string + numbers indicating the build type */
616 if (*pszVersion == '-' || *pszVersion == '_')
617 {
618 /** @todo Should probably restrict this to known build types (alpha,
619 * beta, rc, ++). */
620 do
621 pszVersion++;
622 while ( RT_C_IS_DIGIT(*pszVersion)
623 || RT_C_IS_UPPER(*pszVersion)
624 || *pszVersion == '-'
625 || *pszVersion == '_');
626 }
627
628 return *pszVersion == '\0';
629}
630
631/**
632 * Validates the extension pack edition string.
633 *
634 * @returns true if valid, false if not.
635 * @param pszEdition The edition string to validate.
636 */
637bool VBoxExtPackIsValidEditionString(const char *pszEdition)
638{
639 if (*pszEdition)
640 {
641 if (!RT_C_IS_UPPER(*pszEdition))
642 return false;
643
644 do
645 pszEdition++;
646 while ( RT_C_IS_UPPER(*pszEdition)
647 || RT_C_IS_DIGIT(*pszEdition)
648 || *pszEdition == '-'
649 || *pszEdition == '_');
650 }
651 return *pszEdition == '\0';
652}
653
654/**
655 * Validates an extension pack module string.
656 *
657 * @returns true if valid, false if not.
658 * @param pszModule The module string to validate.
659 */
660bool VBoxExtPackIsValidModuleString(const char *pszModule)
661{
662 if (!pszModule || *pszModule == '\0')
663 return false;
664
665 /* Restricted charset, no extensions (dots). */
666 while ( RT_C_IS_ALNUM(*pszModule)
667 || *pszModule == '-'
668 || *pszModule == '_')
669 pszModule++;
670
671 return *pszModule == '\0';
672}
673
674/**
675 * RTStrPrintfv wrapper.
676 *
677 * @returns @a rc
678 * @param rc The status code to return.
679 * @param pszError The error buffer.
680 * @param cbError The size of the buffer.
681 * @param pszFormat The error message format string.
682 * @param ... Format arguments.
683 */
684static int vboxExtPackReturnError(int rc, char *pszError, size_t cbError, const char *pszFormat, ...)
685{
686 va_list va;
687 va_start(va, pszFormat);
688 RTStrPrintfV(pszError, cbError, pszFormat, va);
689 va_end(va);
690 return rc;
691}
692
693/**
694 * RTStrPrintfv wrapper.
695 *
696 * @param pszError The error buffer.
697 * @param cbError The size of the buffer.
698 * @param pszFormat The error message format string.
699 * @param ... Format arguments.
700 */
701static void vboxExtPackSetError(char *pszError, size_t cbError, const char *pszFormat, ...)
702{
703 va_list va;
704 va_start(va, pszFormat);
705 RTStrPrintfV(pszError, cbError, pszFormat, va);
706 va_end(va);
707}
708
709/**
710 * Verifies the manifest and its signature.
711 *
712 * @returns VBox status code, failures with message.
713 * @param hXmlFile The xml from the extension pack.
714 * @param pszExtPackName The expected extension pack name. This can be
715 * NULL, in which we don't have any expectations.
716 * @param pszError Where to store an error message on failure.
717 * @param cbError The size of the buffer @a pszError points to.
718 */
719static int vboxExtPackVerifyXml(RTVFSFILE hXmlFile, const char *pszExtPackName, char *pszError, size_t cbError)
720{
721 /*
722 * Load the XML.
723 */
724 VBOXEXTPACKDESC ExtPackDesc;
725 RTCString *pstrErr = VBoxExtPackLoadDescFromVfsFile(hXmlFile, &ExtPackDesc, NULL);
726 if (pstrErr)
727 {
728 RTStrCopy(pszError, cbError, pstrErr->c_str());
729 delete pstrErr;
730 return VERR_PARSE_ERROR;
731 }
732
733 /*
734 * Check the name.
735 */
736 /** @todo drop this restriction after the old install interface is
737 * dropped. */
738 int rc = VINF_SUCCESS;
739 if ( pszExtPackName
740 && !ExtPackDesc.strName.equalsIgnoreCase(pszExtPackName))
741 rc = vboxExtPackReturnError(VERR_NOT_EQUAL, pszError, cbError,
742 "The name of the downloaded file and the name stored inside the extension pack does not match"
743 " (xml='%s' file='%s')", ExtPackDesc.strName.c_str(), pszExtPackName);
744 return rc;
745}
746
747/**
748 * Verifies the manifest and its signature.
749 *
750 * @returns VBox status code, failures with message.
751 * @param hOurManifest The manifest we compiled.
752 * @param hManifestFile The manifest file in the extension pack.
753 * @param hSignatureFile The manifest signature file.
754 * @param pszError Where to store an error message on failure.
755 * @param cbError The size of the buffer @a pszError points to.
756 */
757static int vboxExtPackVerifyManifestAndSignature(RTMANIFEST hOurManifest, RTVFSFILE hManifestFile, RTVFSFILE hSignatureFile,
758 char *pszError, size_t cbError)
759{
760 /*
761 * Read the manifest from the extension pack.
762 */
763 int rc = RTVfsFileSeek(hManifestFile, 0, RTFILE_SEEK_BEGIN, NULL);
764 if (RT_FAILURE(rc))
765 return vboxExtPackReturnError(rc, pszError, cbError, "RTVfsFileSeek failed: %Rrc", rc);
766
767 RTMANIFEST hTheirManifest;
768 rc = RTManifestCreate(0 /*fFlags*/, &hTheirManifest);
769 if (RT_FAILURE(rc))
770 return vboxExtPackReturnError(rc, pszError, cbError, "RTManifestCreate failed: %Rrc", rc);
771
772 RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hManifestFile);
773 rc = RTManifestReadStandard(hTheirManifest, hVfsIos);
774 RTVfsIoStrmRelease(hVfsIos);
775 if (RT_SUCCESS(rc))
776 {
777 /*
778 * Compare the manifests.
779 */
780 static const char *s_apszIgnoreEntries[] =
781 {
782 VBOX_EXTPACK_MANIFEST_NAME,
783 VBOX_EXTPACK_SIGNATURE_NAME,
784 "./" VBOX_EXTPACK_MANIFEST_NAME,
785 "./" VBOX_EXTPACK_SIGNATURE_NAME,
786 NULL
787 };
788 char szError[RTPATH_MAX];
789 rc = RTManifestEqualsEx(hOurManifest, hTheirManifest, &s_apszIgnoreEntries[0], NULL,
790 RTMANIFEST_EQUALS_IGN_MISSING_ATTRS /*fFlags*/,
791 szError, sizeof(szError));
792 if (RT_SUCCESS(rc))
793 {
794 /*
795 * Validate the manifest file signature.
796 */
797 /** @todo implement signature stuff */
798 NOREF(hSignatureFile);
799
800 }
801 else if (rc == VERR_NOT_EQUAL && szError[0])
802 vboxExtPackSetError(pszError, cbError, "Manifest mismatch: %s", szError);
803 else
804 vboxExtPackSetError(pszError, cbError, "RTManifestEqualsEx failed: %Rrc", rc);
805#if 0
806 RTVFSIOSTREAM hVfsIosStdOut = NIL_RTVFSIOSTREAM;
807 RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, true, &hVfsIosStdOut);
808 RTVfsIoStrmWrite(hVfsIosStdOut, "Our:\n", sizeof("Our:\n") - 1, true, NULL);
809 RTManifestWriteStandard(hOurManifest, hVfsIosStdOut);
810 RTVfsIoStrmWrite(hVfsIosStdOut, "Their:\n", sizeof("Their:\n") - 1, true, NULL);
811 RTManifestWriteStandard(hTheirManifest, hVfsIosStdOut);
812#endif
813 }
814 else
815 vboxExtPackSetError(pszError, cbError, "Error parsing '%s': %Rrc", VBOX_EXTPACK_MANIFEST_NAME, rc);
816
817 RTManifestRelease(hTheirManifest);
818 return rc;
819}
820
821
822/**
823 * Verifies the file digest (if specified) and returns the SHA-256 of the file.
824 *
825 * @returns
826 * @param hFileManifest Manifest containing a SHA-256 digest of the file
827 * that was calculated as the file was processed.
828 * @param pszFileDigest SHA-256 digest of the file.
829 * @param pStrDigest Where to return the SHA-256 digest. Optional.
830 * @param pszError Where to write an error message on failure.
831 * @param cbError The size of the @a pszError buffer.
832 */
833static int vboxExtPackVerifyFileDigest(RTMANIFEST hFileManifest, const char *pszFileDigest,
834 RTCString *pStrDigest, char *pszError, size_t cbError)
835{
836 /*
837 * Extract the SHA-256 entry for the extpack file.
838 */
839 char szCalculatedDigest[RTSHA256_DIGEST_LEN + 1];
840 int rc = RTManifestEntryQueryAttr(hFileManifest, "extpack", NULL /*no name*/, RTMANIFEST_ATTR_SHA256,
841 szCalculatedDigest, sizeof(szCalculatedDigest), NULL);
842 if (RT_SUCCESS(rc))
843 {
844 /*
845 * Convert the two strings to binary form before comparing.
846 * We convert the calculated hash even if we don't have anything to
847 * compare with, just to validate it.
848 */
849 uint8_t abCalculatedHash[RTSHA256_HASH_SIZE];
850 rc = RTSha256FromString(szCalculatedDigest, abCalculatedHash);
851 if (RT_SUCCESS(rc))
852 {
853 if ( pszFileDigest
854 && *pszFileDigest != '\0')
855 {
856 uint8_t abFileHash[RTSHA256_HASH_SIZE];
857 rc = RTSha256FromString(pszFileDigest, abFileHash);
858 if (RT_SUCCESS(rc))
859 {
860 if (memcmp(abFileHash, abCalculatedHash, sizeof(abFileHash)))
861 {
862 vboxExtPackSetError(pszError, cbError, "The extension pack file has changed (SHA-256 mismatch)");
863 rc = VERR_NOT_EQUAL;
864 }
865 }
866 else
867 vboxExtPackSetError(pszError, cbError, "Bad SHA-256 '%s': %Rrc", szCalculatedDigest, rc);
868 }
869
870 /*
871 * Set the output hash on success.
872 */
873 if (pStrDigest && RT_SUCCESS(rc))
874 {
875 try
876 {
877 *pStrDigest = szCalculatedDigest;
878 }
879 catch (std::bad_alloc &)
880 {
881 rc = VERR_NO_MEMORY;
882 }
883 }
884 }
885 else
886 vboxExtPackSetError(pszError, cbError, "Bad SHA-256 '%s': %Rrc", szCalculatedDigest, rc);
887 }
888 else
889 vboxExtPackSetError(pszError, cbError, "RTManifestEntryGetAttr: %Rrc", rc);
890 return rc;
891}
892
893
894
895/**
896 * Validates a standard file.
897 *
898 * Generally all files are
899 *
900 * @returns VBox status code, failure message in @a pszError.
901 * @param pszAdjName The adjusted member name.
902 * @param enmType The VFS object type.
903 * @param phVfsObj The pointer to the VFS object handle variable.
904 * This is both input and output.
905 * @param phVfsFile Where to store the handle to the memorized
906 * file. This is NULL for license files.
907 * @param pszError Where to write an error message on failure.
908 * @param cbError The size of the @a pszError buffer.
909 */
910static int VBoxExtPackValidateStandardFile(const char *pszAdjName, RTVFSOBJTYPE enmType,
911 PRTVFSOBJ phVfsObj, PRTVFSFILE phVfsFile, char *pszError, size_t cbError)
912{
913 int rc;
914
915 /*
916 * Make sure it's a file and that it isn't too large.
917 */
918 if (phVfsFile && *phVfsFile != NIL_RTVFSFILE)
919 rc = vboxExtPackReturnError(VERR_DUPLICATE, pszError, cbError,
920 "There can only be one '%s'", pszAdjName);
921 else if (enmType != RTVFSOBJTYPE_IO_STREAM && enmType != RTVFSOBJTYPE_FILE)
922 rc = vboxExtPackReturnError(VERR_NOT_A_FILE, pszError, cbError,
923 "Standard member '%s' is not a file", pszAdjName);
924 else
925 {
926 RTFSOBJINFO ObjInfo;
927 rc = RTVfsObjQueryInfo(*phVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
928 if (RT_SUCCESS(rc))
929 {
930 if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
931 rc = vboxExtPackReturnError(VERR_NOT_A_FILE, pszError, cbError,
932 "Standard member '%s' is not a file", pszAdjName);
933 else if (ObjInfo.cbObject >= _1M)
934 rc = vboxExtPackReturnError(VERR_OUT_OF_RANGE, pszError, cbError,
935 "Standard member '%s' is too large: %'RU64 bytes (max 1 MB)",
936 pszAdjName, (uint64_t)ObjInfo.cbObject);
937 else
938 {
939 /*
940 * Make an in memory copy of the stream and check that the file
941 * is UTF-8 clean.
942 */
943 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(*phVfsObj);
944 RTVFSFILE hVfsFile;
945 rc = RTVfsMemorizeIoStreamAsFile(hVfsIos, RTFILE_O_READ, &hVfsFile);
946 if (RT_SUCCESS(rc))
947 {
948 rc = RTVfsIoStrmValidateUtf8Encoding(hVfsIos,
949 RTVFS_VALIDATE_UTF8_BY_RTC_3629 | RTVFS_VALIDATE_UTF8_NO_NULL,
950 NULL);
951 if (RT_SUCCESS(rc))
952 {
953 /*
954 * Replace *phVfsObj with the memorized file.
955 */
956 rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL);
957 if (RT_SUCCESS(rc))
958 {
959 RTVfsObjRelease(*phVfsObj);
960 *phVfsObj = RTVfsObjFromFile(hVfsFile);
961 }
962 else
963 vboxExtPackSetError(pszError, cbError, "RTVfsFileSeek failed on '%s': %Rrc", pszAdjName, rc);
964 }
965
966 if (phVfsFile && RT_SUCCESS(rc))
967 *phVfsFile = hVfsFile;
968 else
969 RTVfsFileRelease(hVfsFile);
970 }
971 else
972 vboxExtPackSetError(pszError, cbError, "RTVfsMemorizeIoStreamAsFile failed on '%s': %Rrc", pszAdjName, rc);
973 RTVfsIoStrmRelease(hVfsIos);
974 }
975 }
976 else
977 vboxExtPackSetError(pszError, cbError, "RTVfsObjQueryInfo failed on '%s': %Rrc", pszAdjName, rc);
978 }
979 return rc;
980}
981
982
983/**
984 * Validates a name in an extension pack.
985 *
986 * We restrict the charset to try make sure the extension pack can be unpacked
987 * on all file systems.
988 *
989 * @returns VBox status code, failures with message.
990 * @param pszName The name to validate.
991 * @param pszError Where to store an error message on failure.
992 * @param cbError The size of the buffer @a pszError points to.
993 */
994static int vboxExtPackValidateMemberName(const char *pszName, char *pszError, size_t cbError)
995{
996 if (RTPathStartsWithRoot(pszName))
997 return vboxExtPackReturnError(VERR_PATH_IS_NOT_RELATIVE, pszError, cbError, "'%s': starts with root spec", pszName);
998
999 const char *pszErr = NULL;
1000 const char *psz = pszName;
1001 int ch;
1002 while ((ch = *psz) != '\0')
1003 {
1004 /* Character set restrictions. */
1005 if (ch < 0 || ch >= 128)
1006 {
1007 pszErr = "Only 7-bit ASCII allowed";
1008 break;
1009 }
1010 if (ch <= 31 || ch == 127)
1011 {
1012 pszErr = "No control characters are not allowed";
1013 break;
1014 }
1015 if (ch == '\\')
1016 {
1017 pszErr = "Only backward slashes are not allowed";
1018 break;
1019 }
1020 if (strchr("'\":;*?|[]<>(){}", ch))
1021 {
1022 pszErr = "The characters ', \", :, ;, *, ?, |, [, ], <, >, (, ), { and } are not allowed";
1023 break;
1024 }
1025
1026 /* Take the simple way out and ban all ".." sequences. */
1027 if ( ch == '.'
1028 && psz[1] == '.')
1029 {
1030 pszErr = "Double dot sequence are not allowed";
1031 break;
1032 }
1033
1034 /* Keep the tree shallow or the hardening checks will fail. */
1035 if (psz - pszName > VBOX_EXTPACK_MAX_MEMBER_NAME_LENGTH)
1036 {
1037 pszErr = "Too long";
1038 break;
1039 }
1040
1041 /* advance */
1042 psz++;
1043 }
1044
1045 if (pszErr)
1046 return vboxExtPackReturnError(VERR_INVALID_NAME, pszError, cbError,
1047 "Bad member name '%s' (pos %zu): %s", pszName, (size_t)(psz - pszName), pszErr);
1048 return RTEXITCODE_SUCCESS;
1049}
1050
1051
1052/**
1053 * Validates a file in an extension pack.
1054 *
1055 * @returns VBox status code, failures with message.
1056 * @param pszName The name of the file.
1057 * @param hVfsObj The VFS object.
1058 * @param pszError Where to store an error message on failure.
1059 * @param cbError The size of the buffer @a pszError points to.
1060 */
1061static int vboxExtPackValidateMemberFile(const char *pszName, RTVFSOBJ hVfsObj, char *pszError, size_t cbError)
1062{
1063 int rc = vboxExtPackValidateMemberName(pszName, pszError, cbError);
1064 if (RT_SUCCESS(rc))
1065 {
1066 RTFSOBJINFO ObjInfo;
1067 rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
1068 if (RT_SUCCESS(rc))
1069 {
1070 if (ObjInfo.cbObject >= 9*_1G64)
1071 rc = vboxExtPackReturnError(VERR_OUT_OF_RANGE, pszError, cbError,
1072 "'%s': too large (%'RU64 bytes)",
1073 pszName, (uint64_t)ObjInfo.cbObject);
1074 if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
1075 rc = vboxExtPackReturnError(VERR_NOT_A_FILE, pszError, cbError,
1076 "The alleged file '%s' has a mode mask stating otherwise (%RTfmode)",
1077 pszName, ObjInfo.Attr.fMode);
1078 }
1079 else
1080 vboxExtPackSetError(pszError, cbError, "RTVfsObjQueryInfo failed on '%s': %Rrc", pszName, rc);
1081 }
1082 return rc;
1083}
1084
1085
1086/**
1087 * Validates a directory in an extension pack.
1088 *
1089 * @returns VBox status code, failures with message.
1090 * @param pszName The name of the directory.
1091 * @param hVfsObj The VFS object.
1092 * @param pszError Where to store an error message on failure.
1093 * @param cbError The size of the buffer @a pszError points to.
1094 */
1095static int vboxExtPackValidateMemberDir(const char *pszName, RTVFSOBJ hVfsObj, char *pszError, size_t cbError)
1096{
1097 int rc = vboxExtPackValidateMemberName(pszName, pszError, cbError);
1098 if (RT_SUCCESS(rc))
1099 {
1100 RTFSOBJINFO ObjInfo;
1101 rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
1102 if (RT_SUCCESS(rc))
1103 {
1104 if (!RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
1105 rc = vboxExtPackReturnError(VERR_NOT_A_DIRECTORY, pszError, cbError,
1106 "The alleged directory '%s' has a mode mask saying differently (%RTfmode)",
1107 pszName, ObjInfo.Attr.fMode);
1108 }
1109 else
1110 vboxExtPackSetError(pszError, cbError, "RTVfsObjQueryInfo failed on '%s': %Rrc", pszName, rc);
1111 }
1112 return rc;
1113}
1114
1115/**
1116 * Validates a member of an extension pack.
1117 *
1118 * @returns VBox status code, failures with message.
1119 * @param pszName The name of the directory.
1120 * @param enmType The object type.
1121 * @param hVfsObj The VFS object.
1122 * @param pszError Where to store an error message on failure.
1123 * @param cbError The size of the buffer @a pszError points to.
1124 */
1125int VBoxExtPackValidateMember(const char *pszName, RTVFSOBJTYPE enmType, RTVFSOBJ hVfsObj, char *pszError, size_t cbError)
1126{
1127 Assert(cbError > 0);
1128 *pszError = '\0';
1129
1130 int rc;
1131 if ( enmType == RTVFSOBJTYPE_FILE
1132 || enmType == RTVFSOBJTYPE_IO_STREAM)
1133 rc = vboxExtPackValidateMemberFile(pszName, hVfsObj, pszError, cbError);
1134 else if ( enmType == RTVFSOBJTYPE_DIR
1135 || enmType == RTVFSOBJTYPE_BASE)
1136 rc = vboxExtPackValidateMemberDir(pszName, hVfsObj, pszError, cbError);
1137 else
1138 rc = vboxExtPackReturnError(VERR_UNEXPECTED_FS_OBJ_TYPE, pszError, cbError,
1139 "'%s' is not a file or directory (enmType=%d)", pszName, enmType);
1140 return rc;
1141}
1142
1143
1144/**
1145 * Rewinds the tarball file handle and creates a gunzip | tar chain that
1146 * results in a filesystem stream.
1147 *
1148 * @returns VBox status code, failures with message.
1149 * @param hTarballFile The handle to the tarball file.
1150 * @param pszError Where to store an error message on failure.
1151 * @param cbError The size of the buffer @a pszError points to.
1152 * @param phTarFss Where to return the filesystem stream handle.
1153 * @param phFileManifest Where to return a manifest where the tarball is
1154 * gettting hashed. The entry will be called
1155 * "extpack" and be ready when the file system
1156 * stream is at an end. Optional.
1157 */
1158int VBoxExtPackOpenTarFss(RTFILE hTarballFile, char *pszError, size_t cbError, PRTVFSFSSTREAM phTarFss,
1159 PRTMANIFEST phFileManifest)
1160{
1161 Assert(cbError > 0);
1162 *pszError = '\0';
1163 *phTarFss = NIL_RTVFSFSSTREAM;
1164
1165 /*
1166 * Rewind the file and set up a VFS chain for it.
1167 */
1168 int rc = RTFileSeek(hTarballFile, 0, RTFILE_SEEK_BEGIN, NULL);
1169 if (RT_FAILURE(rc))
1170 return vboxExtPackReturnError(rc, pszError, cbError, "Failed seeking to the start of the tarball: %Rrc", rc);
1171
1172 RTVFSIOSTREAM hTarballIos;
1173 rc = RTVfsIoStrmFromRTFile(hTarballFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN, true /*fLeaveOpen*/,
1174 &hTarballIos);
1175 if (RT_FAILURE(rc))
1176 return vboxExtPackReturnError(rc, pszError, cbError, "RTVfsIoStrmFromRTFile failed: %Rrc", rc);
1177
1178 RTMANIFEST hFileManifest = NIL_RTMANIFEST;
1179 rc = RTManifestCreate(0 /*fFlags*/, &hFileManifest);
1180 if (RT_SUCCESS(rc))
1181 {
1182 RTVFSIOSTREAM hPtIos;
1183 rc = RTManifestEntryAddPassthruIoStream(hFileManifest, hTarballIos, "extpack", RTMANIFEST_ATTR_SHA256,
1184 true /*read*/, &hPtIos);
1185 if (RT_SUCCESS(rc))
1186 {
1187 RTVFSIOSTREAM hGunzipIos;
1188 rc = RTZipGzipDecompressIoStream(hPtIos, 0 /*fFlags*/, &hGunzipIos);
1189 if (RT_SUCCESS(rc))
1190 {
1191 RTVFSFSSTREAM hTarFss;
1192 rc = RTZipTarFsStreamFromIoStream(hGunzipIos, 0 /*fFlags*/, &hTarFss);
1193 if (RT_SUCCESS(rc))
1194 {
1195 RTVfsIoStrmRelease(hPtIos);
1196 RTVfsIoStrmRelease(hGunzipIos);
1197 RTVfsIoStrmRelease(hTarballIos);
1198 *phTarFss = hTarFss;
1199 if (phFileManifest)
1200 *phFileManifest = hFileManifest;
1201 else
1202 RTManifestRelease(hFileManifest);
1203 return VINF_SUCCESS;
1204 }
1205
1206 vboxExtPackSetError(pszError, cbError, "RTZipTarFsStreamFromIoStream failed: %Rrc", rc);
1207 RTVfsIoStrmRelease(hGunzipIos);
1208 }
1209 else
1210 vboxExtPackSetError(pszError, cbError, "RTZipGzipDecompressIoStream failed: %Rrc", rc);
1211 RTVfsIoStrmRelease(hPtIos);
1212 }
1213 else
1214 vboxExtPackSetError(pszError, cbError, "RTManifestEntryAddPassthruIoStream failed: %Rrc", rc);
1215 RTManifestRelease(hFileManifest);
1216 }
1217 else
1218 vboxExtPackSetError(pszError, cbError, "RTManifestCreate failed: %Rrc", rc);
1219
1220 RTVfsIoStrmRelease(hTarballIos);
1221 return rc;
1222}
1223
1224
1225/**
1226 * Validates the extension pack tarball prior to unpacking.
1227 *
1228 * Operations performed:
1229 * - Mandatory files.
1230 * - Manifest check.
1231 * - Manifest seal check.
1232 * - XML check, match name.
1233 *
1234 * @returns VBox status code, failures with message.
1235 * @param hTarballFile The handle to open the @a pszTarball file.
1236 * @param pszExtPackName The name of the extension pack name. NULL if
1237 * the name is not fixed.
1238 * @param pszTarball The name of the tarball in case we have to
1239 * complain about something.
1240 * @param pszTarballDigest The SHA-256 digest of the tarball. Empty string
1241 * if no digest available.
1242 * @param pszError Where to store an error message on failure.
1243 * @param cbError The size of the buffer @a pszError points to.
1244 * @param phValidManifest Where to optionally return the handle to fully
1245 * validated the manifest for the extension pack.
1246 * This includes all files.
1247 * @param phXmlFile Where to optionally return the memorized XML
1248 * file.
1249 * @param pStrDigest Where to return the digest of the file.
1250 * Optional.
1251 */
1252int VBoxExtPackValidateTarball(RTFILE hTarballFile, const char *pszExtPackName,
1253 const char *pszTarball, const char *pszTarballDigest,
1254 char *pszError, size_t cbError,
1255 PRTMANIFEST phValidManifest, PRTVFSFILE phXmlFile, RTCString *pStrDigest)
1256{
1257 /*
1258 * Clear return values.
1259 */
1260 if (phValidManifest)
1261 *phValidManifest = NIL_RTMANIFEST;
1262 if (phXmlFile)
1263 *phXmlFile = NIL_RTVFSFILE;
1264 Assert(cbError > 1);
1265 *pszError = '\0';
1266 NOREF(pszTarball);
1267
1268 /*
1269 * Open the tar.gz filesystem stream and set up an manifest in-memory file.
1270 */
1271 RTMANIFEST hFileManifest;
1272 RTVFSFSSTREAM hTarFss;
1273 int rc = VBoxExtPackOpenTarFss(hTarballFile, pszError, cbError, &hTarFss, &hFileManifest);
1274 if (RT_FAILURE(rc))
1275 return rc;
1276
1277 RTMANIFEST hOurManifest;
1278 rc = RTManifestCreate(0 /*fFlags*/, &hOurManifest);
1279 if (RT_SUCCESS(rc))
1280 {
1281 /*
1282 * Process the tarball (would be nice to move this to a function).
1283 */
1284 RTVFSFILE hXmlFile = NIL_RTVFSFILE;
1285 RTVFSFILE hManifestFile = NIL_RTVFSFILE;
1286 RTVFSFILE hSignatureFile = NIL_RTVFSFILE;
1287 for (;;)
1288 {
1289 /*
1290 * Get the next stream object.
1291 */
1292 char *pszName;
1293 RTVFSOBJ hVfsObj;
1294 RTVFSOBJTYPE enmType;
1295 rc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj);
1296 if (RT_FAILURE(rc))
1297 {
1298 if (rc != VERR_EOF)
1299 vboxExtPackSetError(pszError, cbError, "RTVfsFsStrmNext failed: %Rrc", rc);
1300 else
1301 rc = VINF_SUCCESS;
1302 break;
1303 }
1304 const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName;
1305
1306 /*
1307 * Check the type & name validity, performing special tests on
1308 * standard extension pack member files.
1309 *
1310 * N.B. We will always reach the end of the loop before breaking on
1311 * failure - cleanup reasons.
1312 */
1313 rc = VBoxExtPackValidateMember(pszName, enmType, hVfsObj, pszError, cbError);
1314 if (RT_SUCCESS(rc))
1315 {
1316 PRTVFSFILE phVfsFile = NULL;
1317 if (!strcmp(pszAdjName, VBOX_EXTPACK_DESCRIPTION_NAME))
1318 phVfsFile = &hXmlFile;
1319 else if (!strcmp(pszAdjName, VBOX_EXTPACK_MANIFEST_NAME))
1320 phVfsFile = &hManifestFile;
1321 else if (!strcmp(pszAdjName, VBOX_EXTPACK_SIGNATURE_NAME))
1322 phVfsFile = &hSignatureFile;
1323 else if (!strncmp(pszAdjName, VBOX_EXTPACK_LICENSE_NAME_PREFIX, sizeof(VBOX_EXTPACK_LICENSE_NAME_PREFIX) - 1))
1324 rc = VBoxExtPackValidateStandardFile(pszAdjName, enmType, &hVfsObj, NULL, pszError, cbError);
1325 if (phVfsFile)
1326 rc = VBoxExtPackValidateStandardFile(pszAdjName, enmType, &hVfsObj, phVfsFile, pszError, cbError);
1327 }
1328
1329 /*
1330 * Add any I/O stream to the manifest
1331 */
1332 if ( RT_SUCCESS(rc)
1333 && ( enmType == RTVFSOBJTYPE_FILE
1334 || enmType == RTVFSOBJTYPE_IO_STREAM))
1335 {
1336 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
1337 rc = RTManifestEntryAddIoStream(hOurManifest, hVfsIos, pszAdjName, RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_SHA256);
1338 if (RT_FAILURE(rc))
1339 vboxExtPackSetError(pszError, cbError, "RTManifestEntryAddIoStream failed on '%s': %Rrc", pszAdjName, rc);
1340 RTVfsIoStrmRelease(hVfsIos);
1341 }
1342
1343 /*
1344 * Clean up and break out on failure.
1345 */
1346 RTVfsObjRelease(hVfsObj);
1347 RTStrFree(pszName);
1348 if (RT_FAILURE(rc))
1349 break;
1350 }
1351
1352 /*
1353 * Check the integrity of the tarball file.
1354 */
1355 if (RT_SUCCESS(rc))
1356 {
1357 RTVfsFsStrmRelease(hTarFss);
1358 hTarFss = NIL_RTVFSFSSTREAM;
1359 rc = vboxExtPackVerifyFileDigest(hFileManifest, pszTarballDigest, pStrDigest, pszError, cbError);
1360 }
1361
1362 /*
1363 * If we've successfully processed the tarball, verify that the
1364 * mandatory files are present.
1365 */
1366 if (RT_SUCCESS(rc))
1367 {
1368 if (hXmlFile == NIL_RTVFSFILE)
1369 rc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, "Mandator file '%s' is missing",
1370 VBOX_EXTPACK_DESCRIPTION_NAME);
1371 if (hManifestFile == NIL_RTVFSFILE)
1372 rc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, "Mandator file '%s' is missing",
1373 VBOX_EXTPACK_MANIFEST_NAME);
1374 if (hSignatureFile == NIL_RTVFSFILE)
1375 rc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, "Mandator file '%s' is missing",
1376 VBOX_EXTPACK_SIGNATURE_NAME);
1377 }
1378
1379 /*
1380 * Check the manifest and it's signature.
1381 */
1382 if (RT_SUCCESS(rc))
1383 rc = vboxExtPackVerifyManifestAndSignature(hOurManifest, hManifestFile, hSignatureFile, pszError, cbError);
1384
1385 /*
1386 * Check the XML.
1387 */
1388 if (RT_SUCCESS(rc))
1389 rc = vboxExtPackVerifyXml(hXmlFile, pszExtPackName, pszError, cbError);
1390
1391 /*
1392 * Returns objects.
1393 */
1394 if (RT_SUCCESS(rc))
1395 {
1396 if (phValidManifest)
1397 {
1398 RTManifestRetain(hOurManifest);
1399 *phValidManifest = hOurManifest;
1400 }
1401 if (phXmlFile)
1402 {
1403 RTVfsFileRetain(hXmlFile);
1404 *phXmlFile = hXmlFile;
1405 }
1406 }
1407
1408 /*
1409 * Release our object references.
1410 */
1411 RTManifestRelease(hOurManifest);
1412 RTVfsFileRelease(hXmlFile);
1413 RTVfsFileRelease(hManifestFile);
1414 RTVfsFileRelease(hSignatureFile);
1415 }
1416 else
1417 vboxExtPackSetError(pszError, cbError, "RTManifestCreate failed: %Rrc", rc);
1418 RTVfsFsStrmRelease(hTarFss);
1419 RTManifestRelease(hFileManifest);
1420
1421 return rc;
1422}
1423
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette