Index: trunk/src/VBox/Main/src-all/TextScript.cpp
===================================================================
--- trunk/src/VBox/Main/src-all/TextScript.cpp	(revision 76167)
+++ trunk/src/VBox/Main/src-all/TextScript.cpp	(revision 76167)
@@ -0,0 +1,358 @@
+/* $Id$ */
+/** @file
+ * Classes for reading/parsing/saving text scripts (unattended installation, ++).
+ */
+
+/*
+ * Copyright (C) 2006-2017 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+*   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, mpSetError->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 = RTVfsFileGetSize(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, mpSetError->tr("'%s' isn't valid UTF-8: %Rrc"), pszFilename, vrc);
+            }
+            else
+                hrc = mpSetError->setErrorVrc(vrc, mpSetError->tr("Error reading '%s': %Rrc"), pszFilename, vrc);
+            mStrScriptFullContent.setNull();
+        }
+        else
+            hrc = mpSetError->setErrorVrc(vrc, mpSetError->tr("Failed to allocate memory (%'RU64 bytes) for '%s'"),
+                                          cbFile, pszFilename);
+    }
+    else if (RT_SUCCESS(vrc))
+        hrc = mpSetError->setErrorVrc(VERR_FILE_TOO_BIG,
+                                      mpSetError->tr("'%s' is too big (max 16MB): %'RU64"), pszFilename, cbFile);
+    else
+        hrc = mpSetError->setErrorVrc(vrc, mpSetError->tr("RTVfsFileGetSize 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, mpSetError->tr("Error writing to '%s' (%Rrc)"), pszFilename, vrc);
+        }
+        else
+            hrc = mpSetError->setErrorVrc(vrc, mpSetError->tr("Error creating/replacing '%s' (%Rrc)"), pszFilename, vrc);
+    }
+    return hrc;
+}
+
+
+
+/*********************************************************************************************************************************
+*   GeneralTextScript Implementation                                                                                             *
+*********************************************************************************************************************************/
+
+HRESULT GeneralTextScript::parse()
+{
+//  AssertReturn(!mfDataParsed, mpSetError->setErrorBoth(E_FAIL, VERR_WRONG_ORDER, "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, "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, "attempting to set line %zu when there are only %zu lines",
+                                          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,
+                                          "attempting search&replace in line %zu when there are only %zu lines",
+                                          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, "appending to line %zu when there are only %zu lines",
+                                          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, "prepending to line %zu when there are only %zu lines",
+                                          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;
+}
+
