summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-all/TextScript.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/src-all/TextScript.cpp')
-rw-r--r--src/VBox/Main/src-all/TextScript.cpp388
1 files changed, 388 insertions, 0 deletions
diff --git a/src/VBox/Main/src-all/TextScript.cpp b/src/VBox/Main/src-all/TextScript.cpp
new file mode 100644
index 00000000..a4b70ca8
--- /dev/null
+++ b/src/VBox/Main/src-all/TextScript.cpp
@@ -0,0 +1,388 @@
+/* $Id: TextScript.cpp $ */
+/** @file
+ * Classes for reading/parsing/saving text scripts (unattended installation, ++).
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED
+#include "LoggingNew.h"
+#include "TextScript.h"
+
+#include <VBox/err.h>
+
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/vfs.h>
+#include <iprt/path.h>
+
+using namespace std;
+
+
+/*********************************************************************************************************************************
+* BaseTextScript Implementation *
+*********************************************************************************************************************************/
+
+HRESULT BaseTextScript::read(const Utf8Str &rStrFilename)
+{
+ /*
+ * Open the file for reading and figure it's size. Capping the size
+ * at 16MB so we don't exaust the heap on bad input.
+ */
+ HRESULT hrc;
+ RTVFSFILE hVfsFile;
+ int vrc = RTVfsFileOpenNormal(rStrFilename.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = readFromHandle(hVfsFile, rStrFilename.c_str());
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ hrc = mpSetError->setErrorVrc(vrc, tr("Failed to open '%s' (%Rrc)"), rStrFilename.c_str(), vrc);
+ return hrc;
+}
+
+HRESULT BaseTextScript::readFromHandle(RTVFSFILE hVfsFile, const char *pszFilename)
+{
+ /*
+ * Open the file for reading and figure it's size. Capping the size
+ * at 16MB so we don't exaust the heap on bad input.
+ */
+ HRESULT hrc;
+ uint64_t cbFile = 0;
+ int vrc = RTVfsFileQuerySize(hVfsFile, &cbFile);
+ if ( RT_SUCCESS(vrc)
+ && cbFile < _16M)
+ {
+ /*
+ * Exploint the jolt() feature of RTCString and read the content directly into
+ * its storage buffer.
+ */
+ vrc = mStrScriptFullContent.reserveNoThrow((size_t)cbFile + 1);
+ if (RT_SUCCESS(vrc))
+ {
+ char *pszDst = mStrScriptFullContent.mutableRaw();
+ vrc = RTVfsFileReadAt(hVfsFile, 0 /*off*/, pszDst, (size_t)cbFile, NULL);
+ pszDst[(size_t)cbFile] = '\0';
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * We must validate the encoding or we'll be subject to potential security trouble.
+ * If this turns out to be problematic, we will need to implement codeset
+ * conversion coping mechanisms.
+ */
+ vrc = RTStrValidateEncodingEx(pszDst, (size_t)cbFile + 1,
+ RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED | RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_SUCCESS(vrc))
+ {
+ mStrScriptFullContent.jolt();
+ return S_OK;
+ }
+
+ hrc = mpSetError->setErrorVrc(vrc, tr("'%s' isn't valid UTF-8: %Rrc"), pszFilename, vrc);
+ }
+ else
+ hrc = mpSetError->setErrorVrc(vrc, tr("Error reading '%s': %Rrc"), pszFilename, vrc);
+ mStrScriptFullContent.setNull();
+ }
+ else
+ hrc = mpSetError->setErrorVrc(vrc, tr("Failed to allocate memory (%'RU64 bytes) for '%s'", "", cbFile),
+ cbFile, pszFilename);
+ }
+ else if (RT_SUCCESS(vrc))
+ hrc = mpSetError->setErrorVrc(VERR_FILE_TOO_BIG, tr("'%s' is too big (max 16MB): %'RU64"), pszFilename, cbFile);
+ else
+ hrc = mpSetError->setErrorVrc(vrc, tr("RTVfsFileQuerySize failed (%Rrc)"), vrc);
+ return hrc;
+}
+
+HRESULT BaseTextScript::save(const Utf8Str &rStrFilename, bool fOverwrite)
+{
+ /*
+ * We may have to append the default filename to the
+ */
+ const char *pszFilename = rStrFilename.c_str();
+ Utf8Str strWithDefaultFilename;
+ if ( getDefaultFilename() != NULL
+ && *getDefaultFilename() != '\0'
+ && RTDirExists(rStrFilename.c_str()) )
+ {
+ try
+ {
+ strWithDefaultFilename = rStrFilename;
+ strWithDefaultFilename.append(RTPATH_SLASH);
+ strWithDefaultFilename.append(getDefaultFilename());
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ pszFilename = strWithDefaultFilename.c_str();
+ }
+
+ /*
+ * Save the filename for later use.
+ */
+ try
+ {
+ mStrSavedPath = pszFilename;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Use the saveToString method to produce the content.
+ */
+ Utf8Str strDst;
+ HRESULT hrc = saveToString(strDst);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Write the content.
+ */
+ RTFILE hFile;
+ uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_ALL;
+ if (fOverwrite)
+ fOpen |= RTFILE_O_CREATE_REPLACE;
+ else
+ fOpen |= RTFILE_O_CREATE;
+ int vrc = RTFileOpen(&hFile, pszFilename, fOpen);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTFileWrite(hFile, strDst.c_str(), strDst.length(), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTFileClose(hFile);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRelFlow(("GeneralTextScript::save(): saved %zu bytes to '%s'\n", strDst.length(), pszFilename));
+ return S_OK;
+ }
+ }
+ RTFileClose(hFile);
+ RTFileDelete(pszFilename);
+ hrc = mpSetError->setErrorVrc(vrc, tr("Error writing to '%s' (%Rrc)"), pszFilename, vrc);
+ }
+ else
+ hrc = mpSetError->setErrorVrc(vrc, tr("Error creating/replacing '%s' (%Rrc)"), pszFilename, vrc);
+ }
+ return hrc;
+}
+
+
+
+/*********************************************************************************************************************************
+* GeneralTextScript Implementation *
+*********************************************************************************************************************************/
+
+HRESULT GeneralTextScript::parse()
+{
+ AssertReturn(!mfDataParsed, mpSetError->setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("parse called more than once")));
+
+ /*
+ * Split the raw context into an array of lines.
+ */
+ try
+ {
+ mScriptContentByLines = mStrScriptFullContent.split("\n");
+ }
+ catch (std::bad_alloc &)
+ {
+ mScriptContentByLines.clear();
+ return E_OUTOFMEMORY;
+ }
+
+ mfDataParsed = true;
+ return S_OK;
+}
+
+HRESULT GeneralTextScript::saveToString(Utf8Str &rStrDst)
+{
+ AssertReturn(mfDataParsed, mpSetError->setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("saveToString() called before parse()")));
+
+ /*
+ * Calc the required size first.
+ */
+ size_t const cLines = mScriptContentByLines.size();
+ size_t cbTotal = 1;
+ for (size_t iLine = 0; iLine < cLines; iLine++)
+ cbTotal = mScriptContentByLines[iLine].length() + 1;
+
+ /*
+ * Clear the output and try reserve sufficient space.
+ */
+ rStrDst.setNull();
+
+ int vrc = rStrDst.reserveNoThrow(cbTotal);
+ if (RT_FAILURE(vrc))
+ return E_OUTOFMEMORY;
+
+ /*
+ * Assemble the output.
+ */
+ for (size_t iLine = 0; iLine < cLines; iLine++)
+ {
+ try
+ {
+ rStrDst.append(mScriptContentByLines[iLine]);
+ rStrDst.append('\n');
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ return S_OK;
+}
+
+const RTCString &GeneralTextScript::getContentOfLine(size_t idxLine)
+{
+ if (idxLine < mScriptContentByLines.size())
+ return mScriptContentByLines[idxLine];
+ return Utf8Str::Empty;
+}
+
+
+HRESULT GeneralTextScript::setContentOfLine(size_t idxLine, const Utf8Str &rStrNewLine)
+{
+ AssertReturn(idxLine < mScriptContentByLines.size(),
+ mpSetError->setErrorBoth(E_FAIL, VERR_OUT_OF_RANGE,
+ tr("attempting to set line %zu when there are only %zu lines", "",
+ mScriptContentByLines.size()),
+ idxLine, mScriptContentByLines.size()));
+ try
+ {
+ mScriptContentByLines[idxLine] = rStrNewLine;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+vector<size_t> GeneralTextScript::findTemplate(const Utf8Str &rStrNeedle,
+ RTCString::CaseSensitivity enmCase /*= RTCString::CaseSensitive*/)
+{
+ vector<size_t> vecHitLineNumbers;
+ size_t const cLines = mScriptContentByLines.size();
+ for (size_t iLine = 0; iLine < cLines; iLine++)
+ if (mScriptContentByLines[iLine].contains(rStrNeedle, enmCase))
+ vecHitLineNumbers.push_back(iLine);
+
+ return vecHitLineNumbers;
+}
+
+HRESULT GeneralTextScript::findAndReplace(size_t idxLine, const Utf8Str &rStrNeedle, const Utf8Str &rStrReplacement)
+{
+ AssertReturn(idxLine < mScriptContentByLines.size(),
+ mpSetError->setErrorBoth(E_FAIL, VERR_OUT_OF_RANGE,
+ tr("attempting search&replace in line %zu when there are only %zu lines", "",
+ mScriptContentByLines.size()),
+ idxLine, mScriptContentByLines.size()));
+
+ RTCString &rDstString = mScriptContentByLines[idxLine];
+ size_t const offNeedle = rDstString.find(&rStrNeedle);
+ if (offNeedle != RTCString::npos)
+ {
+ try
+ {
+ RTCString strBefore(rDstString, 0, offNeedle);
+ RTCString strAfter(rDstString, offNeedle + rStrNeedle.length());
+ rDstString = strBefore;
+ strBefore.setNull();
+ rDstString.append(rStrReplacement);
+ rDstString.append(strAfter);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+ return S_OK;
+}
+
+HRESULT GeneralTextScript::appendToLine(size_t idxLine, const Utf8Str &rStrToAppend)
+{
+ AssertReturn(idxLine < mScriptContentByLines.size(),
+ mpSetError->setErrorBoth(E_FAIL, VERR_OUT_OF_RANGE,
+ tr("appending to line %zu when there are only %zu lines", "",
+ mScriptContentByLines.size()),
+ idxLine, mScriptContentByLines.size()));
+
+ try
+ {
+ mScriptContentByLines[idxLine].append(rStrToAppend);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+HRESULT GeneralTextScript::prependToLine(size_t idxLine, const Utf8Str &rStrToPrepend)
+{
+ AssertReturn(idxLine < mScriptContentByLines.size(),
+ mpSetError->setErrorBoth(E_FAIL, VERR_OUT_OF_RANGE,
+ tr("prepending to line %zu when there are only %zu lines", "",
+ mScriptContentByLines.size()),
+ idxLine, mScriptContentByLines.size()));
+
+ RTCString &rDstString = mScriptContentByLines[idxLine];
+ try
+ {
+ RTCString strCopy;
+ rDstString.swap(strCopy);
+ rDstString.reserve(strCopy.length() + rStrToPrepend.length() + 1);
+ rDstString = rStrToPrepend;
+ rDstString.append(strCopy);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+HRESULT GeneralTextScript::appendLine(const Utf8Str &rStrLineToAppend)
+{
+ AssertReturn(mfDataParsed, mpSetError->setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("appendLine() called before parse()")));
+
+ try
+ {
+ mScriptContentByLines.append(rStrLineToAppend);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}