CPPMyth
Library to interoperate with MythTV server
Loading...
Searching...
No Matches
wsrequest.cpp
1/*
2 * Copyright (C) 2014-2026 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 "wsrequest.h"
23#include "uriencoder.h"
24#include "builtin.h"
25#include "debug.h"
26
27#include <cstdio>
28#include <cstddef> // for size_t
29#include <algorithm>
30
31using namespace NSROOT;
32
33WSRequest::WSRequest(const std::string& server, unsigned port)
34: m_server(server)
35, m_port(port)
36, m_secure_uri(false)
37, m_service_url()
38, m_service_method(WS_METHOD_Get)
39, m_charset(REQUEST_STD_CHARSET)
40, m_accept()
41, m_contentType(WS_CTYPE_Form)
42, m_contentTypeStr()
43, m_contentData()
44{
45 if (port == 443)
46 m_secure_uri = true;
47 // by default allow content encoding if possible
48 RequestAcceptEncoding(true);
49}
50
51WSRequest::WSRequest(const std::string& server, unsigned port, bool secureURI)
52: m_server(server)
53, m_port(port)
54, m_secure_uri(secureURI)
55, m_service_url()
56, m_service_method(WS_METHOD_Get)
57, m_charset(REQUEST_STD_CHARSET)
58, m_accept()
59, m_contentType(WS_CTYPE_Form)
60, m_contentTypeStr()
61, m_contentData()
62{
63 // by default allow content encoding if possible
64 RequestAcceptEncoding(true);
65}
66
67WSRequest::WSRequest(const URIParser& uri, WS_METHOD method)
68: m_port(0)
69, m_secure_uri(false)
70, m_service_method(method)
71, m_charset(REQUEST_STD_CHARSET)
72, m_accept()
73, m_contentType(WS_CTYPE_Form)
74, m_contentTypeStr()
75, m_contentData()
76{
77 if (uri.Host())
78 m_server.assign(uri.Host());
79 if (uri.Scheme() && strncmp(uri.Scheme(), "https", 5) == 0)
80 {
81 m_secure_uri = true;
82 m_port = uri.Port() ? uri.Port() : 443;
83 }
84 else
85 m_port = uri.Port() ? uri.Port() : 80;
86
87 m_service_url = "/";
88 if (uri.Path())
89 m_service_url.append(uri.Path());
90
91 if (uri.Fragment())
92 m_service_url.append("#").append(uri.Fragment());
93
94 if (uri.Params())
95 m_contentData.append(uri.Params());
96
97 // by default allow content encoding if possible
98 RequestAcceptEncoding(true);
99}
100
101WSRequest::~WSRequest()
102{
103}
104
105WSRequest::WSRequest(const WSRequest& o, const URIParser& redirection)
106: m_server(o.m_server)
107, m_port(o.m_port)
108, m_secure_uri(o.m_secure_uri)
109, m_service_method(o.m_service_method)
110, m_charset(o.m_charset)
111, m_accept(o.m_accept)
112, m_contentType(o.m_contentType)
113, m_contentTypeStr(o.m_contentTypeStr)
114, m_contentData(o.m_contentData)
115, m_headers(o.m_headers)
116, m_userAgent(o.m_userAgent)
117{
118 /* The "Location" header field is used in some responses to refer to a
119 * specific resource in relation to the response. The type of relationship
120 * is defined by the combination of request method and status code semantics.
121 */
122 if (redirection.Host())
123 m_server.assign(redirection.Host());
124
125 if (redirection.Scheme())
126 {
127 if (strncmp(redirection.Scheme(), "https", 5) == 0)
128 {
129 m_secure_uri = true;
130 m_port = redirection.Port() ? redirection.Port() : 443;
131 }
132 else
133 {
134 m_secure_uri = false;
135 m_port = redirection.Port() ? redirection.Port() : 80;
136 }
137 }
138
139 URIParser o_uri(o.GetService());
140 m_service_url = "/";
141 if (redirection.Path())
142 m_service_url.append(redirection.Path());
143
144 /* If the Location value provided in a 3xx (Redirection) response does not have
145 * a fragment component, a user agent MUST process the redirection as if the
146 * value inherits the fragment component of the URI reference used to generate
147 * the target URI (i.e., the redirection inherits the original reference's
148 * fragment, if any).
149 */
150 if (redirection.Fragment())
151 m_service_url.append("#").append(redirection.Fragment());
152 else if (o_uri.Fragment())
153 m_service_url.append("#").append(o_uri.Fragment());
154
155 /* params have been copied from original request (content data), therefore
156 * those specified in the new location are ignored
157 */
158}
159
160void WSRequest::RequestService(const std::string& url, WS_METHOD method)
161{
162 m_service_url = url;
163 m_service_method = method;
164}
165
166void WSRequest::RequestAccept(const std::string& contentType)
167{
168 m_accept = contentType;
169}
170
171void WSRequest::RequestAcceptEncoding(bool yesno)
172{
173#if HAVE_ZLIB
174 if (yesno)
175 SetHeader(ws_header_to_str(WS_HEADER_Accept_Encoding), "gzip, deflate");
176 else
177 SetHeader(ws_header_to_str(WS_HEADER_Accept_Encoding), "");
178#else
179 (void)yesno;
180 SetHeader(ws_header_to_str(WS_HEADER_Accept_Encoding), "");
181#endif
182}
183
184void WSRequest::SetUserAgent(const std::string& value)
185{
186 m_userAgent = value;
187}
188
189void WSRequest::SetHeader(const std::string& field, const std::string& value)
190{
191 std::string _key(field);
192 std::transform(_key.cbegin(), _key.cend(), _key.begin(), ::toupper);
193 m_headers[_key] = std::make_pair(field, value);
194}
195
196void WSRequest::ClearHeader(const std::string& field)
197{
198 std::string _key(field);
199 std::transform(_key.cbegin(), _key.cend(), _key.begin(), ::toupper);
200 std::map<std::string, header_t>::const_iterator it = m_headers.find(_key);
201 if (it != m_headers.end())
202 m_headers.erase(it);
203}
204
205void WSRequest::SetContentParam(const std::string& param, const std::string& value)
206{
207 if (m_contentType != WS_CTYPE_Form)
208 return;
209 if (!m_contentData.empty())
210 m_contentData.append("&");
211 m_contentData.append(param).append("=").append(urlencode(value));
212}
213
214void WSRequest::SetContentCustom(const std::string& contentType, const char *content)
215{
216 m_contentType = WS_CTYPE_UNKNOWN;
217 m_contentTypeStr = contentType;
218 m_contentData = content;
219}
220
221void WSRequest::ClearContent()
222{
223 m_contentData.clear();
224 m_contentType = WS_CTYPE_Form;
225 m_contentTypeStr.clear();
226}
227
228bool WSRequest::WriteMessage(WSRequestStreamSink& sink) const
229{
230 switch (m_service_method)
231 {
232 case WS_METHOD_Get:
233 return WriteMessageGET(sink, "GET");
234 case WS_METHOD_Post:
235 return WriteMessagePOST(sink, "POST");
236 case WS_METHOD_Head:
237 return WriteMessageGET(sink, "HEAD");
238 case WS_METHOD_Subscribe:
239 return WriteMessageGET(sink, "SUBSCRIBE");
240 case WS_METHOD_Unsubscribe:
241 return WriteMessageGET(sink, "UNSUBSCRIBE");
242 case WS_METHOD_Notify:
243 return WriteMessagePOST(sink, "NOTIFY");
244 case WS_METHOD_Put:
245 return WriteMessagePOST(sink, "PUT");
246 case WS_METHOD_Delete:
247 return WriteMessageGET(sink, "DELETE");
248 case WS_METHOD_Options:
249 return WriteMessageGET(sink, "OPTIONS");
250 default:
251 return false;
252 }
253}
254
255bool WSRequest::WriteCommonHeading(WSRequestStreamSink& sink) const
256{
257 BUILTIN_BUFFER buf;
258 std::string msg;
259 msg.reserve(255);
260
261 // Host
262 msg.append(ws_header_to_str(WS_HEADER_Host)).append(": ");
263 if (m_server.find(':') == std::string::npos)
264 msg.append(m_server);
265 else
266 msg.append("[").append(m_server).append("]");
267 unsigned len = uint_to_strdec(m_port, buf.data, 12, 0);
268 msg.append(":").append(buf.data, len).append(WS_CRLF);
269
270 // User-Agent
271 if (m_userAgent.empty())
272 msg.append(ws_header_to_str(WS_HEADER_User_Agent)).append(": " REQUEST_USER_AGENT WS_CRLF);
273 else
274 msg.append(ws_header_to_str(WS_HEADER_User_Agent)).append(": ").append(m_userAgent).append(WS_CRLF);
275
276 // Connection
277 msg.append(ws_header_to_str(WS_HEADER_Connection)).append(": " REQUEST_CONNECTION WS_CRLF);
278
279 // Accept
280 if (!m_accept.empty())
281 msg.append(ws_header_to_str(WS_HEADER_Accept)).append(": ").append(m_accept).append(WS_CRLF);
282
283 // Accept-Charset
284 msg.append(ws_header_to_str(WS_HEADER_Accept_Charset)).append(": ").append(m_charset).append(WS_CRLF);
285
286 if (!sink.WriteRequestStream(msg.c_str(), msg.size()))
287 return false;
288
289 // the rest
290 for (std::map<std::string, header_t>::const_iterator it = m_headers.begin(); it != m_headers.end(); ++it)
291 {
292 msg.assign(it->second.first).append(": ").append(it->second.second).append(WS_CRLF);
293 if (!sink.WriteRequestStream(msg.c_str(), msg.size()))
294 return false;
295 }
296
297 return true;
298}
299
300bool WSRequest::WriteMessageGET(WSRequestStreamSink& sink, const char* method) const
301{
302 std::string msg;
303 msg.reserve(127);
304
305 // the request with parameters
306 msg.append(method).append(" ").append(m_service_url);
307 if (!m_contentData.empty() && m_contentType == WS_CTYPE_Form)
308 msg.append("?").append(m_contentData);
309 msg.append(" " REQUEST_PROTOCOL WS_CRLF);
310
311 DBG(DBG_PROTO, "%s: %s", __FUNCTION__, msg.c_str());
312 if (!sink.WriteRequestStream(msg.c_str(), msg.size()))
313 return false;
314 if (!WriteCommonHeading(sink))
315 return false;
316 // close headers
317 if (!sink.WriteRequestStream(WS_CRLF, WS_CRLF_LEN))
318 return false;
319
320 return sink.FlushRequestStream();
321}
322
323bool WSRequest::WriteMessagePOST(WSRequestStreamSink& sink, const char* method) const
324{
325 std::string msg;
326 msg.reserve(127);
327
328 // the request
329 msg.append(method).append(" ").append(m_service_url).append(" " REQUEST_PROTOCOL WS_CRLF);
330
331 DBG(DBG_PROTO, "%s: %s", __FUNCTION__, msg.c_str());
332 if (!sink.WriteRequestStream(msg.c_str(), msg.size()))
333 return false;
334 if (!WriteCommonHeading(sink))
335 return false;
336
337 if (!m_contentData.empty() && m_contentType != WS_CTYPE_None)
338 {
339 BUILTIN_BUFFER buf;
340 unsigned bl = uint_to_strdec(m_contentData.size(), buf.data, 12, 0);
341 msg.clear();
342 if (m_contentType == WS_CTYPE_UNKNOWN)
343 msg.append(ws_header_to_str(WS_HEADER_Content_Type)).append(": ").append(m_contentTypeStr);
344 else
345 msg.append(ws_header_to_str(WS_HEADER_Content_Type)).append(": ").append(ws_ctype_to_str(m_contentType));
346 msg.append("; charset=" REQUEST_STD_CHARSET WS_CRLF);
347 msg.append(ws_header_to_str(WS_HEADER_Content_Length)).append(": ").append(buf.data, bl).append(WS_CRLF);
348 // close headers
349 msg.append(WS_CRLF);
350 if (!sink.WriteRequestStream(msg.c_str(), msg.size()))
351 return false;
352 // the body
353 if (!sink.WriteRequestStream(m_contentData.c_str(), m_contentData.size()))
354 return false;
355 }
356 else
357 {
358 // close headers
359 if (!sink.WriteRequestStream(WS_CRLF, WS_CRLF_LEN))
360 return false;
361 }
362
363 return sink.FlushRequestStream();
364}