1 /*******************************************************************************
2 *
3 * Copyright (c) 2000-2003 Intel Corporation
4 * All rights reserved.
5 * Copyright (c) 2012 France Telecom All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * - Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 * - Neither name of Intel Corporation nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
27 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 *
31 ******************************************************************************/
32
33 /* Misc HTTP-related utilities */
34
35 #include "config.h"
36
37 #include "httputils.h"
38
39 #include <cctype>
40 #include <cinttypes>
41 #include <climits>
42 #include <cstdio>
43 #include <cstdlib>
44 #include <cstring>
45 #include <cstdint>
46
47 #include <string>
48 #include <sstream>
49 #include <iostream>
50 #include <map>
51
52 #include <curl/curl.h>
53
54 #include "UpnpInet.h"
55 #include "genut.h"
56 #include "statcodes.h"
57 #include "upnp.h"
58 #include "upnpdebug.h"
59 #include "uri.h"
60 #include "genut.h"
61 #include "upnpapi.h"
62
63 static const std::string bogus_soap_post{"SMPOST"};
64 static const std::map<std::string, int> Http_Method_Table {
65 {"GET", HTTPMETHOD_GET},
66 {"HEAD", HTTPMETHOD_HEAD},
67 {"M-POST", HTTPMETHOD_MPOST},
68 {"M-SEARCH", HTTPMETHOD_MSEARCH},
69 {"NOTIFY", HTTPMETHOD_NOTIFY},
70 {"POST", HTTPMETHOD_POST},
71 {"SUBSCRIBE", HTTPMETHOD_SUBSCRIBE},
72 {"UNSUBSCRIBE", HTTPMETHOD_UNSUBSCRIBE},
73 {bogus_soap_post, SOAPMETHOD_POST},
74 };
75
76 static const std::map<std::string, int> Http_Header_Names {
77 {"accept", HDR_ACCEPT},
78 {"accept-charset", HDR_ACCEPT_CHARSET},
79 {"accept-encoding", HDR_ACCEPT_ENCODING},
80 {"accept-language", HDR_ACCEPT_LANGUAGE},
81 {"accept-ranges", HDR_ACCEPT_RANGE},
82 {"cache-control", HDR_CACHE_CONTROL},
83 {"callback", HDR_CALLBACK},
84 {"content-encoding", HDR_CONTENT_ENCODING},
85 {"content-language", HDR_CONTENT_LANGUAGE},
86 {"content-length", HDR_CONTENT_LENGTH},
87 {"content-location", HDR_CONTENT_LOCATION},
88 {"content-range", HDR_CONTENT_RANGE},
89 {"content-type", HDR_CONTENT_TYPE},
90 {"date", HDR_DATE},
91 {"ext", HDR_EXT},
92 {"host", HDR_HOST},
93 {"if-range", HDR_IF_RANGE},
94 {"location", HDR_LOCATION},
95 {"man", HDR_MAN},
96 {"mx", HDR_MX},
97 {"nt", HDR_NT},
98 {"nts", HDR_NTS},
99 {"range", HDR_RANGE},
100 {"seq", HDR_SEQ},
101 {"server", HDR_SERVER},
102 {"sid", HDR_SID},
103 {"soapaction", HDR_SOAPACTION},
104 {"st", HDR_ST},
105 {"te", HDR_TE},
106 {"timeout", HDR_TIMEOUT},
107 {"transfer-encoding", HDR_TRANSFER_ENCODING},
108 {"user-agent", HDR_USER_AGENT},
109 {"usn", HDR_USN},
110 };
111
copyClientAddress(struct sockaddr_storage * dest) const112 void MHDTransaction::copyClientAddress(struct sockaddr_storage *dest) const
113 {
114 if (nullptr == dest)
115 return;
116 if (nullptr == client_address) {
117 *dest = {};
118 return;
119 }
120 if (client_address->ss_family == AF_INET) {
121 memcpy(dest, client_address, sizeof(struct sockaddr_in));
122 } else {
123 memcpy(dest, client_address, sizeof(struct sockaddr_in6));
124 }
125 }
126
copyHeader(const std::string & name,std::string & value)127 bool MHDTransaction::copyHeader(const std::string& name,
128 std::string& value)
129 {
130 auto it = headers.find(stringtolower(name));
131 if (it == headers.end()) {
132 return false;
133 }
134 value = it->second;
135 return true;
136 }
137
httpmethod_str2enum(const char * methname)138 http_method_t httpmethod_str2enum(const char *methname)
139 {
140 const auto it = Http_Method_Table.find(methname);
141 if (it == Http_Method_Table.end()) {
142 return HTTPMETHOD_UNKNOWN;
143 }
144
145 return static_cast<http_method_t>(it->second);
146 }
147
httpheader_str2int(const std::string & headername)148 int httpheader_str2int(const std::string& headername)
149 {
150 auto it = Http_Header_Names.find(headername);
151 if (it == Http_Header_Names.end())
152 return -1;
153 return it->second;
154 }
155
http_FixStrUrl(const std::string & surl,uri_type * fixed_url)156 int http_FixStrUrl(const std::string& surl, uri_type *fixed_url)
157 {
158 uri_type url;
159
160 if (parse_uri(surl, &url) != UPNP_E_SUCCESS) {
161 return UPNP_E_INVALID_URL;
162 }
163
164 *fixed_url = url;
165 if (stringlowercmp("http", fixed_url->scheme) ||
166 fixed_url->hostport.text.empty()) {
167 return UPNP_E_INVALID_URL;
168 }
169
170 /* set pathquery to "/" if it is empty */
171 if (fixed_url->path.empty()) {
172 fixed_url->path = "/";
173 }
174
175 return UPNP_E_SUCCESS;
176 }
177
178 /************************************************************************
179 * Function: http_Download
180 *
181 * Parameters:
182 * IN const char* url_str; String as a URL
183 * IN int timeout_secs; time out value
184 * OUT char** document; buffer to store the document extracted
185 * from the donloaded message.
186 * OUT int* doc_length; length of the extracted document
187 * OUT char* content_type; Type of content
188 *
189 * Description:
190 * Download the document message and extract the document
191 * from the message.
192 *
193 * Return: int
194 * UPNP_E_SUCCESS
195 * UPNP_E_INVALID_URL
196 ************************************************************************/
http_Download(const char * _surl,int timeout_secs,char ** document,size_t *,char * content_type)197 int http_Download(const char *_surl, int timeout_secs,
198 char **document, size_t *, char *content_type)
199 {
200 uri_type url;
201 UpnpPrintf(UPNP_INFO, HTTP, __FILE__, __LINE__, "http_Download: %s\n",_surl);
202 int ret_code = http_FixStrUrl(_surl, &url);
203 if (ret_code != UPNP_E_SUCCESS)
204 return ret_code;
205
206 std::map<std::string, std::string> http_headers;
207 std::string data;
208
209 CURL *easy = curl_easy_init();
210 char curlerrormessage[CURL_ERROR_SIZE];
211 curl_easy_setopt(easy, CURLOPT_ERRORBUFFER, curlerrormessage);
212 std::string surl = uri_asurlstr(url);
213 curl_easy_setopt(easy, CURLOPT_URL, surl.c_str());
214 curl_easy_setopt(easy, CURLOPT_TIMEOUT, timeout_secs);
215 curl_easy_setopt(easy, CURLOPT_HEADERFUNCTION, header_callback_curl);
216 curl_easy_setopt(easy, CURLOPT_HEADERDATA, &http_headers);
217 curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, write_callback_str_curl);
218 curl_easy_setopt(easy, CURLOPT_WRITEDATA, &data);
219
220 struct curl_slist *list = nullptr;
221 list = curl_slist_append(list, (std::string("USER-AGENT: ") + get_sdk_client_info()).c_str());
222 list = curl_slist_append(list, "Connection: close");
223 curl_easy_setopt(easy, CURLOPT_HTTPHEADER, list);
224
225 CURLcode code = curl_easy_perform(easy);
226
227 if (code != CURLE_OK) {
228 curl_easy_cleanup(easy);
229 curl_slist_free_all(list);
230 /* We may want to detail things here, depending on the curl error */
231 UpnpPrintf(UPNP_INFO, HTTP, __FILE__, __LINE__,
232 "http_Download: curl failed with: %s\n", curlerrormessage);
233 return UPNP_E_SOCKET_CONNECT;
234 }
235 long http_status;
236 curl_easy_getinfo (easy, CURLINFO_RESPONSE_CODE, &http_status);
237 UpnpPrintf(UPNP_INFO, HTTP, __FILE__, __LINE__, "Response. Status %ld\n",
238 http_status);
239
240 curl_easy_cleanup(easy);
241 curl_slist_free_all(list);
242
243 /* optional content-type */
244 if (content_type) {
245 auto it = http_headers.find("content-type");
246 if (it == http_headers.end()) {
247 *content_type = '\0'; /* no content-type */
248 } else {
249 upnp_strlcpy(content_type, it->second, LINE_SIZE);
250 }
251 }
252
253 auto it = http_headers.find("content-length");
254 if (it != http_headers.end()) {
255 uint64_t sizefromheaders = atoll(it->second.c_str());
256 if (sizefromheaders != data.size()) {
257 UpnpPrintf(UPNP_INFO, HTTP, __FILE__, __LINE__,
258 "Response content-length %" PRIu64
259 " differs from data size %"
260 PRIu64 "\n", sizefromheaders, static_cast<uint64_t>(data.size()));
261 }
262 }
263
264 *document = nullptr;
265 if (http_status == HTTP_OK) {
266 /* extract doc from msg */
267 if (!data.empty()) {
268 *document = nullptr;
269 *document = static_cast<char *>(malloc(data.size() + 1));
270 if (*document == nullptr) {
271 return UPNP_E_OUTOF_MEMORY;
272 }
273 memcpy(*document, data.c_str(), data.size());
274 (*document)[data.size()] = 0;
275 }
276 return 0;
277 }
278
279 return http_status;
280 }
281
282 /************************************************************************
283 * Function: http_SendStatusResponse
284 *
285 * Parameters:
286 * IN int http_status_code; error code returned while making
287 * or sending the response message
288 * IN int request_major_version; request major version
289 * IN int request_minor_version; request minor version
290 *
291 * Description:
292 * Generate a response message for the status query and send the
293 * status response.
294 *
295 * Return: int
296 * 0 -- success
297 * UPNP_E_OUTOF_MEMORY
298 * UPNP_E_SOCKET_WRITE
299 * UPNP_E_TIMEDOUT
300 ************************************************************************/
http_SendStatusResponse(MHDTransaction * mhdt,int status_code)301 int http_SendStatusResponse(MHDTransaction *mhdt, int status_code)
302 {
303 std::ostringstream body;
304 body << "<html><body><h1>" << status_code << " " <<
305 http_get_code_text(status_code) << "</h1></body></html>";
306 mhdt->response = MHD_create_response_from_buffer(
307 body.str().size(), const_cast<char*>(body.str().c_str()), MHD_RESPMEM_MUST_COPY);
308 MHD_add_response_header(mhdt->response, "Content-Type", "text/html");
309 mhdt->httpstatus = status_code;
310 return UPNP_E_SUCCESS;
311 }
312
has_xml_content_type(MHDTransaction * mhdt)313 bool has_xml_content_type(MHDTransaction *mhdt)
314 {
315 static const char *xmlmtype = "text/xml";
316 static const size_t mtlen = strlen(xmlmtype);
317
318 auto it = mhdt->headers.find("content-type");
319 if (it == mhdt->headers.end()) {
320 UpnpPrintf(UPNP_INFO, HTTP, __FILE__, __LINE__,
321 "has_xml_content: no content type header\n");
322 return false;
323 }
324 if (strncasecmp(xmlmtype, it->second.c_str(), mtlen)) {
325 UpnpPrintf(UPNP_INFO, HTTP, __FILE__, __LINE__, "has_xml_content: "
326 "text/xml not found in [%s]\n", it->second.c_str());
327 return false;
328 }
329 return true;
330 }
331
timeout_header_value(std::map<std::string,std::string> & headers,int * time_out)332 bool timeout_header_value(std::map<std::string, std::string>& headers,
333 int *time_out)
334 {
335 auto ittimo = headers.find("timeout");
336 if (ittimo == headers.end()) {
337 UpnpPrintf(UPNP_INFO, HTTP, __FILE__, __LINE__,
338 "timeout_header_value: no timeout header\n");
339 return false;
340 }
341 stringtolower(ittimo->second);
342 if (ittimo->second == "second-infinite") {
343 *time_out = -1;
344 return true;
345 }
346 char cbuf[2];
347 if (sscanf(ittimo->second.c_str(),"second-%d%1c",time_out,cbuf) != 1) {
348 UpnpPrintf(UPNP_INFO, HTTP, __FILE__, __LINE__, "timeout_header_value: "
349 "bad header value [%s]\n", ittimo->second.c_str());
350 return false;
351 }
352 return true;
353 }
354
355 #ifdef _WIN32
http_gmtime_r(const time_t * clock,struct tm * result)356 struct tm *http_gmtime_r(const time_t *clock, struct tm *result)
357 {
358 if (clock == NULL || *clock < 0 || result == NULL)
359 return NULL;
360
361 /* gmtime in VC runtime is thread safe. */
362 *result = *gmtime(clock);
363 return result;
364 }
365
366 #else /* !_WIN32 ->*/
367
368 #include <sys/utsname.h>
369 #define http_gmtime_r gmtime_r
370
371 #endif
372
get_sdk_common_info()373 static const std::string& get_sdk_common_info()
374 {
375 static std::string sdk_common_info;
376 if (sdk_common_info.empty()) {
377 std::ostringstream ostr;
378 #ifdef UPNP_ENABLE_UNSPECIFIED_SERVER
379 ostr << "Unspecified UPnP/1.0 Unspecified"
380 #else /* UPNP_ENABLE_UNSPECIFIED_SERVER */
381 #ifdef _WIN32
382 OSVERSIONINFO versioninfo;
383 versioninfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
384
385 if (GetVersionEx(&versioninfo) != 0)
386 ostr << versioninfo.dwMajorVersion << "." <<
387 versioninfo.dwMinorVersion << "." <<
388 versioninfo.dwBuildNumber << " " << versioninfo.dwPlatformId
389 << "/" << versioninfo.szCSDVersion;
390 #else
391 struct utsname sys_info;
392
393 if (uname(&sys_info) != -1)
394 ostr << sys_info.sysname << "/" << sys_info.release;
395 #endif
396 #endif /* UPNP_ENABLE_UNSPECIFIED_SERVER */
397
398 ostr << " UPnP/1.0 ";
399 sdk_common_info = ostr.str();
400 }
401 return sdk_common_info;
402 }
403
get_sdk_device_info(const std::string & customvalue)404 const std::string get_sdk_device_info(const std::string& customvalue)
405 {
406 return get_sdk_common_info() +
407 (!customvalue.empty() ? customvalue :
408 std::string("Portable SDK for UPnP devices/" PACKAGE_VERSION));
409 }
410
get_sdk_client_info(const std::string & newvalue)411 const std::string& get_sdk_client_info(const std::string& newvalue)
412 {
413 static std::string sdk_client_info;
414 if (sdk_client_info.empty() || !newvalue.empty()) {
415 // If this was never set, or the client wants to set its name, compute
416 sdk_client_info = get_sdk_common_info() +
417 (!newvalue.empty() ? newvalue :
418 std::string("Portable SDK for UPnP devices/" PACKAGE_VERSION));
419 }
420
421 return sdk_client_info;
422 }
423
make_date_string(time_t thetime)424 std::string make_date_string(time_t thetime)
425 {
426 const char *weekday_str = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
427 const char *month_str = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0"
428 "Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
429
430 time_t curr_time = thetime ? thetime : time(nullptr);
431 struct tm date_storage;
432 struct tm *date = http_gmtime_r(&curr_time, &date_storage);
433 if (date == nullptr)
434 return std::string();
435 char tempbuf[200];
436 snprintf(tempbuf, sizeof(tempbuf),
437 "%s, %02d %s %d %02d:%02d:%02d GMT",
438 &weekday_str[date->tm_wday * 4],
439 date->tm_mday, &month_str[date->tm_mon * 4],
440 date->tm_year + 1900, date->tm_hour,
441 date->tm_min, date->tm_sec);
442 return tempbuf;
443 }
444
query_encode(const std::string & qs)445 std::string query_encode(const std::string& qs)
446 {
447 std::string out;
448 out.reserve(qs.size());
449 const char *h = "0123456789ABCDEF";
450 const char *cp = qs.c_str();
451 while (*cp) {
452 if ((*cp >= 'A' && *cp <= 'Z') ||
453 (*cp >= 'a' && *cp <= 'z') || (*cp >= '0' && *cp <= '9') ||
454 *cp == '*' || *cp == '-' || *cp== '.' || *cp == '_') {
455 out += *cp;
456 } else {
457 out += '%';
458 out += h[((uint32_t(*cp)) >> 4) & 0xf];
459 out += h[uint32_t(*cp) & 0xf];
460 }
461 cp++;
462 }
463 return out;
464 }
465
header_callback_curl(char * buffer,size_t size,size_t nitems,void * s)466 size_t header_callback_curl(char *buffer, size_t size, size_t nitems, void *s)
467 {
468 size_t bufsize = size * nitems;
469 auto headers = static_cast<std::map<std::string, std::string>*>(s);
470 const char *colon = std::strchr(buffer, ':');
471 if (nullptr != colon) {
472 size_t colpos = colon - buffer;
473 std::string nm = std::string(buffer, colpos);
474 std::string value = std::string(colon + 1, bufsize - colpos -1);
475 if (!nm.empty()) {
476 trimstring(nm, " \t");
477 stringtolower(nm);
478 trimstring(value, " \t\r\n");
479 UpnpPrintf(UPNP_ALL, HTTP, __FILE__, __LINE__,
480 "CURL header: [%s] -> [%s]\n", nm.c_str(), value.c_str());
481 (*headers)[nm] = value;
482 }
483 }
484 return bufsize;
485 }
486
write_callback_null_curl(char * buffer,size_t size,size_t nitems,void *)487 size_t write_callback_null_curl(char *buffer, size_t size, size_t nitems, void *)
488 {
489 (void)buffer;
490 #if 0
491 fprintf(stderr, "DATA: [");
492 fwrite(buffer, size, nitems, stderr);
493 fprintf(stderr, "]\n");
494 fflush(stderr);
495 #endif
496 return size*nitems;
497 }
498
write_callback_str_curl(char * buf,size_t sz,size_t nits,void * s)499 size_t write_callback_str_curl(char *buf, size_t sz, size_t nits, void *s)
500 {
501 (static_cast<std::string*>(s))->append(buf, sz * nits);
502 return sz * nits;
503 }
504