summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/common/rest/RTCRestClientResponseBase.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Runtime/common/rest/RTCRestClientResponseBase.cpp')
-rw-r--r--src/VBox/Runtime/common/rest/RTCRestClientResponseBase.cpp413
1 files changed, 413 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/rest/RTCRestClientResponseBase.cpp b/src/VBox/Runtime/common/rest/RTCRestClientResponseBase.cpp
new file mode 100644
index 00000000..90bc84b5
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/RTCRestClientResponseBase.cpp
@@ -0,0 +1,413 @@
+/* $Id: RTCRestClientResponseBase.cpp $ */
+/** @file
+ * IPRT - C++ REST, RTCRestClientResponseBase implementation.
+ */
+
+/*
+ * Copyright (C) 2018-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_REST
+#include <iprt/cpp/restclient.h>
+
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/cpp/reststringmap.h>
+
+
+/**
+ * Default constructor.
+ */
+RTCRestClientResponseBase::RTCRestClientResponseBase() RT_NOEXCEPT
+ : m_rcStatus(VERR_WRONG_ORDER)
+ , m_rcHttp(VERR_NOT_AVAILABLE)
+ , m_pErrInfo(NULL)
+{
+}
+
+
+/**
+ * Destructor.
+ */
+RTCRestClientResponseBase::~RTCRestClientResponseBase()
+{
+ deleteErrInfo();
+}
+
+
+/**
+ * Copy constructor.
+ */
+RTCRestClientResponseBase::RTCRestClientResponseBase(RTCRestClientResponseBase const &a_rThat)
+ : m_rcStatus(a_rThat.m_rcStatus)
+ , m_rcHttp(a_rThat.m_rcHttp)
+ , m_pErrInfo(NULL)
+ , m_strContentType(a_rThat.m_strContentType)
+{
+ if (a_rThat.m_pErrInfo)
+ copyErrInfo(a_rThat.m_pErrInfo);
+}
+
+
+/**
+ * Copy assignment operator.
+ */
+RTCRestClientResponseBase &RTCRestClientResponseBase::operator=(RTCRestClientResponseBase const &a_rThat)
+{
+ m_rcStatus = a_rThat.m_rcStatus;
+ m_rcHttp = a_rThat.m_rcHttp;
+ m_strContentType = a_rThat.m_strContentType;
+ if (a_rThat.m_pErrInfo)
+ copyErrInfo(a_rThat.m_pErrInfo);
+ else if (m_pErrInfo)
+ deleteErrInfo();
+
+ return *this;
+}
+
+
+void RTCRestClientResponseBase::reset() RT_NOEXCEPT
+{
+ /* Return to default constructor state. */
+ m_rcStatus = VERR_WRONG_ORDER;
+ m_rcHttp = VERR_NOT_AVAILABLE;
+ if (m_pErrInfo)
+ deleteErrInfo();
+ m_strContentType.setNull();
+}
+
+
+int RTCRestClientResponseBase::receivePrepare(RTHTTP a_hHttp) RT_NOEXCEPT
+{
+ int rc = RTHttpSetHeaderCallback(a_hHttp, receiveHttpHeaderCallback, this);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+
+void RTCRestClientResponseBase::receiveComplete(int a_rcStatus, RTHTTP a_hHttp) RT_NOEXCEPT
+{
+ RT_NOREF_PV(a_hHttp);
+ m_rcStatus = a_rcStatus;
+ if (a_rcStatus >= 0)
+ m_rcHttp = a_rcStatus;
+
+ int rc = RTHttpSetHeaderCallback(a_hHttp, NULL, NULL);
+ AssertRC(rc);
+}
+
+
+int RTCRestClientResponseBase::consumeHeader(uint32_t a_uMatchWord, const char *a_pchField, size_t a_cchField,
+ const char *a_pchValue, size_t a_cchValue) RT_NOEXCEPT
+{
+ if ( a_uMatchWord == RTHTTP_MAKE_HDR_MATCH_WORD(sizeof("Content-Type") - 1, 'c', 'o', 'n')
+ && RTStrNICmpAscii(a_pchField, RT_STR_TUPLE("Content-Type")) == 0)
+ {
+ int rc = RTStrValidateEncodingEx(a_pchValue, a_cchValue, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ return m_strContentType.assignNoThrow(a_pchValue, a_cchValue);
+ }
+ RT_NOREF(a_cchField);
+ return VINF_SUCCESS;
+}
+
+
+/*static*/ DECLCALLBACK(int)
+RTCRestClientResponseBase::receiveHttpHeaderCallback(RTHTTP hHttp, uint32_t uMatchWord, const char *pchField, size_t cchField,
+ const char *pchValue, size_t cchValue, void *pvUser) RT_NOEXCEPT
+{
+ RTCRestClientResponseBase *pThis = (RTCRestClientResponseBase *)pvUser;
+ RT_NOREF(hHttp);
+ return pThis->consumeHeader(uMatchWord, pchField, cchField, pchValue, cchValue);
+}
+
+
+void RTCRestClientResponseBase::consumeBody(const char *a_pchData, size_t a_cbData) RT_NOEXCEPT
+{
+ RT_NOREF(a_pchData, a_cbData);
+}
+
+
+void RTCRestClientResponseBase::receiveFinal() RT_NOEXCEPT
+{
+}
+
+
+PRTERRINFO RTCRestClientResponseBase::getErrInfoInternal(void) RT_NOEXCEPT
+{
+ if (m_pErrInfo)
+ return m_pErrInfo;
+ size_t cbMsg = _4K;
+ m_pErrInfo = (PRTERRINFO)RTMemAllocZ(sizeof(*m_pErrInfo) + cbMsg);
+ if (m_pErrInfo)
+ return RTErrInfoInit(m_pErrInfo, (char *)(m_pErrInfo + 1), cbMsg);
+ return NULL;
+}
+
+
+void RTCRestClientResponseBase::deleteErrInfo(void) RT_NOEXCEPT
+{
+ if (m_pErrInfo)
+ {
+ RTMemFree(m_pErrInfo);
+ m_pErrInfo = NULL;
+ }
+}
+
+
+void RTCRestClientResponseBase::copyErrInfo(PCRTERRINFO pErrInfo) RT_NOEXCEPT
+{
+ deleteErrInfo();
+ m_pErrInfo = (PRTERRINFO)RTMemDup(pErrInfo, pErrInfo->cbMsg + sizeof(*pErrInfo));
+ if (m_pErrInfo)
+ {
+ m_pErrInfo->pszMsg = (char *)(m_pErrInfo + 1);
+ m_pErrInfo->apvReserved[0] = NULL;
+ m_pErrInfo->apvReserved[1] = NULL;
+ }
+}
+
+
+int RTCRestClientResponseBase::addError(int rc, const char *pszFormat, ...) RT_NOEXCEPT
+{
+ PRTERRINFO pErrInfo = getErrInfoInternal();
+ if (pErrInfo)
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ if ( !RTErrInfoIsSet(pErrInfo)
+ || pErrInfo->cbMsg == 0
+ || pErrInfo->pszMsg[pErrInfo->cbMsg - 1] == '\n')
+ RTErrInfoAddV(pErrInfo, rc, pszFormat, va);
+ else
+ RTErrInfoAddF(pErrInfo, rc, "\n%N", pszFormat, &va);
+ va_end(va);
+ }
+ if (RT_SUCCESS(m_rcStatus) && RT_FAILURE_NP(rc))
+ m_rcStatus = rc;
+ return rc;
+}
+
+
+RTCRestClientResponseBase::PrimaryJsonCursorForBody::PrimaryJsonCursorForBody(RTJSONVAL hValue, const char *pszName,
+ RTCRestClientResponseBase *a_pThat) RT_NOEXCEPT
+ : RTCRestJsonPrimaryCursor(hValue, pszName, a_pThat->getErrInfoInternal())
+ , m_pThat(a_pThat)
+{
+}
+
+
+int RTCRestClientResponseBase::PrimaryJsonCursorForBody::addError(RTCRestJsonCursor const &a_rCursor, int a_rc,
+ const char *a_pszFormat, ...) RT_NOEXCEPT
+{
+ va_list va;
+ va_start(va, a_pszFormat);
+ char szPath[256];
+ m_pThat->addError(a_rc, "response body/%s: %N", getPath(a_rCursor, szPath, sizeof(szPath)), a_pszFormat, &va);
+ va_end(va);
+ return a_rc;
+}
+
+
+int RTCRestClientResponseBase::PrimaryJsonCursorForBody::unknownField(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ char szPath[256];
+ m_pThat->addError(VWRN_NOT_FOUND, "response body/%s: unknown field (type %s)",
+ getPath(a_rCursor, szPath, sizeof(szPath)), RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
+ return VWRN_NOT_FOUND;
+}
+
+
+int RTCRestClientResponseBase::deserializeHeader(RTCRestObjectBase *a_pObj, const char *a_pchValue, size_t a_cchValue,
+ uint32_t a_fFlags, const char *a_pszErrorTag) RT_NOEXCEPT
+{
+ /*
+ * Start by checking the encoding and transfering the value to a RTCString object.
+ */
+ int rc = RTStrValidateEncodingEx(a_pchValue, a_cchValue, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_SUCCESS(rc))
+ {
+ RTCString strValue;
+ rc = strValue.assignNoThrow(a_pchValue, a_cchValue);
+ if (RT_SUCCESS(rc))
+ {
+ LogRel7(("< %s: :%s = %s\n",
+ getOperationName(), a_pszErrorTag, strValue.c_str()));
+
+ /*
+ * Try deserialize it.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ rc = a_pObj->fromString(strValue, a_pszErrorTag, RTErrInfoInitStatic(&ErrInfo), a_fFlags);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (RTErrInfoIsSet(&ErrInfo.Core))
+ addError(rc, "Error %Rrc parsing header field '%s': %s", rc, a_pszErrorTag, ErrInfo.Core.pszMsg);
+ else
+ addError(rc, "Error %Rrc parsing header field '%s'", rc, a_pszErrorTag);
+ }
+ }
+ else
+ {
+ addError(rc, "Error %Rrc validating value encoding of header field '%s': %.*Rhxs",
+ rc, a_pszErrorTag, a_cchValue, a_pchValue);
+ rc = VINF_SUCCESS; /* ignore */
+ }
+ return rc;
+}
+
+
+int RTCRestClientResponseBase::deserializeHeaderIntoMap(RTCRestStringMapBase *a_pMap, const char *a_pchField, size_t a_cchField,
+ const char *a_pchValue, size_t a_cchValue, uint32_t a_fFlags,
+ const char *a_pszErrorTag) RT_NOEXCEPT
+{
+ /*
+ * Start by checking the encoding of both the field and value,
+ * then transfering the value to a RTCString object.
+ */
+ int rc = RTStrValidateEncodingEx(a_pchField, a_cchField, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrValidateEncodingEx(a_pchValue, a_cchValue, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_SUCCESS(rc))
+ {
+ RTCString strValue;
+ rc = strValue.assignNoThrow(a_pchValue, a_cchValue);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a value object and put it into the map.
+ */
+ RTCRestObjectBase *pValue;
+ rc = a_pMap->putNewValue(&pValue, a_pchField, a_cchField);
+ if (RT_SUCCESS(rc))
+ {
+ LogRel7(("< %s: :%s%.*s = %s\n",
+ getOperationName(), a_pszErrorTag, a_cchField, a_pchField, strValue.c_str()));
+
+ /*
+ * Try deserialize the value.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ rc = pValue->fromString(strValue, a_pszErrorTag, RTErrInfoInitStatic(&ErrInfo), a_fFlags);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (RTErrInfoIsSet(&ErrInfo.Core))
+ addError(rc, "Error %Rrc parsing header field '%s' subfield '%.*s': %s",
+ rc, a_pszErrorTag, a_cchField, a_pchField, ErrInfo.Core.pszMsg);
+ else
+ addError(rc, "Error %Rrc parsing header field '%s' subfield '%.*s'",
+ rc, a_pszErrorTag, a_cchField, a_pchField);
+ }
+ }
+ }
+ else
+ {
+ addError(rc, "Error %Rrc validating value encoding of header field '%s': %.*Rhxs",
+ rc, a_pszErrorTag, a_cchValue, a_pchValue);
+ rc = VINF_SUCCESS; /* ignore */
+ }
+ }
+ else
+ {
+ addError(rc, "Error %Rrc validating sub-field encoding of header field '%s*': %.*Rhxs",
+ rc, a_pszErrorTag, a_cchField, a_pchField);
+ rc = VINF_SUCCESS; /* ignore */
+ }
+ return rc;
+}
+
+
+void RTCRestClientResponseBase::deserializeBody(const char *a_pchData, size_t a_cbData, const char *a_pszBodyName) RT_NOEXCEPT
+{
+ if (m_strContentType.startsWith("application/json"))
+ {
+ int rc = RTStrValidateEncodingEx(a_pchData, a_cbData, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_SUCCESS(rc))
+ {
+ if (LogRelIs7Enabled())
+ {
+ /* skip m_ or m_p prefix */
+ const char *pszName = a_pszBodyName;
+ if (pszName[0] == 'm' && pszName[1] == '_')
+ {
+ if (pszName[2] == 'p')
+ pszName += 3;
+ else
+ pszName += 2;
+ }
+
+ LogRel7(("< %s: %d: %s = %.*s\n",
+ getOperationName(), m_rcHttp, pszName, a_cbData, a_pchData));
+ }
+
+ RTERRINFOSTATIC ErrInfo;
+ RTJSONVAL hValue;
+ rc = RTJsonParseFromBuf(&hValue, (const uint8_t *)a_pchData, a_cbData, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ PrimaryJsonCursorForBody PrimaryCursor(hValue, a_pszBodyName, this); /* note: consumes hValue */
+ deserializeBodyFromJsonCursor(PrimaryCursor.m_Cursor);
+ }
+ else if (RTErrInfoIsSet(&ErrInfo.Core))
+ addError(rc, "Error %Rrc parsing server response as JSON (type %s): %s",
+ rc, a_pszBodyName, ErrInfo.Core.pszMsg);
+ else
+ addError(rc, "Error %Rrc parsing server response as JSON (type %s)", rc, a_pszBodyName);
+ }
+ else if (rc == VERR_INVALID_UTF8_ENCODING)
+ addError(VERR_REST_RESPONSE_INVALID_UTF8_ENCODING, "Invalid UTF-8 body encoding (object type %s; Content-Type: %s)",
+ a_pszBodyName, m_strContentType.c_str());
+ else if (rc == VERR_BUFFER_UNDERFLOW)
+ addError(VERR_REST_RESPONSE_EMBEDDED_ZERO_CHAR, "Embedded zero character in response (object type %s; Content-Type: %s)",
+ a_pszBodyName, m_strContentType.c_str());
+ else
+ addError(rc, "Unexpected body validation error (object type %s; Content-Type: %s): %Rrc",
+ a_pszBodyName, m_strContentType.c_str(), rc);
+ }
+ else
+ addError(VERR_REST_RESPONSE_CONTENT_TYPE_NOT_SUPPORTED, "Unsupported content type for '%s': %s",
+ a_pszBodyName, m_strContentType.c_str());
+}
+
+
+void RTCRestClientResponseBase::deserializeBodyFromJsonCursor(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ a_rCursor.m_pPrimary->addError(a_rCursor, VERR_REST_INTERNAL_ERROR_8, "deserializeBodyFromJsonCursor must be overridden!");
+ AssertFailed();
+}
+