Torc  0.1
torchttprequest.cpp
Go to the documentation of this file.
1 /* Class TorcHTTPRequest
2 *
3 * This file is part of the Torc project.
4 *
5 * Copyright (C) Mark Kendall 2012-18
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20 * USA.
21 */
22 
23 // Qt
24 #include <QScopedPointer>
25 #include <QTcpSocket>
26 #include <QTextStream>
27 #include <QStringList>
28 #include <QDateTime>
29 #include <QRegExp>
30 #include <QFile>
31 #include <QUrl>
32 
33 // Torc
34 #include "torccompat.h"
35 #include "torclocaldefs.h"
36 #include "torclogging.h"
37 #include "torccoreutils.h"
38 #include "torcmime.h"
39 #include "torchttpserver.h"
40 #include "torcserialiser.h"
41 #include "torcjsonserialiser.h"
42 #include "torcxmlserialiser.h"
43 #include "torcplistserialiser.h"
45 #include "torchttprequest.h"
46 
47 #if defined(Q_OS_LINUX)
48 #include <sys/sendfile.h>
49 #include <sys/errno.h>
50 #elif defined(Q_OS_MAC)
51 #include <sys/socket.h>
52 #endif
53 
67 QRegExp gRegExp = QRegExp("[ \r\n][ \r\n]*");
68 char TorcHTTPRequest::DateFormat[] = "ddd, dd MMM yyyy HH:mm:ss 'UTC'";
69 
71  : m_fullUrl(),
72  m_path(),
73  m_method(),
74  m_query(),
75  m_redirectedTo(),
76  m_type(HTTPRequest),
77  m_requestType(HTTPUnknownType),
78  m_protocol(HTTPUnknownProtocol),
79  m_connection(HTTPConnectionClose),
80  m_ranges(),
81  m_headers(),
82  m_queries(),
83  m_content(),
84  m_secure(false),
85  m_allowGZip(false),
86  m_allowCORS(false),
87  m_allowed(0),
88  m_authorised(HTTPNotAuthorised),
89  m_responseType(HTTPResponseUnknown),
90  m_cache(HTTPCacheNone),
91  m_cacheTag(QStringLiteral("")),
92  m_responseStatus(HTTP_NotFound),
93  m_responseContent(),
94  m_responseFile(),
95  m_responseHeaders()
96 {
97  if (Reader)
98  {
100  Initialise(Reader->GetMethod());
101  }
102  else
103  {
104  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("NULL Reader"));
105  }
106 }
107 
108 void TorcHTTPRequest::Initialise(const QString &Method)
109 {
110  QStringList items = Method.split(gRegExp, QString::SkipEmptyParts);
111  QString item;
112 
113  if (!items.isEmpty())
114  {
115  item = items.takeFirst();
116 
117  // response of type 'HTTP/1.1 200 OK'
118  if (item.startsWith(QStringLiteral("HTTP")))
119  {
121 
122  // HTTP/1.1
123  m_protocol = ProtocolFromString(item.trimmed());
124 
125  // 200 OK
126  if (!items.isEmpty())
127  m_responseStatus = StatusFromString(items.takeFirst().trimmed());
128  }
129  // request of type 'GET /method HTTP/1.1'
130  else
131  {
133 
134  // GET
135  m_requestType = RequestTypeFromString(item.trimmed());
136 
137  if (!items.isEmpty())
138  {
139  // /method
140  QUrl url = QUrl::fromEncoded(items.takeFirst().toUtf8());
141  m_path = url.path();
142  m_fullUrl = url.toString();
143 
144  int index = m_path.lastIndexOf('/');
145  if (index > -1)
146  {
147  m_method = m_path.mid(index + 1).trimmed();
148  m_path = m_path.left(index + 1).trimmed();
149  }
150 
151  if (url.hasQuery())
152  {
153  QStringList pairs = url.query().split('&');
154  foreach (const QString &pair, pairs)
155  {
156  int index = pair.indexOf('=');
157  QString key = pair.left(index);
158  QString val = pair.mid(index + 1);
159  m_queries.insert(key, val);
160  }
161  }
162  }
163 
164  // HTTP/1.1
165  if (!items.isEmpty())
166  m_protocol = ProtocolFromString(items.takeFirst());
167  }
168  }
169 
172 
173  QString connection = m_headers.value(QStringLiteral("Connection")).toLower();
174 
175  if (connection == QStringLiteral("keep-alive"))
177  else if (connection == QStringLiteral("close"))
179 
180  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("HTTP request: path '%1' method '%2'").arg(m_path, m_method));
181 }
182 
184 {
185  m_connection = Connection;
186 }
187 
189 {
190  m_responseStatus = Status;
191 }
192 
194 {
195  m_responseType = Type;
196 }
197 
198 void TorcHTTPRequest::SetResponseContent(const QByteArray &Content)
199 {
200  m_responseFile = QStringLiteral();
201  m_responseContent = Content;
202 }
203 
204 void TorcHTTPRequest::SetResponseFile(const QString &File)
205 {
206  m_responseFile = File;
207  m_responseContent = QByteArray();
208 }
209 
210 void TorcHTTPRequest::SetResponseHeader(const QString &Header, const QString &Value)
211 {
212  m_responseHeaders.insert(Header, Value);
213 }
214 
216 {
217  m_allowed = Allowed;
218 }
219 
222 {
223  m_allowGZip = Allowed;
224 }
225 
227 {
228  m_allowCORS = Allowed;
229 }
230 
232 {
233  return m_allowCORS;
234 }
235 
245 void TorcHTTPRequest::SetCache(int Cache, const QString &Tag /* = QString("")*/)
246 {
247  m_cache = Cache;
248  m_cacheTag = Tag;
249 }
250 
251 void TorcHTTPRequest::SetSecure(bool Secure)
252 {
253  m_secure = Secure;
254 }
255 
257 {
258  return m_secure;
259 }
260 
262 {
263  return m_responseStatus;
264 }
265 
267 {
268  return m_type;
269 }
270 
272 {
273  return m_requestType;
274 }
275 
277 {
278  return m_protocol;
279 }
280 
281 QString TorcHTTPRequest::GetUrl(void) const
282 {
283  return m_fullUrl;
284 }
285 
286 QString TorcHTTPRequest::GetPath(void) const
287 {
288  return m_path;
289 }
290 
291 QString TorcHTTPRequest::GetMethod(void) const
292 {
293  return m_method;
294 }
295 
296 QString TorcHTTPRequest::GetCache(void) const
297 {
298  return m_cacheTag;
299 }
300 
301 const QMap<QString,QString>& TorcHTTPRequest::Headers(void) const
302 {
303  return m_headers;
304 }
305 
306 const QMap<QString,QString>& TorcHTTPRequest::Queries(void) const
307 {
308  return m_queries;
309 }
310 
311 void TorcHTTPRequest::Respond(QTcpSocket *Socket)
312 {
313  if (!Socket)
314  return;
315 
316  QFile file;
317  if (!m_responseFile.isEmpty())
318  file.setFileName(m_responseFile);
319 
320  // this is not the file you are looking for...
322  {
323  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("'%1' not found").arg(m_fullUrl));
325 
326  QByteArray result;
327  QTextStream stream(&result);
328  stream << "<html><head><title>" << TORC_REALM << "</title></head>";
329  stream << "<body><h1><a href='/'>" << TORC_REALM << "</a></h1>";
330  stream << "<p>File not found";
331  stream << "</body></html>";
332  this->SetResponseContent(result);
334  }
335 
336  QString contenttype = ResponseTypeToString(m_responseType);
337 
338  // set the response type based upon file
339  if (!m_responseFile.isEmpty())
340  {
341  // only use the file contents if the file name returns an ambiguous result
342  QStringList types = TorcMime::MimeTypeForFileName(m_responseFile);
343  if (types.size() == 1)
344  contenttype = types.first();
345  else
348  }
349 
350  QByteArray contentheader = QStringLiteral("Content-Type: %1\r\n").arg(contenttype).toLatin1();
351 
352  // process byte range requests
353  qint64 totalsize = !m_responseContent.isEmpty() ? m_responseContent.size() : !m_responseFile.isEmpty() ? file.size() : 0;
354  qint64 sendsize = totalsize;
355  bool multipart = false;
356  static QByteArray seperator("\r\n--STaRT\r\n");
357  QList<QByteArray> partheaders;
358 
359  if (m_headers.contains(QStringLiteral("Range")) && m_responseStatus == HTTP_OK)
360  {
361  m_ranges = StringToRanges(m_headers.value(QStringLiteral("Range")), totalsize, sendsize);
362 
363  if (m_ranges.isEmpty())
364  {
365  QByteArray empty;
366  SetResponseContent(empty);
368  }
369  else
370  {
372  multipart = m_ranges.size() > 1;
373 
374  if (multipart)
375  {
376  QVector<QPair<quint64,quint64> >::const_iterator it = m_ranges.constBegin();
377  for ( ; it != m_ranges.constEnd(); ++it)
378  {
379  QByteArray header = seperator + contentheader + QStringLiteral("Content-Range: bytes %1\r\n\r\n").arg(RangeToString((*it), totalsize)).toLatin1();
380  partheaders << header;
381  sendsize += header.size();
382  }
383  }
384  }
385  }
386 
387  // format response headers
388  QScopedPointer<QByteArray> headers(new QByteArray);
389  QTextStream response(headers.data());
390 
392  response << "Date: " << QDateTime::currentDateTimeUtc().toString(DateFormat) << "\r\n";
393  response << "Server: " << TorcHTTPServer::PlatformName() << "\r\n";
394  response << "Connection: " << TorcHTTPRequest::ConnectionToString(m_connection) << "\r\n";
395  response << "Accept-Ranges: bytes\r\n";
396 
397  if (m_cache & HTTPCacheNone)
398  {
399  response << "Cache-Control: private, no-cache, no-store, must-revalidate\r\nExpires: 0\r\nPragma: no-cache\r\n";
400  }
401  else
402  {
403  // cache-control (in preference to expires for its simplicity)
405  response << "Cache-Control: public, max-age=3600\r\n"; // 1 hour
406  else if (m_cache & HTTPCacheLongLife)
407  response << "Cache-Control: public, max-age=31536000\r\n"; // 1 year (max per spec)
408 
409  // either last-modified or etag (not both) if requested
410  if (!m_cacheTag.isEmpty())
411  {
412  if (m_cache & HTTPCacheETag)
413  response << QStringLiteral("ETag: \"%1\"\r\n").arg(m_cacheTag);
414  else if (m_cache & HTTPCacheLastModified)
415  response << QStringLiteral("Last-Modified: %1\r\n").arg(m_cacheTag);
416  }
417  }
418 
419  // Use compression if:-
420  // - it was requested by the client.
421  // - zlip support is available locally.
422  // - the responder allows gzip responses.
423  // - there is some content and it is smaller than 1Mb in size (arbitrary limit)
424  // - the response is not a range request with single or multipart response
425  if (m_allowGZip && totalsize > 0 && totalsize < 0x100000 && TorcCoreUtils::HasZlib() && m_responseStatus == HTTP_OK &&
426  m_headers.contains(QStringLiteral("Accept-Encoding")) &&
427  m_headers.value(QStringLiteral("Accept-Encoding")).contains(QStringLiteral("gzip"), Qt::CaseInsensitive))
428  {
429  if (!m_responseContent.isEmpty())
430  {
431  QByteArray newcontent = TorcCoreUtils::GZipCompress(m_responseContent);
432  SetResponseContent(newcontent);
433  }
434  else if (!m_responseFile.isEmpty())
435  {
436  QByteArray newcontent = TorcCoreUtils::GZipCompressFile(file);
437  SetResponseContent(newcontent);
438  }
439  sendsize = m_responseContent.size();
440  response << "Content-Encoding: gzip\r\n";
441  }
442 
443  if (multipart)
444  response << "Content-Type: multipart/byteranges; boundary=STaRT\r\n";
445  else if (m_responseType != HTTPResponseNone)
446  response << contentheader;
447 
448  if (m_allowed)
449  response << "Allow: " << AllowedToString(m_allowed) << "\r\n";
450  response << "Content-Length: " << QString::number(sendsize) << "\r\n";
451 
452  if (m_responseStatus == HTTP_PartialContent && !multipart)
453  response << "Content-Range: bytes " << RangeToString(m_ranges[0], totalsize) << "\r\n";
455  response << "Content-Range: bytes */" << QString::number(totalsize) << "\r\n";
456 
458  response << "Location: " << m_redirectedTo.toLatin1() << "\r\n";
459 
460  // process any custom headers
461  QMap<QString,QString>::const_iterator it = m_responseHeaders.constBegin();
462  for ( ; it != m_responseHeaders.constEnd(); ++it)
463  response << it.key().toLatin1() << ": " << it.value().toLatin1() << "\r\n";
464 
465  response << "\r\n";
466  response.flush();
467 
468  // send headers
469  qint64 headersize = headers.data()->size();
470  qint64 sent = Socket->write(headers.data()->constData(), headersize);
471  if (headersize != sent)
472  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Buffer size %1 - but sent %2").arg(headersize).arg(sent));
473  else
474  LOG(VB_NETWORK, LOG_DEBUG, QStringLiteral("Sent %1 header bytes").arg(sent));
475 
476  LOG(VB_NETWORK, LOG_DEBUG, QString(headers->data()));
477 
478  // send content
479  if (!m_responseContent.isEmpty() && m_requestType != HTTPHead)
480  {
481  if (multipart)
482  {
483  QVector<QPair<quint64,quint64> >::const_iterator it = m_ranges.constBegin();
484  QList<QByteArray>::const_iterator bit = partheaders.constBegin();
485  for ( ; it != m_ranges.constEnd(); ++it, ++bit)
486  {
487  qint64 sent = Socket->write((*bit).data(), (*bit).size());
488  if ((*bit).size() != sent)
489  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Buffer size %1 - but sent %2").arg((*bit).size()).arg(sent));
490  else
491  LOG(VB_NETWORK, LOG_DEBUG, QStringLiteral("Sent %1 multipart header bytes").arg(sent));
492 
493  quint64 start = (*it).first;
494  qint64 chunksize = (*it).second - start + 1;
495  sent = Socket->write(m_responseContent.constData() + start, chunksize);
496  if (chunksize != sent)
497  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Buffer size %1 - but sent %2").arg(chunksize).arg(sent));
498  else
499  LOG(VB_NETWORK, LOG_DEBUG, QStringLiteral("Sent %1 content bytes").arg(sent));
500  }
501  }
502  else
503  {
504  qint64 size = sendsize;
505  qint64 offset = m_ranges.isEmpty() ? 0 : m_ranges.value(0).first;
506  qint64 sent = Socket->write(m_responseContent.constData() + offset, size);
507  if (size != sent)
508  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Buffer size %1 - but sent %2").arg(size).arg(sent));
509  else
510  LOG(VB_NETWORK, LOG_DEBUG, QStringLiteral("Sent %1 content bytes").arg(sent));
511  }
512  }
513  else if (!m_responseFile.isEmpty() && m_requestType != HTTPHead)
514  {
515  if (!file.isOpen())
516  file.open(QIODevice::ReadOnly);
517 
518  if (multipart)
519  {
520  QScopedPointer<QByteArray> buffer(new QByteArray(READ_CHUNK_SIZE, 0));
521 
522  QVector<QPair<quint64,quint64> >::const_iterator it = m_ranges.constBegin();
523  QList<QByteArray>::const_iterator bit = partheaders.constBegin();
524  for ( ; it != m_ranges.constEnd(); ++it, ++bit)
525  {
526  off64_t sent = Socket->write((*bit).data(), (*bit).size());
527  if ((*bit).size() != sent)
528  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Buffer size %1 - but sent %2").arg((*bit).size()).arg(sent));
529  else
530  LOG(VB_NETWORK, LOG_DEBUG, QStringLiteral("Sent %1 multipart header bytes").arg(sent));
531 
532  sent = 0;
533  off64_t offset = (*it).first;
534  off64_t size = (*it).second - offset + 1;
535 
536 #if defined(Q_OS_LINUX)
537  if (size > sent)
538  {
539  // sendfile64 accesses the socket directly, bypassing Qt's cache, so we must flush first
540  Socket->flush();
541 
542  do
543  {
544  off64_t remaining = qMin(size - sent, (off64_t)READ_CHUNK_SIZE);
545  off64_t send = sendfile64(Socket->socketDescriptor(), file.handle(), &offset, remaining);
546 
547  if (send < 0)
548  {
549  if (errno == EAGAIN || errno == EWOULDBLOCK)
550  {
551  QThread::usleep(5000);
552  continue;
553  }
554 
555  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error sending data (%1) %2").arg(errno).arg(strerror(errno)));
556  break;
557  }
558  else
559  {
560  LOG(VB_NETWORK, LOG_DEBUG, QStringLiteral("Sent %1 for %2").arg(send).arg(file.handle()));
561  }
562 
563  sent += send;
564  }
565  while (sent < size);
566  }
567 #elif defined(Q_OS_MAC)
568  if (size > sent)
569  {
570  // sendfile accesses the socket directly, bypassing Qt's cache, so we must flush first
571  Socket->flush();
572 
573  off64_t bytessent = 0;
574  off64_t off = offset;
575 
576  do
577  {
578  bytessent = qMin(size - sent, (qint64)READ_CHUNK_SIZE);
579  if (sendfile(file.handle(), Socket->socketDescriptor(), off, &bytessent, nullptr, 0) < 0)
580  {
581  if (errno != EAGAIN)
582  {
583  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error sending data (%1) %2").arg(errno).arg(strerror(errno)));
584  break;
585  }
586 
587  QThread::usleep(5000);
588  }
589  else
590  {
591  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Sent %1 for %2").arg(bytessent).arg(file.handle()));
592  }
593 
594  sent += bytessent;
595  off += bytessent;
596  }
597  while (sent < size);
598  }
599 #else
600  if (size > sent)
601  {
602  file.seek(offset);
603 
604  do
605  {
606  qint64 remaining = qMin(size - sent, (qint64)READ_CHUNK_SIZE);
607  qint64 read = file.read(buffer.data()->data(), remaining);
608  if (read < 0)
609  {
610  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error reading from '%1' (%2)").arg(file.fileName()).arg(file.errorString()));
611  break;
612  }
613 
614  qint64 send = Socket->write(buffer.data()->data(), read);
615 
616  if (send != read)
617  {
618  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error sending data (%1)").arg(Socket->errorString()));
619  break;
620  }
621 
622  sent += read;
623  }
624  while (sent < size);
625 
626  if (sent < size)
627  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to send all data for '%1'").arg(file.fileName()));
628  }
629 #endif
630  }
631  }
632  else
633  {
634  off64_t sent = 0;
635  off64_t size = sendsize;
636  off64_t offset = m_ranges.isEmpty() ? 0 : m_ranges.value(0).first;
637 
638 #if defined(Q_OS_LINUX)
639  if (size > sent)
640  {
641  // sendfile64 accesses the socket directly, bypassing Qt's cache, so we must flush first
642  Socket->flush();
643 
644  do
645  {
646  off64_t remaining = qMin(size - sent, (off64_t)READ_CHUNK_SIZE);
647  off64_t send = sendfile64(Socket->socketDescriptor(), file.handle(), &offset, remaining);
648 
649  if (send < 0)
650  {
651  if (errno == EAGAIN || errno == EWOULDBLOCK)
652  {
653  QThread::usleep(5000);
654  continue;
655  }
656 
657  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error sending data (%1 - '%2')").arg(errno).arg(strerror(errno)));
658  break;
659  }
660  else
661  {
662  LOG(VB_NETWORK, LOG_DEBUG, QStringLiteral("Sent %1 for %2").arg(send).arg(file.handle()));
663  }
664 
665  sent += send;
666  }
667  while (sent < size);
668  }
669 #elif defined(Q_OS_MAC)
670  if (size > sent)
671  {
672  // sendfile accesses the socket directly, bypassing Qt's cache, so we must flush first
673  Socket->flush();
674 
675  off_t bytessent = 0;
676  off_t off = offset;
677 
678  do
679  {
680  bytessent = qMin(size - sent, (qint64)READ_CHUNK_SIZE);;
681 
682  if (sendfile(file.handle(), Socket->socketDescriptor(), off, &bytessent, nullptr, 0) < 0)
683  {
684  if (errno != EAGAIN)
685  {
686  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error sending data (%1) %2").arg(errno).arg(strerror(errno)));
687  break;
688  }
689 
690  QThread::usleep(5000);
691  }
692  else
693  {
694  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Sent %1 for %2").arg(bytessent).arg(file.handle()));
695  }
696 
697  sent += bytessent;
698  off += bytessent;
699  }
700  while (sent < size);
701  }
702 #else
703  if (size > sent)
704  {
705  file.seek(offset);
706  QScopedPointer<QByteArray> buffer(new QByteArray(READ_CHUNK_SIZE, 0));
707 
708  do
709  {
710  qint64 remaining = qMin(size - sent, (qint64)READ_CHUNK_SIZE);
711  qint64 read = file.read(buffer.data()->data(), remaining);
712  if (read < 0)
713  {
714  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error reading from '%1' (%2)").arg(file.fileName()).arg(file.errorString()));
715  break;
716  }
717 
718  qint64 send = Socket->write(buffer.data()->data(), read);
719 
720  if (send != read)
721  {
722  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error sending data (%1)").arg(Socket->errorString()));
723  break;
724  }
725 
726  sent += read;
727  }
728  while (sent < size);
729 
730  if (sent < size)
731  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to send all data for '%1'").arg(file.fileName()));
732  }
733 #endif
734  }
735  }
736 
737  Socket->flush();
738 
740  Socket->disconnectFromHost();
741 }
742 
744 {
748 }
749 
751 {
752  if (Type == QStringLiteral("GET")) return HTTPGet;
753  if (Type == QStringLiteral("HEAD")) return HTTPHead;
754  if (Type == QStringLiteral("POST")) return HTTPPost;
755  if (Type == QStringLiteral("PUT")) return HTTPPut;
756  if (Type == QStringLiteral("OPTIONS")) return HTTPOptions;
757  if (Type == QStringLiteral("DELETE")) return HTTPDelete;
758 
759  return HTTPUnknownType;
760 }
761 
763 {
764  switch (Type)
765  {
766  case HTTPHead: return QStringLiteral("HEAD");
767  case HTTPGet: return QStringLiteral("GET");
768  case HTTPPost: return QStringLiteral("POST");
769  case HTTPPut: return QStringLiteral("PUT");
770  case HTTPDelete: return QStringLiteral("DELETE");
771  case HTTPOptions: return QStringLiteral("OPTIONS");
772  case HTTPDisabled: return QStringLiteral("DISABLED"); // for completeness
773  default:
774  break;
775  }
776 
777  return QStringLiteral("UNKNOWN");
778 }
779 
781 {
782  if (Protocol.startsWith(QStringLiteral("HTTP")))
783  {
784  if (Protocol.endsWith(QStringLiteral("1.1"))) return HTTPOneDotOne;
785  if (Protocol.endsWith(QStringLiteral("1.0"))) return HTTPOneDotZero;
786  if (Protocol.endsWith(QStringLiteral("0.9"))) return HTTPZeroDotNine;
787  }
788 
789  return HTTPUnknownProtocol;
790 }
791 
793 {
794  if (Status.startsWith(QStringLiteral("200"))) return HTTP_OK;
795  if (Status.startsWith(QStringLiteral("101"))) return HTTP_SwitchingProtocols;
796  if (Status.startsWith(QStringLiteral("206"))) return HTTP_PartialContent;
797  if (Status.startsWith(QStringLiteral("301"))) return HTTP_MovedPermanently;
798  if (Status.startsWith(QStringLiteral("304"))) return HTTP_NotModified;
799  //if (Status.startsWith(QStringLiteral("400"))) return HTTP_BadRequest;
800  if (Status.startsWith(QStringLiteral("401"))) return HTTP_Unauthorized;
801  if (Status.startsWith(QStringLiteral("402"))) return HTTP_Forbidden;
802  if (Status.startsWith(QStringLiteral("404"))) return HTTP_NotFound;
803  if (Status.startsWith(QStringLiteral("405"))) return HTTP_MethodNotAllowed;
804  if (Status.startsWith(QStringLiteral("416"))) return HTTP_RequestedRangeNotSatisfiable;
805  if (Status.startsWith(QStringLiteral("429"))) return HTTP_TooManyRequests;
806  if (Status.startsWith(QStringLiteral("500"))) return HTTP_InternalServerError;
807 
808  return HTTP_BadRequest;
809 }
810 
812 {
813  switch (Protocol)
814  {
815  case HTTPOneDotOne: return QStringLiteral("HTTP/1.1");
816  case HTTPOneDotZero: return QStringLiteral("HTTP/1.0");
817  case HTTPZeroDotNine: return QStringLiteral("HTTP/0.9");
818  case HTTPUnknownProtocol: return QStringLiteral("Error");
819  }
820 
821  return QStringLiteral("Error");
822 }
823 
825 {
826  switch (Status)
827  {
828  case HTTP_SwitchingProtocols: return QStringLiteral("101 Switching Protocols");
829  case HTTP_OK: return QStringLiteral("200 OK");
830  case HTTP_PartialContent: return QStringLiteral("206 Partial Content");
831  case HTTP_MovedPermanently: return QStringLiteral("301 Moved Permanently");
832  case HTTP_NotModified: return QStringLiteral("304 Not Modified");
833  case HTTP_BadRequest: return QStringLiteral("400 Bad Request");
834  case HTTP_Unauthorized: return QStringLiteral("401 Unauthorized");
835  case HTTP_Forbidden: return QStringLiteral("403 Forbidden");
836  case HTTP_NotFound: return QStringLiteral("404 Not Found");
837  case HTTP_MethodNotAllowed: return QStringLiteral("405 Method Not Allowed");
838  case HTTP_RequestedRangeNotSatisfiable: return QStringLiteral("416 Requested Range Not Satisfiable");
839  case HTTP_TooManyRequests: return QStringLiteral("429 Too Many Requests");
840  case HTTP_InternalServerError: return QStringLiteral("500 Internal Server Error");
841  case HTTP_NotImplemented: return QStringLiteral("501 Not Implemented");
842  case HTTP_BadGateway: return QStringLiteral("502 Bad Gateway");
843  case HTTP_ServiceUnavailable: return QStringLiteral("503 Service Unavailable");
844  case HTTP_NetworkAuthenticationRequired: return QStringLiteral("511 Network Authentication Required");
845  }
846 
847  return QStringLiteral("Error");
848 }
849 
851 {
852  switch (Response)
853  {
854  case HTTPResponseNone: return QStringLiteral("");
855  case HTTPResponseXML: return QStringLiteral("text/xml; charset=\"UTF-8\"");
856  case HTTPResponseHTML: return QStringLiteral("text/html; charset=\"UTF-8\"");
857  case HTTPResponseJSON: return QStringLiteral("application/json");
858  case HTTPResponseJSONJavascript: return QStringLiteral("text/javascript");
859  case HTTPResponsePList: return QStringLiteral("application/plist");
860  case HTTPResponseBinaryPList: return QStringLiteral("application/x-plist");
861  case HTTPResponsePListApple: return QStringLiteral("text/x-apple-plist+xml");
862  case HTTPResponseBinaryPListApple: return QStringLiteral("application/x-apple-binary-plist");
863  case HTTPResponsePlainText: return QStringLiteral("text/plain");
864  case HTTPResponseM3U8: return QStringLiteral("application/x-mpegurl");
865  case HTTPResponseM3U8Apple: return QStringLiteral("application/vnd.apple.mpegurl");
866  case HTTPResponseMPD: return QStringLiteral("application/dash+xml");
867  case HTTPResponseMPEGTS: return QStringLiteral("video/mp2t");
868  case HTTPResponseMP4: return QStringLiteral("video/mp4");
869  default: break;
870  }
871 
872  return QStringLiteral("text/plain");
873 }
874 
876 {
877  QStringList result;
878 
879  if (Allowed & HTTPHead) result << QStringLiteral("HEAD");
880  if (Allowed & HTTPGet) result << QStringLiteral("GET");
881  if (Allowed & HTTPPost) result << QStringLiteral("POST");
882  if (Allowed & HTTPPut) result << QStringLiteral("PUT");
883  if (Allowed & HTTPDelete) result << QStringLiteral("DELETE");
884  if (Allowed & HTTPOptions) result << QStringLiteral("OPTIONS");
885 
886  return result.join(QStringLiteral(", "));
887 }
888 
890 {
891  switch (Connection)
892  {
893  case HTTPConnectionClose: return QStringLiteral("close");
894  case HTTPConnectionKeepAlive: return QStringLiteral("keep-alive");
895  case HTTPConnectionUpgrade: return QStringLiteral("Upgrade");
896  }
897 
898  return QStringLiteral();
899 }
900 
901 int TorcHTTPRequest::StringToAllowed(const QString &Allowed)
902 {
903  int allowed = 0;
904 
905  if (Allowed.contains(QStringLiteral("HEAD"), Qt::CaseInsensitive)) allowed += HTTPHead;
906  if (Allowed.contains(QStringLiteral("GET"), Qt::CaseInsensitive)) allowed += HTTPGet;
907  if (Allowed.contains(QStringLiteral("POST"), Qt::CaseInsensitive)) allowed += HTTPPost;
908  if (Allowed.contains(QStringLiteral("PUT"), Qt::CaseInsensitive)) allowed += HTTPPut;
909  if (Allowed.contains(QStringLiteral("DELETE"), Qt::CaseInsensitive)) allowed += HTTPDelete;
910  if (Allowed.contains(QStringLiteral("OPTIONS"), Qt::CaseInsensitive)) allowed += HTTPOptions;
911  if (Allowed.contains(QStringLiteral("AUTH"), Qt::CaseInsensitive)) allowed += HTTPAuth;
912 
913  return allowed;
914 }
915 
916 QVector<QPair<quint64,quint64> > TorcHTTPRequest::StringToRanges(const QString &Ranges, qint64 Size, qint64 &SizeToSend)
917 {
918  qint64 tosend = 0;
919  QVector<QPair<quint64,quint64> > results;
920  if (Size < 1)
921  return results;
922 
923  if (Ranges.contains(QStringLiteral("bytes"), Qt::CaseInsensitive))
924  {
925  QStringList newranges = Ranges.split('=');
926  if (newranges.size() == 2)
927  {
928  QStringList rangelist = newranges[1].split(',', QString::SkipEmptyParts);
929 
930  foreach (const QString &range, rangelist)
931  {
932  QStringList parts = range.split('-');
933 
934  if (parts.size() != 2)
935  continue;
936 
937  qint64 start = 0;
938  qint64 end = 0;
939  bool ok = false;
940  bool havefirst = !parts[0].trimmed().isEmpty();
941  bool havesecond = !parts[1].trimmed().isEmpty();
942 
943  if (havefirst && havesecond)
944  {
945  start = parts[0].toULongLong(&ok);
946  if (ok)
947  {
948  // invalid per spec
949  if (start >= Size)
950  continue;
951 
952  end = parts[1].toULongLong(&ok);
953 
954  // invalid per spec
955  if (end < start)
956  continue;
957  }
958  }
959  else if (havefirst && !havesecond)
960  {
961  start = parts[0].toULongLong(&ok);
962 
963  // invalid per spec
964  if (ok && start >= Size)
965  continue;
966 
967  end = Size - 1;
968  }
969  else if (!havefirst && havesecond)
970  {
971  end = Size -1;
972  start = parts[1].toULongLong(&ok);
973  if (ok)
974  {
975  start = Size - start;
976 
977  // invalid per spec
978  if (start >= Size)
979  continue;
980  }
981  }
982 
983  if (ok)
984  {
985  results << QPair<quint64,quint64>(start, end);
986  tosend += end - start + 1;
987  }
988  }
989  }
990  }
991 
992  if (results.isEmpty())
993  {
994  SizeToSend = 0;
995  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error parsing byte ranges ('%1')").arg(Ranges));
996  }
997  else
998  {
999  // rationalise overlapping range requests
1000  bool check = true;
1001 
1002  while (results.size() > 1 && check)
1003  {
1004  check = false;
1005  quint64 laststart = results[0].first;
1006  quint64 lastend = results[0].second;
1007 
1008  for (int i = 1; i < results.size(); ++i)
1009  {
1010  quint64 thisstart = results[i].first;
1011  quint64 thisend = results[i].second;
1012 
1013  if (thisstart > lastend)
1014  {
1015  laststart = thisstart;
1016  lastend = thisend;
1017  continue;
1018  }
1019 
1020  tosend -= (lastend - laststart + 1);
1021  tosend -= (thisend - thisstart + 1);
1022  results[i - 1].first = qMin(laststart, thisstart);
1023  results[i - 1].second = qMax(lastend, thisend);
1024  tosend += results[i - 1].second - results[i - 1].first + 1;
1025  results.removeAt(i);
1026  check = true;
1027  break;
1028  }
1029  }
1030 
1031  // set content size
1032  SizeToSend = tosend;
1033  }
1034 
1035  return results;
1036 }
1037 
1038 QString TorcHTTPRequest::RangeToString(QPair<quint64, quint64> Range, qint64 Size)
1039 {
1040  return QStringLiteral("%1-%2/%3").arg(Range.first).arg(Range.second).arg(Size);
1041 }
1042 
1043 void TorcHTTPRequest::Serialise(const QVariant &Data, const QString &Type)
1044 {
1045  TorcSerialiser *serialiser = TorcSerialiser::GetSerialiser(m_headers.value(QStringLiteral("Accept")));
1046  SetResponseType(serialiser->ResponseType());
1047  serialiser->Serialise(m_responseContent, Data, Type);
1049  delete serialiser;
1050 }
1051 
1057 bool TorcHTTPRequest::Unmodified(const QDateTime &LastModified)
1058 {
1059  if ((m_cache & HTTPCacheLastModified) && m_headers.contains(QStringLiteral("If-Modified-Since")))
1060  {
1061  QDateTime since = QDateTime::fromString(m_headers.value(QStringLiteral("If-Modified-Since")), DateFormat);
1062 
1063  if (LastModified <= since)
1064  {
1067  return true;
1068  }
1069  }
1070 
1071  return false;
1072 }
1073 
1083 {
1084  if ((m_cache & HTTPCacheETag) && !m_cacheTag.isEmpty() && m_headers.contains(QStringLiteral("If-None-Match")))
1085  {
1086  if (m_cacheTag == m_headers.value(QStringLiteral("If-None-Match")))
1087  {
1090  return true;
1091  }
1092  }
1093 
1094  return false;
1095 }
1096 
1098 {
1099  m_authorised = Authorisation;
1100 }
1101 
1103 {
1104  return m_authorised;
1105 }
void SetAllowed(int Allowed)
QString GetMethod(void) const
bool GetAllowCORS(void) const
static char DateFormat[]
QMap< QString, QString > m_queries
HTTPRequestType GetHTTPRequestType(void) const
QByteArray m_responseContent
static QString RequestTypeToString(HTTPRequestType Type)
static QStringList MimeTypeForFileName(const QString &FileName)
Return a list of possible MIME types for the file named Name.
Definition: torcmime.cpp:85
QString GetCache(void) const
bool HasZlib(void)
Return true if zlib support is available.
void SetConnection(HTTPConnection Connection)
A convenience class to read HTTP requests from a QTcpSocket.
Definition: torchttpreader.h:8
HTTPStatus m_responseStatus
HTTPProtocol m_protocol
HTTPStatus GetHTTPStatus(void) const
static QString MimeTypeForFileNameAndData(const QString &FileName, QIODevice *Device)
Return a MIME type for the media described by FileName and Device.
Definition: torcmime.cpp:55
static QString ResponseTypeToString(HTTPResponseType Response)
static QString StatusToString(HTTPStatus Status)
void Initialise(const QString &Method)
HTTPRequestType m_requestType
HTTPProtocol
void SetAllowCORS(bool Allowed)
HTTPAuthorisation IsAuthorised(void) const
bool Unmodified(void)
Check whether the resource is equivalent to the last seen version.
void Serialise(const QVariant &Data, const QString &Type)
static HTTPRequestType RequestTypeFromString(const QString &Type)
void Serialise(QByteArray &Dest, const QVariant &Data, const QString &Type=QString())
static TorcSerialiser * GetSerialiser(const QString &MimeType)
QString GetPath(void) const
const QMap< QString, QString > & Headers(void) const
void SetCache(int Cache, const QString &Tag=QStringLiteral(""))
Set the caching behaviour for this response.
void SetResponseContent(const QByteArray &Content)
QString GetMethod(void) const
void SetAllowGZip(bool Allowed)
Allow gzip compression for the contents of this request.
QMap< QString, QString > m_headers
HTTPResponseType
#define READ_CHUNK_SIZE
virtual HTTPResponseType ResponseType(void)=0
HTTPType
TorcHTTPRequest(TorcHTTPReader *Reader)
QByteArray m_content
void SetResponseHeader(const QString &Header, const QString &Value)
HTTPStatus
static QVector< QPair< quint64, quint64 > > StringToRanges(const QString &Ranges, qint64 Size, qint64 &SizeToSend)
HTTPAuthorisation m_authorised
HTTPConnection
static QString ProtocolToString(HTTPProtocol Protocol)
void Redirected(const QString &Redirected)
static int StringToAllowed(const QString &Allowed)
void SetSecure(bool Secure)
void SetStatus(HTTPStatus Status)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
HTTPAuthorisation
static HTTPProtocol ProtocolFromString(const QString &Protocol)
static QString PlatformName(void)
#define TORC_REALM
Definition: torclocaldefs.h:9
void TakeRequest(QByteArray &Content, QMap< QString, QString > &Headers)
Take ownership of the contents and headers. New owner is responsible for deleting.
static QString RangeToString(QPair< quint64, quint64 > Range, qint64 Size)
HTTPRequestType
void SetResponseFile(const QString &File)
void SetResponseType(HTTPResponseType Type)
QString GetUrl(void) const
QVector< QPair< quint64, quint64 > > m_ranges
static HTTPStatus StatusFromString(const QString &Status)
QByteArray GZipCompressFile(QFile &Source)
Compress the given file using GZip.
static QString ConnectionToString(HTTPConnection Connection)
HTTPProtocol GetHTTPProtocol(void) const
QByteArray GZipCompress(QByteArray &Source)
Compress the supplied data using GZip.
HTTPConnection m_connection
QMap< QString, QString > m_responseHeaders
HTTPType GetHTTPType(void) const
const QMap< QString, QString > & Queries(void) const
void Authorise(HTTPAuthorisation Authorisation)
QRegExp gRegExp
static QString AllowedToString(int Allowed)
HTTPResponseType m_responseType
void Respond(QTcpSocket *Socket)