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