text: //
  //  HTTPConnection.cpp
  //  libraries/embedded-webserver/src
  //
  //  Created by Stephen Birarda on 1/16/14.
  //  Copyright 2014 High Fidelity, Inc.
  //
  //  Distributed under the Apache License, Version 2.0.
  //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
  //
  
  
  #include <QBuffer>
  #include <QCryptographicHash>
  #include <QTcpSocket>
  
  #include "HTTPConnection.h"
  #include "HTTPManager.h"
  
  const char* HTTPConnection::StatusCode200 = "200 OK";
  const char* HTTPConnection::StatusCode301 = "301 Moved Permanently";
  const char* HTTPConnection::StatusCode302 = "302 Found";
  const char* HTTPConnection::StatusCode400 = "400 Bad Request";
  const char* HTTPConnection::StatusCode401 = "401 Unauthorized";
  const char* HTTPConnection::StatusCode404 = "404 Not Found";
  const char* HTTPConnection::DefaultContentType = "text/plain; charset=ISO-8859-1";
  
  HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager) :
      QObject(parentManager),
      _parentManager(parentManager),
      _socket(socket),
      _stream(socket),
      _address(socket->peerAddress())
  {
      // take over ownership of the socket
      _socket->setParent(this);
  
      // connect initial slots
      connect(socket, SIGNAL(readyRead()), SLOT(readRequest()));
      connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(deleteLater()));
      connect(socket, SIGNAL(disconnected()), SLOT(deleteLater()));
  }
  
  HTTPConnection::~HTTPConnection() {
      // log the destruction
      if (_socket->error() != QAbstractSocket::UnknownSocketError
          && _socket->error() != QAbstractSocket::RemoteHostClosedError) {
          qDebug() << _socket->errorString() << "-" << _socket->error();
      }
  }
  
  QList<FormData> HTTPConnection::parseFormData() const {
      // make sure we have the correct MIME type
      QList<QByteArray> elements = _requestHeaders.value("Content-Type").split(';');
      if (elements.at(0).trimmed() != "multipart/form-data") {
          return QList<FormData>();
      }
  
      // retrieve the boundary marker
      QByteArray boundary;
      for (int ii = 1, nn = elements.size(); ii < nn; ii++) {
          QByteArray element = elements.at(ii).trimmed();
          if (element.startsWith("boundary")) {
              boundary = element.mid(element.indexOf('=') + 1).trimmed();
              break;
          }
      }
      
      QByteArray start = "--" + boundary;
      QByteArray end = "\r\n--" + boundary + "--\r\n";
  
      QList<FormData> data;
      QBuffer buffer(const_cast<QByteArray*>(&_requestContent));
      buffer.open(QIODevice::ReadOnly);
      while (buffer.canReadLine()) {
          QByteArray line = buffer.readLine().trimmed();
          if (line == start) {
              FormData datum;
              while (buffer.canReadLine()) {
                  QByteArray line = buffer.readLine().trimmed();
                  if (line.isEmpty()) {
                      // content starts after this line
                      int idx = _requestContent.indexOf(end, buffer.pos());
                      if (idx == -1) {
                          qWarning() << "Missing end boundary." << _address;
                          return data;
                      }
                      datum.second = _requestContent.mid(buffer.pos(), idx - buffer.pos());
                      data.append(datum);
                      buffer.seek(idx + end.length());
  
                  } else {
                      // it's a header element
                      int idx = line.indexOf(':');
                      if (idx == -1) {
                          qWarning() << "Invalid header line." << _address << line;
                          continue;
                      }
                      datum.first.insert(line.left(idx).trimmed(), line.mid(idx + 1).trimmed());
                  }
              }
          }
      }
  
      return data;
  }
  
  void HTTPConnection::respond(const char* code, const QByteArray& content, const char* contentType, const Headers& headers) {
      _socket->write("HTTP/1.1 ");
      _socket->write(code);
      _socket->write("\r\n");
  
      int csize = content.size();
  
      for (Headers::const_iterator it = headers.constBegin(), end = headers.constEnd();
              it != end; it++) {
          _socket->write(it.key());
          _socket->write(": ");
          _socket->write(it.value());
          _socket->write("\r\n");
      }
      if (csize > 0) {
          _socket->write("Content-Length: ");
          _socket->write(QByteArray::number(csize));
          _socket->write("\r\n");
  
          _socket->write("Content-Type: ");
          _socket->write(contentType);
          _socket->write("\r\n");
      }
      _socket->write("Connection: close\r\n\r\n");
  
      if (csize > 0) {
          _socket->write(content);
      }
  
      // make sure we receive no further read notifications
      _socket->disconnect(SIGNAL(readyRead()), this);
  
      _socket->disconnectFromHost();
  }
  
  void HTTPConnection::readRequest() {
      if (!_socket->canReadLine()) {
          return;
      }
      // parse out the method and resource
      QByteArray line = _socket->readLine().trimmed();
      if (line.startsWith("HEAD")) {
          _requestOperation = QNetworkAccessManager::HeadOperation;
  
      } else if (line.startsWith("GET")) {
          _requestOperation = QNetworkAccessManager::GetOperation;
  
      } else if (line.startsWith("PUT")) {
          _requestOperation = QNetworkAccessManager::PutOperation;
  
      } else if (line.startsWith("POST")) {
          _requestOperation = QNetworkAccessManager::PostOperation;
  
      } else if (line.startsWith("DELETE")) {
          _requestOperation = QNetworkAccessManager::DeleteOperation;
  
      } else {
          qWarning() << "Unrecognized HTTP operation." << _address << line;
          respond("400 Bad Request", "Unrecognized operation.");
          return;
      }
      int idx = line.indexOf(' ') + 1;
      _requestUrl.setUrl(line.mid(idx, line.lastIndexOf(' ') - idx));
  
      // switch to reading the header
      _socket->disconnect(this, SLOT(readRequest()));
      connect(_socket, SIGNAL(readyRead()), SLOT(readHeaders()));
  
      // read any headers immediately available
      readHeaders();
  }
  
  void HTTPConnection::readHeaders() {
      while (_socket->canReadLine()) {
          QByteArray line = _socket->readLine();
          QByteArray trimmed = line.trimmed();
          if (trimmed.isEmpty()) {
              _socket->disconnect(this, SLOT(readHeaders()));
  
              QByteArray clength = _requestHeaders.value("Content-Length");
              if (clength.isEmpty()) {
                  _parentManager->handleHTTPRequest(this, _requestUrl);
  
              } else {
                  _requestContent.resize(clength.toInt());
                  connect(_socket, SIGNAL(readyRead()), SLOT(readContent()));
  
                  // read any content immediately available
                  readContent();
              }
              return;
          }
          char first = line.at(0);
          if (first == ' ' || first == '\t') { // continuation
              _requestHeaders[_lastRequestHeader].append(trimmed);
              continue;
          }
          int idx = trimmed.indexOf(':');
          if (idx == -1) {
              qWarning() << "Invalid header." << _address << trimmed;
              respond("400 Bad Request", "The header was malformed.");
              return;
          }
          _lastRequestHeader = trimmed.left(idx);
          QByteArray& value = _requestHeaders[_lastRequestHeader];
          if (!value.isEmpty()) {
              value.append(", ");
          }
          value.append(trimmed.mid(idx + 1).trimmed());
      }
  }
  
  void HTTPConnection::readContent() {
      int size = _requestContent.size();
      if (_socket->bytesAvailable() < size) {
          return;
      }
      _socket->read(_requestContent.data(), size);
      _socket->disconnect(this, SLOT(readContent()));
  
      _parentManager->handleHTTPRequest(this, _requestUrl.path());
  }
  