CPPMyth
Library to interoperate with MythTV server
wsresponse.cpp
1 /*
2  * Copyright (C) 2014-2015 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 "debug.h"
25 #include "cppdef.h"
26 #include "compressor.h"
27 
28 #include <cstdlib> // for atol
29 #include <cstdio>
30 #include <cstring>
31 
32 #define HTTP_TOKEN_MAXSIZE 20
33 #define HTTP_HEADER_MAXSIZE 4000
34 #define RESPONSE_BUFFER_SIZE 4000
35 
36 using namespace NSROOT;
37 
38 bool WSResponse::ReadHeaderLine(NetSocket *socket, const char *eol, std::string& line, size_t *len)
39 {
40  char buf[RESPONSE_BUFFER_SIZE];
41  const char *s_eol;
42  int p = 0, p_eol = 0, l_eol;
43  size_t l = 0;
44 
45  if (eol != NULL)
46  s_eol = eol;
47  else
48  s_eol = "\n";
49  l_eol = strlen(s_eol);
50 
51  line.clear();
52  do
53  {
54  if (socket->ReceiveData(&buf[p], 1) > 0)
55  {
56  if (buf[p++] == s_eol[p_eol])
57  {
58  if (++p_eol >= l_eol)
59  {
60  buf[p - l_eol] = '\0';
61  line.append(buf);
62  l += p - l_eol;
63  break;
64  }
65  }
66  else
67  {
68  p_eol = 0;
69  if (p > (RESPONSE_BUFFER_SIZE - 2 - l_eol))
70  {
71  buf[p] = '\0';
72  line.append(buf);
73  l += p;
74  p = 0;
75  }
76  }
77  }
78  else
79  {
80  /* No EOL found until end of data */
81  *len = l;
82  return false;
83  }
84  }
85  while (l < HTTP_HEADER_MAXSIZE);
86 
87  *len = l;
88  return true;
89 }
90 
91 WSResponse::WSResponse(const WSRequest &request)
92 : m_socket(NULL)
93 , m_successful(false)
94 , m_statusCode(0)
95 , m_serverInfo()
96 , m_etag()
97 , m_location()
98 , m_contentType(CT_NONE)
99 , m_contentEncoding(CE_NONE)
100 , m_contentChunked(false)
101 , m_contentLength(0)
102 , m_consumed(0)
103 , m_chunkBuffer(NULL)
104 , m_chunkPtr(NULL)
105 , m_chunkEOR(NULL)
106 , m_chunkEnd(NULL)
107 , m_decoder(NULL)
108 {
109  if (request.IsSecureURI())
110  m_socket = SSLSessionFactory::Instance().NewSocket();
111  else
112  m_socket = new TcpSocket();
113  if (!m_socket)
114  DBG(DBG_ERROR, "%s: create socket failed\n", __FUNCTION__);
115  else if (m_socket->Connect(request.GetServer().c_str(), request.GetPort(), SOCKET_RCVBUF_MINSIZE))
116  {
117  m_socket->SetReadAttempt(6); // 60 sec to hang up
118  if (SendRequest(request) && GetResponse())
119  {
120  if (m_statusCode < 200)
121  DBG(DBG_WARN, "%s: status %d\n", __FUNCTION__, m_statusCode);
122  else if (m_statusCode < 300)
123  m_successful = true;
124  else if (m_statusCode < 400)
125  m_successful = false;
126  else if (m_statusCode < 500)
127  DBG(DBG_ERROR, "%s: bad request (%d)\n", __FUNCTION__, m_statusCode);
128  else
129  DBG(DBG_ERROR, "%s: server error (%d)\n", __FUNCTION__, m_statusCode);
130  }
131  else
132  DBG(DBG_ERROR, "%s: invalid response\n", __FUNCTION__);
133  }
134 }
135 
136 WSResponse::~WSResponse()
137 {
138  SAFE_DELETE(m_decoder);
139  SAFE_DELETE_ARRAY(m_chunkBuffer);
140  SAFE_DELETE(m_socket);
141 }
142 
143 bool WSResponse::SendRequest(const WSRequest &request)
144 {
145  std::string msg;
146 
147  request.MakeMessage(msg);
148  DBG(DBG_PROTO, "%s: %s\n", __FUNCTION__, msg.c_str());
149  if (!m_socket->SendData(msg.c_str(), msg.size()))
150  {
151  DBG(DBG_ERROR, "%s: failed (%d)\n", __FUNCTION__, m_socket->GetErrNo());
152  return false;
153  }
154  return true;
155 }
156 
157 bool WSResponse::GetResponse()
158 {
159  size_t len;
160  std::string strread;
161  char token[HTTP_TOKEN_MAXSIZE + 1];
162  int n = 0, token_len = 0;
163  bool ret = false;
164 
165  token[0] = 0;
166  while (ReadHeaderLine(m_socket, "\r\n", strread, &len))
167  {
168  const char *line = strread.c_str(), *val = NULL;
169  int value_len = 0;
170 
171  DBG(DBG_PROTO, "%s: %s\n", __FUNCTION__, line);
172  /*
173  * The first line of a Response message is the Status-Line, consisting of
174  * the protocol version followed by a numeric status code and its associated
175  * textual phrase, with each element separated by SP characters.
176  */
177  if (++n == 1)
178  {
179  int status;
180  if (len > 5 && 0 == memcmp(line, "HTTP", 4) && 1 == sscanf(line, "%*s %d", &status))
181  {
182  /* We have received a valid feedback */
183  m_statusCode = status;
184  ret = true;
185  }
186  else
187  {
188  /* Not a response header */
189  return false;
190  }
191  }
192 
193  if (len == 0)
194  {
195  /* End of header */
196  break;
197  }
198 
199  /*
200  * Header fields can be extended over multiple lines by preceding each
201  * extra line with at least one SP or HT.
202  */
203  if ((line[0] == ' ' || line[0] == '\t') && token_len)
204  {
205  /* Append value of previous token */
206  val = line;
207  }
208  /*
209  * Each header field consists of a name followed by a colon (":") and the
210  * field value. Field names are case-insensitive. The field value MAY be
211  * preceded by any amount of LWS, though a single SP is preferred.
212  */
213  else if ((val = strchr(line, ':')))
214  {
215  int p;
216  if ((token_len = val - line) > HTTP_TOKEN_MAXSIZE)
217  token_len = HTTP_TOKEN_MAXSIZE;
218  for (p = 0; p < token_len; ++p)
219  token[p] = toupper(line[p]);
220  token[token_len] = 0;
221  value_len = len - (val - line + 1);
222  while (*(++val) == ' ' && value_len > 0) --value_len;
223  m_headers.push_front(std::make_pair(token, ""));
224  }
225  else
226  {
227  /* Unknown syntax! Close previous token */
228  token_len = 0;
229  token[token_len] = 0;
230  }
231 
232  if (token_len)
233  {
234  m_headers.front().second.append(val);
235  switch (token_len)
236  {
237  case 4:
238  if (memcmp(token, "ETAG", token_len) == 0)
239  m_etag.append(val);
240  break;
241  case 6:
242  if (memcmp(token, "SERVER", token_len) == 0)
243  m_serverInfo.append(val);
244  break;
245  case 8:
246  if (memcmp(token, "LOCATION", token_len) == 0)
247  m_location.append(val);
248  break;
249  case 12:
250  if (memcmp(token, "CONTENT-TYPE", token_len) == 0)
251  m_contentType = ContentTypeFromMime(val);
252  break;
253  case 14:
254  if (memcmp(token, "CONTENT-LENGTH", token_len) == 0)
255  m_contentLength = atol(val);
256  break;
257  case 16:
258  if (memcmp(token, "CONTENT-ENCODING", token_len) == 0)
259  {
260  if (value_len > 6 && memcmp(val, "deflate", 7) == 0)
261  m_contentEncoding = CE_DEFLATE;
262  else if (value_len > 3 && memcmp(val, "gzip", 4) == 0)
263  m_contentEncoding = CE_GZIP;
264  else
265  {
266  m_contentEncoding = CE_UNKNOWN;
267  DBG(DBG_ERROR, "%s: unsupported content encoding (%s) %d\n", __FUNCTION__, val, value_len);
268  }
269  }
270  break;
271  case 17:
272  if (memcmp(token, "TRANSFER-ENCODING", token_len) == 0)
273  {
274  if (value_len > 6 && memcmp(val, "chunked", 7) == 0)
275  m_contentChunked = true;
276  }
277  break;
278  default:
279  break;
280  }
281  }
282  }
283 
284  return ret;
285 }
286 
287 size_t WSResponse::ReadChunk(void *buf, size_t buflen)
288 {
289  size_t s = 0;
290  if (m_contentChunked)
291  {
292  // no more pending byte in chunk buffer
293  if (m_chunkPtr >= m_chunkEnd)
294  {
295  // process next chunk
296  SAFE_DELETE_ARRAY(m_chunkBuffer);
297  m_chunkBuffer = m_chunkPtr = m_chunkEOR = m_chunkEnd = NULL;
298  std::string strread;
299  size_t len = 0;
300  while (ReadHeaderLine(m_socket, "\r\n", strread, &len) && len == 0);
301  DBG(DBG_PROTO, "%s: chunked data (%s)\n", __FUNCTION__, strread.c_str());
302  std::string chunkStr("0x0");
303  uint32_t chunkSize;
304  if (!strread.empty() && sscanf(chunkStr.append(strread).c_str(), "%x", &chunkSize) == 1 && chunkSize > 0)
305  {
306  if (!(m_chunkBuffer = new char[chunkSize]))
307  return 0;
308  m_chunkPtr = m_chunkEOR = m_chunkBuffer;
309  m_chunkEnd = m_chunkBuffer + chunkSize;
310  }
311  else
312  return 0; // that's the end of chunks
313  }
314  // fill chunk buffer
315  if (m_chunkPtr >= m_chunkEOR)
316  {
317  // ask for new data to fill in the chunk buffer
318  // fill at last read position and until to the end
319  m_chunkEOR += m_socket->ReceiveData(m_chunkEOR, m_chunkEnd - m_chunkEOR);
320  }
321  if ((s = m_chunkEOR - m_chunkPtr) > buflen)
322  s = buflen;
323  memcpy(buf, m_chunkPtr, s);
324  m_chunkPtr += s;
325  m_consumed += s;
326  }
327  return s;
328 }
329 
330 int WSResponse::SocketStreamReader(void *hdl, void *buf, int sz)
331 {
332  WSResponse *resp = static_cast<WSResponse*>(hdl);
333  if (resp == NULL)
334  return 0;
335  size_t s = 0;
336  // let read on unknown length
337  if (!resp->m_contentLength)
338  s = resp->m_socket->ReceiveData(buf, sz);
339  else if (resp->m_contentLength > resp->m_consumed)
340  {
341  size_t len = resp->m_contentLength - resp->m_consumed;
342  s = resp->m_socket->ReceiveData(buf, len > (size_t)sz ? (size_t)sz : len);
343  }
344  resp->m_consumed += s;
345  return s;
346 }
347 
348 int WSResponse::ChunkStreamReader(void *hdl, void *buf, int sz)
349 {
350  WSResponse *resp = static_cast<WSResponse*>(hdl);
351  return (resp == NULL ? 0 : resp->ReadChunk(buf, sz));
352 }
353 
354 size_t WSResponse::ReadContent(char* buf, size_t buflen)
355 {
356  size_t s = 0;
357  if (!m_contentChunked)
358  {
359  if (m_contentEncoding == CE_NONE)
360  {
361  // let read on unknown length
362  if (!m_contentLength)
363  s = m_socket->ReceiveData(buf, buflen);
364  else if (m_contentLength > m_consumed)
365  {
366  size_t len = m_contentLength - m_consumed;
367  s = m_socket->ReceiveData(buf, len > buflen ? buflen : len);
368  }
369  m_consumed += s;
370  }
371  else if (m_contentEncoding == CE_GZIP || m_contentEncoding == CE_DEFLATE)
372  {
373  if (m_decoder == NULL)
374  m_decoder = new Decompressor(&SocketStreamReader, this);
375  if (m_decoder->HasOutputData())
376  s = m_decoder->ReadOutput(buf, buflen);
377  if (s == 0 && !m_decoder->IsCompleted())
378  {
379  if (m_decoder->HasStreamError())
380  DBG(DBG_ERROR, "%s: decoding failed: stream error\n", __FUNCTION__);
381  else if (m_decoder->HasBufferError())
382  DBG(DBG_ERROR, "%s: decoding failed: buffer error\n", __FUNCTION__);
383  else
384  DBG(DBG_ERROR, "%s: decoding failed\n", __FUNCTION__);
385  }
386  }
387  }
388  else
389  {
390  if (m_contentEncoding == CE_NONE)
391  {
392  s = ReadChunk(buf, buflen);
393  }
394  else if (m_contentEncoding == CE_GZIP || m_contentEncoding == CE_DEFLATE)
395  {
396  if (m_decoder == NULL)
397  m_decoder = new Decompressor(&ChunkStreamReader, this);
398  if (m_decoder->HasOutputData())
399  s = m_decoder->ReadOutput(buf, buflen);
400  if (s == 0 && !m_decoder->IsCompleted())
401  {
402  if (m_decoder->HasStreamError())
403  DBG(DBG_ERROR, "%s: decoding failed: stream error\n", __FUNCTION__);
404  else if (m_decoder->HasBufferError())
405  DBG(DBG_ERROR, "%s: decoding failed: buffer error\n", __FUNCTION__);
406  else
407  DBG(DBG_ERROR, "%s: decoding failed\n", __FUNCTION__);
408  }
409  }
410  }
411  return s;
412 }
413 
414 bool WSResponse::GetHeaderValue(const std::string& header, std::string& value)
415 {
416  for (HeaderList::const_iterator it = m_headers.begin(); it != m_headers.end(); ++it)
417  {
418  if (it->first != header)
419  continue;
420  value.assign(it->second);
421  return true;
422  }
423  return false;
424 }
char * m_chunkPtr
The next position to read data from the chunk.
Definition: wsresponse.h:71
char * m_chunkBuffer
The chunk data buffer.
Definition: wsresponse.h:70
char * m_chunkEOR
The end of received data in the chunk.
Definition: wsresponse.h:72
char * m_chunkEnd
The end of the chunk buffer.
Definition: wsresponse.h:73
virtual size_t ReceiveData(void *buf, size_t n)
Definition: socket.cpp:318