Hobrasoft httpd server
Embedded HTTP server for Qt and C++
httprequest.cpp
Go to the documentation of this file.
1 
8 #include "httprequest.h"
9 #include "httpconnection.h"
10 #include "httpsettings.h"
11 #include <QList>
12 #include <QDir>
13 #include <QMapIterator>
14 #include <QDebug>
15 
16 using namespace HobrasoftHttpd;
17 
18 
20  m_datetime = QDateTime::currentDateTime();
21  m_status = StatusWaitForRequest;
22  m_currentSize = 0;
23  m_expectedBodySize = 0;
24  m_connection = parent;
25 }
26 
27 
28 void HttpRequest::readFromSocket(QTcpSocket *socket) {
29  switch(m_status) {
30  case StatusComplete:
31  return;
32  break;
33  case StatusAbort:
34  return;
35  break;
37  readRequest(socket);
38  break;
40  readHeader(socket);
41  break;
42  case StatusWaitForBody:
43  readBody(socket);
44  break;
45  };
46  if (m_currentSize > m_connection->settings()->maxRequestSize()) {
47  m_status = StatusAbort;
48  }
49  if (m_status == StatusComplete) {
52  }
53 }
54 
55 
62 void HttpRequest::readRequest(QTcpSocket *socket) {
63  int toRead = m_connection->settings()->maxRequestSize() - m_currentSize + 1;
64  QByteArray newdata = socket->readLine(toRead).trimmed();
65  m_currentSize += newdata.size();
66 
67  if (newdata.isEmpty()) {
68  return;
69  }
70 
71  QStringList list = QString::fromUtf8(newdata).split(' ');
72  if (list.size() != 3 || !list[2].contains("HTTP")) {
73  qWarning("HttpRequest: received broken HTTP request, invalid first line");
74  m_status = StatusAbort;
75  return;
76  }
77 
78  m_method = list[0];
79  m_path = list[1];
80  m_version = list[2];
81  m_status = StatusWaitForHeader;
82  m_fullpath = m_path;
83  m_connection->setObjectName(m_path);
84 }
85 
86 
90 void HttpRequest::readHeader(QTcpSocket *socket) {
91  int toRead = m_connection->settings()->maxRequestSize() - m_currentSize + 1;
92  QByteArray newdata = socket->readLine(toRead).trimmed();
93  m_currentSize += newdata.size();
94 
95  int colonpos = newdata.indexOf(':');
96  if (colonpos > 0) { // new header
97  m_currentHeader = QString::fromUtf8(newdata.left(colonpos));
98  QString value = QString::fromUtf8(newdata.mid(colonpos+1).trimmed());
99  m_headers.insert(m_currentHeader,value);
100  return;
101  }
102 
103  if (colonpos <= 0 && !newdata.isEmpty()) { // header continues
104  m_headers.insert(
105  m_currentHeader,
106  m_headers.value(m_currentHeader) + QString(" ") + QString::fromUtf8(newdata)
107  );
108  return;
109  }
110 
111  if (newdata.isEmpty()) { // Empty line, end of headers
112  QString contentType = header("Content-Type");
113 
114  if (contentType.startsWith("multipart/form-data", Qt::CaseInsensitive)) {
115  int pos = contentType.indexOf("boundary=", 0, Qt::CaseInsensitive);
116  if (pos >= 0) {
117  m_boundary = contentType.mid(pos+9).toUtf8();
118  }
119  }
120 
121  m_expectedBodySize = header("Content-Length").toInt();
122  if (m_expectedBodySize <= 0) {
123  m_status = StatusComplete;
124  return;
125  }
126 
127  if (m_boundary.isEmpty() && m_expectedBodySize + m_currentSize > m_connection->settings()->maxRequestSize()) {
128  qWarning("HttpRequest: expected body is too large");
129  m_status = StatusAbort;
130  return;
131  }
132 
133  if (!m_boundary.isEmpty() && m_expectedBodySize > m_connection->settings()->maxMultiPartSize()) {
134  qWarning("HttpRequest: expected multipart body is too large");
135  m_status = StatusAbort;
136  return;
137  }
138 
139  m_status = StatusWaitForBody;
140 
141  }
142 
143 }
144 
145 
149 void HttpRequest::readBody(QTcpSocket *socket) {
150  // normal body, no multipart
151  if (m_boundary.isEmpty()) {
152  int toRead = m_expectedBodySize - m_bodyData.size();
153  QByteArray newdata = socket->read(toRead);
154  m_currentSize += newdata.size();
155 
156  m_bodyData.append(newdata);
157  if (m_bodyData.size() >= m_expectedBodySize) {
158  m_status = StatusComplete;
159  }
160  return;
161  }
162 
163  // Multipart body
164  if (!m_boundary.isEmpty()) {
165  if (!m_tempFile.isOpen()) {
166  m_tempFile.open();
167  }
168  int filesize = m_tempFile.size();
169  int toRead = m_expectedBodySize - filesize;
170  if (toRead > 65536) {
171  toRead = 65536;
172  }
173 
174  filesize += m_tempFile.write(socket->read(toRead));
175  if (filesize >= m_connection->settings()->maxMultiPartSize()) {
176  qWarning("HttpRequest: received too many multipart bytes");
177  m_status = StatusAbort;
178  return;
179  }
180 
181  if (filesize >= m_expectedBodySize) {
182  m_tempFile.flush();
184  m_tempFile.close();
185  m_status = StatusComplete;
186  }
187  }
188 
189 }
190 
191 
196  QString rawParameters;
197  if (header("Content-Type").toLower() == "application/x-www-form-urlencoded") {
198  rawParameters = m_bodyData;
199  } else {
200  int questionmarkpos = m_path.indexOf('?');
201  if (questionmarkpos >= 0) {
202  rawParameters = m_path.mid(questionmarkpos+1);
203  m_path = m_path.left(questionmarkpos);
204  }
205  }
206 
207  QStringList list = rawParameters.split('&');
208  for (int i=0; i<list.size(); i++) {
209  QString& part = list[i];
210  int eqpos = part.indexOf('=');
211  if (eqpos >= 0) {
212  QString name = urlDecode( part.left(eqpos).trimmed() );
213  QString value = urlDecode( part.mid(eqpos+1).trimmed() );
214  m_parameters.insert(name, value);
215  continue;
216  }
217 
218  if (!part.isEmpty()) {
219  QString name = urlDecode( part );
220  m_parameters.insert(name,"");
221  }
222  }
223 }
224 
225 
230  QStringList cookies = headers("Cookie");
231  for (int i=0; i<cookies.size(); i++) {
232  // rozdělit řádek podle středníků
233  QStringList parts = cookies[i].split(";");
234  for (int i=0; i<parts.size(); i++) {
235  QString& part = parts[i];
236  int eqpos = part.indexOf('=');
237  if (eqpos >= 0) {
238  QString name = part.left(eqpos).trimmed();
239  QString value = part.mid(eqpos+1).trimmed();
240  if (name.startsWith("$")) {
241  continue;
242  }
243  m_cookies.insert(name, value);
244  continue;
245  }
246  if (eqpos < 0) {
247  m_cookies.insert(part, "");
248  continue;
249  }
250  }
251  }
252  header("Cookie");
253 }
254 
255 
259 QString HttpRequest::urlDecode(const QString& text) {
260  QByteArray buffer(text.toUtf8());
261  buffer.replace('+',' ');
262  int percentpos = buffer.indexOf('%');
263  while (percentpos >= 0) {
264  bool ok;
265  // QChar byte = QChar((int)buffer.mid(percentpos+1, 2).toInt(&ok, 16));
266  int byte = buffer.mid(percentpos+1, 2).toInt(&ok, 16);
267  if (ok) {
268  buffer.remove(percentpos,3);
269  buffer.insert(percentpos, byte);
270  }
271  percentpos = buffer.indexOf('%', percentpos+1);
272  }
273  return QString::fromUtf8(buffer);
274 }
275 
276 
281  m_tempFile.seek(0);
282  bool finished=false;
283  while (!m_tempFile.atEnd() && !finished && !m_tempFile.error()) {
284 
285  QString fieldName;
286  QString fileName;
287  QString contentType;
288  while (!m_tempFile.atEnd() && !finished && !m_tempFile.error()) {
289  QString line = QString::fromUtf8(m_tempFile.readLine(65536).trimmed());
290  if (line.startsWith("--"+m_boundary)) { continue; }
291  if (line.isEmpty()) { break; }
292  if (line.startsWith("Content-Disposition:", Qt::CaseInsensitive)) {
293  if (line.contains("form-data", Qt::CaseInsensitive)) {
294  int start=line.indexOf(" name=\"", 0, Qt::CaseInsensitive);
295  int end=line.indexOf("\"",start+7);
296  if (start>=0 && end>=start) {
297  fieldName=line.mid(start+7,end-start-7);
298  }
299  start=line.indexOf(" filename=\"", 0, Qt::CaseInsensitive);
300  end=line.indexOf("\"",start+11);
301  if (start>=0 && end>=start) {
302  fileName=line.mid(start+11,end-start-11);
303  }
304  continue;
305  }
306  }
307  if (line.startsWith("Content-Type:", Qt::CaseInsensitive)) {
308  contentType = line.remove(QRegExp("^Content-Type:\\s*", Qt::CaseInsensitive));
309  continue;
310  }
311 
312  qDebug() << "HttpRequest: ignoring unsupported content part" << line;
313  }
314 
315  QTemporaryFile* uploadedFile=0;
316  QByteArray fieldValue;
317  while (!m_tempFile.atEnd() && !finished && !m_tempFile.error()) {
318  QByteArray line = m_tempFile.readLine(65536);
319  if (line.startsWith("--"+m_boundary)) {
320  // Boundary found. Until now we have collected 2 bytes too much,
321  // so remove them from the last result
322  if (fileName.isEmpty() && !fieldName.isEmpty()) {
323  // last field was a form field
324  fieldValue.remove(fieldValue.size()-2,2);
325  m_parameters.insert(fieldName,fieldValue);
326  }
327  else if (!fileName.isEmpty() && !fieldName.isEmpty()) {
328  // last field was a file
329  uploadedFile->resize(uploadedFile->size()-2);
330  uploadedFile->flush();
331  uploadedFile->seek(0);
332  m_parameters.insert(fieldName,fileName);
333  m_uploadedFiles.insert(fieldName,uploadedFile);
334  m_contentTypes.insert(fieldName,contentType);
335  }
336  if (line.contains(m_boundary+"--")) {
337  finished=true;
338  }
339  break;
340  } else {
341  if (fileName.isEmpty() && !fieldName.isEmpty()) {
342  // this is a form field.
343  m_currentSize+=line.size();
344  fieldValue.append(line);
345  }
346  else if (!fileName.isEmpty() && !fieldName.isEmpty()) {
347  // this is a file
348  if (!uploadedFile) {
349  uploadedFile=new QTemporaryFile();
350  uploadedFile->open();
351  }
352  uploadedFile->write(line);
353  if (uploadedFile->error()) {
354  qCritical("HttpRequest: error writing temp file, %s",qPrintable(uploadedFile->errorString()));
355  }
356  }
357  }
358  }
359  }
360  if (m_tempFile.error()) {
361  qCritical("HttpRequest: cannot read temp file, %s",qPrintable(m_tempFile.errorString()));
362  }
363 }
364 
365 
366 QString HttpRequest::header(const QString& name) const {
367  QMapIterator<QString, QString> iterator(m_headers);
368  while (iterator.hasNext()) {
369  iterator.next();
370  if (iterator.key().toLower() == name.toLower()) {
371  return iterator.value();
372  }
373  }
374  return QString();
375 }
376 
377 
378 QList<QString> HttpRequest::headers(const QString& name) const {
379  QMapIterator<QString, QString> iterator(m_headers);
380  while (iterator.hasNext()) {
381  iterator.next();
382  if (iterator.key().toLower() == name.toLower()) {
383  return m_headers.values(iterator.key());
384  }
385  }
386  return QList<QString>();
387 }
388 
389 QString HttpRequest::statusString() const {
390  QString text;
391  switch (m_status) {
392  case StatusWaitForRequest: text="Wait for Request"; break;
393  case StatusWaitForHeader: text="Wait for Header"; break;
394  case StatusWaitForBody: text="Wait for Body"; break;
395  case StatusComplete: text="Complete"; break;
396  case StatusAbort: text="Abort"; break;
397  }
398  return text;
399 }
400 
QString statusString() const
Returns current status of the request as a string.
QList< QString > headers(const QString &name) const
Returns all headers of HTTP request in QList, case insensitive.
void readHeader(QTcpSocket *socket)
Reads HTTP headers of the request.
Definition: httprequest.cpp:90
void parseMultiPartFile()
Parses the mime multipart request.
QString contentType(const QString &fieldName) const
Returns content type of uploaded file.
Definition: httprequest.h:184
QTemporaryFile * uploadedFile(const QString &fieldName)
Returns temporary file with uploaded file from html form.
Definition: httprequest.h:156
QString header(const QString &name) const
Returns requested header value, case insensitive.
static QString urlDecode(const QString &source)
Converts URL encoded string to UTF8 string.
void readBody(QTcpSocket *socket)
Reads body of the request.
void readFromSocket(QTcpSocket *socket)
Reads data from socket.
Definition: httprequest.cpp:28
void readRequest(QTcpSocket *socket)
Reads request from socket.
Definition: httprequest.cpp:62
void extractCookies()
Parses cookies.
Namespace of HTTP server.
HttpRequest(HttpConnection *connection)
Constructor sets default falues from configuration.
Definition: httprequest.cpp:19
One single connection to http server.
void decodeRequestParams()
Parses parameters of the URL.