VirtualBox

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

Last change on this file since 78582 was 76553, checked in by vboxsync, 6 years ago

scm --update-copyright-year

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