CPPMyth
Library to interoperate with MythTV server
Loading...
Searching...
No Matches
wsresponse.cpp
1/*
2 * Copyright (C) 2014-2026 Jean-Luc Barriere
3 *
4 * This library is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published
6 * by the Free Software Foundation; either version 3, or (at your option)
7 * any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library; see the file COPYING. If not, write to
16 * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston,
17 * MA 02110-1301 USA
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 */
21
22#include "wsresponse.h"
23#include "securesocket.h"
24#include "compressor.h"
25#include "debug.h"
26
27#include <cstdlib> // for atol
28#include <cstdio>
29#include <cstring>
30
31#define HTTP_TOKEN_MAXLEN 79
32#define HTTP_HEADER_MAXLEN 0x4000 // maximum header length (16k)
33#define RESPONSE_BUFFER_SIZE 0x400 // size of read buffer for headers
34#define RESPONSE_MAX_SIZE 0x20000 // maximum size for the entire response
35#define CHUNK_MAX_SIZE 0x20000
36#define REQUEST_BUFFER_SIZE 0x1000
37
38using namespace NSROOT;
39
40void WSResponse::init(const WSRequest &request, int maxRedirs, bool trustedLocation, bool followAny)
41{
42 p = new _response(request);
43 while (0 < maxRedirs--)
44 {
45 int status = p->GetStatusCode();
46 if (status == 301 || status == 302)
47 {
48 // handle redirection
49 URIParser uri(p->Redirection());
50 bool trusted = (uri.Scheme() && strncmp("https", uri.Scheme(), 5) == 0);
51 unsigned port = uri.Port();
52 if (!port)
53 port = (trusted ? 443 : 80);
54 bool samehost = (!uri.Host() || request.GetServer() == uri.Host());
55 bool sameorigin = (!uri.Host() || (samehost && request.GetPort() == port));
56 if (
57 /* same origin */ sameorigin ||
58 /* same host */ (samehost && (!trustedLocation || trusted)) ||
59 /* follow any */ (followAny && (!trustedLocation || trusted))
60 )
61 {
62 DBG(DBG_DEBUG, "%s: (%d) LOCATION = %s\n", __FUNCTION__, p->GetStatusCode(), p->Redirection().c_str());
63 WSRequest redir(request, uri);
64 if (!sameorigin)
65 {
66 /* clear credentials */
67 redir.ClearHeader(ws_header_to_upperstr(WS_HEADER_Authorization));
68 redir.ClearHeader("COOKIE");
69 }
70 delete p;
71 p = new _response(redir);
72 continue;
73 }
74 }
75 break;
76 }
77}
78
79WSResponse::~WSResponse()
80{
81 if (p)
82 delete p;
83 p = nullptr;
84}
85
86bool WSResponse::ReadHeaderLine(NetSocket *socket, const char *eol, std::string& line, size_t *len)
87{
88 char buf[RESPONSE_BUFFER_SIZE];
89 const char *s_eol;
90 int p = 0, p_eol = 0, l_eol;
91 size_t l = 0;
92
93 if (eol != nullptr)
94 s_eol = eol;
95 else
96 s_eol = "\n";
97 l_eol = strlen(s_eol);
98
99 line.clear();
100 do
101 {
102 if (socket->ReceiveData(&buf[p], 1) > 0)
103 {
104 if (buf[p++] == s_eol[p_eol])
105 {
106 if (++p_eol >= l_eol)
107 {
108 buf[p - l_eol] = '\0';
109 line.append(buf);
110 l += p - l_eol;
111 break;
112 }
113 }
114 else
115 {
116 p_eol = 0;
117 if (p > (RESPONSE_BUFFER_SIZE - 2 - l_eol))
118 {
119 buf[p] = '\0';
120 line.append(buf);
121 l += p;
122 p = 0;
123 }
124 }
125 }
126 else
127 {
128 /* No EOL found until end of data */
129 *len = l;
130 return false;
131 }
132 }
133 while (l < HTTP_HEADER_MAXLEN);
134
135 *len = l;
136 return true;
137}
138
139WSResponse::_response::_response(const WSRequest &request)
140: m_socket(nullptr)
141, m_successful(false)
142, m_statusCode(0)
143, m_serverInfo()
144, m_etag()
145, m_location()
146, m_contentEncoding(WS_CENCODING_None)
147, m_hasContent(false)
148, m_contentEmpty(false)
149, m_contentChunked(false)
150, m_chunkNext(false)
151, m_contentLength(0)
152, m_consumed(0)
153, m_chunkBuffer(nullptr)
154, m_chunkPtr(nullptr)
155, m_chunkEOR(nullptr)
156, m_chunkEnd(nullptr)
157, m_decoder(nullptr)
158{
159 if (request.IsSecureURI())
160 m_socket = SSLSessionFactory::Instance().NewClientSocket();
161 else
162 m_socket = new TcpSocket();
163 if (!m_socket)
164 DBG(DBG_ERROR, "%s: create socket failed\n", __FUNCTION__);
165 else if (m_socket->Connect(request.GetServer().c_str(), request.GetPort(), SOCKET_RCVBUF_MINSIZE))
166 {
167 m_socket->SetReadAttempt(6); // 60 sec to hang up
168 if (!request.WriteMessage(*this))
169 DBG(DBG_WARN, "%s: broken request\n", __FUNCTION__);
170 if (ReadResponse())
171 {
172 if (m_statusCode < 200)
173 DBG(DBG_WARN, "%s: status %d\n", __FUNCTION__, m_statusCode);
174 else if (m_statusCode < 300)
175 m_successful = true;
176 else if (m_statusCode < 400)
177 m_successful = false;
178 else if (m_statusCode < 500)
179 DBG(DBG_ERROR, "%s: bad request (%d)\n", __FUNCTION__, m_statusCode);
180 else
181 DBG(DBG_ERROR, "%s: server error (%d)\n", __FUNCTION__, m_statusCode);
182 }
183 else
184 DBG(DBG_ERROR, "%s: invalid response\n", __FUNCTION__);
185 }
186}
187
188WSResponse::_response::~_response()
189{
190 if (m_decoder)
191 delete m_decoder;
192 m_decoder = nullptr;
193 if (m_chunkBuffer)
194 delete [] m_chunkBuffer;
196 if (m_socket)
197 delete m_socket;
198 m_socket = nullptr;
199}
200
201bool WSResponse::_response::WriteRequestStream(const char * data, unsigned len)
202{
203 if (!m_chunkBuffer)
204 {
205 if (!(m_chunkBuffer = new char[REQUEST_BUFFER_SIZE]))
206 return false;
207 m_chunkPtr = m_chunkEOR = m_chunkBuffer;
208 m_chunkEnd = m_chunkBuffer + REQUEST_BUFFER_SIZE;
209 }
210
211 for(;;)
212 {
213 size_t s = m_chunkEnd - m_chunkPtr;
214 if (s >= len )
215 {
216 memcpy(m_chunkPtr, data, len);
217 m_chunkPtr += len;
218 return true;
219 }
220 else
221 {
222 memcpy(m_chunkPtr, data, s);
223 data += s;
224 if (!m_socket->SendData(m_chunkBuffer, REQUEST_BUFFER_SIZE))
225 {
226 DBG(DBG_ERROR, "%s: failed (%d)\n", __FUNCTION__, m_socket->GetErrNo());
227 delete [] m_chunkBuffer;
228 m_chunkBuffer = m_chunkPtr = m_chunkEOR = m_chunkEnd = nullptr;
229 return false;
230 }
231 m_chunkPtr = m_chunkBuffer;
232 len -= s;
233 }
234 }
235}
236
237bool WSResponse::_response::FlushRequestStream()
238{
239 if (!m_chunkBuffer)
240 return false;
241 bool ret = true;
242 size_t s = m_chunkPtr - m_chunkBuffer;
243 if (s > 0 && !m_socket->SendData(m_chunkBuffer, s))
244 {
245 DBG(DBG_ERROR, "%s: failed (%d)\n", __FUNCTION__, m_socket->GetErrNo());
246 ret = false;
247 }
248 delete [] m_chunkBuffer;
249 m_chunkBuffer = m_chunkPtr = m_chunkEOR = m_chunkEnd = nullptr;
250 return ret;
251}
252
253bool WSResponse::_response::ReadResponse()
254{
255 size_t len;
256 size_t sum = 0;
257 std::string strread;
258 char token[HTTP_TOKEN_MAXLEN + 1];
259 int n = 0, token_len = 0;
260 bool ret = false;
261
262 token[0] = 0;
263 while (WSResponse::ReadHeaderLine(m_socket, WS_CRLF, strread, &len))
264 {
265 /* The response length shouldn't exceed the limit */
266 sum += len;
267 if (sum > RESPONSE_MAX_SIZE)
268 return false;
269
270 const char *line = strread.c_str(), *val = nullptr;
271 int value_len = 0;
272
273 DBG(DBG_PROTO, "%s: %s\n", __FUNCTION__, line);
274 /*
275 * The first line of a Response message is the Status-Line, consisting of
276 * the protocol version followed by a numeric status code and its associated
277 * textual phrase, with each element separated by SP characters.
278 */
279 if (++n == 1)
280 {
281 int status;
282 if (len > 5 && 0 == memcmp(line, "HTTP", 4) && 1 == sscanf(line, "%*s %d", &status))
283 {
284 /* We have received a valid feedback */
285 m_statusCode = status;
286 ret = true;
287 }
288 else
289 {
290 /* Not a response header */
291 return false;
292 }
293 }
294
295 if (len == 0)
296 {
297 /* End of header */
298 break;
299 }
300
301 /*
302 * Header fields can be extended over multiple lines by preceding each
303 * extra line with at least one SP or HT.
304 */
305 if ((line[0] == ' ' || line[0] == '\t') && token_len)
306 {
307 /* Append value of previous token */
308 val = line;
309 }
310 /*
311 * Each header field consists of a name followed by a colon (":") and the
312 * field value. Field names are case-insensitive. The field value MAY be
313 * preceded by any amount of LWS, though a single SP is preferred.
314 */
315 else if ((val = strchr(line, ':')))
316 {
317 int p;
318 if ((token_len = val - line) > HTTP_TOKEN_MAXLEN)
319 token_len = HTTP_TOKEN_MAXLEN;
320 for (p = 0; p < token_len; ++p)
321 token[p] = toupper(line[p]);
322 token[token_len] = 0;
323 value_len = len - (val - line + 1);
324 while (value_len > 0 && (*(++val) == ' ' || *val == '\t')) --value_len;
325 WSHeader& hv = m_headers[token];
326 hv.SetName(line, token_len);
327 hv.AddValue("");
328 }
329 else
330 {
331 /* Unknown syntax! Close previous token */
332 token_len = 0;
333 token[token_len] = 0;
334 }
335
336 if (token_len && val)
337 {
338 std::string& newval = m_headers[token].Back().append(val);
339 switch (ws_header_from_upperstr(token))
340 {
341 case WS_HEADER_ETag:
342 m_etag.assign(newval);
343 break;
344 case WS_HEADER_Server:
345 m_serverInfo.assign(newval);
346 break;
347 case WS_HEADER_Location:
348 m_location.assign(newval);
349 break;
350 case WS_HEADER_Content_Type:
351 m_hasContent = true;
352 break;
353 case WS_HEADER_Content_Length:
354 {
355 int64_t num = atol(newval.c_str());
356 if (num >= 0)
357 {
358 m_contentLength = (size_t)num;
359 m_contentEmpty = (num == 0);
360 }
361 break;
362 }
363 case WS_HEADER_Content_Encoding:
364 m_contentEncoding = ws_cencoding_from_str(newval.c_str());
365 if (m_contentEncoding == WS_CENCODING_UNKNOWN)
366 DBG(DBG_ERROR, "%s: unsupported content encoding (%s)\n", __FUNCTION__, newval.c_str());
367 break;
368 case WS_HEADER_Transfer_Encoding:
369 if (newval.find("chunked") != std::string::npos)
370 {
371 m_contentChunked = true;
372 m_chunkNext = true;
373 }
374 break;
375 default:
376 break;
377 }
378 }
379 }
380
381 return ret;
382}
383
384int WSResponse::_response::ReadChunk(void *buf, size_t buflen)
385{
386 int s = 0;
387 if (m_contentChunked)
388 {
389 // no more pending byte in chunk buffer
390 if (m_chunkPtr >= m_chunkEnd)
391 {
392 // process next chunk
393 if (m_chunkBuffer)
394 delete [] m_chunkBuffer;
395 m_chunkBuffer = m_chunkPtr = m_chunkEOR = m_chunkEnd = nullptr;
396 std::string strread;
397 size_t len = 0;
398 while (WSResponse::ReadHeaderLine(m_socket, WS_CRLF, strread, &len) && len == 0);
399 DBG(DBG_PROTO, "%s: chunked data (%s)\n", __FUNCTION__, strread.c_str());
400 std::string chunkStr("0x0");
401 uint32_t chunkSize;
402 if (strread.empty() || sscanf(chunkStr.append(strread.substr(0, strread.find(';'))).c_str(), "%x", &chunkSize) != 1)
403 return (-1);
404 if (chunkSize > 0)
405 {
406 // check chunk-size overflow
407 if (chunkSize > CHUNK_MAX_SIZE)
408 {
409 DBG(DBG_ERROR, "%s: chunk-size overflow (req=%u) (max=%u)\n", __FUNCTION__, chunkSize, (unsigned)CHUNK_MAX_SIZE);
410 return (-1);
411 }
412 if (!(m_chunkBuffer = new char[chunkSize]))
413 return (-1);
414 m_chunkPtr = m_chunkEOR = m_chunkBuffer;
415 m_chunkEnd = m_chunkBuffer + chunkSize;
416 }
417 else
418 {
419 // read chunk trailers
420 while (WSResponse::ReadHeaderLine(m_socket, WS_CRLF, strread, &len) && len != 0);
421 return 0; // that's the end of chunks
422 }
423 }
424 // fill chunk buffer
425 if (m_chunkPtr >= m_chunkEOR)
426 {
427 // ask for new data to fill in the chunk buffer
428 // fill at last read position and until to the end
429 m_chunkEOR += m_socket->ReceiveData(m_chunkEOR, m_chunkEnd - m_chunkEOR);
430 }
431 if ((s = m_chunkEOR - m_chunkPtr) < 0)
432 return (-1);
433 if (buflen < (size_t)s)
434 s = (int)buflen;
435 memcpy(buf, m_chunkPtr, s);
436 m_chunkPtr += s;
437 m_consumed += s;
438 }
439 return s;
440}
441
442int WSResponse::_response::SocketStreamReader(void *hdl, void *buf, int sz)
443{
444 _response *resp = static_cast<_response*>(hdl);
445 if (resp == nullptr)
446 return 0;
447 int s = 0;
448 // let read on unknown length
449 if (!resp->m_contentLength)
450 s = (int)resp->m_socket->ReceiveData(buf, sz);
451 else if (resp->m_contentLength > resp->m_consumed)
452 {
453 size_t len = resp->m_contentLength - resp->m_consumed;
454 s = (int)resp->m_socket->ReceiveData(buf, len > (size_t)sz ? (size_t)sz : len);
455 }
456 if (s <= 0)
457 resp->m_consumed = resp->m_contentLength;
458 else
459 resp->m_consumed += s;
460 return s;
461}
462
463int WSResponse::_response::ChunkStreamReader(void *hdl, void *buf, int sz)
464{
465 _response *resp = static_cast<_response*>(hdl);
466 if (resp && resp->m_chunkNext)
467 {
468 int s = resp->ReadChunk(buf, sz);
469 if (s <= 0)
470 resp->m_chunkNext = false;
471 return s;
472 }
473 return 0;
474}
475
476int WSResponse::_response::ReadContent(char* buf, size_t buflen)
477{
478 if (!m_contentChunked)
479 {
480 if (m_contentEncoding == WS_CENCODING_None)
481 {
482 if (m_contentLength > m_consumed)
483 {
484 size_t len = m_contentLength - m_consumed;
485 int s = (int)m_socket->ReceiveData(buf, len > buflen ? buflen : len);
486 if (s <= 0)
487 m_consumed = m_contentLength;
488 else
489 m_consumed += s;
490 return s;
491 }
492 else if (!m_contentEmpty && m_hasContent)
493 {
494 // let read on unknown length
495 int s = (int)m_socket->ReceiveData(buf, buflen);
496 return s;
497 }
498 }
499 else if (m_contentEncoding == WS_CENCODING_Gzip || m_contentEncoding == WS_CENCODING_Deflate)
500 {
501 int s = 0;
502 if (m_decoder == nullptr)
503 m_decoder = new Decompressor(&SocketStreamReader, this, (m_contentEncoding == WS_CENCODING_Gzip));
504 if (m_decoder->HasOutputData())
505 s = (int)m_decoder->ReadOutput(buf, buflen);
506 if (s == 0 && !m_decoder->IsCompleted())
507 {
508 if (m_decoder->HasStreamError())
509 DBG(DBG_ERROR, "%s: decoding failed: stream error\n", __FUNCTION__);
510 else if (m_decoder->HasBufferError())
511 DBG(DBG_ERROR, "%s: decoding failed: buffer error\n", __FUNCTION__);
512 else
513 DBG(DBG_ERROR, "%s: decoding failed\n", __FUNCTION__);
514 return (-1);
515 }
516 return s;
517 }
518 }
519 else
520 {
521 if (m_contentEncoding == WS_CENCODING_None)
522 {
523 if (m_chunkNext)
524 {
525 int s = ReadChunk(buf, buflen);
526 if (s <= 0)
527 m_chunkNext = false;
528 return s;
529 }
530 }
531 else if (m_contentEncoding == WS_CENCODING_Gzip || m_contentEncoding == WS_CENCODING_Deflate)
532 {
533 int s = 0;
534 if (m_decoder == nullptr)
535 m_decoder = new Decompressor(&ChunkStreamReader, this, (m_contentEncoding == WS_CENCODING_Gzip));
536 if (m_decoder->HasOutputData())
537 s = (int)m_decoder->ReadOutput(buf, buflen);
538 if (s == 0 && !m_decoder->IsCompleted())
539 {
540 if (m_decoder->HasStreamError())
541 DBG(DBG_ERROR, "%s: decoding failed: stream error\n", __FUNCTION__);
542 else if (m_decoder->HasBufferError())
543 DBG(DBG_ERROR, "%s: decoding failed: buffer error\n", __FUNCTION__);
544 else
545 DBG(DBG_ERROR, "%s: decoding failed\n", __FUNCTION__);
546 return (-1);
547 }
548 return s;
549 }
550 }
551 return 0;
552}
553
554bool WSResponse::_response::GetHeaderValue(const std::string& header, std::string& value)
555{
556 value.clear();
557 VARS::const_iterator it = m_headers.find(header);
558 if (it == m_headers.end())
559 return false;
560 value.assign(it->second.Last());
561 return true;
562}
563
564const std::string& WSResponse::_response::GetHeaderValue(const std::string& header) const
565{
566 static std::string emptyStr = "";
567 VARS::const_iterator it = m_headers.find(header);
568 if (it != m_headers.end())
569 return it->second.Last();
570 return emptyStr;
571}
char * m_chunkEOR
The end of received data in the chunk.
Definition wsresponse.h:117
char * m_chunkPtr
The next position to read data from the chunk.
Definition wsresponse.h:116
char * m_chunkEnd
The end of the chunk buffer.
Definition wsresponse.h:118
char * m_chunkBuffer
The chunk data buffer.
Definition wsresponse.h:115