VirtualBox

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

Last change on this file since 73958 was 73933, checked in by vboxsync, 7 years ago

IPRT/rest: Adding genric factor methods for every class and use it to generalize response header field parsing. bugref:9167

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.7 KB
Line 
1/* $Id: RTCRestClientResponseBase.cpp 73933 2018-08-28 20:57:23Z vboxsync $ */
2/** @file
3 * IPRT - C++ REST, RTCRestClientResponseBase implementation.
4 */
5
6/*
7 * Copyright (C) 2018 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#include <iprt/cpp/restbase.h>
32
33#include <iprt/ctype.h>
34#include <iprt/err.h>
35
36
37/**
38 * Default constructor.
39 */
40RTCRestClientResponseBase::RTCRestClientResponseBase()
41 : m_rcStatus(VERR_WRONG_ORDER)
42 , m_rcHttp(VERR_NOT_AVAILABLE)
43 , m_pErrInfo(NULL)
44{
45}
46
47
48/**
49 * Destructor.
50 */
51RTCRestClientResponseBase::~RTCRestClientResponseBase()
52{
53 deleteErrInfo();
54}
55
56
57/**
58 * Copy constructor.
59 */
60RTCRestClientResponseBase::RTCRestClientResponseBase(RTCRestClientResponseBase const &a_rThat)
61 : m_rcStatus(a_rThat.m_rcStatus)
62 , m_rcHttp(a_rThat.m_rcHttp)
63 , m_pErrInfo(NULL)
64 , m_strContentType(a_rThat.m_strContentType)
65{
66 if (m_pErrInfo)
67 copyErrInfo(m_pErrInfo);
68}
69
70
71/**
72 * Copy assignment operator.
73 */
74RTCRestClientResponseBase &RTCRestClientResponseBase::operator=(RTCRestClientResponseBase const &a_rThat)
75{
76 m_rcStatus = a_rThat.m_rcStatus;
77 m_rcHttp = a_rThat.m_rcHttp;
78 m_strContentType = a_rThat.m_strContentType;
79 if (a_rThat.m_pErrInfo)
80 copyErrInfo(a_rThat.m_pErrInfo);
81 else if (m_pErrInfo)
82 deleteErrInfo();
83
84 return *this;
85}
86
87
88int RTCRestClientResponseBase::receivePrepare(RTHTTP a_hHttp, void ***a_pppvHdr, void ***a_pppvBody)
89{
90 RT_NOREF(a_hHttp, a_pppvHdr, a_pppvBody);
91 return VINF_SUCCESS;
92}
93
94
95void RTCRestClientResponseBase::receiveComplete(int a_rcStatus, RTHTTP a_hHttp)
96{
97 RT_NOREF_PV(a_hHttp);
98 m_rcStatus = a_rcStatus;
99 if (a_rcStatus >= 0)
100 m_rcHttp = a_rcStatus;
101}
102
103
104void RTCRestClientResponseBase::consumeHeaders(const char *a_pchData, size_t a_cbData)
105{
106 /*
107 * Get the the content type.
108 */
109 int rc = extractHeaderFromBlob(RT_STR_TUPLE("Content-Type"), a_pchData, a_cbData, &m_strContentType);
110 if (rc == VERR_NOT_FOUND)
111 rc = VINF_SUCCESS;
112 AssertRCReturnVoidStmt(rc, m_rcStatus = rc);
113}
114
115
116void RTCRestClientResponseBase::consumeBody(const char *a_pchData, size_t a_cbData)
117{
118 RT_NOREF(a_pchData, a_cbData);
119}
120
121
122void RTCRestClientResponseBase::receiveFinal()
123{
124}
125
126
127PRTERRINFO RTCRestClientResponseBase::getErrInfo(void)
128{
129 if (m_pErrInfo)
130 return m_pErrInfo;
131 size_t cbMsg = _4K;
132 m_pErrInfo = (PRTERRINFO)RTMemAllocZ(sizeof(*m_pErrInfo) + cbMsg);
133 if (m_pErrInfo)
134 return RTErrInfoInit(m_pErrInfo, (char *)(m_pErrInfo + 1), cbMsg);
135 return NULL;
136}
137
138
139void RTCRestClientResponseBase::deleteErrInfo(void)
140{
141 if (m_pErrInfo)
142 {
143 RTMemFree(m_pErrInfo);
144 m_pErrInfo = NULL;
145 }
146}
147
148
149void RTCRestClientResponseBase::copyErrInfo(PCRTERRINFO pErrInfo)
150{
151 deleteErrInfo();
152 m_pErrInfo = (PRTERRINFO)RTMemDup(pErrInfo, pErrInfo->cbMsg + sizeof(*pErrInfo));
153 if (m_pErrInfo)
154 {
155 m_pErrInfo->pszMsg = (char *)(m_pErrInfo + 1);
156 m_pErrInfo->apvReserved[0] = NULL;
157 m_pErrInfo->apvReserved[1] = NULL;
158 }
159}
160
161
162int RTCRestClientResponseBase::addError(int rc, const char *pszFormat, ...)
163{
164 PRTERRINFO pErrInfo = getErrInfo();
165 if (pErrInfo)
166 {
167 va_list va;
168 va_start(va, pszFormat);
169 if ( !RTErrInfoIsSet(pErrInfo)
170 || pErrInfo->cbMsg == 0
171 || pErrInfo->pszMsg[pErrInfo->cbMsg - 1] == '\n')
172 RTErrInfoAddV(pErrInfo, rc, pszFormat, va);
173 else
174 RTErrInfoAddF(pErrInfo, rc, "\n%N", pszFormat, &va);
175 va_end(va);
176 }
177 if (RT_SUCCESS(m_rcStatus) && RT_FAILURE_NP(rc))
178 m_rcStatus = rc;
179 return rc;
180}
181
182
183void RTCRestClientResponseBase::extracHeaderFieldsFromBlob(HEADERFIELDDESC const *a_paFieldDescs,
184 RTCRestObjectBase ***a_pappFieldValues,
185 size_t a_cFields, const char *a_pchData, size_t a_cbData)
186
187{
188 RTCString strValue; /* (Keep it out here to encourage buffer allocation reuse and default construction call.) */
189
190 /*
191 * Work our way through the header blob.
192 */
193 while (a_cbData >= 2)
194 {
195 /*
196 * Determine length of the header name:value combo.
197 * Note! Multi-line field values are not currently supported.
198 */
199 const char *pchEol = (const char *)memchr(a_pchData, '\n', a_cbData);
200 while (pchEol && (pchEol == a_pchData || pchEol[-1] != '\r'))
201 pchEol = (const char *)memchr(pchEol, '\n', a_cbData - (pchEol - a_pchData));
202
203 size_t const cchField = pchEol ? pchEol - a_pchData + 1 : a_cbData;
204 size_t const cchFieldNoCrLf = pchEol ? pchEol - a_pchData - 1 : a_cbData;
205
206 const char *pchColon = (const char *)memchr(a_pchData, ':', cchFieldNoCrLf);
207 Assert(pchColon);
208 if (pchColon)
209 {
210 size_t const cchName = pchColon - a_pchData;
211 size_t const offValue = cchName + (RT_C_IS_BLANK(pchColon[1]) ? 2 : 1);
212 size_t const cchValue = cchFieldNoCrLf - offValue;
213
214 /*
215 * Match headers.
216 */
217 bool fHaveValue = false;
218 for (size_t i = 0; i < a_cFields; i++)
219 {
220 size_t const cchThisName = a_paFieldDescs[i].cchName;
221 if ( !(a_paFieldDescs[i].fFlags & kHdrField_MapCollection)
222 ? cchThisName == cchName
223 && RTStrNICmpAscii(a_pchData, a_paFieldDescs[i].pszName, cchThisName) == 0
224 : cchThisName <= cchName
225 && RTStrNICmpAscii(a_pchData, a_paFieldDescs[i].pszName, cchThisName - 1) == 0)
226 {
227 /* Get and clean the value. */
228 int rc = VINF_SUCCESS;
229 if (!fHaveValue)
230 {
231 rc = strValue.assignNoThrow(&a_pchData[offValue], cchValue);
232 if (RT_SUCCESS(rc))
233 {
234 RTStrPurgeEncoding(strValue.mutableRaw()); /** @todo this is probably a little wrong... */
235 fHaveValue = true;
236 }
237 else
238 {
239 addError(rc, "Error allocating %u bytes for header field %s", a_paFieldDescs[i].pszName);
240 break;
241 }
242 }
243
244 /*
245 * Create field to deserialize.
246 */
247 RTCRestObjectBase *pObj = NULL;
248 if (!(a_paFieldDescs[i].fFlags & (kHdrField_MapCollection | kHdrField_ArrayCollection)))
249 {
250 /* Only once. */
251 if (!*a_pappFieldValues[i])
252 {
253 pObj = a_paFieldDescs[i].pfnCreateInstance();
254 if (pObj)
255 *a_pappFieldValues[i] = pObj;
256 else
257 {
258 addError(VERR_NO_MEMORY, "out of memory");
259 continue;
260 }
261 }
262 else
263 {
264 addError(VERR_REST_RESPONSE_REPEAT_HEADER_FIELD, "Already saw header field '%s'", a_paFieldDescs[i].pszName);
265 continue;
266 }
267 }
268 else
269 {
270 Assert(a_paFieldDescs[i].pszName[cchThisName - 1] == '*');
271 AssertMsgFailed(("impl field collections"));
272 continue;
273 }
274
275 /*
276 * Deserialize it.
277 */
278 RTERRINFOSTATIC ErrInfo;
279 rc = pObj->fromString(strValue, a_paFieldDescs[i].pszName, RTErrInfoInitStatic(&ErrInfo),
280 a_paFieldDescs[i].fFlags & RTCRestObjectBase::kCollectionFormat_Mask);
281 if (RT_SUCCESS(rc))
282 { /* likely */ }
283 else if (RTErrInfoIsSet(&ErrInfo.Core))
284 addError(rc, "Error %Rrc parsing header field '%s': %s",
285 rc, a_paFieldDescs[i].pszName, ErrInfo.Core.pszMsg);
286 else
287 addError(rc, "Error %Rrc parsing header field '%s'", rc, a_paFieldDescs[i].pszName);
288 }
289 }
290 }
291
292 /*
293 * Advance to the next field.
294 */
295 a_cbData -= cchField;
296 a_pchData += cchField;
297 }
298}
299
300int RTCRestClientResponseBase::extractHeaderFromBlob(const char *a_pszField, size_t a_cchField,
301 const char *a_pchData, size_t a_cbData,
302 RTCString *a_pStrDst)
303{
304 char const chUpper0 = RT_C_TO_UPPER(a_pszField[0]);
305 char const chLower0 = RT_C_TO_LOWER(a_pszField[0]);
306 Assert(!RT_C_IS_SPACE(chUpper0));
307
308 while (a_cbData > a_cchField)
309 {
310 /* Determine length of the header name:value combo.
311 Note! Multi-line field values are not currently supported. */
312 const char *pchEol = (const char *)memchr(a_pchData, '\n', a_cbData);
313 while (pchEol && (pchEol == a_pchData || pchEol[-1] != '\r'))
314 pchEol = (const char *)memchr(pchEol, '\n', a_cbData - (pchEol - a_pchData));
315
316 size_t cchField = pchEol ? pchEol - a_pchData + 1 : a_cbData;
317
318 /* Try match */
319 if ( a_pchData[a_cchField] == ':'
320 && ( a_pchData[0] == chUpper0
321 || a_pchData[0] == chLower0)
322 && RTStrNICmpAscii(a_pchData, a_pszField, a_cchField) == 0)
323 {
324 /* Drop CRLF. */
325 if (pchEol)
326 cchField -= 2;
327
328 /* Drop the field name and optional whitespace. */
329 cchField -= a_cchField + 1;
330 a_pchData += a_cchField + 1;
331 if (cchField > 0 && RT_C_IS_BLANK(*a_pchData))
332 {
333 a_pchData++;
334 cchField--;
335 }
336
337 /* Return the value. */
338 int rc = a_pStrDst->assignNoThrow(a_pchData, cchField);
339 if (RT_SUCCESS(rc))
340 RTStrPurgeEncoding(a_pStrDst->mutableRaw()); /** @todo this is probably a little wrong... */
341 return rc;
342 }
343
344 /* Advance to the next field. */
345 a_pchData += cchField;
346 a_cbData -= cchField;
347 }
348
349 return VERR_NOT_FOUND;
350}
351
352
353
354RTCRestClientResponseBase::PrimaryJsonCursorForBody::PrimaryJsonCursorForBody(RTJSONVAL hValue, const char *pszName,
355 RTCRestClientResponseBase *a_pThat)
356 : RTCRestJsonPrimaryCursor(hValue, pszName, a_pThat->getErrInfo())
357 , m_pThat(a_pThat)
358{
359}
360
361
362int RTCRestClientResponseBase::PrimaryJsonCursorForBody::addError(RTCRestJsonCursor const &a_rCursor, int a_rc,
363 const char *a_pszFormat, ...)
364{
365 va_list va;
366 va_start(va, a_pszFormat);
367 char szPath[256];
368 m_pThat->addError(a_rc, "response body/%s: %N", getPath(a_rCursor, szPath, sizeof(szPath)), a_pszFormat, &va);
369 va_end(va);
370 return a_rc;
371}
372
373
374int RTCRestClientResponseBase::PrimaryJsonCursorForBody::unknownField(RTCRestJsonCursor const &a_rCursor)
375{
376 char szPath[256];
377 m_pThat->addError(VWRN_NOT_FOUND, "response body/%s: unknown field (type %s)",
378 getPath(a_rCursor, szPath, sizeof(szPath)), RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
379 return VWRN_NOT_FOUND;
380}
381
382
383void RTCRestClientResponseBase::deserializeBody(RTCRestObjectBase *a_pDst, const char *a_pchData, size_t a_cbData)
384{
385 if (m_strContentType.startsWith("application/json;"))
386 {
387 int rc = RTStrValidateEncodingEx(a_pchData, a_cbData, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
388 if (RT_SUCCESS(rc))
389 {
390 RTERRINFOSTATIC ErrInfo;
391 RTJSONVAL hValue;
392 rc = RTJsonParseFromBuf(&hValue, (const uint8_t *)a_pchData, a_cbData, RTErrInfoInitStatic(&ErrInfo));
393 if (RT_SUCCESS(rc))
394 {
395 PrimaryJsonCursorForBody PrimaryCursor(hValue, a_pDst->getType(), this); /* note: consumes hValue */
396 a_pDst->deserializeFromJson(PrimaryCursor.m_Cursor);
397 }
398 else if (RTErrInfoIsSet(&ErrInfo.Core))
399 addError(rc, "Error %Rrc parsing server response as JSON (type %s): %s",
400 rc, a_pDst->getType(), ErrInfo.Core.pszMsg);
401 else
402 addError(rc, "Error %Rrc parsing server response as JSON (type %s)", rc, a_pDst->getType());
403 }
404 else if (rc == VERR_INVALID_UTF8_ENCODING)
405 addError(VERR_REST_RESPONSE_INVALID_UTF8_ENCODING, "Invalid UTF-8 body encoding (object type %s; Content-Type: %s)",
406 a_pDst->getType(), m_strContentType.c_str());
407 else if (rc == VERR_BUFFER_UNDERFLOW)
408 addError(VERR_REST_RESPONSE_EMBEDDED_ZERO_CHAR, "Embedded zero character in response (object type %s; Content-Type: %s)",
409 a_pDst->getType(), m_strContentType.c_str());
410 else
411 addError(rc, "Unexpected body validation error (object type %s; Content-Type: %s): %Rrc",
412 a_pDst->getType(), m_strContentType.c_str(), rc);
413 }
414 else
415 addError(VERR_REST_RESPONSE_CONTENT_TYPE_NOT_SUPPORTED, "Unsupported content type for '%s': %s",
416 a_pDst->getType(), m_strContentType.c_str());
417}
418
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette