VirtualBox

source: vbox/trunk/src/VBox/Main/ExtPackUtil.cpp@ 34789

Last change on this file since 34789 was 34787, checked in by vboxsync, 14 years ago

Main,VBoxManage,FE/Qt: Implemented IExtPackFile and dropped IExtPackManager::Install.

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