1 //mainpage WEBEM
2 //
3 //Detailed class and method documentation of the WEBEM C++ embedded web server source code.
4 //
5 //Modified, extended etc by Robbert E. Peters/RTSS B.V.
6 #include "stdafx.h"
7 #include "cWebem.h"
8 #include <boost/bind.hpp>
9 #include "reply.hpp"
10 #include "request.hpp"
11 #include "mime_types.hpp"
12 #include "utf.hpp"
13 #include "Base64.h"
14 #include "sha1.hpp"
15 #include "GZipHelper.h"
16 #include <stdarg.h>
17 #include <fstream>
18 #include <sstream>
19 #include <cstdlib>
20 #include "../main/Helper.h"
21 #include "../main/localtime_r.h"
22 #include "../main/Logger.h"
23 
24 #define SHORT_SESSION_TIMEOUT 600 // 10 minutes
25 #define LONG_SESSION_TIMEOUT (30 * 86400) // 30 days
26 
27 #ifdef _WIN32
28 #define gmtime_r(timep, result) gmtime_s(result, timep)
29 #endif
30 
31 #define websocket_protocol "domoticz"
32 
33 int m_failcounter = 0;
34 
35 namespace http {
36 	namespace server {
37 
38 		/**
39 		Webem constructor
40 
41 		@param[in] server_settings  Server settings (IP address, listening port, ssl options...)
42 		@param[in] doc_root path to folder containing html e.g. "./"
43 		*/
cWebem(const server_settings & settings,const std::string & doc_root)44 		cWebem::cWebem(
45 			const server_settings & settings,
46 			const std::string& doc_root) :
47 			m_io_service(),
48 			m_settings(settings),
49 			m_authmethod(AUTH_LOGIN),
50 			mySessionStore(NULL),
51 			myRequestHandler(doc_root, this),
52 			m_DigistRealm("Domoticz.com"),
53 			m_session_clean_timer(m_io_service, boost::posix_time::minutes(1)),
54 			m_sessions(), // Rene, make sure we initialize m_sessions first, before starting a server
55 			myServer(server_factory::create(settings, myRequestHandler))
56 		{
57 			// associate handler to timer and schedule the first iteration
58 			m_session_clean_timer.async_wait(boost::bind(&cWebem::CleanSessions, this));
59 			m_io_service_thread = std::make_shared<std::thread>(boost::bind(&boost::asio::io_service::run, &m_io_service));
60 			SetThreadName(m_io_service_thread->native_handle(), "Webem_ssncleaner");
61 		}
62 
~cWebem()63 		cWebem::~cWebem()
64 		{
65 			// Remove reference to CWebServer before its deletion (fix a "pure virtual method called" exception on server termination)
66 			mySessionStore = NULL;
67 			// Delete server (no need with smart pointer)
68 		}
69 
70 		/**
71 
72 		Start the server.
73 
74 		This does not return.
75 
76 		IMPORTANT: This method does not return. If application needs to continue, start new thread with call to this method.
77 
78 		*/
Run()79 		void cWebem::Run()
80 		{
81 			// Start Web server
82 			if (myServer != NULL)
83 			{
84 				myServer->run();
85 			}
86 		}
87 
88 		/**
89 
90 		Stop and delete the internal server.
91 
92 		IMPORTANT:  To start the server again, delete it and create a new cWebem instance.
93 
94 		*/
Stop()95 		void cWebem::Stop()
96 		{
97 			// Stop session cleaner
98 			try
99 			{
100 				if (!m_io_service.stopped())
101 				{
102 					m_io_service.stop();
103 				}
104 				if (m_io_service_thread)
105 				{
106 					m_io_service_thread->join();
107 					m_io_service_thread.reset();
108 				}
109 			}
110 			catch (...)
111 			{
112 				_log.Log(LOG_ERROR, "[web:%s] exception thrown while stopping session cleaner", GetPort().c_str());
113 			}
114 			// Stop Web server
115 			if (myServer != NULL)
116 			{
117 				myServer->stop();
118 			}
119 		}
120 
SetAuthenticationMethod(const _eAuthenticationMethod amethod)121 		void cWebem::SetAuthenticationMethod(const _eAuthenticationMethod amethod)
122 		{
123 			m_authmethod = amethod;
124 		}
125 
SetWebCompressionMode(_eWebCompressionMode gzmode)126 		void cWebem::SetWebCompressionMode(_eWebCompressionMode gzmode)
127 		{
128 			m_gzipmode = gzmode;
129 		}
130 
131 
132 		/**
133 
134 		Create a link between a string ID and a function to calculate the dynamic content of the string
135 
136 		The function should return a pointer to a character buffer.  This should be contain only ASCII characters
137 		( Unicode code points 1 to 127 )
138 
139 		@param[in] idname  string identifier
140 		@param[in] fun pointer to function which calculates the string to be displayed
141 
142 		*/
143 
RegisterIncludeCode(const char * idname,webem_include_function fun)144 		void cWebem::RegisterIncludeCode(const char* idname, webem_include_function fun)
145 		{
146 			myIncludes.insert(std::pair<std::string, webem_include_function >(std::string(idname), fun));
147 		}
148 		/**
149 
150 		Create a link between a string ID and a function to calculate the dynamic content of the string
151 
152 		The function should return a pointer to wide character buffer.  This should contain a wide character UTF-16 encoded unicode string.
153 		WEBEM will convert the string to UTF-8 encoding before sending to the browser.
154 
155 		@param[in] idname  string identifier
156 		@param[in] fun pointer to function which calculates the string to be displayed
157 
158 		*/
159 
RegisterIncludeCodeW(const char * idname,webem_include_function_w fun)160 		void cWebem::RegisterIncludeCodeW(const char* idname, webem_include_function_w fun)
161 		{
162 			myIncludes_w.insert(std::pair<std::string, webem_include_function_w >(std::string(idname), fun));
163 		}
164 
165 
RegisterPageCode(const char * pageurl,webem_page_function fun,bool bypassAuthentication)166 		void cWebem::RegisterPageCode(const char* pageurl, webem_page_function fun, bool bypassAuthentication)
167 		{
168 			myPages.insert(std::pair<std::string, webem_page_function >(std::string(pageurl), fun));
169 			if (bypassAuthentication)
170 			{
171 				RegisterWhitelistURLString(pageurl);
172 			}
173 		}
RegisterPageCodeW(const char * pageurl,webem_page_function fun,bool bypassAuthentication)174 		void cWebem::RegisterPageCodeW(const char* pageurl, webem_page_function fun, bool bypassAuthentication)
175 		{
176 			myPages_w.insert(std::pair<std::string, webem_page_function >(std::string(pageurl), fun));
177 			if (bypassAuthentication)
178 			{
179 				RegisterWhitelistURLString(pageurl);
180 			}
181 		}
182 
183 
184 		/**
185 
186 		Specify link between form and application function to run when form submitted
187 
188 		@param[in] idname string identifier
189 		@param[in] fun fpointer to function
190 
191 		*/
RegisterActionCode(const char * idname,webem_action_function fun)192 		void cWebem::RegisterActionCode(const char* idname, webem_action_function fun)
193 		{
194 			myActions.insert(std::pair<std::string, webem_action_function >(std::string(idname), fun));
195 		}
196 
197 		//Used by non basic-auth authentication (for example login forms) to bypass returning false when not authenticated
RegisterWhitelistURLString(const char * idname)198 		void cWebem::RegisterWhitelistURLString(const char* idname)
199 		{
200 			myWhitelistURLs.push_back(idname);
201 		}
RegisterWhitelistCommandsString(const char * idname)202 		void cWebem::RegisterWhitelistCommandsString(const char* idname)
203 		{
204 			myWhitelistCommands.push_back(idname);
205 		}
206 
207 
208 		/**
209 
210 		  Do not call from application code, used by server to include generated text.
211 
212 		  @param[in/out] reply  text to include generated
213 
214 		  The text is searched for "<!--#cWebemX-->".
215 		  The X can be any string not containing "-->"
216 
217 		  If X has been registered with cWebem then the associated function
218 		  is called to generate text to be inserted.
219 
220 		  returns true if any text is replaced
221 
222 
223 		*/
Include(std::string & reply)224 		bool cWebem::Include(std::string& reply)
225 		{
226 			bool res = false;
227 			size_t p = 0;
228 			while (1)
229 			{
230 				// find next request for generated text
231 				p = reply.find("<!--#embed", p);
232 				if (p == std::string::npos)
233 				{
234 					break;
235 				}
236 				size_t q = reply.find("-->", p);
237 				if (q == std::string::npos)
238 					break;
239 				q += 3;
240 
241 				size_t reply_len = reply.length();
242 
243 				// code identifying text generator
244 				std::string code = reply.substr(p + 11, q - p - 15);
245 
246 				// find the function associated with this code
247 				std::map < std::string, webem_include_function >::iterator pf = myIncludes.find(code);
248 				if (pf != myIncludes.end())
249 				{
250 					// insert generated text
251 					std::string content_part;
252 					try
253 					{
254 						pf->second(content_part);
255 					}
256 					catch (...)
257 					{
258 
259 					}
260 					reply.insert(p, content_part);
261 					res = true;
262 				}
263 				else
264 				{
265 					// no function found, look for a wide character fuction
266 					std::map < std::string, webem_include_function_w >::iterator pf = myIncludes_w.find(code);
267 					if (pf != myIncludes_w.end())
268 					{
269 						// function found
270 						// get return string and convert from UTF-16 to UTF-8
271 						std::wstring content_part_w;
272 						try
273 						{
274 							pf->second(content_part_w);
275 						}
276 						catch (...)
277 						{
278 
279 						}
280 						cUTF utf(content_part_w.c_str());
281 						// insert generated text
282 						reply.insert(p, utf.get8());
283 						res = true;
284 					}
285 				}
286 
287 				// adjust pointer into text for insertion
288 				p = q + reply.length() - reply_len;
289 			}
290 			return res;
291 		}
292 
safeGetline(std::istream & is,std::string & line)293 		std::istream & safeGetline(std::istream & is, std::string & line)
294 		{
295 			std::string myline;
296 			if (getline(is, myline))
297 			{
298 				if (myline.size() && myline[myline.size() - 1] == '\r')
299 				{
300 					line = myline.substr(0, myline.size() - 1);
301 				}
302 				else
303 				{
304 					line = myline;
305 				}
306 			}
307 			return is;
308 		}
309 
IsAction(const request & req)310 		bool cWebem::IsAction(const request& req)
311 		{
312 			// look for cWebem form action request
313 			std::string uri = req.uri;
314 			size_t q = uri.find(".webem");
315 			if (q == std::string::npos)
316 				return false;
317 			return true;
318 		}
319 
320 		/**
321 		Do not call from application code,
322 		used by server to handle form submissions.
323 
324 		returns false is authentication is invalid
325 
326 		*/
CheckForAction(WebEmSession & session,request & req)327 		bool cWebem::CheckForAction(WebEmSession & session, request& req)
328 		{
329 			// look for cWebem form action request
330 			if (!IsAction(req))
331 				return false;
332 
333 			req.parameters.clear();
334 
335 			std::string uri = ExtractRequestPath(req.uri);
336 
337 			// find function matching action code
338 			size_t q = uri.find(".webem");
339 			std::string code = uri.substr(1, q - 1);
340 			std::map < std::string, webem_action_function >::iterator
341 				pfun = myActions.find(code);
342 			if (pfun == myActions.end())
343 				return false;
344 
345 			// decode the values
346 
347 			if (req.method == "POST")
348 			{
349 				const char *pContent_Type = request::get_req_header(&req, "Content-Type");
350 				if (pContent_Type)
351 				{
352 					if (strstr(pContent_Type, "multipart/form-data") != NULL)
353 					{
354 						std::string szContent = req.content;
355 						size_t pos;
356 						std::string szVariable, szContentType, szValue;
357 
358 						//first line is our boundary
359 						pos = szContent.find("\r\n");
360 						if (pos == std::string::npos)
361 							return false;
362 						std::string szBoundary = szContent.substr(0, pos);
363 						szContent = szContent.substr(pos + 2);
364 
365 						while (!szContent.empty())
366 						{
367 							//Next line will contain our variable name
368 							pos = szContent.find("\r\n");
369 							if (pos == std::string::npos)
370 								return false;
371 							szVariable = szContent.substr(0, pos);
372 							szContent = szContent.substr(pos + 2);
373 							if (szVariable.find("Content-Disposition") != 0)
374 								return true;
375 							pos = szVariable.find("name=\"");
376 							if (pos == std::string::npos)
377 								return false;
378 							szVariable = szVariable.substr(pos + 6);
379 							pos = szVariable.find("\"");
380 							if (pos == std::string::npos)
381 								return false;
382 							szVariable = szVariable.substr(0, pos);
383 							//Next line could be empty, or a Content-Type, if its empty, it is just a string
384 							pos = szContent.find("\r\n");
385 							if (pos == std::string::npos)
386 								return false;
387 							szContentType = szContent.substr(0, pos);
388 							szContent = szContent.substr(pos + 2);
389 							if (
390 								(szContentType.find("application/octet-stream") != std::string::npos) ||
391 								(szContentType.find("application/json") != std::string::npos) ||
392 								(szContentType.find("Content-Type: text/xml") != std::string::npos) ||
393 								(szContentType.find("Content-Type: text/x-hex") != std::string::npos) ||
394 								(szContentType.find("Content-Type: image/") != std::string::npos)
395 								)
396 							{
397 								//Its a file/stream, next line should be empty
398 								pos = szContent.find("\r\n");
399 								if (pos == std::string::npos)
400 									return false;
401 								szContent = szContent.substr(pos + 2);
402 							}
403 							else
404 							{
405 								//next line should be empty
406 								if (!szContentType.empty())
407 									return false;//dont know this one
408 							}
409 							pos = szContent.find(szBoundary);
410 							if (pos == std::string::npos)
411 								return false;
412 							szValue = szContent.substr(0, pos - 2);
413 							req.parameters.insert(std::pair< std::string, std::string >(szVariable, szValue));
414 
415 							szContent = szContent.substr(pos + szBoundary.size());
416 							pos = szContent.find("\r\n");
417 							if (pos == std::string::npos)
418 								return false;
419 							szContent = szContent.substr(pos + 2);
420 						}
421 						//we should have at least one value
422 						if (req.parameters.empty())
423 							return false;
424 						// call the function
425 						try
426 						{
427 							pfun->second(session, req, req.uri);
428 						}
429 						catch (...)
430 						{
431 
432 						}
433 						if ((req.uri[0] == '/') && (m_webRoot.length() > 0))
434 						{
435 							// possible incorrect root reference
436 							size_t q = req.uri.find(m_webRoot);
437 							if (q != 0)
438 							{
439 								std::string olduri = req.uri;
440 								req.uri = m_webRoot + olduri;
441 							}
442 						}
443 						return true;
444 					}
445 					else if (
446 						(strstr(pContent_Type, "text/plain") != NULL)
447 						|| (strstr(pContent_Type, "application/json") != NULL)
448 						|| (strstr(pContent_Type, "application/xml") != NULL)
449 						)
450 					{
451 						//Raw data
452 						req.parameters.insert(std::pair< std::string, std::string >("data", req.content));
453 						// call the function
454 						try
455 						{
456 							pfun->second(session, req, req.uri);
457 						}
458 						catch (...)
459 						{
460 
461 						}
462 						return true;
463 					}
464 				}
465 				uri = req.content;
466 				q = 0;
467 			}
468 			else
469 			{
470 				q += 7;
471 			}
472 
473 			std::string name;
474 			std::string value;
475 
476 			size_t p = q;
477 			int flag_done = 0;
478 			while (!flag_done)
479 			{
480 				q = uri.find("=", p);
481 				if (q == std::string::npos)
482 					return false;
483 				name = uri.substr(p, q - p);
484 				p = q + 1;
485 				q = uri.find("&", p);
486 				if (q != std::string::npos)
487 					value = uri.substr(p, q - p);
488 				else
489 				{
490 					value = uri.substr(p);
491 					flag_done = 1;
492 				}
493 				// the browser sends blanks as +
494 				while (1)
495 				{
496 					size_t p = value.find("+");
497 					if (p == std::string::npos)
498 						break;
499 					value.replace(p, 1, " ");
500 				}
501 
502 				req.parameters.insert(std::pair< std::string, std::string >(name, value));
503 				p = q + 1;
504 			}
505 
506 			// call the function
507 			try
508 			{
509 				pfun->second(session, req, req.uri);
510 			}
511 			catch (...)
512 			{
513 
514 			}
515 			if ((req.uri[0] == '/') && (m_webRoot.length() > 0))
516 			{
517 				// possible incorrect root reference
518 				size_t q = req.uri.find(m_webRoot);
519 				if (q != 0)
520 				{
521 					std::string olduri = req.uri;
522 					req.uri = m_webRoot + olduri;
523 				}
524 			}
525 
526 			return true;
527 		}
528 
IsPageOverride(const request & req,reply & rep)529 		bool cWebem::IsPageOverride(const request& req, reply& rep)
530 		{
531 			std::string request_path;
532 			if (!request_handler::url_decode(req.uri, request_path))
533 			{
534 				rep = reply::stock_reply(reply::bad_request);
535 				return false;
536 			}
537 
538 			request_path = ExtractRequestPath(request_path);
539 
540 			size_t paramPos = request_path.find_first_of('?');
541 			if (paramPos != std::string::npos)
542 			{
543 				request_path = request_path.substr(0, paramPos);
544 			}
545 
546 			std::map < std::string, webem_page_function >::iterator
547 				pfun = myPages.find(request_path);
548 
549 			if (pfun != myPages.end())
550 				return true;
551 			//check wchar_t
552 			std::map < std::string, webem_page_function >::iterator
553 				pfunW = myPages_w.find(request_path);
554 			if (pfunW != myPages_w.end())
555 				return true;
556 			return false;
557 		}
558 
CheckForPageOverride(WebEmSession & session,request & req,reply & rep)559 		bool cWebem::CheckForPageOverride(WebEmSession & session, request& req, reply& rep)
560 		{
561 			// Decode url to path.
562 			std::string request_path;
563 			if (!request_handler::url_decode(req.uri, request_path))
564 			{
565 				rep = reply::stock_reply(reply::bad_request);
566 				return false;
567 			}
568 
569 			request_path = ExtractRequestPath(request_path);
570 
571 			req.parameters.clear();
572 
573 			std::string request_path2 = req.uri; // we need the raw request string to parse the get-request
574 			size_t paramPos = request_path2.find_first_of('?');
575 			if (paramPos != std::string::npos)
576 			{
577 				std::string params = request_path2.substr(paramPos + 1);
578 				std::string name;
579 				std::string value;
580 
581 				size_t q = 0;
582 				size_t p = q;
583 				int flag_done = 0;
584 				std::string uri = params;
585 				while (!flag_done)
586 				{
587 					q = uri.find("=", p);
588 					if (q == std::string::npos)
589 					{
590 						break;
591 					}
592 					name = uri.substr(p, q - p);
593 					p = q + 1;
594 					q = uri.find("&", p);
595 					if (q != std::string::npos)
596 						value = uri.substr(p, q - p);
597 					else
598 					{
599 						value = uri.substr(p);
600 						flag_done = 1;
601 					}
602 					// the browser sends blanks as +
603 					while (1)
604 					{
605 						size_t p = value.find("+");
606 						if (p == std::string::npos)
607 							break;
608 						value.replace(p, 1, " ");
609 					}
610 
611 					// now, url-decode only the value
612 					std::string decoded;
613 					request_handler::url_decode(value, decoded);
614 					req.parameters.insert(std::pair< std::string, std::string >(name, decoded));
615 					p = q + 1;
616 				}
617 			}
618 			if (req.method == "POST")
619 			{
620 				const char *pContent_Type = request::get_req_header(&req, "Content-Type");
621 				if (pContent_Type)
622 				{
623 					if (strstr(pContent_Type, "multipart/form-data") != NULL)
624 					{
625 						std::string szContent = req.content;
626 						size_t pos;
627 						std::string szVariable, szContentType, szValue;
628 
629 						//first line is our boundary
630 						pos = szContent.find("\r\n");
631 						if (pos == std::string::npos)
632 							return true;
633 						std::string szBoundary = szContent.substr(0, pos);
634 						szContent = szContent.substr(pos + 2);
635 
636 						while (!szContent.empty())
637 						{
638 							//Next line will contain our variable name
639 							pos = szContent.find("\r\n");
640 							if (pos == std::string::npos)
641 								return true;
642 							szVariable = szContent.substr(0, pos);
643 							szContent = szContent.substr(pos + 2);
644 							if (szVariable.find("Content-Disposition") != 0)
645 								return true;
646 							pos = szVariable.find("name=\"");
647 							if (pos == std::string::npos)
648 								return true;
649 							szVariable = szVariable.substr(pos + 6);
650 							pos = szVariable.find("\"");
651 							if (pos == std::string::npos)
652 								return true;
653 							szVariable = szVariable.substr(0, pos);
654 							//Next line could be empty, or a Content-Type, if its empty, it is just a string
655 							pos = szContent.find("\r\n");
656 							if (pos == std::string::npos)
657 								return true;
658 							szContentType = szContent.substr(0, pos);
659 							szContent = szContent.substr(pos + 2);
660 							if (
661 								(szContentType.find("application/octet-stream") != std::string::npos)
662 								|| (szContentType.find("application/json") != std::string::npos)
663 								|| (szContentType.find("application/x-zip") != std::string::npos)
664 								|| (szContentType.find("Content-Type: text/xml") != std::string::npos)
665 								)
666 							{
667 								//Its a file/stream, next line should be empty
668 								pos = szContent.find("\r\n");
669 								if (pos == std::string::npos)
670 									return true;
671 								szContent = szContent.substr(pos + 2);
672 							}
673 							else
674 							{
675 								//next line should be empty
676 								if (!szContentType.empty())
677 									return true;//dont know this one
678 							}
679 							pos = szContent.find(szBoundary);
680 							if (pos == std::string::npos)
681 								return true;
682 							szValue = szContent.substr(0, pos - 2);
683 							req.parameters.insert(std::pair< std::string, std::string >(szVariable, szValue));
684 
685 							szContent = szContent.substr(pos + szBoundary.size());
686 							pos = szContent.find("\r\n");
687 							if (pos == std::string::npos)
688 								return true;
689 							szContent = szContent.substr(pos + 2);
690 						}
691 						//we should have at least one value
692 						if (req.parameters.empty())
693 							return true;
694 					} //if (strstr(pContent_Type, "multipart/form-data") != NULL)
695 					else if (strstr(pContent_Type, "application/x-www-form-urlencoded") != NULL)
696 					{
697 						std::string params = req.content;
698 						std::string name;
699 						std::string value;
700 
701 						size_t q = 0;
702 						size_t p = q;
703 						int flag_done = 0;
704 						std::string uri = params;
705 						while (!flag_done)
706 						{
707 							q = uri.find("=", p);
708 							if (q == std::string::npos)
709 							{
710 								break;
711 							}
712 							name = uri.substr(p, q - p);
713 							p = q + 1;
714 							q = uri.find("&", p);
715 							if (q != std::string::npos)
716 								value = uri.substr(p, q - p);
717 							else
718 							{
719 								value = uri.substr(p);
720 								flag_done = 1;
721 							}
722 							// the browser sends blanks as +
723 							while (1)
724 							{
725 								size_t p = value.find("+");
726 								if (p == std::string::npos)
727 									break;
728 								value.replace(p, 1, " ");
729 							}
730 
731 							// now, url-decode only the value
732 							std::string decoded;
733 							request_handler::url_decode(value, decoded);
734 							req.parameters.insert(std::pair< std::string, std::string >(name, decoded));
735 							p = q + 1;
736 						}
737 					}
738 				}
739 			}
740 
741 			// Determine the file extension.
742 			std::string extension;
743 			if (req.uri.find("json.htm") != std::string::npos)
744 			{
745 				extension = "json";
746 			}
747 			else
748 			{
749 				std::size_t last_slash_pos = request_path.find_last_of("/");
750 				std::size_t last_dot_pos = request_path.find_last_of(".");
751 				if (last_dot_pos != std::string::npos && last_dot_pos > last_slash_pos)
752 				{
753 					extension = request_path.substr(last_dot_pos + 1);
754 				}
755 			}
756 			std::string strMimeType = mime_types::extension_to_type(extension);
757 
758 			std::map < std::string, webem_page_function >::iterator
759 				pfun = myPages.find(request_path);
760 
761 			if (pfun != myPages.end())
762 			{
763 				rep.status = reply::ok;
764 				try
765 				{
766 					pfun->second(session, req, rep);
767 				}
768 				catch (std::exception& e)
769 				{
770 					_log.Log(LOG_ERROR, "WebServer PO exception occurred : '%s'", e.what());
771 				}
772 				catch (...)
773 				{
774 					_log.Log(LOG_ERROR, "WebServer PO unknown exception occurred");
775 				}
776 				std::string attachment;
777 				size_t num = rep.headers.size();
778 				for (size_t h = 0; h < num; h++)
779 				{
780 					if (boost::iequals(rep.headers[h].name, "Content-Disposition"))
781 					{
782 						attachment = rep.headers[h].value.substr(rep.headers[h].value.find("=") + 1);
783 						std::size_t last_dot_pos = attachment.find_last_of(".");
784 						if (last_dot_pos != std::string::npos)
785 						{
786 							extension = attachment.substr(last_dot_pos + 1);
787 							strMimeType = mime_types::extension_to_type(extension);
788 						}
789 						break;
790 					}
791 				}
792 
793 				reply::add_header(&rep, "Content-Length", std::to_string(rep.content.size()));
794 				if (!boost::algorithm::starts_with(strMimeType, "image"))
795 				{
796 					if (!strMimeType.empty())
797 						strMimeType += ";charset=UTF-8";
798 					reply::add_header(&rep, "Cache-Control", "no-cache");
799 					reply::add_header(&rep, "Pragma", "no-cache");
800 					reply::add_header(&rep, "Access-Control-Allow-Origin", "*");
801 				}
802 				else
803 				{
804 					reply::add_header(&rep, "Cache-Control", "max-age=3600, public");
805 				}
806 				reply::add_header_content_type(&rep, strMimeType);
807 				return true;
808 			}
809 
810 			//check wchar_t
811 			std::map < std::string, webem_page_function >::iterator
812 				pfunW = myPages_w.find(request_path);
813 			if (pfunW == myPages_w.end())
814 				return false;
815 
816 			try
817 			{
818 				pfunW->second(session, req, rep);
819 			}
820 			catch (...)
821 			{
822 
823 			}
824 
825 			rep.status = reply::ok;
826 			reply::add_header(&rep, "Content-Length", std::to_string(rep.content.size()));
827 			reply::add_header(&rep, "Content-Type", strMimeType + "; charset=UTF-8");
828 			reply::add_header(&rep, "Cache-Control", "no-cache");
829 			reply::add_header(&rep, "Pragma", "no-cache");
830 			reply::add_header(&rep, "Access-Control-Allow-Origin", "*");
831 
832 			return true;
833 		}
834 
SetWebTheme(const std::string & themename)835 		void cWebem::SetWebTheme(const std::string &themename)
836 		{
837 			m_actTheme = "/styles/" + themename;
838 		}
839 
SetWebRoot(const std::string & webRoot)840 		void cWebem::SetWebRoot(const std::string &webRoot)
841 		{
842 			// remove trailing slash if required
843 			if (webRoot.size() > 0 && webRoot[webRoot.size() - 1] == '/')
844 			{
845 				m_webRoot = webRoot.substr(0, webRoot.size() - 1);
846 			}
847 			else
848 			{
849 				m_webRoot = webRoot;
850 			}
851 			// put slash at the front if required
852 			if (m_webRoot.size() > 0 && m_webRoot[0] != '/')
853 			{
854 				m_webRoot = "/" + webRoot;
855 			}
856 		}
857 
ExtractRequestPath(const std::string & original_request_path)858 		std::string cWebem::ExtractRequestPath(const std::string& original_request_path)
859 		{
860 			std::string request_path(original_request_path);
861 			size_t paramPos = request_path.find_first_of('?');
862 			if (paramPos != std::string::npos)
863 			{
864 				request_path = request_path.substr(0, paramPos);
865 			}
866 
867 			if (request_path.find(m_webRoot + "/@login") == 0)
868 			{
869 				request_path = m_webRoot + "/";
870 			}
871 
872 			// If path ends in slash (i.e. is a directory) then add "index.html".
873 			if (request_path[request_path.size() - 1] == '/')
874 			{
875 				request_path += "index.html";
876 			}
877 
878 			if (m_webRoot.size() > 0)
879 			{
880 				// remove web root if present otherwise
881 				// create invalid request
882 				if (request_path.find(m_webRoot) == 0)
883 				{
884 					request_path = request_path.substr(m_webRoot.size());
885 				}
886 				else
887 				{
888 					request_path = "";
889 				}
890 			}
891 
892 			if (request_path.find("/acttheme/") == 0)
893 			{
894 				request_path = m_actTheme + request_path.substr(9);
895 			}
896 			return request_path;
897 		}
898 
IsBadRequestPath(const std::string & request_path)899 		bool cWebem::IsBadRequestPath(const std::string& request_path)
900 		{
901 			// Request path must be absolute and not contain "..".
902 			if (request_path.empty() || request_path[0] != '/'
903 				|| request_path.find("..") != std::string::npos)
904 			{
905 				return true;
906 			}
907 
908 			// don't allow access to control files
909 			if (request_path.find(".htpasswd") != std::string::npos)
910 			{
911 				return true;
912 			}
913 
914 			// if we have a web root set the request must start with it
915 			if (m_webRoot.size() > 0)
916 			{
917 				if (request_path.find(m_webRoot) != 0)
918 				{
919 					return true;
920 				}
921 			}
922 
923 			return false;
924 		}
925 
AddUserPassword(const unsigned long ID,const std::string & username,const std::string & password,const _eUserRights userrights,const int activetabs)926 		void cWebem::AddUserPassword(const unsigned long ID, const std::string &username, const std::string &password, const _eUserRights userrights, const int activetabs)
927 		{
928 			_tWebUserPassword wtmp;
929 			wtmp.ID = ID;
930 			wtmp.Username = username;
931 			wtmp.Password = password;
932 			wtmp.userrights = userrights;
933 			wtmp.ActiveTabs = activetabs;
934 			wtmp.TotSensors = 0;
935 			m_userpasswords.push_back(wtmp);
936 		}
937 
ClearUserPasswords()938 		void cWebem::ClearUserPasswords()
939 		{
940 			m_userpasswords.clear();
941 
942 			std::unique_lock<std::mutex> lock(m_sessionsMutex);
943 			m_sessions.clear(); //TODO : check if it is really necessary
944 		}
945 
AddLocalNetworks(std::string network)946 		void cWebem::AddLocalNetworks(std::string network)
947 		{
948 			_tIPNetwork ipnetwork;
949 			ipnetwork.network = 0;
950 			ipnetwork.mask = 0;
951 			ipnetwork.hostname = "";
952 
953 			if (network == "")
954 			{
955 				//add local host
956 				char ac[256];
957 				if (gethostname(ac, sizeof(ac)) != SOCKET_ERROR)
958 				{
959 					ipnetwork.hostname = ac;
960 					stdlower(ipnetwork.hostname);
961 					m_localnetworks.push_back(ipnetwork);
962 				}
963 				return;
964 			}
965 			std::string inetwork = network;
966 			std::string inetworkmask = network;
967 			std::string mask = network;
968 
969 
970 			size_t pos = network.find_first_of("*");
971 			if (pos > 0)
972 			{
973 				stdreplace(inetwork, "*", "0");
974 				int a, b, c, d;
975 				if (sscanf(inetwork.c_str(), "%d.%d.%d.%d", &a, &b, &c, &d) != 4)
976 					return;
977 				std::stringstream newnetwork;
978 				newnetwork << std::dec << a << "." << std::dec << b << "." << std::dec << c << "." << std::dec << d;
979 				inetwork = newnetwork.str();
980 
981 				stdreplace(inetworkmask, "*", "999");
982 				int e, f, g, h;
983 				if (sscanf(inetworkmask.c_str(), "%d.%d.%d.%d", &e, &f, &g, &h) != 4)
984 					return;
985 
986 				std::stringstream newmask;
987 				if (e != 999) newmask << "255"; else newmask << "0";
988 				newmask << ".";
989 				if (f != 999) newmask << "255"; else newmask << "0";
990 				newmask << ".";
991 				if (g != 999) newmask << "255"; else newmask << "0";
992 				newmask << ".";
993 				if (h != 999) newmask << "255"; else newmask << "0";
994 				mask = newmask.str();
995 
996 				ipnetwork.network = IPToUInt(inetwork);
997 				ipnetwork.mask = IPToUInt(mask);
998 			}
999 			else
1000 			{
1001 				pos = network.find_first_of("/");
1002 				if (pos > 0)
1003 				{
1004 					unsigned char keepbits = (unsigned char)atoi(network.substr(pos + 1).c_str());
1005 					uint32_t imask = keepbits > 0 ? 0x00 - (1 << (32 - keepbits)) : 0xFFFFFFFF;
1006 					inetwork = network.substr(0, pos);
1007 					ipnetwork.network = IPToUInt(inetwork);
1008 					ipnetwork.mask = imask;
1009 				}
1010 				else
1011 				{
1012 					//Is it an IP address of hostname?
1013 					boost::system::error_code ec;
1014 					boost::asio::ip::address::from_string(network, ec);
1015 					if (ec)
1016 					{
1017 						//only allow ip's, localhost is covered above
1018 						return;
1019 						//ipnetwork.hostname=network;
1020 					}
1021 					else
1022 					{
1023 						//single IP?
1024 						ipnetwork.network = IPToUInt(inetwork);
1025 						ipnetwork.mask = IPToUInt("255.255.255.255");
1026 					}
1027 				}
1028 			}
1029 
1030 			m_localnetworks.push_back(ipnetwork);
1031 		}
1032 
ClearLocalNetworks()1033 		void cWebem::ClearLocalNetworks()
1034 		{
1035 			m_localnetworks.clear();
1036 		}
1037 
AddRemoteProxyIPs(const std::string & ipaddr)1038 		void cWebem::AddRemoteProxyIPs(const std::string &ipaddr)
1039 		{
1040 			myRemoteProxyIPs.push_back(ipaddr);
1041 		}
1042 
ClearRemoteProxyIPs()1043 		void cWebem::ClearRemoteProxyIPs()
1044 		{
1045 			myRemoteProxyIPs.clear();
1046 		}
1047 
SetDigistRealm(const std::string & realm)1048 		void cWebem::SetDigistRealm(const std::string &realm)
1049 		{
1050 			m_DigistRealm = realm;
1051 		}
1052 
SetZipPassword(const std::string & password)1053 		void cWebem::SetZipPassword(const std::string &password)
1054 		{
1055 			m_zippassword = password;
1056 		}
1057 
SetSessionStore(session_store_impl_ptr sessionStore)1058 		void cWebem::SetSessionStore(session_store_impl_ptr sessionStore)
1059 		{
1060 			mySessionStore = sessionStore;
1061 		}
1062 
GetSessionStore()1063 		session_store_impl_ptr cWebem::GetSessionStore()
1064 		{
1065 			return mySessionStore;
1066 		}
1067 
1068 		// Check the user's password, return 1 if OK
check_password(struct ah * ah,const std::string & ha1,const std::string & realm)1069 		static int check_password(struct ah *ah, const std::string &ha1, const std::string &realm)
1070 		{
1071 			if (
1072 				(ah->nonce.size() == 0) &&
1073 				(ah->response.size() != 0)
1074 				)
1075 			{
1076 				return (ha1 == GenerateMD5Hash(ah->response));
1077 			}
1078 			return 0;
1079 		}
1080 
GetPort()1081 		const std::string cWebem::GetPort()
1082 		{
1083 			return m_settings.listening_port;
1084 		}
1085 
GetWebRoot()1086 		const std::string cWebem::GetWebRoot()
1087 		{
1088 			return m_webRoot;
1089 		}
1090 
GetSession(const std::string & ssid)1091 		WebEmSession * cWebem::GetSession(const std::string & ssid)
1092 		{
1093 			std::unique_lock<std::mutex> lock(m_sessionsMutex);
1094 			std::map<std::string, WebEmSession>::iterator itt = m_sessions.find(ssid);
1095 			if (itt != m_sessions.end())
1096 			{
1097 				return &itt->second;
1098 			}
1099 			return NULL;
1100 		}
1101 
AddSession(const WebEmSession & session)1102 		void cWebem::AddSession(const WebEmSession & session)
1103 		{
1104 			std::unique_lock<std::mutex> lock(m_sessionsMutex);
1105 			m_sessions[session.id] = session;
1106 		}
1107 
RemoveSession(const WebEmSession & session)1108 		void cWebem::RemoveSession(const WebEmSession & session)
1109 		{
1110 			RemoveSession(session.id);
1111 		}
1112 
RemoveSession(const std::string & ssid)1113 		void cWebem::RemoveSession(const std::string & ssid)
1114 		{
1115 			std::unique_lock<std::mutex> lock(m_sessionsMutex);
1116 			std::map<std::string, WebEmSession>::iterator itt = m_sessions.find(ssid);
1117 			if (itt != m_sessions.end())
1118 			{
1119 				m_sessions.erase(itt);
1120 			}
1121 		}
1122 
CountSessions()1123 		int cWebem::CountSessions()
1124 		{
1125 			std::unique_lock<std::mutex> lock(m_sessionsMutex);
1126 			return (int)m_sessions.size();
1127 		}
1128 
CleanSessions()1129 		void cWebem::CleanSessions()
1130 		{
1131 			_log.Debug(DEBUG_WEBSERVER, "[web:%s] cleaning sessions...", GetPort().c_str());
1132 
1133 			int before = CountSessions();
1134 			// Clean up timed out sessions from memory
1135 			std::vector<std::string> ssids;
1136 			{
1137 				std::unique_lock<std::mutex> lock(m_sessionsMutex);
1138 				time_t now = mytime(NULL);
1139 				std::map<std::string, WebEmSession>::iterator itt;
1140 				for (itt = m_sessions.begin(); itt != m_sessions.end(); ++itt)
1141 				{
1142 					if (itt->second.timeout < now)
1143 					{
1144 						ssids.push_back(itt->second.id);
1145 					}
1146 				}
1147 			}
1148 			std::vector<std::string>::iterator ssitt;
1149 			for (ssitt = ssids.begin(); ssitt != ssids.end(); ++ssitt)
1150 			{
1151 				std::string ssid = *ssitt;
1152 				RemoveSession(ssid);
1153 			}
1154 			int after = CountSessions();
1155 			std::stringstream ss;
1156 			{
1157 				std::unique_lock<std::mutex> lock(m_sessionsMutex);
1158 				std::map<std::string, WebEmSession>::iterator itt;
1159 				int i = 0;
1160 				for (itt = m_sessions.begin(); itt != m_sessions.end(); ++itt)
1161 				{
1162 					if (i > 0)
1163 					{
1164 						ss << ",";
1165 					}
1166 					ss << itt->second.id;
1167 					i += 1;
1168 				}
1169 			}
1170 			// Clean up expired sessions from database in order to avoid to wait for the domoticz restart (long time running instance)
1171 			if (mySessionStore != NULL)
1172 			{
1173 				this->mySessionStore->CleanSessions();
1174 			}
1175 			// Schedule next cleanup
1176 			m_session_clean_timer.expires_at(m_session_clean_timer.expires_at() + boost::posix_time::minutes(15));
1177 			m_session_clean_timer.async_wait(boost::bind(&cWebem::CleanSessions, this));
1178 		}
1179 
1180 		// Return 1 on success. Always initializes the ah structure.
parse_auth_header(const request & req,struct ah * ah)1181 		int cWebemRequestHandler::parse_auth_header(const request& req, struct ah *ah)
1182 		{
1183 			const char *auth_header;
1184 
1185 			if ((auth_header = request::get_req_header(&req, "Authorization")) == NULL)
1186 			{
1187 				return 0;
1188 			}
1189 
1190 			// X509 Auth header
1191 			if (boost::icontains(auth_header, "/CN="))
1192 			{
1193 				// DN looks like: /C=Country/ST=State/L=City/O=Org/OU=OrganizationUnit/CN=username/emailAddress=user@mail.com
1194 				std::string dn = auth_header;
1195 				size_t spos, epos;
1196 
1197 				spos = dn.find("/CN=");
1198 				epos = dn.find("/", spos + 1);
1199 				if (spos != std::string::npos)
1200 				{
1201 					if (epos == std::string::npos)
1202 					{
1203 						epos = dn.size();
1204 					}
1205 					ah->user = dn.substr(spos + 4, epos - spos - 4);
1206 				}
1207 
1208 				spos = dn.find("/emailAddress=");
1209 				epos = dn.find("/", spos + 1);
1210 				if (spos != std::string::npos)
1211 				{
1212 					if (epos == std::string::npos)
1213 					{
1214 						epos = dn.size();
1215 					}
1216 					ah->response = dn.substr(spos + 14, epos - spos - 14);
1217 				}
1218 
1219 				if (ah->user.empty()) // TODO: Should ah->response be not empty ?
1220 				{
1221 					return 0;
1222 				}
1223 				return 1;
1224 			}
1225 			// Basic Auth header
1226 			else if (boost::icontains(auth_header, "Basic "))
1227 			{
1228 				std::string decoded = base64_decode(auth_header + 6);
1229 				size_t npos = decoded.find(':');
1230 				if (npos == std::string::npos)
1231 				{
1232 					return 0;
1233 				}
1234 
1235 				ah->user = decoded.substr(0, npos);
1236 				ah->response = decoded.substr(npos + 1);
1237 				return 1;
1238 			}
1239 
1240 			return 0;
1241 		}
1242 
1243 		// Authorize against the opened passwords file. Return 1 if authorized.
authorize(WebEmSession & session,const request & req,reply & rep)1244 		int cWebemRequestHandler::authorize(WebEmSession & session, const request& req, reply& rep)
1245 		{
1246 			struct ah _ah;
1247 
1248 			std::string uname = "";
1249 			std::string upass = "";
1250 
1251 			if (!parse_auth_header(req, &_ah))
1252 			{
1253 				size_t uPos = req.uri.find("username=");
1254 				size_t pPos = req.uri.find("password=");
1255 				if (
1256 					(uPos == std::string::npos) ||
1257 					(pPos == std::string::npos)
1258 					)
1259 				{
1260 					return 0;
1261 				}
1262 				uPos += 9; //strlen("username=")
1263 				pPos += 9; //strlen("password=")
1264 				size_t uEnd = req.uri.find("&", uPos);
1265 				size_t pEnd = req.uri.find("&", pPos);
1266 				std::string tmpuname;
1267 				std::string tmpupass;
1268 				if (uEnd == std::string::npos)
1269 					tmpuname = req.uri.substr(uPos);
1270 				else
1271 					tmpuname = req.uri.substr(uPos, uEnd - uPos);
1272 				if (pEnd == std::string::npos)
1273 					tmpupass = req.uri.substr(pPos);
1274 				else
1275 					tmpupass = req.uri.substr(pPos, pEnd - pPos);
1276 				if (request_handler::url_decode(tmpuname, uname))
1277 				{
1278 					if (request_handler::url_decode(tmpupass, upass))
1279 					{
1280 						uname = base64_decode(uname);
1281 						upass = GenerateMD5Hash(base64_decode(upass));
1282 
1283 						std::vector<_tWebUserPassword>::iterator itt;
1284 						for (itt = myWebem->m_userpasswords.begin(); itt != myWebem->m_userpasswords.end(); ++itt)
1285 						{
1286 							if (itt->Username == uname)
1287 							{
1288 								if (itt->Password != upass)
1289 								{
1290 									m_failcounter++;
1291 									return 0;
1292 								}
1293 								session.isnew = true;
1294 								session.username = itt->Username;
1295 								session.rights = itt->userrights;
1296 								session.rememberme = false;
1297 								m_failcounter = 0;
1298 								return 1;
1299 							}
1300 						}
1301 					}
1302 				}
1303 				m_failcounter++;
1304 				return 0;
1305 			}
1306 
1307 			std::vector<_tWebUserPassword>::iterator itt;
1308 			for (itt = myWebem->m_userpasswords.begin(); itt != myWebem->m_userpasswords.end(); ++itt)
1309 			{
1310 				if (itt->Username == _ah.user)
1311 				{
1312 					int bOK = check_password(&_ah, itt->Password, myWebem->m_DigistRealm);
1313 					if (!bOK)
1314 					{
1315 						m_failcounter++;
1316 						return 0;
1317 					}
1318 					session.isnew = true;
1319 					session.username = itt->Username;
1320 					session.rights = itt->userrights;
1321 					session.rememberme = false;
1322 					m_failcounter = 0;
1323 					return 1;
1324 				}
1325 			}
1326 			m_failcounter++;
1327 			return 0;
1328 		}
1329 
IsIPInRange(const std::string & ip,const _tIPNetwork & ipnetwork)1330 		bool IsIPInRange(const std::string &ip, const _tIPNetwork &ipnetwork)
1331 		{
1332 			if (ipnetwork.hostname.size() != 0)
1333 			{
1334 				return (ip == ipnetwork.hostname);
1335 			}
1336 			uint32_t ip_addr = IPToUInt(ip);
1337 			if (ip_addr == 0)
1338 				return false;
1339 
1340 			uint32_t net_lower = (ipnetwork.network & ipnetwork.mask);
1341 			uint32_t net_upper = (net_lower | (~ipnetwork.mask));
1342 
1343 			if (ip_addr >= net_lower &&
1344 				ip_addr <= net_upper)
1345 				return true;
1346 			return false;
1347 		}
1348 
1349 		//Returns true is the connected host is in the local network
AreWeInLocalNetwork(const std::string & sHost,const request & req)1350 		bool cWebemRequestHandler::AreWeInLocalNetwork(const std::string &sHost, const request& req)
1351 		{
1352 			//check if in local network(s)
1353 			if (myWebem->m_localnetworks.size() == 0)
1354 				return false;
1355 			if (sHost.size() < 3)
1356 				return false;
1357 
1358 			std::vector<_tIPNetwork>::const_iterator itt;
1359 
1360 			/* RK, this doesn't work with IPv6 addresses.
1361 			pos=host.find_first_of(":");
1362 			if (pos!=std::string::npos)
1363 				host=host.substr(0,pos);
1364 			*/
1365 
1366 			for (itt = myWebem->m_localnetworks.begin(); itt != myWebem->m_localnetworks.end(); ++itt)
1367 			{
1368 				if (IsIPInRange(sHost, *itt))
1369 				{
1370 					return true;
1371 				}
1372 			}
1373 			return false;
1374 		}
1375 
1376 		const char * months[12] = {
1377 			"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1378 		};
1379 
1380 		const char * wkdays[7] = {
1381 			"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
1382 		};
1383 
make_web_time(const time_t rawtime)1384 		char *make_web_time(const time_t rawtime)
1385 		{
1386 			static char buffer[256];
1387 			struct tm gmt;
1388 #ifdef _WIN32
1389 			if (gmtime_r(&rawtime, &gmt)) //windows returns errno_t, which returns zero when successful
1390 #else
1391 			if (gmtime_r(&rawtime, &gmt) == NULL)
1392 #endif
1393 			{
1394 				strcpy(buffer, "Thu, 1 Jan 1970 00:00:00 GMT");
1395 			}
1396 			else
1397 			{
1398 				sprintf(buffer, "%s, %02d %s %04d %02d:%02d:%02d GMT",
1399 					wkdays[gmt.tm_wday],
1400 					gmt.tm_mday,
1401 					months[gmt.tm_mon],
1402 					gmt.tm_year + 1900,
1403 					gmt.tm_hour,
1404 					gmt.tm_min,
1405 					gmt.tm_sec);
1406 
1407 
1408 			}
1409 			return buffer;
1410 		}
1411 
send_remove_cookie(reply & rep)1412 		void cWebemRequestHandler::send_remove_cookie(reply& rep)
1413 		{
1414 			std::stringstream sstr;
1415 			sstr << "DMZSID=none";
1416 			// RK, we removed path=/ so you can be logged in to two Domoticz's at the same time on https://my.domoticz.com/.
1417 			sstr << "; HttpOnly; Expires=" << make_web_time(0);
1418 			reply::add_header(&rep, "Set-Cookie", sstr.str(), false);
1419 		}
1420 
generateSessionID()1421 		std::string cWebemRequestHandler::generateSessionID()
1422 		{
1423 			// Session id should not be predictable
1424 			std::string randomValue = GenerateUUID();
1425 
1426 			std::string sessionId = GenerateMD5Hash(base64_encode(randomValue));
1427 
1428 			_log.Debug(DEBUG_WEBSERVER, "[web:%s] generate new session id token %s", myWebem->GetPort().c_str(), sessionId.c_str());
1429 
1430 			return sessionId;
1431 		}
1432 
generateAuthToken(const WebEmSession & session,const request & req)1433 		std::string cWebemRequestHandler::generateAuthToken(const WebEmSession & session, const request & req)
1434 		{
1435 			// Authentication token should not be predictable
1436 			std::string randomValue = GenerateUUID();
1437 
1438 			std::string authToken = base64_encode(randomValue);
1439 
1440 			_log.Debug(DEBUG_WEBSERVER, "[web:%s] generate new authentication token %s", myWebem->GetPort().c_str(), authToken.c_str());
1441 
1442 			session_store_impl_ptr sstore = myWebem->GetSessionStore();
1443 			if (sstore != NULL)
1444 			{
1445 				WebEmStoredSession storedSession;
1446 				storedSession.id = session.id;
1447 				storedSession.auth_token = GenerateMD5Hash(authToken); // only save the hash to avoid a security issue if database is stolen
1448 				storedSession.username = session.username;
1449 				storedSession.expires = session.expires;
1450 				storedSession.remote_host = session.remote_host; // to trace host
1451 				sstore->StoreSession(storedSession); // only one place to do that
1452 			}
1453 
1454 			return authToken;
1455 		}
1456 
send_cookie(reply & rep,const WebEmSession & session)1457 		void cWebemRequestHandler::send_cookie(reply& rep, const WebEmSession & session)
1458 		{
1459 			std::stringstream sstr;
1460 			sstr << "DMZSID=" << session.id << "_" << session.auth_token << "." << session.expires;
1461 			sstr << "; HttpOnly; path=/; Expires=" << make_web_time(session.expires);
1462 			reply::add_header(&rep, "Set-Cookie", sstr.str(), false);
1463 		}
1464 
send_authorization_request(reply & rep)1465 		void cWebemRequestHandler::send_authorization_request(reply& rep)
1466 		{
1467 			rep = reply::stock_reply(reply::unauthorized);
1468 			rep.status = reply::unauthorized;
1469 			send_remove_cookie(rep);
1470 			if (myWebem->m_authmethod == AUTH_BASIC)
1471 			{
1472 				char szAuthHeader[200];
1473 				sprintf(szAuthHeader,
1474 					"Basic "
1475 					"realm=\"%s\"",
1476 					myWebem->m_DigistRealm.c_str());
1477 				reply::add_header(&rep, "WWW-Authenticate", szAuthHeader);
1478 			}
1479 		}
1480 
CompressWebOutput(const request & req,reply & rep)1481 		bool cWebemRequestHandler::CompressWebOutput(const request& req, reply& rep)
1482 		{
1483 			if (myWebem->m_gzipmode != WWW_USE_GZIP)
1484 				return false;
1485 
1486 			std::string request_path;
1487 			if (!url_decode(req.uri, request_path))
1488 				return false;
1489 			if (
1490 				(request_path.find(".png") != std::string::npos) ||
1491 				(request_path.find(".jpg") != std::string::npos)
1492 				)
1493 			{
1494 				//don't compress 'compressed' images
1495 				return false;
1496 			}
1497 
1498 			const char *encoding_header;
1499 			//check gzip support if yes, send it back in gzip format
1500 			if ((encoding_header = request::get_req_header(&req, "Accept-Encoding")) != NULL)
1501 			{
1502 				//see if we support gzip
1503 				bool bHaveGZipSupport = (strstr(encoding_header, "gzip") != NULL);
1504 				if (bHaveGZipSupport)
1505 				{
1506 					CA2GZIP gzip((char*)rep.content.c_str(), (int)rep.content.size());
1507 					if ((gzip.Length > 0) && (gzip.Length < (int)rep.content.size()))
1508 					{
1509 						rep.bIsGZIP = true; // flag for later
1510 						rep.content.clear();
1511 						rep.content.append((char*)gzip.pgzip, gzip.Length);
1512 						//Set new content length
1513 						reply::add_header(&rep, "Content-Length", std::to_string(rep.content.size()));
1514 						//Add gzip header
1515 						reply::add_header(&rep, "Content-Encoding", "gzip");
1516 						return true;
1517 					}
1518 				}
1519 			}
1520 			return false;
1521 		}
1522 
compute_accept_header(const std::string & websocket_key)1523 		std::string cWebemRequestHandler::compute_accept_header(const std::string &websocket_key)
1524 		{
1525 			// the length of an sha1 hash
1526 			const int sha1len = 20;
1527 			// the GUID as specified in RFC 6455
1528 			const char *GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
1529 			std::string combined = websocket_key + GUID;
1530 			unsigned char sha1result[sha1len];
1531 			sha1::calc((void *)combined.c_str(), combined.length(), sha1result);
1532 			std::string accept = base64_encode(sha1result, sha1len);
1533 			return accept;
1534 		}
1535 
is_upgrade_request(WebEmSession & session,const request & req,reply & rep)1536 		bool cWebemRequestHandler::is_upgrade_request(WebEmSession & session, const request& req, reply& rep)
1537 		{
1538 			// request method should be GET
1539 			if (req.method != "GET")
1540 			{
1541 				return false;
1542 			}
1543 			// http version should be 1.1 at least
1544 			if (((req.http_version_major * 10) + req.http_version_minor) < 11)
1545 			{
1546 				return false;
1547 			}
1548 			const char *h;
1549 			// client MUST include Connection: Upgrade header
1550 			h = request::get_req_header(&req, "Connection");
1551 			if (!h)
1552 			{
1553 				return false;
1554 			}
1555 
1556 /*
1557 			std::string connection_header = h;
1558 			if (!boost::iequals(connection_header, "upgrade"))
1559 			{
1560 				return false;
1561 			};
1562 */
1563 			// client MUST include Upgrade: websocket
1564 			h = request::get_req_header(&req, "Upgrade");
1565 			if (!h)
1566 			{
1567 				return false;
1568 			}
1569 
1570 			if (!CheckAuthentication(session, req, rep))
1571 				return false;
1572 
1573 
1574 			std::string upgrade_header = h;
1575 			if (!boost::iequals(upgrade_header, "websocket"))
1576 			{
1577 				return false;
1578 			};
1579 			// we only have one service until now
1580 			if (req.uri.find("/json") == std::string::npos)
1581 			{
1582 				// todo: request uri could be an absolute URI as well!!!
1583 				rep = reply::stock_reply(reply::not_found);
1584 				return true;
1585 			}
1586 			h = request::get_req_header(&req, "Host");
1587 			// request MUST include a host header, even if we don't check it
1588 			if (h == NULL)
1589 			{
1590 				rep = reply::stock_reply(reply::forbidden);
1591 				return true;
1592 			}
1593 			h = request::get_req_header(&req, "Origin");
1594 			// request MUST include an origin header, even if we don't check it
1595 			// we only "allow" connections from browser clients
1596 			if (h == NULL)
1597 			{
1598 				rep = reply::stock_reply(reply::forbidden);
1599 				return true;
1600 			}
1601 			h = request::get_req_header(&req, "Sec-Websocket-Version");
1602 			// request MUST include a version number
1603 			if (h == NULL)
1604 			{
1605 				rep = reply::stock_reply(reply::internal_server_error);
1606 				return true;
1607 			}
1608 			int version = atoi(h);
1609 			// we support versions 13 (and higher)
1610 			if (version < 13)
1611 			{
1612 				rep = reply::stock_reply(reply::internal_server_error);
1613 				return true;
1614 			}
1615 			h = request::get_req_header(&req, "Sec-Websocket-Protocol");
1616 			// check if a protocol is given, and it includes the {websocket_protocol}.
1617 			if (!h)
1618 			{
1619 				return false;
1620 			}
1621 			std::string protocol_header = h;
1622 			if (protocol_header.find(websocket_protocol) == std::string::npos)
1623 			{
1624 				rep = reply::stock_reply(reply::internal_server_error);
1625 				return true;
1626 			}
1627 			h = request::get_req_header(&req, "Sec-Websocket-Key");
1628 			// request MUST include a sec-websocket-key header and we need to respond to it
1629 			if (h == NULL)
1630 			{
1631 				rep = reply::stock_reply(reply::internal_server_error);
1632 				return true;
1633 			}
1634 			std::string websocket_key = h;
1635 			rep = reply::stock_reply(reply::switching_protocols);
1636 			reply::add_header(&rep, "Connection", "Upgrade");
1637 			reply::add_header(&rep, "Upgrade", "websocket");
1638 
1639 			std::string accept = compute_accept_header(websocket_key);
1640 			if (accept.empty())
1641 			{
1642 				rep = reply::stock_reply(reply::internal_server_error);
1643 				return true;
1644 			}
1645 			reply::add_header(&rep, "Sec-Websocket-Accept", accept);
1646 			// we only speak the {websocket_protocol} subprotocol
1647 			reply::add_header(&rep, "Sec-Websocket-Protocol", websocket_protocol);
1648 			return true;
1649 		}
1650 
GetURICommandParameter(const std::string & uri,std::string & cmdparam)1651 		static bool GetURICommandParameter(const std::string &uri, std::string &cmdparam)
1652 		{
1653 			if (uri.find("type=command") == std::string::npos)
1654 				return false;
1655 			size_t ppos1 = uri.find("&param=");
1656 			size_t ppos2 = uri.find("?param=");
1657 			if (
1658 				(ppos1 == std::string::npos) &&
1659 				(ppos2 == std::string::npos)
1660 				)
1661 				return false;
1662 			cmdparam = uri;
1663 			size_t ppos = ppos1;
1664 			if (ppos == std::string::npos)
1665 				ppos = ppos2;
1666 			else
1667 			{
1668 				if ((ppos2 < ppos) && (ppos != std::string::npos))
1669 					ppos = ppos2;
1670 			}
1671 			cmdparam = uri.substr(ppos + 7);
1672 			ppos = cmdparam.find("&");
1673 			if (ppos != std::string::npos)
1674 			{
1675 				cmdparam = cmdparam.substr(0, ppos);
1676 			}
1677 			return true;
1678 		}
1679 
CheckAuthentication(WebEmSession & session,const request & req,reply & rep)1680 		bool cWebemRequestHandler::CheckAuthentication(WebEmSession & session, const request& req, reply& rep)
1681 		{
1682 			session.rights = -1; // no rights
1683 			session.id = "";
1684 
1685 			if (myWebem->m_userpasswords.size() == 0)
1686 			{
1687 				session.rights = 2;
1688 			}
1689 
1690 			if (AreWeInLocalNetwork(session.remote_host, req))
1691 			{
1692 				session.rights = 2;
1693 			}
1694 
1695 			//Check cookie if still valid
1696 			const char* cookie_header = request::get_req_header(&req, "Cookie");
1697 			if (cookie_header != NULL)
1698 			{
1699 				std::string sSID;
1700 				std::string sAuthToken;
1701 				std::string szTime;
1702 				bool expired = false;
1703 
1704 				// Parse session id and its expiration date
1705 				std::string scookie = cookie_header;
1706 				size_t fpos = scookie.find("DMZSID=");
1707 				if (fpos != std::string::npos)
1708 				{
1709 					scookie = scookie.substr(fpos);
1710 					fpos = 0;
1711 					size_t epos = scookie.find(';');
1712 					if (epos != std::string::npos)
1713 					{
1714 						scookie = scookie.substr(0, epos);
1715 					}
1716 				}
1717 				size_t upos = scookie.find("_", fpos);
1718 				size_t ppos = scookie.find(".", upos);
1719 				time_t now = mytime(NULL);
1720 				if ((fpos != std::string::npos) && (upos != std::string::npos) && (ppos != std::string::npos))
1721 				{
1722 					sSID = scookie.substr(fpos + 7, upos - fpos - 7);
1723 					sAuthToken = scookie.substr(upos + 1, ppos - upos - 1);
1724 					szTime = scookie.substr(ppos + 1);
1725 
1726 					time_t stime;
1727 					std::stringstream sstr;
1728 					sstr << szTime;
1729 					sstr >> stime;
1730 
1731 					expired = stime < now;
1732 				}
1733 
1734 				if (session.rights == 2)
1735 				{
1736 					if (!sSID.empty())
1737 					{
1738 						WebEmSession* oldSession = myWebem->GetSession(sSID);
1739 						if (oldSession == NULL)
1740 						{
1741 							session.id = sSID;
1742 							session.auth_token = sAuthToken;
1743 							expired = (!checkAuthToken(session));
1744 						}
1745 						else
1746 						{
1747 							session = *oldSession;
1748 							expired = (oldSession->expires < now);
1749 						}
1750 					}
1751 					if (sSID.empty() || expired)
1752 						session.isnew = true;
1753 					return true;
1754 				}
1755 
1756 				if (!(sSID.empty() || sAuthToken.empty() || szTime.empty()))
1757 				{
1758 					WebEmSession* oldSession = myWebem->GetSession(sSID);
1759 					if ((oldSession != NULL) && (oldSession->expires < now))
1760 					{
1761 						// Check if session stored in memory is not expired (prevent from spoofing expiration time)
1762 						expired = true;
1763 					}
1764 					if (expired)
1765 					{
1766 						//expired session, remove session
1767 						m_failcounter = 0;
1768 						if (oldSession != NULL)
1769 						{
1770 							// session exists (delete it from memory and database)
1771 							myWebem->RemoveSession(sSID);
1772 							removeAuthToken(sSID);
1773 						}
1774 						send_authorization_request(rep);
1775 						return false;
1776 					}
1777 					if (oldSession != NULL)
1778 					{
1779 						// session already exists
1780 						session = *oldSession;
1781 					}
1782 					else
1783 					{
1784 						// Session does not exists
1785 						session.id = sSID;
1786 					}
1787 					session.auth_token = sAuthToken;
1788 					// Check authen_token and restore session
1789 					if (checkAuthToken(session))
1790 					{
1791 						// user is authenticated
1792 						return true;
1793 					}
1794 
1795 					send_authorization_request(rep);
1796 					return false;
1797 
1798 				}
1799 				else
1800 				{
1801 					// invalid cookie
1802 					if (myWebem->m_authmethod != AUTH_BASIC)
1803 					{
1804 						//Check if we need to bypass authentication (not when using basic-auth)
1805 						for (auto itt : myWebem->myWhitelistURLs)
1806 						{
1807 							if (req.uri.find(itt) == 0)
1808 							{
1809 								return true;
1810 							}
1811 						}
1812 
1813 						std::string cmdparam;
1814 						if (GetURICommandParameter(req.uri, cmdparam))
1815 						{
1816 							for (auto itt : myWebem->myWhitelistCommands)
1817 							{
1818 								if (cmdparam.find(itt) == 0)
1819 									return true;
1820 							}
1821 						}
1822 						// Force login form
1823 						send_authorization_request(rep);
1824 						return false;
1825 					}
1826 				}
1827 			}
1828 
1829 			if ((session.rights == 2) && (session.id.empty()))
1830 			{
1831 				session.isnew = true;
1832 				return true;
1833 			}
1834 
1835 			//patch to let always support basic authentication function for script calls
1836 			if (req.uri.find("json.htm") != std::string::npos)
1837 			{
1838 				//Check first if we have a basic auth
1839 				if (authorize(session, req, rep))
1840 				{
1841 					return true;
1842 				}
1843 			}
1844 
1845 			if (myWebem->m_authmethod == AUTH_BASIC)
1846 			{
1847 				if (!authorize(session, req, rep))
1848 				{
1849 					if (m_failcounter > 0)
1850 					{
1851 						_log.Log(LOG_ERROR, "[web:%s] Failed authentication attempt, ignoring client request (remote address: %s)", myWebem->GetPort().c_str(), session.remote_host.c_str());
1852 					}
1853 					if (m_failcounter > 2)
1854 					{
1855 						m_failcounter = 0;
1856 						rep = reply::stock_reply(reply::service_unavailable);
1857 						return false;
1858 					}
1859 					send_authorization_request(rep);
1860 					return false;
1861 				}
1862 				return true;
1863 			}
1864 
1865 			//Check if we need to bypass authentication (not when using basic-auth)
1866 			for (auto itt : myWebem->myWhitelistURLs)
1867 			{
1868 				if (req.uri.find(itt) == 0)
1869 				{
1870 					return true;
1871 				}
1872 			}
1873 			std::string cmdparam;
1874 			if (GetURICommandParameter(req.uri, cmdparam))
1875 			{
1876 				for (auto itt : myWebem->myWhitelistCommands)
1877 				{
1878 					if (cmdparam.find(itt) == 0)
1879 					{
1880 						return true;
1881 					}
1882 				}
1883 			}
1884 
1885 			send_authorization_request(rep);
1886 			return false;
1887 		}
1888 
1889 		/**
1890 		 * Check authentication token if exists and restore the user session if necessary
1891 		 */
checkAuthToken(WebEmSession & session)1892 		bool cWebemRequestHandler::checkAuthToken(WebEmSession & session)
1893 		{
1894 			session_store_impl_ptr sstore = myWebem->GetSessionStore();
1895 			if (sstore == NULL)
1896 			{
1897 				_log.Log(LOG_ERROR, "CheckAuthToken([%s_%s]) : no store defined", session.id.c_str(), session.auth_token.c_str());
1898 				return true;
1899 			}
1900 
1901 			if (session.id.empty() || session.auth_token.empty())
1902 			{
1903 				_log.Log(LOG_ERROR, "CheckAuthToken(%s_%s) : session id or auth token is empty", session.id.c_str(), session.auth_token.c_str());
1904 				return false;
1905 			}
1906 			WebEmStoredSession storedSession = sstore->GetSession(session.id);
1907 			if (storedSession.id.empty())
1908 			{
1909 				_log.Log(LOG_ERROR, "CheckAuthToken(%s_%s) : session id not found", session.id.c_str(), session.auth_token.c_str());
1910 				return false;
1911 			}
1912 			if (storedSession.auth_token != GenerateMD5Hash(session.auth_token))
1913 			{
1914 				_log.Log(LOG_ERROR, "CheckAuthToken(%s_%s) : auth token mismatch", session.id.c_str(), session.auth_token.c_str());
1915 				removeAuthToken(session.id);
1916 				return false;
1917 			}
1918 
1919 			_log.Debug(DEBUG_WEBSERVER, "[web:%s] CheckAuthToken(%s_%s_%s) : user authenticated", myWebem->GetPort().c_str(), session.id.c_str(), session.auth_token.c_str(), session.username.c_str());
1920 
1921 			if (session.rights == 2)
1922 			{
1923 				// we are already admin - restore session from db
1924 				session.expires = storedSession.expires;
1925 				time_t now = mytime(NULL);
1926 				if (session.expires < now)
1927 				{
1928 					removeAuthToken(session.id);
1929 					return false;
1930 				}
1931 				else
1932 				{
1933 					session.timeout = now + SHORT_SESSION_TIMEOUT;
1934 					myWebem->AddSession(session);
1935 					return true;
1936 				}
1937 			}
1938 
1939 			if (session.username.empty())
1940 			{
1941 				// Restore session if user exists and session does not already exist
1942 				bool userExists = false;
1943 				bool sessionExpires = false;
1944 				session.username = storedSession.username;
1945 				session.expires = storedSession.expires;
1946 				std::vector<_tWebUserPassword>::iterator ittu;
1947 				for (ittu = myWebem->m_userpasswords.begin(); ittu != myWebem->m_userpasswords.end(); ++ittu)
1948 				{
1949 					if (ittu->Username == session.username) // the user still exists
1950 					{
1951 						userExists = true;
1952 						session.rights = ittu->userrights;
1953 						break;
1954 					}
1955 				}
1956 
1957 				time_t now = mytime(NULL);
1958 				sessionExpires = session.expires < now;
1959 
1960 				if (!userExists || sessionExpires)
1961 				{
1962 					_log.Debug(DEBUG_WEBSERVER, "[web:%s] CheckAuthToken(%s_%s) : cannot restore session, user not found or session expired", myWebem->GetPort().c_str(), session.id.c_str(), session.auth_token.c_str());
1963 					removeAuthToken(session.id);
1964 					return false;
1965 				}
1966 
1967 				WebEmSession* oldSession = myWebem->GetSession(session.id);
1968 				if (oldSession == NULL)
1969 				{
1970 					_log.Debug(DEBUG_WEBSERVER, "[web:%s] CheckAuthToken(%s_%s_%s) : restore session", myWebem->GetPort().c_str(), session.id.c_str(), session.auth_token.c_str(), session.username.c_str());
1971 					myWebem->AddSession(session);
1972 				}
1973 			}
1974 
1975 			return true;
1976 		}
1977 
removeAuthToken(const std::string & sessionId)1978 		void cWebemRequestHandler::removeAuthToken(const std::string & sessionId)
1979 		{
1980 			session_store_impl_ptr sstore = myWebem->GetSessionStore();
1981 			if (sstore != NULL)
1982 			{
1983 				sstore->RemoveSession(sessionId);
1984 			}
1985 		}
1986 
strftime_t(const char * format,const time_t rawtime)1987 		char *cWebemRequestHandler::strftime_t(const char *format, const time_t rawtime)
1988 		{
1989 			static char buffer[1024];
1990 			struct tm ltime;
1991 			localtime_r(&rawtime, &ltime);
1992 			strftime(buffer, sizeof(buffer), format, &ltime);
1993 			return buffer;
1994 		}
1995 
handle_request(const request & req,reply & rep)1996 		void cWebemRequestHandler::handle_request(const request& req, reply& rep)
1997 		{
1998 			_log.Debug(DEBUG_WEBSERVER, "web: Host:%s Uri;%s", req.host_address.c_str(), req.uri.c_str());
1999 
2000 			// Initialize session
2001 			WebEmSession session;
2002 			session.remote_host = req.host_address;
2003 
2004 			if (myWebem->myRemoteProxyIPs.size() > 0)
2005 			{
2006 				for (std::vector<std::string>::size_type i = 0; i < myWebem->myRemoteProxyIPs.size(); ++i)
2007 				{
2008 					if (session.remote_host == myWebem->myRemoteProxyIPs[i])
2009 					{
2010 						const char *host_header = request::get_req_header(&req, "X-Forwarded-For");
2011 						if (host_header != NULL)
2012 						{
2013 							if (strstr(host_header, ",") != NULL)
2014 							{
2015 								//Multiple proxies are used... this is not very common
2016 								host_header = request::get_req_header(&req, "X-Real-IP"); //try our NGINX header
2017 								if (!host_header)
2018 								{
2019 									_log.Log(LOG_ERROR, "Webserver: Multiple proxies are used (Or possible spoofing attempt), ignoring client request (remote address: %s)", session.remote_host.c_str());
2020 									rep = reply::stock_reply(reply::forbidden);
2021 									return;
2022 								}
2023 							}
2024 							session.remote_host = host_header;
2025 						}
2026 					}
2027 				}
2028 			}
2029 
2030 			session.reply_status = reply::ok;
2031 			session.isnew = false;
2032 			session.forcelogin = false;
2033 			session.rememberme = false;
2034 
2035 			rep.status = reply::ok;
2036 			rep.bIsGZIP = false;
2037 
2038 			bool isPage = myWebem->IsPageOverride(req, rep);
2039 			bool isAction = myWebem->IsAction(req);
2040 
2041 			// Respond to CORS Preflight request (for JSON API)
2042 			if (req.method == "OPTIONS")
2043 			{
2044 				reply::add_header(&rep, "Content-Length", "0");
2045 				reply::add_header(&rep, "Content-Type", "text/plain");
2046 				reply::add_header(&rep, "Access-Control-Max-Age", "3600");
2047 				reply::add_header(&rep, "Access-Control-Allow-Origin", "*");
2048 				reply::add_header(&rep, "Access-Control-Allow-Methods", "GET, POST");
2049 				reply::add_header(&rep, "Access-Control-Allow-Headers", "Authorization, Content-Type");
2050 				return;
2051 			}
2052 
2053 			// Check authentication on each page or action, if it exists.
2054 			bool bCheckAuthentication = false;
2055 			if (isPage || isAction)
2056 			{
2057 				bCheckAuthentication = true;
2058 			}
2059 
2060 			if (isPage && (req.uri.find("dologout") != std::string::npos))
2061 			{
2062 				//Remove session id based on cookie
2063 				const char *cookie;
2064 				cookie = request::get_req_header(&req, "Cookie");
2065 				if (cookie != NULL)
2066 				{
2067 					std::string scookie = cookie;
2068 					size_t fpos = scookie.find("DMZSID=");
2069 					size_t upos = scookie.find("_", fpos);
2070 					if ((fpos != std::string::npos) && (upos != std::string::npos))
2071 					{
2072 						std::string sSID = scookie.substr(fpos + 7, upos - fpos - 7);
2073 						_log.Debug(DEBUG_WEBSERVER, "Web: Logout : remove session %s", sSID.c_str());
2074 						std::map<std::string, WebEmSession>::iterator itt = myWebem->m_sessions.find(sSID);
2075 						if (itt != myWebem->m_sessions.end())
2076 						{
2077 							myWebem->m_sessions.erase(itt);
2078 						}
2079 						removeAuthToken(sSID);
2080 					}
2081 				}
2082 				session.username = "";
2083 				session.rights = -1;
2084 				session.forcelogin = true;
2085 				bCheckAuthentication = false; // do not authenticate the user, just logout
2086 				send_authorization_request(rep);
2087 				return;
2088 			}
2089 
2090 			// Check if this is an upgrade request to a websocket connection
2091 			if (is_upgrade_request(session, req, rep))
2092 			{
2093 				return;
2094 			}
2095 			// Check user authentication on each page or action, if it exists.
2096 			if ((isPage || isAction || bCheckAuthentication) && !CheckAuthentication(session, req, rep))
2097 			{
2098 				return;
2099 			}
2100 
2101 			// Copy the request to be able to fill its parameters attribute
2102 			request requestCopy = req;
2103 
2104 			bool bHandledAction = false;
2105 			// Run action if exists
2106 			if (isAction)
2107 			{
2108 				// Post actions only allowed when authenticated and user has admin rights
2109 				if (session.rights != 2)
2110 				{
2111 					rep = reply::stock_reply(reply::forbidden);
2112 					return;
2113 				}
2114 				bHandledAction = myWebem->CheckForAction(session, requestCopy);
2115 				if (!requestCopy.uri.empty())
2116 				{
2117 					if ((requestCopy.method == "POST") && (requestCopy.uri[0] != '/'))
2118 					{
2119 						//Send back as data instead of a redirect uri
2120 						rep.status = reply::ok;
2121 						rep.content = requestCopy.uri;
2122 						reply::add_header(&rep, "Content-Length", std::to_string(rep.content.size()));
2123 						reply::add_header(&rep, "Last-Modified", make_web_time(mytime(NULL)), true);
2124 						reply::add_header(&rep, "Content-Type", "application/json;charset=UTF-8");
2125 						return;
2126 					}
2127 				}
2128 			}
2129 
2130 			if (!bHandledAction)
2131 			{
2132 				if (myWebem->CheckForPageOverride(session, requestCopy, rep))
2133 				{
2134 					if (rep.status == reply::status_type::download_file)
2135 						return;
2136 
2137 					if (session.reply_status != reply::ok) // forbidden
2138 					{
2139 						rep = reply::stock_reply(static_cast<reply::status_type>(session.reply_status));
2140 						return;
2141 					}
2142 
2143 					if (!rep.bIsGZIP)
2144 					{
2145 						CompressWebOutput(req, rep);
2146 					}
2147 				}
2148 				else
2149 				{
2150 					modify_info mInfo;
2151 					if (session.reply_status != reply::ok)
2152 					{
2153 						rep = reply::stock_reply(static_cast<reply::status_type>(session.reply_status));
2154 						return;
2155 					}
2156 					if (rep.status != reply::ok) // bad request
2157 					{
2158 						return;
2159 					}
2160 
2161 					// do normal handling
2162 					try
2163 					{
2164 						std::string uri = myWebem->ExtractRequestPath(requestCopy.uri);
2165 						if (uri.find("/images/") == 0)
2166 						{
2167 							std::string theme_images_path = myWebem->m_actTheme + uri;
2168 							if (file_exist((doc_root_ + theme_images_path).c_str()))
2169 								requestCopy.uri = myWebem->GetWebRoot() + theme_images_path;
2170 						}
2171 
2172 						request_handler::handle_request(requestCopy, rep, mInfo);
2173 					}
2174 					catch (...)
2175 					{
2176 						rep = reply::stock_reply(reply::internal_server_error);
2177 						return;
2178 					}
2179 
2180 					// find content type header
2181 					std::string content_type;
2182 					for (unsigned int h = 0; h < rep.headers.size(); h++)
2183 					{
2184 						if (boost::iequals(rep.headers[h].name, "Content-Type"))
2185 						{
2186 							content_type = rep.headers[h].value;
2187 							break;
2188 						}
2189 					}
2190 
2191 					if (content_type == "text/html"
2192 						|| content_type == "text/plain"
2193 						|| content_type == "text/css"
2194 						|| content_type == "text/javascript"
2195 						|| content_type == "application/javascript"
2196 						)
2197 					{
2198 						// check if content is not gzipped, include won't work with non-text content
2199 						if (!rep.bIsGZIP)
2200 						{
2201 							// Find and include any special cWebem strings
2202 							if (!myWebem->Include(rep.content))
2203 							{
2204 								if (mInfo.mtime_support && !mInfo.is_modified)
2205 								{
2206 									_log.Debug(DEBUG_WEBSERVER, "[web:%s] %s not modified (1).", myWebem->GetPort().c_str(), req.uri.c_str());
2207 									rep = reply::stock_reply(reply::not_modified);
2208 									return;
2209 								}
2210 							}
2211 
2212 							// adjust content length header
2213 							// ( Firefox ignores this, but apparently some browsers truncate display without it.
2214 							// fix provided by http://www.codeproject.com/Members/jaeheung72 )
2215 
2216 							reply::add_header(&rep, "Content-Length", std::to_string(rep.content.size()));
2217 
2218 							if (!mInfo.mtime_support)
2219 							{
2220 								reply::add_header(&rep, "Last-Modified", make_web_time(mytime(NULL)), true);
2221 							}
2222 
2223 							//check gzip support if yes, send it back in gzip format
2224 							CompressWebOutput(req, rep);
2225 						}
2226 
2227 						// tell browser that we are using UTF-8 encoding
2228 						reply::add_header(&rep, "Content-Type", content_type + ";charset=UTF-8");
2229 					}
2230 					else if (mInfo.mtime_support && !mInfo.is_modified)
2231 					{
2232 						rep = reply::stock_reply(reply::not_modified);
2233 						_log.Debug(DEBUG_WEBSERVER, "[web:%s] %s not modified (2).", myWebem->GetPort().c_str(), req.uri.c_str());
2234 						return;
2235 					}
2236 					else if (content_type.find("image/") != std::string::npos)
2237 					{
2238 						//Cache images
2239 						reply::add_header(&rep, "Expires", make_web_time(mytime(NULL) + 3600 * 24 * 365)); // one year
2240 					}
2241 					else
2242 					{
2243 						// tell browser that we are using UTF-8 encoding
2244 						reply::add_header(&rep, "Content-Type", content_type + ";charset=UTF-8");
2245 					}
2246 				}
2247 			}
2248 
2249 			// Set timeout to make session in use
2250 			session.timeout = mytime(NULL) + SHORT_SESSION_TIMEOUT;
2251 
2252 			if ((session.isnew == true) &&
2253 				(session.rights == 2) &&
2254 				(req.uri.find("json.htm") != std::string::npos) &&
2255 				(req.uri.find("logincheck") == std::string::npos)
2256 				)
2257 			{
2258 				// client is possibly a script that does not send cookies - see if we have the IP address registered as a session ID
2259 				WebEmSession* memSession = myWebem->GetSession(session.remote_host);
2260 				time_t now = mytime(NULL);
2261 				if (memSession != NULL)
2262 				{
2263 					if (memSession->expires < now)
2264 					{
2265 						myWebem->RemoveSession(session.remote_host);
2266 					}
2267 					else
2268 					{
2269 						session.isnew = false;
2270 						if (memSession->expires - (SHORT_SESSION_TIMEOUT / 2) < now)
2271 						{
2272 							memSession->expires = now + SHORT_SESSION_TIMEOUT;
2273 
2274 							// unsure about the point of the forced removal of 'live' sessions and restore from
2275 							// database but these 'fake' sessions are memory only and can't be restored that way.
2276 							// Should I do a RemoveSession() followed by a AddSession()?
2277 							// For now: keep 'timeout' in sync with 'expires'
2278 							memSession->timeout = memSession->expires;
2279 						}
2280 					}
2281 				}
2282 
2283 				if (session.isnew == true)
2284 				{
2285 					// register a 'fake' IP based session so we can reference that if the client returns here
2286 					session.id = session.remote_host;
2287 					session.rights = -1; // predictable session ID must have no rights
2288 					session.expires = session.timeout;
2289 					myWebem->AddSession(session);
2290 					session.rights = 2; // restore session rights
2291 				}
2292 			}
2293 
2294 			if (session.isnew == true)
2295 			{
2296 				_log.Log(LOG_STATUS, "Incoming connection from: %s", session.remote_host.c_str());
2297 				// Create a new session ID
2298 				session.id = generateSessionID();
2299 				session.expires = session.timeout;
2300 				if (session.rememberme)
2301 				{
2302 					// Extend session by 30 days
2303 					session.expires += LONG_SESSION_TIMEOUT;
2304 				}
2305 				session.auth_token = generateAuthToken(session, req); // do it after expires to save it also
2306 				session.isnew = false;
2307 				myWebem->AddSession(session);
2308 				send_cookie(rep, session);
2309 			}
2310 			else if (session.forcelogin == true)
2311 			{
2312 				_log.Debug(DEBUG_WEBSERVER, "[web:%s] Logout : remove session %s", myWebem->GetPort().c_str(), session.id.c_str());
2313 
2314 				myWebem->RemoveSession(session.id);
2315 				removeAuthToken(session.id);
2316 				if (myWebem->m_authmethod == AUTH_BASIC)
2317 				{
2318 					send_authorization_request(rep);
2319 				}
2320 
2321 			}
2322 			else if (session.id.size() > 0)
2323 			{
2324 				// Renew session expiration and authentication token
2325 				WebEmSession* memSession = myWebem->GetSession(session.id);
2326 				if (memSession != NULL)
2327 				{
2328 					time_t now = mytime(NULL);
2329 					// Renew session expiration date if half of session duration has been exceeded ("dont remember me" sessions, 10 minutes)
2330 					if (memSession->expires - (SHORT_SESSION_TIMEOUT / 2) < now)
2331 					{
2332 						memSession->expires = now + SHORT_SESSION_TIMEOUT;
2333 						memSession->auth_token = generateAuthToken(*memSession, req); // do it after expires to save it also
2334 						send_cookie(rep, *memSession);
2335 					}
2336 					// Renew session expiration date if half of session duration has been exceeded ("remember me" sessions, 30 days)
2337 					else if ((memSession->expires > SHORT_SESSION_TIMEOUT + now) && (memSession->expires - (LONG_SESSION_TIMEOUT / 2) < now))
2338 					{
2339 						memSession->expires = now + LONG_SESSION_TIMEOUT;
2340 						memSession->auth_token = generateAuthToken(*memSession, req); // do it after expires to save it also
2341 						send_cookie(rep, *memSession);
2342 					}
2343 				}
2344 			}
2345 		}
2346 
2347 	} //namespace server {
2348 } //namespace http {
2349