MythTV master
mythhttprequest.cpp
Go to the documentation of this file.
1// Qt
2#include <QHash>
3#include <QString>
4#include <QTcpSocket>
5
6// MythTV
7#include "mythlogging.h"
8#include "http/mythhttpdata.h"
11
12#define LOC QString("HTTPParse: ")
13
25 HTTPHeaders Headers, HTTPData Content, QTcpSocket* Socket /*=nullptr*/)
26 : m_serverName(Config.m_serverName),
27 m_method(std::move(Method)),
28 m_headers(std::move(Headers)),
29 m_content(std::move(Content)),
30 m_root(Config.m_rootDir),
31 m_timeout(Config.m_timeout)
32{
33 // TODO is the simplified() call here always safe?
34 QStringList tokens = m_method.simplified().split(' ', Qt::SkipEmptyParts);
35
36 // Validation
37 // Must have verb and url and optional version
38 if (tokens.size() < 2 || tokens.size() > 3)
39 {
40 LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to parse HTTP method");
41 return;
42 }
43
44 // Note
45 // tokens[0] = GET, POST, etc
46 // tokens[1] = rest of URL
47 // tokens[2] = HTTP/1.1
48
50 m_url = tokens[1];
51
52 // If no version, assume HTTP/1.1
54 if (tokens.size() > 2)
56
57 // Unknown HTTP version
59 {
60 LOG(VB_GENERAL, LOG_WARNING, LOC + "Unknown HTTP version");
62 return;
63 }
64
65 // Unknown request type
66 if (m_type == HTTPUnknown)
67 {
68 LOG(VB_GENERAL, LOG_WARNING, LOC + "Unknown HTTP request");
69 return;
70 }
71
72 // HTTP/1.1 requires the HOST header - even if empty.
73 bool havehost = m_headers->contains("host");
74 if ((m_version == HTTPOneDotOne) && !havehost)
75 {
76 LOG(VB_GENERAL, LOG_WARNING, LOC + "No host header for HTTP/1.1");
77 return;
78 }
79
80 // Multiple host headers are also forbidden - assume for any version not just 1/1
81 if (havehost && m_headers->count("host") > 1)
82 {
83 LOG(VB_GENERAL, LOG_WARNING, LOC + "Multiple 'Host' headers forbidden");
84 return;
85 }
86
87 // If a host is provided, ensure we recognise it. This may be over zealous:)
88 if (havehost)
89 {
90
91 // Commented this check because people should be able to set up something in their
92 // hosts file that does not match the server. Then the host name in the request
93 // may not match.
94
95 // N.B. host port is optional - but our host list has both versions
96 // QString host = MythHTTP::GetHeader(m_headers, "host").toLower();
97 // QStringList hostParts = host.split(":");
98 // if (!Config.m_hosts.contains(hostParts[0]))
99 // {
100 // LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Invalid 'Host' header. '%1' not recognised")
101 // .arg(host));
102 // return;
103 // }
104
105 // TODO Ensure the host address has a port - as below when we add one manually
106 }
107 else if (Socket)
108 {
109 // Use the socket address to add a host address. This just ensures the
110 // response always has a valid address for this thread/socket that can be used
111 // when building a (somewhat dynamic) response.
112 QHostAddress host = Socket->localAddress();
113 m_headers->insert("host", QString("%1:%2").arg(MythHTTP::AddressToString(host)).arg(Socket->localPort()));
114 }
115
116 if (Socket)
117 m_peerAddress = Socket->peerAddress();
118
119 // Need a valid URL
120 if (!m_url.isValid())
121 {
122 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Invalid URL: '%1'").arg(m_url.toString()));
123 return;
124 }
125
126 // Parse the URL into its useful components (path/filename) - queries later
127 m_path = m_url.toString(QUrl::RemoveFilename | QUrl::RemoveFragment | QUrl::RemoveQuery);
128 m_fileName = m_url.fileName();
129
130 // Parse the connection header
131 // HTTP/1.1 default to KeepAlive, HTTP/1.0 default to close
132 // HTTP/0.9 is unlikely but assume KeepAlive
134 auto connection = MythHTTP::GetHeader(m_headers, "connection").toLower();
135 if (connection.contains(QStringLiteral("keep-alive")))
137 else if (connection.contains(QStringLiteral("close")))
139
140 // Parse the content type if present - and pull out any form data
141 if (m_content.get() && !m_content->isEmpty() && ((m_type == HTTPPut) || (m_type == HTTPPost)))
143
144 // Only parse queries if we do not have form data
145 if (m_queries.isEmpty() && m_url.hasQuery())
146 m_queries = ParseQuery(m_url.query());
147
149}
150
152{
153 HTTPQueries result;
154 QStringList params = Query.split('&', Qt::SkipEmptyParts);
155 for (const auto & param : std::as_const(params))
156 {
157 QString key = param.section('=', 0, 0);
158 QString value = param.section('=', 1);
159 QByteArray rawvalue = value.toUtf8();
160 value = QUrl::fromPercentEncoding(rawvalue);
161 value.replace("+", " ");
162 if (!key.isEmpty())
163 result.insert(key.trimmed().toLower(), value);
164 }
165 return result;
166}
static void GetContentType(MythHTTPRequest *Request)
Parse the incoming Content-Type header for POST/PUT content.
MythHTTPRequest(const MythHTTPConfig &Config, QString Method, HTTPHeaders Headers, HTTPData Content, QTcpSocket *Socket=nullptr)
MythHTTPConnection m_connection
static HTTPQueries ParseQuery(const QString &Query)
HTTPHeaders m_headers
HTTPQueries m_queries
MythHTTPRequestType m_type
MythHTTPVersion m_version
QHostAddress m_peerAddress
MythHTTPStatus m_status
static MythHTTPVersion VersionFromString(const QString &Version)
static QString AddressToString(QHostAddress &Address)
static QString GetHeader(const HTTPHeaders &Headers, const QString &Value, const QString &Default="")
static MythHTTPRequestType RequestFromString(const QString &Type)
#define LOC
@ HTTPOK
@ HTTPConnectionClose
@ HTTPConnectionKeepAlive
std::shared_ptr< HTTPMap > HTTPHeaders
Definition: mythhttptypes.h:36
@ HTTPUnknown
Definition: mythhttptypes.h:92
@ HTTPPut
Definition: mythhttptypes.h:96
@ HTTPPost
Definition: mythhttptypes.h:95
@ HTTPUnknownVersion
Definition: mythhttptypes.h:84
@ HTTPOneDotOne
Definition: mythhttptypes.h:87
@ HTTPOneDotZero
Definition: mythhttptypes.h:86
std::shared_ptr< MythHTTPData > HTTPData
Definition: mythhttptypes.h:37
HTTPMap HTTPQueries
Definition: mythhttptypes.h:35
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
STL namespace.