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