VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/rest/RTCRestClientResponseBase.cpp@ 86681

Last change on this file since 86681 was 86681, checked in by vboxsync, 4 years ago

IPRT/rest: bugref:9167 - Add release logging for REST operations.
Request are logged at level 5, and responses at level 7, so they are
not on by default. The format is a bit ad-hoc, but all the essential
information should be there. It's mostly done in base classes except
for request bodies (it can probably be done in the base class too, but
would require more runtime logic, while here we can only generate them
as needed).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.0 KB
Line 
1/* $Id: RTCRestClientResponseBase.cpp 86681 2020-10-22 23:36:16Z vboxsync $ */
2/** @file
3 * IPRT - C++ REST, RTCRestClientResponseBase implementation.
4 */
5
6/*
7 * Copyright (C) 2018-2020 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 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#define LOG_GROUP RTLOGGROUP_REST
32#include <iprt/cpp/restclient.h>
33
34#include <iprt/ctype.h>
35#include <iprt/err.h>
36#include <iprt/log.h>
37#include <iprt/cpp/reststringmap.h>
38
39
40/**
41 * Default constructor.
42 */
43RTCRestClientResponseBase::RTCRestClientResponseBase() RT_NOEXCEPT
44 : m_rcStatus(VERR_WRONG_ORDER)
45 , m_rcHttp(VERR_NOT_AVAILABLE)
46 , m_pErrInfo(NULL)
47{
48}
49
50
51/**
52 * Destructor.
53 */
54RTCRestClientResponseBase::~RTCRestClientResponseBase()
55{
56 deleteErrInfo();
57}
58
59
60/**
61 * Copy constructor.
62 */
63RTCRestClientResponseBase::RTCRestClientResponseBase(RTCRestClientResponseBase const &a_rThat)
64 : m_rcStatus(a_rThat.m_rcStatus)
65 , m_rcHttp(a_rThat.m_rcHttp)
66 , m_pErrInfo(NULL)
67 , m_strContentType(a_rThat.m_strContentType)
68{
69 if (a_rThat.m_pErrInfo)
70 copyErrInfo(a_rThat.m_pErrInfo);
71}
72
73
74/**
75 * Copy assignment operator.
76 */
77RTCRestClientResponseBase &RTCRestClientResponseBase::operator=(RTCRestClientResponseBase const &a_rThat)
78{
79 m_rcStatus = a_rThat.m_rcStatus;
80 m_rcHttp = a_rThat.m_rcHttp;
81 m_strContentType = a_rThat.m_strContentType;
82 if (a_rThat.m_pErrInfo)
83 copyErrInfo(a_rThat.m_pErrInfo);
84 else if (m_pErrInfo)
85 deleteErrInfo();
86
87 return *this;
88}
89
90
91void RTCRestClientResponseBase::reset() RT_NOEXCEPT
92{
93 /* Return to default constructor state. */
94 m_rcStatus = VERR_WRONG_ORDER;
95 m_rcHttp = VERR_NOT_AVAILABLE;
96 if (m_pErrInfo)
97 deleteErrInfo();
98 m_strContentType.setNull();
99}
100
101
102int RTCRestClientResponseBase::receivePrepare(RTHTTP a_hHttp) RT_NOEXCEPT
103{
104 int rc = RTHttpSetHeaderCallback(a_hHttp, receiveHttpHeaderCallback, this);
105 AssertRCReturn(rc, rc);
106
107 return VINF_SUCCESS;
108}
109
110
111void RTCRestClientResponseBase::receiveComplete(int a_rcStatus, RTHTTP a_hHttp) RT_NOEXCEPT
112{
113 RT_NOREF_PV(a_hHttp);
114 m_rcStatus = a_rcStatus;
115 if (a_rcStatus >= 0)
116 m_rcHttp = a_rcStatus;
117
118 int rc = RTHttpSetHeaderCallback(a_hHttp, NULL, NULL);
119 AssertRC(rc);
120}
121
122
123int RTCRestClientResponseBase::consumeHeader(uint32_t a_uMatchWord, const char *a_pchField, size_t a_cchField,
124 const char *a_pchValue, size_t a_cchValue) RT_NOEXCEPT
125{
126 if ( a_uMatchWord == RTHTTP_MAKE_HDR_MATCH_WORD(sizeof("Content-Type") - 1, 'c', 'o', 'n')
127 && RTStrNICmpAscii(a_pchField, RT_STR_TUPLE("Content-Type")) == 0)
128 {
129 int rc = RTStrValidateEncodingEx(a_pchValue, a_cchValue, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
130 AssertRC(rc);
131 if (RT_SUCCESS(rc))
132 return m_strContentType.assignNoThrow(a_pchValue, a_cchValue);
133 }
134 RT_NOREF(a_cchField);
135 return VINF_SUCCESS;
136}
137
138
139/*static*/ DECLCALLBACK(int)
140RTCRestClientResponseBase::receiveHttpHeaderCallback(RTHTTP hHttp, uint32_t uMatchWord, const char *pchField, size_t cchField,
141 const char *pchValue, size_t cchValue, void *pvUser) RT_NOEXCEPT
142{
143 RTCRestClientResponseBase *pThis = (RTCRestClientResponseBase *)pvUser;
144 RT_NOREF(hHttp);
145 return pThis->consumeHeader(uMatchWord, pchField, cchField, pchValue, cchValue);
146}
147
148
149void RTCRestClientResponseBase::consumeBody(const char *a_pchData, size_t a_cbData) RT_NOEXCEPT
150{
151 RT_NOREF(a_pchData, a_cbData);
152}
153
154
155void RTCRestClientResponseBase::receiveFinal() RT_NOEXCEPT
156{
157}
158
159
160PRTERRINFO RTCRestClientResponseBase::getErrInfoInternal(void) RT_NOEXCEPT
161{
162 if (m_pErrInfo)
163 return m_pErrInfo;
164 size_t cbMsg = _4K;
165 m_pErrInfo = (PRTERRINFO)RTMemAllocZ(sizeof(*m_pErrInfo) + cbMsg);
166 if (m_pErrInfo)
167 return RTErrInfoInit(m_pErrInfo, (char *)(m_pErrInfo + 1), cbMsg);
168 return NULL;
169}
170
171
172void RTCRestClientResponseBase::deleteErrInfo(void) RT_NOEXCEPT
173{
174 if (m_pErrInfo)
175 {
176 RTMemFree(m_pErrInfo);
177 m_pErrInfo = NULL;
178 }
179}
180
181
182void RTCRestClientResponseBase::copyErrInfo(PCRTERRINFO pErrInfo) RT_NOEXCEPT
183{
184 deleteErrInfo();
185 m_pErrInfo = (PRTERRINFO)RTMemDup(pErrInfo, pErrInfo->cbMsg + sizeof(*pErrInfo));
186 if (m_pErrInfo)
187 {
188 m_pErrInfo->pszMsg = (char *)(m_pErrInfo + 1);
189 m_pErrInfo->apvReserved[0] = NULL;
190 m_pErrInfo->apvReserved[1] = NULL;
191 }
192}
193
194
195int RTCRestClientResponseBase::addError(int rc, const char *pszFormat, ...) RT_NOEXCEPT
196{
197 PRTERRINFO pErrInfo = getErrInfoInternal();
198 if (pErrInfo)
199 {
200 va_list va;
201 va_start(va, pszFormat);
202 if ( !RTErrInfoIsSet(pErrInfo)
203 || pErrInfo->cbMsg == 0
204 || pErrInfo->pszMsg[pErrInfo->cbMsg - 1] == '\n')
205 RTErrInfoAddV(pErrInfo, rc, pszFormat, va);
206 else
207 RTErrInfoAddF(pErrInfo, rc, "\n%N", pszFormat, &va);
208 va_end(va);
209 }
210 if (RT_SUCCESS(m_rcStatus) && RT_FAILURE_NP(rc))
211 m_rcStatus = rc;
212 return rc;
213}
214
215
216RTCRestClientResponseBase::PrimaryJsonCursorForBody::PrimaryJsonCursorForBody(RTJSONVAL hValue, const char *pszName,
217 RTCRestClientResponseBase *a_pThat) RT_NOEXCEPT
218 : RTCRestJsonPrimaryCursor(hValue, pszName, a_pThat->getErrInfoInternal())
219 , m_pThat(a_pThat)
220{
221}
222
223
224int RTCRestClientResponseBase::PrimaryJsonCursorForBody::addError(RTCRestJsonCursor const &a_rCursor, int a_rc,
225 const char *a_pszFormat, ...) RT_NOEXCEPT
226{
227 va_list va;
228 va_start(va, a_pszFormat);
229 char szPath[256];
230 m_pThat->addError(a_rc, "response body/%s: %N", getPath(a_rCursor, szPath, sizeof(szPath)), a_pszFormat, &va);
231 va_end(va);
232 return a_rc;
233}
234
235
236int RTCRestClientResponseBase::PrimaryJsonCursorForBody::unknownField(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
237{
238 char szPath[256];
239 m_pThat->addError(VWRN_NOT_FOUND, "response body/%s: unknown field (type %s)",
240 getPath(a_rCursor, szPath, sizeof(szPath)), RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
241 return VWRN_NOT_FOUND;
242}
243
244
245int RTCRestClientResponseBase::deserializeHeader(RTCRestObjectBase *a_pObj, const char *a_pchValue, size_t a_cchValue,
246 uint32_t a_fFlags, const char *a_pszErrorTag) RT_NOEXCEPT
247{
248 /*
249 * Start by checking the encoding and transfering the value to a RTCString object.
250 */
251 int rc = RTStrValidateEncodingEx(a_pchValue, a_cchValue, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
252 if (RT_SUCCESS(rc))
253 {
254 RTCString strValue;
255 rc = strValue.assignNoThrow(a_pchValue, a_cchValue);
256 if (RT_SUCCESS(rc))
257 {
258 LogRel7(("< %s: :%s = %s\n",
259 getOperationName(), a_pszErrorTag, strValue.c_str()));
260
261 /*
262 * Try deserialize it.
263 */
264 RTERRINFOSTATIC ErrInfo;
265 rc = a_pObj->fromString(strValue, a_pszErrorTag, RTErrInfoInitStatic(&ErrInfo), a_fFlags);
266 if (RT_SUCCESS(rc))
267 { /* likely */ }
268 else if (RTErrInfoIsSet(&ErrInfo.Core))
269 addError(rc, "Error %Rrc parsing header field '%s': %s", rc, a_pszErrorTag, ErrInfo.Core.pszMsg);
270 else
271 addError(rc, "Error %Rrc parsing header field '%s'", rc, a_pszErrorTag);
272 }
273 }
274 else
275 {
276 addError(rc, "Error %Rrc validating value encoding of header field '%s': %.*Rhxs",
277 rc, a_pszErrorTag, a_cchValue, a_pchValue);
278 rc = VINF_SUCCESS; /* ignore */
279 }
280 return rc;
281}
282
283
284int RTCRestClientResponseBase::deserializeHeaderIntoMap(RTCRestStringMapBase *a_pMap, const char *a_pchField, size_t a_cchField,
285 const char *a_pchValue, size_t a_cchValue, uint32_t a_fFlags,
286 const char *a_pszErrorTag) RT_NOEXCEPT
287{
288 /*
289 * Start by checking the encoding of both the field and value,
290 * then transfering the value to a RTCString object.
291 */
292 int rc = RTStrValidateEncodingEx(a_pchField, a_cchField, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
293 if (RT_SUCCESS(rc))
294 {
295 rc = RTStrValidateEncodingEx(a_pchValue, a_cchValue, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
296 if (RT_SUCCESS(rc))
297 {
298 RTCString strValue;
299 rc = strValue.assignNoThrow(a_pchValue, a_cchValue);
300 if (RT_SUCCESS(rc))
301 {
302 /*
303 * Create a value object and put it into the map.
304 */
305 RTCRestObjectBase *pValue;
306 rc = a_pMap->putNewValue(&pValue, a_pchField, a_cchField);
307 if (RT_SUCCESS(rc))
308 {
309 LogRel7(("< %s: :%s%.*s = %s\n",
310 getOperationName(), a_pszErrorTag, a_cchField, a_pchField, strValue.c_str()));
311
312 /*
313 * Try deserialize the value.
314 */
315 RTERRINFOSTATIC ErrInfo;
316 rc = pValue->fromString(strValue, a_pszErrorTag, RTErrInfoInitStatic(&ErrInfo), a_fFlags);
317 if (RT_SUCCESS(rc))
318 { /* likely */ }
319 else if (RTErrInfoIsSet(&ErrInfo.Core))
320 addError(rc, "Error %Rrc parsing header field '%s' subfield '%.*s': %s",
321 rc, a_pszErrorTag, a_cchField, a_pchField, ErrInfo.Core.pszMsg);
322 else
323 addError(rc, "Error %Rrc parsing header field '%s' subfield '%.*s'",
324 rc, a_pszErrorTag, a_cchField, a_pchField);
325 }
326 }
327 }
328 else
329 {
330 addError(rc, "Error %Rrc validating value encoding of header field '%s': %.*Rhxs",
331 rc, a_pszErrorTag, a_cchValue, a_pchValue);
332 rc = VINF_SUCCESS; /* ignore */
333 }
334 }
335 else
336 {
337 addError(rc, "Error %Rrc validating sub-field encoding of header field '%s*': %.*Rhxs",
338 rc, a_pszErrorTag, a_cchField, a_pchField);
339 rc = VINF_SUCCESS; /* ignore */
340 }
341 return rc;
342}
343
344
345void RTCRestClientResponseBase::deserializeBody(const char *a_pchData, size_t a_cbData, const char *a_pszBodyName) RT_NOEXCEPT
346{
347 if (m_strContentType.startsWith("application/json"))
348 {
349 int rc = RTStrValidateEncodingEx(a_pchData, a_cbData, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
350 if (RT_SUCCESS(rc))
351 {
352 if (LogRelIs7Enabled())
353 {
354 /* skip m_ or m_p prefix */
355 const char *pszName = a_pszBodyName;
356 if (pszName[0] == 'm' && pszName[1] == '_')
357 {
358 if (pszName[2] == 'p')
359 pszName += 3;
360 else
361 pszName += 2;
362 }
363
364 LogRel7(("< %s: %d: %s = %.*s\n",
365 getOperationName(), m_rcHttp, pszName, a_cbData, a_pchData));
366 }
367
368 RTERRINFOSTATIC ErrInfo;
369 RTJSONVAL hValue;
370 rc = RTJsonParseFromBuf(&hValue, (const uint8_t *)a_pchData, a_cbData, RTErrInfoInitStatic(&ErrInfo));
371 if (RT_SUCCESS(rc))
372 {
373 PrimaryJsonCursorForBody PrimaryCursor(hValue, a_pszBodyName, this); /* note: consumes hValue */
374 deserializeBodyFromJsonCursor(PrimaryCursor.m_Cursor);
375 }
376 else if (RTErrInfoIsSet(&ErrInfo.Core))
377 addError(rc, "Error %Rrc parsing server response as JSON (type %s): %s",
378 rc, a_pszBodyName, ErrInfo.Core.pszMsg);
379 else
380 addError(rc, "Error %Rrc parsing server response as JSON (type %s)", rc, a_pszBodyName);
381 }
382 else if (rc == VERR_INVALID_UTF8_ENCODING)
383 addError(VERR_REST_RESPONSE_INVALID_UTF8_ENCODING, "Invalid UTF-8 body encoding (object type %s; Content-Type: %s)",
384 a_pszBodyName, m_strContentType.c_str());
385 else if (rc == VERR_BUFFER_UNDERFLOW)
386 addError(VERR_REST_RESPONSE_EMBEDDED_ZERO_CHAR, "Embedded zero character in response (object type %s; Content-Type: %s)",
387 a_pszBodyName, m_strContentType.c_str());
388 else
389 addError(rc, "Unexpected body validation error (object type %s; Content-Type: %s): %Rrc",
390 a_pszBodyName, m_strContentType.c_str(), rc);
391 }
392 else
393 addError(VERR_REST_RESPONSE_CONTENT_TYPE_NOT_SUPPORTED, "Unsupported content type for '%s': %s",
394 a_pszBodyName, m_strContentType.c_str());
395}
396
397
398void RTCRestClientResponseBase::deserializeBodyFromJsonCursor(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
399{
400 a_rCursor.m_pPrimary->addError(a_rCursor, VERR_REST_INTERNAL_ERROR_8, "deserializeBodyFromJsonCursor must be overridden!");
401 AssertFailed();
402}
403
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