VirtualBox

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

Last change on this file since 92154 was 91722, checked in by vboxsync, 3 years ago

Main: bugref:1909: Fixed compilation issues

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

© 2023 Oracle
ContactPrivacy policyTerms of Use