1 /**************************************************************************
2 *
3 * Copyright (c) 2000-2003 Intel Corporation
4 * All rights reserved.
5 * Copyright (c) 2012 France Telecom All rights reserved.
6 * Copyright (c) 2020 J.F. Dockes
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
10 *
11 * - Redistributions of source code must retain the above copyright notice,
12 * this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above copyright notice,
14 * this list of conditions and the following disclaimer in the documentation
15 * and/or other materials provided with the distribution.
16 * - Neither name of Intel Corporation nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 *
32 **************************************************************************/
33
34 /*!
35 * \file
36 *
37 * \brief General purpose web server (non-soap/gena/ssdp requests)
38 */
39
40 #include "config.h"
41
42 #if EXCLUDE_WEB_SERVER == 0
43
44 #include "webserver.h"
45
46 #include <map>
47 #include <iostream>
48 #include <algorithm>
49 #include <mutex>
50 #include <condition_variable>
51
52 #include <cinttypes>
53
54 #include "httputils.h"
55 #include "ssdplib.h"
56 #include "statcodes.h"
57 #include "upnp.h"
58 #include "upnpapi.h"
59 #include "VirtualDir.h"
60 #include "genut.h"
61
62 #include <cassert>
63 #include <fcntl.h>
64 #include <sys/stat.h>
65
66 #if !defined(S_IFLNK)
67 #define S_IFLNK 0
68 #endif
69 #ifndef S_ISDIR
70 # define S_ISDIR(ST_MODE) (((ST_MODE) & _S_IFMT) == _S_IFDIR)
71 #endif
72 #ifndef S_ISREG
73 # define S_ISREG(ST_MODE) (((ST_MODE) & _S_IFMT) == _S_IFREG)
74 #endif
75
76 #ifdef _MSC_VER
77 #include <io.h>
78 #define OPEN _open
79 #else
80 #define OPEN open
81 #endif
82
83 /*!
84 * Response Types.
85 */
86 enum resp_type {
87 RESP_FILEDOC,
88 RESP_WEBDOC,
89 RESP_XMLDOC,
90 };
91
92 struct SendInstruction {
93 std::string AcceptLanguageHeader;
94 /* Offset to begin reading at. Set by range header if present */
95 int64_t offset{0};
96 /*! Requested read size. -1 -> end of resource. Set by range
97 header if present */
98 int64_t ReadSendSize{-1};
99 /*! Cookie associated with the virtualDir. This is set if the path
100 matches a VirtualDir entry, and is passed to subsequent
101 virtual dir calls. */
102 const void *cookie{nullptr};
103 /* Copy of the data if this request is for a localdoc, e.g. the
104 description document set by the API if we serve it this way,
105 instead of as a local file or a virtualdir entry (this depends
106 on the kind of registerrootdevice call) */
107 std::string data;
108 /* This is set by the Virtual Dir GetInfo user callback and passed to
109 further VirtualDirectory calls for the same request */
110 const void* request_cookie{nullptr};
111 };
112
113 /*!
114 * module variables - Globals, static and externs.
115 */
116
117 static const std::map<std::string, const char*> gEncodedMediaTypes = {
118 {"aif", "audio/aiff"},
119 {"aifc", "audio/aiff"},
120 {"aiff", "audio/aiff"},
121 {"asf", "video/x-ms-asf"},
122 {"asx", "video/x-ms-asf"},
123 {"au", "audio/basic"},
124 {"avi", "video/msvideo"},
125 {"bmp", "image/bmp"},
126 {"css", "text/css"},
127 {"dcr", "application/x-director"},
128 {"dib", "image/bmp"},
129 {"dir", "application/x-director"},
130 {"dxr", "application/x-director"},
131 {"gif", "image/gif"},
132 {"hta", "text/hta"},
133 {"htm", "text/html"},
134 {"html", "text/html"},
135 {"jar", "application/java-archive"},
136 {"jfif", "image/pjpeg"},
137 {"jpe", "image/jpeg"},
138 {"jpeg", "image/jpeg"},
139 {"jpg", "image/jpeg"},
140 {"js", "application/x-javascript"},
141 {"kar", "audio/midi"},
142 {"m3u", "audio/mpegurl"},
143 {"mid", "audio/midi"},
144 {"midi", "audio/midi"},
145 {"mov", "video/quicktime"},
146 {"mp2v", "video/x-mpeg2"},
147 {"mp3", "audio/mpeg"},
148 {"mpe", "video/mpeg"},
149 {"mpeg", "video/mpeg"},
150 {"mpg", "video/mpeg"},
151 {"mpv", "video/mpeg"},
152 {"mpv2", "video/x-mpeg2"},
153 {"pdf", "application/pdf"},
154 {"pjp", "image/jpeg"},
155 {"pjpeg", "image/jpeg"},
156 {"plg", "text/html"},
157 {"pls", "audio/scpls"},
158 {"png", "image/png"},
159 {"qt", "video/quicktime"},
160 {"ram", "audio/x-pn-realaudio"},
161 {"rmi", "audio/mid"},
162 {"rmm", "audio/x-pn-realaudio"},
163 {"rtf", "application/rtf"},
164 {"shtml", "text/html"},
165 {"smf", "audio/midi"},
166 {"snd", "audio/basic"},
167 {"spl", "application/futuresplash"},
168 {"ssm", "application/streamingmedia"},
169 {"swf", "application/x-shockwave-flash"},
170 {"tar", "application/tar"},
171 {"tcl", "application/x-tcl"},
172 {"text", "text/plain"},
173 {"tif", "image/tiff"},
174 {"tiff", "image/tiff"},
175 {"txt", "text/plain"},
176 {"ulw", "audio/basic"},
177 {"wav", "audio/wav"},
178 {"wax", "audio/x-ms-wax"},
179 {"wm", "video/x-ms-wm"},
180 {"wma", "audio/x-ms-wma"},
181 {"wmv", "video/x-ms-wmv"},
182 {"wvx", "video/x-ms-wvx"},
183 {"xbm", "image/x-xbitmap"},
184 {"xml", "text/xml"},
185 {"xsl", "text/xml"},
186 {"z", "application/x-compress"},
187 {"zip", "application/zip"}
188 };
189
190
191 /*! Global variable. A file system directory which serves as webserver
192 root. If this is not set from the API UpnpSetWebServerRootDir()
193 call, we do not serve files from the file system at all (only
194 possibly the virtual dir and/or the localDocs). */
195 static std::string gDocumentRootDir;
196
197 struct LocalDoc {
198 std::string data;
199 time_t last_modified;
200 };
201
202 // Data which we serve directly: usually description
203 // documents. Indexed by path. Content-Type is always text/xml
204 // The map is tested after the virtualdir, so the latter has priority.
205 static std::map<std::string, LocalDoc> localDocs;
206
207 static std::mutex gWebMutex;
208
209 class VirtualDirListEntry {
210 public:
211 std::string path;
212 const void *cookie;
213 };
214 /* Virtual directory list. This is used with a linear search by the
215 web server to perform prefix matches. */
216 static std::vector<VirtualDirListEntry> virtualDirList;
217 static std::mutex vdlmutex;
218
219
220 /* Compute MIME type from file name extension. */
get_content_type(const char * filename,std::string & content_type)221 static UPNP_INLINE int get_content_type(
222 const char *filename, std::string& content_type)
223 {
224 const char *ctname = "application/octet-stream";
225 content_type.clear();
226 /* get ext */
227 const char *e = strrchr(filename, '.');
228 if (e) {
229 e++;
230 std::string le = stringtolower(e);
231 auto it = gEncodedMediaTypes.find(le);
232 if (it != gEncodedMediaTypes.end()) {
233 ctname = it->second;
234 }
235 }
236 content_type = ctname;
237 return 0;
238 }
239
web_server_set_localdoc(const std::string & path,const std::string & data,time_t last_modified)240 int web_server_set_localdoc(
241 const std::string& path, const std::string& data, time_t last_modified)
242 {
243 if (path.empty() || path.front() != '/') {
244 return UPNP_E_INVALID_PARAM;
245 }
246 LocalDoc doc;
247 doc.data = data;
248 doc.last_modified = last_modified;
249 std::unique_lock<std::mutex> lck(gWebMutex);
250 localDocs[path] = doc;
251 return UPNP_E_SUCCESS;
252 }
253
web_server_unset_localdoc(const std::string & path)254 int web_server_unset_localdoc(const std::string& path)
255 {
256 std::unique_lock<std::mutex> lck(gWebMutex);
257 auto it = localDocs.find(path);
258 if (it != localDocs.end())
259 localDocs.erase(it);
260 return UPNP_E_SUCCESS;
261 }
262
263 /* Get file information, local file system version */
get_file_info(const char * filename,struct File_Info * info)264 static int get_file_info(const char *filename, struct File_Info *info)
265 {
266 info->content_type.clear();
267 struct stat s;
268 if (stat(filename, &s) == -1)
269 return -1;
270 if (S_ISDIR(s.st_mode))
271 info->is_directory = true;
272 else if (S_ISREG(s.st_mode))
273 info->is_directory = false;
274 else
275 return -1;
276 /* check readable */
277 FILE *fp = fopen(filename, "r");
278 info->is_readable = (fp != nullptr);
279 if (fp)
280 fclose(fp);
281 info->file_length = s.st_size;
282 info->last_modified = s.st_mtime;
283 int rc = get_content_type(filename, info->content_type);
284 UpnpPrintf(UPNP_INFO, HTTP, __FILE__, __LINE__,
285 "get_file_info: %s, sz: %" PRIi64 ", mtime=%s rdable=%d\n",
286 filename, info->file_length,
287 make_date_string(info->last_modified).c_str(),
288 info->is_readable);
289
290 return rc;
291 }
292
web_server_set_root_dir(const char * root_dir)293 int web_server_set_root_dir(const char *root_dir)
294 {
295 gDocumentRootDir = root_dir;
296 /* remove trailing '/', if any */
297 if (!gDocumentRootDir.empty() && gDocumentRootDir.back() == '/') {
298 gDocumentRootDir.pop_back();
299 }
300
301 return 0;
302 }
303
304
web_server_add_virtual_dir(const char * dirname,const void * cookie,const void ** oldcookie)305 int web_server_add_virtual_dir(
306 const char *dirname, const void *cookie, const void **oldcookie)
307 {
308 if (!dirname || !*dirname) {
309 return UPNP_E_INVALID_PARAM;
310 }
311
312 UpnpPrintf(UPNP_DEBUG, HTTP, __FILE__, __LINE__,
313 "web_server_add_virtual_dir: [%s]\n", dirname);
314
315 VirtualDirListEntry entry;
316 entry.cookie = cookie;
317 if (dirname[0] != '/') {
318 // Gerbera does this ?? I think that it should be illegal,
319 // esp. since it then proceeds to use the absolute path for
320 // requests. Not sure why it works with libupnp
321 entry.path = std::string("/") + dirname;
322 } else {
323 entry.path = dirname;
324 }
325 if (entry.path.back() != '/') {
326 entry.path += '/';
327 }
328
329 std::lock_guard<std::mutex> lock(vdlmutex);
330 auto old = std::find_if(virtualDirList.begin(), virtualDirList.end(),
331 [entry](const VirtualDirListEntry& old) {
332 return entry.path == old.path;
333 });
334 if (old != virtualDirList.end()) {
335 if (oldcookie) {
336 *oldcookie = old->cookie;
337 }
338 *old = entry;
339 } else {
340 virtualDirList.push_back(entry);
341 }
342 return UPNP_E_SUCCESS;
343 }
344
web_server_remove_virtual_dir(const char * dirname)345 int web_server_remove_virtual_dir(const char *dirname)
346 {
347 if (dirname == nullptr) {
348 return UPNP_E_INVALID_PARAM;
349 }
350 std::lock_guard<std::mutex> lock(vdlmutex);
351 for (auto it = virtualDirList.begin(); it != virtualDirList.end(); it++) {
352 if (it->path == dirname) {
353 virtualDirList.erase(it);
354 return UPNP_E_SUCCESS;
355 }
356 }
357
358 return UPNP_E_INVALID_PARAM;
359 }
360
web_server_clear_virtual_dirs()361 void web_server_clear_virtual_dirs()
362 {
363 std::lock_guard<std::mutex> lock(vdlmutex);
364 virtualDirList.clear();
365 }
366
367 /*!
368 * \brief Compares filePath with paths from the list of virtual directory
369 * lists.
370 *
371 * \return nullptr or entry pointer.
372 */
isFileInVirtualDir(const std::string & path)373 static const VirtualDirListEntry *isFileInVirtualDir(const std::string& path)
374 {
375 // We ensure that vd entries paths end with /. Meaning that if
376 // the paths compare equal up to the vd path len, the input
377 // path is in a subdir of the vd path.
378 std::lock_guard<std::mutex> lock(vdlmutex);
379 for (const auto& vd : virtualDirList)
380 if (!vd.path.compare(0, vd.path.size(), path, 0, vd.path.size()))
381 return &vd;
382
383 return nullptr;
384 }
385
386 /* Parse a Range header */
parseRanges(const std::string & ranges,std::vector<std::pair<int64_t,int64_t>> & oranges)387 static bool parseRanges(
388 const std::string& ranges, std::vector<std::pair<int64_t, int64_t>>& oranges)
389 {
390 oranges.clear();
391 std::string::size_type pos = ranges.find("bytes=");
392 if (pos == std::string::npos) {
393 return false;
394 }
395 pos += 6;
396 bool done = false;
397 while(!done) {
398 std::string::size_type dash = ranges.find('-', pos);
399 if (dash == std::string::npos) {
400 return false;
401 }
402 std::string::size_type comma = ranges.find(',', pos);
403 std::string firstPart = ranges.substr(pos, dash-pos);
404 int64_t start = firstPart.empty() ? 0 : atoll(firstPart.c_str());
405 std::string secondPart = ranges.substr(
406 dash+1, comma != std::string::npos ?
407 comma-dash-1 : std::string::npos);
408 int64_t fin = secondPart.empty() ? -1 : atoll(secondPart.c_str());
409 std::pair<int64_t, int64_t> nrange(start,fin);
410 oranges.push_back(nrange);
411 if (comma != std::string::npos) {
412 pos = comma + 1;
413 }
414 done = comma == std::string::npos;
415 }
416 return true;
417 }
418
419 /*!
420 * \brief Other header processing. Only HDR_ACCEPT_LANGUAGE for now.
421 */
CheckOtherHTTPHeaders(MHDTransaction * mhdt,struct SendInstruction * RespInstr,int64_t)422 static int CheckOtherHTTPHeaders(
423 MHDTransaction *mhdt, struct SendInstruction *RespInstr, int64_t)
424 {
425 for (const auto& header : mhdt->headers) {
426 /* find header type. */
427 int index = httpheader_str2int(header.first);
428 const std::string& hvalue = header.second;
429 if (index >= 0) {
430 switch (index) {
431 case HDR_ACCEPT_LANGUAGE:
432 RespInstr->AcceptLanguageHeader = hvalue;
433 break;
434 default:
435 /* TODO? */
436 break;
437 }
438 }
439 }
440
441 return HTTP_OK;
442 }
443
444 /*!
445 * \brief Processes the request and returns the result in the output parameters.
446 *
447 * \return HTTP status code.
448 */
process_request(MHDTransaction * mhdt,enum resp_type * rtype,std::map<std::string,std::string> & headers,std::string & filename,struct SendInstruction * RespInstr)449 static int process_request(
450 MHDTransaction *mhdt,
451 /*! [out] Type of response: what source type the data will come from. */
452 enum resp_type *rtype,
453 /*! [out] Headers for the response. */
454 std::map<std::string, std::string>& headers,
455 /*! [out] Computed actual file path. */
456 std::string& filename,
457 /*! [out] Send Instruction object where the response is set up. */
458 struct SendInstruction *RespInstr)
459 {
460 struct File_Info finfo;
461 LocalDoc localdoc;
462
463 assert(mhdt->method == HTTPMETHOD_GET ||
464 mhdt->method == HTTPMETHOD_HEAD ||
465 mhdt->method == HTTPMETHOD_POST ||
466 mhdt->method == HTTPMETHOD_SIMPLEGET);
467
468 if (mhdt->method == HTTPMETHOD_POST) {
469 return HTTP_FORBIDDEN;
470 }
471
472 std::vector<std::pair<int64_t, int64_t> > ranges;
473 auto it = mhdt->headers.find("range");
474 if (it != mhdt->headers.end()) {
475 if (parseRanges(it->second, ranges) && !ranges.empty()) {
476 if (ranges.size() > 1) {
477 return HTTP_REQUEST_RANGE_NOT_SATISFIABLE;
478 }
479
480 RespInstr->offset = ranges[0].first;
481 if (ranges[0].second >= 0) {
482 RespInstr->ReadSendSize = ranges[0].second -
483 ranges[0].first + 1;
484 if (RespInstr->ReadSendSize < 0) {
485 RespInstr->ReadSendSize = 0;
486 }
487 } else {
488 RespInstr->ReadSendSize = -1;
489 }
490 }
491 }
492
493 /* init */
494 const VirtualDirListEntry *entryp{nullptr};
495
496 /* Data we supply as input to the file info gathering functions */
497 finfo.request_headers = mhdt->headers;
498 mhdt->copyClientAddress(&finfo.CtrlPtIPAddr);
499 mhdt->copyHeader("user-agent", finfo.Os);
500
501 /* Unescape and canonize the path. Note that MHD has already
502 stripped a possible query part ("?param=value...) for us */
503 std::string request_doc = remove_escaped_chars(mhdt->url);
504 request_doc = remove_dots(request_doc);
505 if (request_doc.empty()) {
506 return HTTP_FORBIDDEN;
507 }
508 if (request_doc[0] != '/') {
509 /* no slash */
510 return HTTP_BAD_REQUEST;
511 }
512 entryp = isFileInVirtualDir(request_doc);
513 if (!entryp) {
514 std::unique_lock<std::mutex> lck(gWebMutex);
515 auto localdocit = localDocs.find(request_doc);
516 // Just make a copy. Could do better using a
517 // map<string,share_ptr> like the original, but I don't think
518 // that the perf impact is significant
519 if (localdocit != localDocs.end()) {
520 localdoc = localdocit->second;
521 }
522 }
523 if (entryp) {
524 *rtype = RESP_WEBDOC;
525 RespInstr->cookie = entryp->cookie;
526 filename = request_doc;
527 std::string qs;
528 if (!mhdt->queryvalues.empty()) {
529 qs = "?";
530 for (const auto& entry : mhdt->queryvalues) {
531 qs += query_encode(entry.first) + "=" +
532 query_encode(entry.second);
533 qs += "&";
534 }
535 qs.pop_back();
536 }
537 std::string bfilename{filename};
538 filename += qs;
539 /* get file info */
540 if (virtualDirCallback.get_info(
541 filename.c_str(), &finfo, entryp->cookie,
542 &RespInstr->request_cookie) != UPNP_E_SUCCESS) {
543 return HTTP_NOT_FOUND;
544 }
545 /* try index.html if req is a dir */
546 if (finfo.is_directory) {
547 const char *temp_str;
548 if (bfilename.back() == '/') {
549 temp_str = "index.html";
550 } else {
551 temp_str = "/index.html";
552 }
553 bfilename += temp_str;
554 filename = bfilename + qs;
555 /* get info */
556 if ((virtualDirCallback.get_info(
557 filename.c_str(), &finfo, entryp->cookie,
558 &RespInstr->request_cookie) != UPNP_E_SUCCESS) ||
559 finfo.is_directory) {
560 return HTTP_NOT_FOUND;
561 }
562 }
563 /* not readable */
564 if (!finfo.is_readable) {
565 return HTTP_FORBIDDEN;
566 }
567 } else if (!localdoc.data.empty()) {
568 *rtype = RESP_XMLDOC;
569 finfo.content_type = "text/xml";
570 finfo.file_length = localdoc.data.size();
571 finfo.is_readable = true;
572 finfo.is_directory = false;
573 finfo.last_modified = localdoc.last_modified;
574 RespInstr->data.swap(localdoc.data);
575 } else {
576 *rtype = RESP_FILEDOC;
577 if (gDocumentRootDir.empty()) {
578 return HTTP_FORBIDDEN;
579 }
580 /* get file name */
581 filename = gDocumentRootDir;
582 filename += request_doc;
583 /* remove trailing slashes */
584 while (!filename.empty() && filename.back() == '/') {
585 filename.pop_back();
586 }
587
588 /* get info on file */
589 if (get_file_info(filename.c_str(), &finfo) != 0) {
590 return HTTP_NOT_FOUND;
591 }
592 /* try index.html if req is a dir */
593 if (finfo.is_directory) {
594 const char *temp_str;
595 if (filename.back() == '/') {
596 temp_str = "index.html";
597 } else {
598 temp_str = "/index.html";
599 }
600 filename += temp_str;
601 /* get info */
602 if (get_file_info(filename.c_str(), &finfo) != 0 ||
603 finfo.is_directory) {
604 return HTTP_NOT_FOUND;
605 }
606 }
607 /* not readable */
608 if (!finfo.is_readable) {
609 return HTTP_FORBIDDEN;
610 }
611 }
612
613 if (RespInstr->ReadSendSize < 0) {
614 // No range specified or open-ended range
615 RespInstr->ReadSendSize = finfo.file_length - RespInstr->offset;
616 } else if (RespInstr->offset + RespInstr->ReadSendSize > finfo.file_length) {
617 RespInstr->ReadSendSize = finfo.file_length - RespInstr->offset;
618 }
619
620 //std::cerr << "process_request: offset " << RespInstr->offset <<
621 // " count " << RespInstr->ReadSendSize << "\n";
622
623 int code = CheckOtherHTTPHeaders(mhdt, RespInstr, finfo.file_length);
624 if (code != HTTP_OK) {
625 return code;
626 }
627
628 /* simple get http 0.9 as specified in http 1.0 */
629 /* don't send headers */
630 if (mhdt->method == HTTPMETHOD_SIMPLEGET) {
631 headers.clear();
632 return HTTP_OK;
633 }
634
635 // Add any headers created by the client in the GetInfo callback
636 headers.insert(finfo.response_headers.begin(), finfo.response_headers.end());
637
638 if (!finfo.content_type.empty()) {
639 headers["content-type"] = finfo.content_type;
640 }
641 if (RespInstr->AcceptLanguageHeader[0] && WEB_SERVER_CONTENT_LANGUAGE[0]) {
642 headers["content-language"] = WEB_SERVER_CONTENT_LANGUAGE;
643 }
644 {
645 std::string date = make_date_string(0);
646 if (!date.empty())
647 headers["date"] = date;
648 if (finfo.last_modified) {
649 headers["last-modified"] = make_date_string(finfo.last_modified);
650 }
651 }
652 headers["x-user-agent"] = X_USER_AGENT;
653
654 return HTTP_OK;
655 }
656
657 class VFileReaderCtxt {
658 public:
659 ~VFileReaderCtxt() = default;
660 UpnpWebFileHandle fp{nullptr};
661 const void *cookie;
662 const void *request_cookie;
663 };
664
vFileReaderCallback(void * cls,uint64_t pos,char * buf,size_t max)665 static ssize_t vFileReaderCallback(void *cls, uint64_t pos, char *buf, size_t max)
666 {
667 (void)pos;
668 auto ctx = static_cast<VFileReaderCtxt*>(cls);
669 if (nullptr == ctx->fp) {
670 UpnpPrintf(UPNP_ERROR, MSERV, __FILE__, __LINE__, "vFileReaderCallback: fp is null !\n");
671 return MHD_CONTENT_READER_END_WITH_ERROR;
672 }
673 //std::cerr << "vFileReaderCallback: pos " << pos << " cnt " << max << "\n";
674
675 #if LOCKS_UP_GERBERA_DONT_DO_IT
676 if (virtualDirCallback.seek(
677 ctx->fp, pos, SEEK_SET, ctx->cookie, ctx->request_cookie) !=
678 (int64_t)pos) {
679 return MHD_CONTENT_READER_END_WITH_ERROR;
680 }
681 #endif
682
683 int ret = virtualDirCallback.read(ctx->fp, buf, max, ctx->cookie, ctx->request_cookie);
684
685 /* From the microhttpd manual: Note that returning zero will cause
686 MHD to try again. Thus, returning zero should only be used in
687 conjunction with MHD_suspend_connection() to avoid busy
688 waiting. */
689 if (ret > 0)
690 return ret;
691 return ret < 0 ? MHD_CONTENT_READER_END_WITH_ERROR : MHD_CONTENT_READER_END_OF_STREAM;
692 }
693
vFileFreeCallback(void * cls)694 static void vFileFreeCallback (void *cls)
695 {
696 if (cls) {
697 auto ctx = static_cast<VFileReaderCtxt*>(cls);
698 virtualDirCallback.close(ctx->fp, ctx->cookie, ctx->request_cookie);
699 delete ctx;
700 }
701 }
702
web_server_callback(MHDTransaction * mhdt)703 static void web_server_callback(MHDTransaction *mhdt)
704 {
705 int ret;
706 auto rtype = static_cast<enum resp_type>(0);
707 std::map<std::string,std::string> headers;
708 std::string filename;
709 struct SendInstruction RespInstr;
710
711 /* Process request should create the different kind of header depending
712 on the the type of request. */
713 ret = process_request(mhdt, &rtype, headers, filename, &RespInstr);
714 if (ret != HTTP_OK) {
715 /* send error code */
716 http_SendStatusResponse(mhdt, ret);
717 } else {
718 /* send response */
719 if (RespInstr.ReadSendSize < 0) {
720 RespInstr.ReadSendSize = -1;
721 }
722 switch (rtype) {
723 case RESP_FILEDOC:
724 {
725 int fd = OPEN(filename.c_str(), 0);
726 if (fd < 0) {
727 http_SendStatusResponse(mhdt, HTTP_FORBIDDEN);
728 } else {
729 #if MHD_VERSION <= 0x00093700
730 // Not sure exactly at_offset64 appeared, but 0.9.37
731 // did not have it
732 mhdt->response = MHD_create_response_from_fd_at_offset(
733 RespInstr.ReadSendSize, fd, static_cast<off_t>(RespInstr.offset));
734 #else
735 mhdt->response = MHD_create_response_from_fd_at_offset64(
736 RespInstr.ReadSendSize, fd, RespInstr.offset);
737 #endif
738 mhdt->httpstatus = 200;
739 }
740 }
741 break;
742
743 case RESP_WEBDOC:
744 {
745 auto ctx = new VFileReaderCtxt;
746 ctx->fp = virtualDirCallback.open(
747 filename.c_str(), UPNP_READ, RespInstr.cookie, RespInstr.request_cookie);
748 if (ctx->fp == nullptr) {
749 http_SendStatusResponse(mhdt, HTTP_INTERNAL_SERVER_ERROR);
750 }
751 ctx->cookie = RespInstr.cookie;
752 ctx->request_cookie = RespInstr.request_cookie;
753 if (RespInstr.offset) {
754 virtualDirCallback.seek(
755 ctx->fp, RespInstr.offset, SEEK_SET, ctx->cookie, ctx->request_cookie);
756 }
757 mhdt->response = MHD_create_response_from_callback(
758 RespInstr.ReadSendSize, 4096, vFileReaderCallback, ctx, vFileFreeCallback);
759 mhdt->httpstatus = 200;
760 }
761 break;
762
763 case RESP_XMLDOC:
764 mhdt->response = MHD_create_response_from_buffer(
765 RespInstr.data.size(), strdup(RespInstr.data.c_str()), MHD_RESPMEM_MUST_FREE);
766 mhdt->httpstatus = 200;
767 break;
768
769 default:
770 UpnpPrintf(UPNP_INFO, HTTP, __FILE__, __LINE__,
771 "webserver: Generated an invalid response type.\n");
772 assert(0);
773 }
774 }
775
776 bool serverhfound{false};
777 for (const auto& header : headers) {
778 //std::cerr << "web_server_callback: adding header [" << header.first <<
779 //"]->[" << header.second << "]\n";
780 if (!stringlowercmp("server", header.first)) {
781 serverhfound = true;
782 }
783 MHD_add_response_header(mhdt->response, header.first.c_str(),
784 header.second.c_str());
785 }
786 if (!serverhfound) {
787 MHD_add_response_header(mhdt->response, "SERVER",
788 get_sdk_device_info("").c_str());
789 }
790 MHD_add_response_header(mhdt->response, "Accept-Ranges", "bytes");
791
792 UpnpPrintf(UPNP_DEBUG,HTTP,__FILE__,__LINE__,
793 "webserver: response ready. Status %d\n", mhdt->httpstatus);
794 }
795
web_server_init()796 int web_server_init()
797 {
798 bWebServerState = WEB_SERVER_ENABLED;
799 SetHTTPGetCallback(web_server_callback);
800 return 0;
801 }
802
803 /*
804 * Release memory allocated for the global web server root directory
805 * and the global XML document. Resets the flag bWebServerState to
806 * WEB_SERVER_DISABLED.
807 */
web_server_destroy()808 void web_server_destroy()
809 {
810 if (bWebServerState == WEB_SERVER_ENABLED) {
811 SetHTTPGetCallback(nullptr);
812 gDocumentRootDir.clear();
813 localDocs.clear();
814 bWebServerState = WEB_SERVER_DISABLED;
815 }
816 }
817
818 #endif /* EXCLUDE_WEB_SERVER */
819