1 #include "stdafx.h"
2 #include <iostream>
3 #include "Camera.h"
4 #include "HTMLSanitizer.h"
5 #include "localtime_r.h"
6 #include "Logger.h"
7 #include "Helper.h"
8 #include "mainworker.h"
9 #include "../httpclient/HTTPClient.h"
10 #include "../smtpclient/SMTPClient.h"
11 #include "../webserver/Base64.h"
12 #include "SQLHelper.h"
13 #include "WebServer.h"
14 #include "../webserver/cWebem.h"
15 #include <json/json.h>
16 
17 #define CAMERA_POLL_INTERVAL 30
18 
19 extern std::string szUserDataFolder;
20 
CCameraHandler(void)21 CCameraHandler::CCameraHandler(void)
22 {
23 	m_seconds_counter = 0;
24 }
25 
~CCameraHandler(void)26 CCameraHandler::~CCameraHandler(void)
27 {
28 }
29 
ReloadCameras()30 void CCameraHandler::ReloadCameras()
31 {
32 	std::vector<std::string> _AddedCameras;
33 	std::lock_guard<std::mutex> l(m_mutex);
34 	m_cameradevices.clear();
35 	std::vector<std::vector<std::string> > result;
36 
37 	result = m_sql.safe_query("SELECT ID, Name, Address, Port, Username, Password, ImageURL, Protocol FROM Cameras WHERE (Enabled == 1) ORDER BY ID");
38 	if (!result.empty())
39 	{
40 		_log.Log(LOG_STATUS, "Camera: settings (re)loaded");
41 		for (const auto & itt : result)
42 		{
43 			std::vector<std::string> sd = itt;
44 
45 			cameraDevice citem;
46 			citem.ID = std::stoull(sd[0]);
47 			citem.Name = sd[1];
48 			citem.Address = sd[2];
49 			citem.Port = atoi(sd[3].c_str());
50 			citem.Username = base64_decode(sd[4]);
51 			citem.Password = base64_decode(sd[5]);
52 			citem.ImageURL = sd[6];
53 			citem.Protocol = (eCameraProtocol)atoi(sd[7].c_str());
54 			m_cameradevices.push_back(citem);
55 			_AddedCameras.push_back(sd[0]);
56 		}
57 	}
58 
59 	for (const auto & ittCam : _AddedCameras)
60 	{
61 		//Get Active Devices/Scenes
62 		ReloadCameraActiveDevices(ittCam);
63 	}
64 }
65 
ReloadCameraActiveDevices(const std::string & CamID)66 void CCameraHandler::ReloadCameraActiveDevices(const std::string &CamID)
67 {
68 	cameraDevice *pCamera = GetCamera(CamID);
69 	if (pCamera == NULL)
70 		return;
71 	pCamera->mActiveDevices.clear();
72 	std::vector<std::vector<std::string> > result;
73 	result = m_sql.safe_query("SELECT ID, DevSceneType, DevSceneRowID FROM CamerasActiveDevices WHERE (CameraRowID=='%q') ORDER BY ID", CamID.c_str());
74 	if (!result.empty())
75 	{
76 		for (const auto & itt : result)
77 		{
78 			std::vector<std::string> sd = itt;
79 			cameraActiveDevice aDevice;
80 			aDevice.ID = std::stoull(sd[0]);
81 			aDevice.DevSceneType = (unsigned char)atoi(sd[1].c_str());
82 			aDevice.DevSceneRowID = std::stoull(sd[2]);
83 			pCamera->mActiveDevices.push_back(aDevice);
84 		}
85 	}
86 }
87 
88 //Return 0 if NO, otherwise Cam IDX
IsDevSceneInCamera(const unsigned char DevSceneType,const std::string & DevSceneID)89 uint64_t CCameraHandler::IsDevSceneInCamera(const unsigned char DevSceneType, const std::string &DevSceneID)
90 {
91 	return IsDevSceneInCamera(DevSceneType, std::stoull(DevSceneID));
92 }
93 
IsDevSceneInCamera(const unsigned char DevSceneType,const uint64_t DevSceneID)94 uint64_t CCameraHandler::IsDevSceneInCamera(const unsigned char DevSceneType, const uint64_t DevSceneID)
95 {
96 	std::lock_guard<std::mutex> l(m_mutex);
97 	for (const auto & itt : m_cameradevices)
98 	{
99 		for (const auto & itt2 : itt.mActiveDevices)
100 		{
101 			if (
102 				(itt2.DevSceneType == DevSceneType) &&
103 				(itt2.DevSceneRowID == DevSceneID)
104 				)
105 				return itt.ID;
106 		}
107 	}
108 	return 0;
109 }
110 
GetCameraURL(const std::string & CamID)111 std::string CCameraHandler::GetCameraURL(const std::string &CamID)
112 {
113 	cameraDevice* pCamera = GetCamera(CamID);
114 	if (pCamera == NULL)
115 		return "";
116 	return GetCameraURL(pCamera);
117 }
118 
GetCameraURL(const uint64_t CamID)119 std::string CCameraHandler::GetCameraURL(const uint64_t CamID)
120 {
121 	cameraDevice* pCamera = GetCamera(CamID);
122 	if (pCamera == NULL)
123 		return "";
124 	return GetCameraURL(pCamera);
125 }
126 
GetCameraURL(cameraDevice * pCamera)127 std::string CCameraHandler::GetCameraURL(cameraDevice *pCamera)
128 {
129 	std::stringstream s_str;
130 
131 	bool bHaveUPinURL = (pCamera->ImageURL.find("#USERNAME") != std::string::npos) || (pCamera->ImageURL.find("#PASSWORD") != std::string::npos);
132 
133 	std::string szURLPreFix = (pCamera->Protocol == CPROTOCOL_HTTP) ? "http" : "https";
134 
135 	if ((!bHaveUPinURL) && ((pCamera->Username != "") || (pCamera->Password != "")))
136 		s_str << szURLPreFix << "://" << CURLEncode::URLEncode(pCamera->Username) << ":" << CURLEncode::URLEncode(pCamera->Password) << "@" << pCamera->Address << ":" << pCamera->Port;
137 	else
138 		s_str << szURLPreFix << "://" << pCamera->Address << ":" << pCamera->Port;
139 	return s_str.str();
140 }
141 
GetCamera(const std::string & CamID)142 CCameraHandler::cameraDevice* CCameraHandler::GetCamera(const std::string &CamID)
143 {
144 	return GetCamera(std::stoull(CamID));
145 }
146 
GetCamera(const uint64_t CamID)147 CCameraHandler::cameraDevice* CCameraHandler::GetCamera(const uint64_t CamID)
148 {
149 	for (auto & itt : m_cameradevices)
150 	{
151 		if (itt.ID == CamID)
152 			return &itt;
153 	}
154 	return NULL;
155 }
156 
TakeSnapshot(const std::string & CamID,std::vector<unsigned char> & camimage)157 bool CCameraHandler::TakeSnapshot(const std::string &CamID, std::vector<unsigned char> &camimage)
158 {
159 	return TakeSnapshot(std::stoull(CamID), camimage);
160 }
161 
TakeRaspberrySnapshot(std::vector<unsigned char> & camimage)162 bool CCameraHandler::TakeRaspberrySnapshot(std::vector<unsigned char> &camimage)
163 {
164 	std::string raspparams = "-w 800 -h 600 -t 1";
165 	m_sql.GetPreferencesVar("RaspCamParams", raspparams);
166 
167 	std::string OutputFileName = szUserDataFolder + "tempcam.jpg";
168 
169 	std::string raspistillcmd = "raspistill " + raspparams + " -o " + OutputFileName;
170 	std::remove(OutputFileName.c_str());
171 
172 	//Get our image
173 	int ret = system(raspistillcmd.c_str());
174 	if (ret != 0)
175 	{
176 		_log.Log(LOG_ERROR, "Error executing raspistill command. returned: %d", ret);
177 		return false;
178 	}
179 	//If all went correct, we should have our file
180 	try
181 	{
182 		std::ifstream is(OutputFileName.c_str(), std::ios::in | std::ios::binary);
183 		if (is)
184 		{
185 			if (is.is_open())
186 			{
187 				char buf[512];
188 				while (is.read(buf, sizeof(buf)).gcount() > 0)
189 					camimage.insert(camimage.end(), buf, buf + (unsigned int)is.gcount());
190 				is.close();
191 				std::remove(OutputFileName.c_str());
192 				return true;
193 			}
194 		}
195 	}
196 	catch (...)
197 	{
198 
199 	}
200 
201 	return false;
202 }
203 
TakeUVCSnapshot(const std::string & device,std::vector<unsigned char> & camimage)204 bool CCameraHandler::TakeUVCSnapshot(const std::string &device, std::vector<unsigned char> &camimage)
205 {
206 	std::string uvcparams = "-S80 -B128 -C128 -G80 -x800 -y600 -q100";
207 	m_sql.GetPreferencesVar("UVCParams", uvcparams);
208 
209 	std::string OutputFileName = szUserDataFolder + "tempcam.jpg";
210 	std::string nvcmd = "uvccapture " + uvcparams + " -o" + OutputFileName;
211 	if (!device.empty()) {
212 		nvcmd += " -d/dev/" + device;
213 	}
214 	std::remove(OutputFileName.c_str());
215 
216 	try
217 	{
218 		//Get our image
219 		int ret = system(nvcmd.c_str());
220 		if (ret != 0)
221 		{
222 			_log.Log(LOG_ERROR, "Error executing uvccapture command. returned: %d", ret);
223 			return false;
224 		}
225 		//If all went correct, we should have our file
226 		std::ifstream is(OutputFileName.c_str(), std::ios::in | std::ios::binary);
227 		if (is)
228 		{
229 			if (is.is_open())
230 			{
231 				char buf[512];
232 				while (is.read(buf, sizeof(buf)).gcount() > 0)
233 					camimage.insert(camimage.end(), buf, buf + (unsigned int)is.gcount());
234 				is.close();
235 				std::remove(OutputFileName.c_str());
236 				return true;
237 			}
238 		}
239 	}
240 	catch (...)
241 	{
242 
243 	}
244 	return false;
245 }
246 
TakeSnapshot(const uint64_t CamID,std::vector<unsigned char> & camimage)247 bool CCameraHandler::TakeSnapshot(const uint64_t CamID, std::vector<unsigned char> &camimage)
248 {
249 	std::lock_guard<std::mutex> l(m_mutex);
250 
251 	cameraDevice *pCamera = GetCamera(CamID);
252 	if (pCamera == NULL)
253 		return false;
254 
255 	std::string szURL = GetCameraURL(pCamera);
256 	szURL += "/" + pCamera->ImageURL;
257 	stdreplace(szURL, "#USERNAME", pCamera->Username);
258 	stdreplace(szURL, "#PASSWORD", pCamera->Password);
259 
260 	if (pCamera->ImageURL == "raspberry.cgi")
261 		return TakeRaspberrySnapshot(camimage);
262 	else if (pCamera->ImageURL == "uvccapture.cgi")
263 		return TakeUVCSnapshot(pCamera->Username, camimage);
264 
265 	std::vector<std::string> ExtraHeaders;
266 	return HTTPClient::GETBinary(szURL, ExtraHeaders, camimage, 5);
267 }
268 
WrapBase64(const std::string & szSource,const size_t lsize=72)269 std::string WrapBase64(const std::string &szSource, const size_t lsize = 72)
270 {
271 	std::string cstring = szSource;
272 	std::string ret = "";
273 	while (cstring.size() > lsize)
274 	{
275 		std::string pstring = cstring.substr(0, lsize);
276 		if (!ret.empty())
277 			ret += '\n';
278 		ret += pstring;
279 		cstring = cstring.substr(lsize);
280 	}
281 	if (!cstring.empty())
282 	{
283 		ret += '\n' + cstring;
284 	}
285 	return ret;
286 }
287 
EmailCameraSnapshot(const std::string & CamIdx,const std::string & subject)288 bool CCameraHandler::EmailCameraSnapshot(const std::string &CamIdx, const std::string &subject)
289 {
290 	int nValue;
291 	if (!m_sql.GetPreferencesVar("EmailEnabled", nValue))
292 	{
293 		return false;//no email setup
294 	}
295 	if (!nValue)
296 		return false; //disabled
297 
298 	std::string sValue;
299 	if (!m_sql.GetPreferencesVar("EmailServer", sValue))
300 	{
301 		return false;//no email setup
302 	}
303 	if (sValue == "")
304 	{
305 		return false;//no email setup
306 	}
307 	if (CamIdx == "")
308 		return false;
309 
310 	std::vector<std::string> splitresults;
311 	StringSplit(CamIdx, ";", splitresults);
312 
313 	std::string EmailFrom;
314 	std::string EmailTo;
315 	std::string EmailServer = sValue;
316 	int EmailPort = 25;
317 	std::string EmailUsername;
318 	std::string EmailPassword;
319 	int EmailAsAttachment = 0;
320 	m_sql.GetPreferencesVar("EmailFrom", EmailFrom);
321 	m_sql.GetPreferencesVar("EmailTo", EmailTo);
322 	m_sql.GetPreferencesVar("EmailUsername", EmailUsername);
323 	m_sql.GetPreferencesVar("EmailPassword", EmailPassword);
324 	m_sql.GetPreferencesVar("EmailPort", EmailPort);
325 	m_sql.GetPreferencesVar("EmailAsAttachment", EmailAsAttachment);
326 	std::string htmlMsg =
327 		"<html>\r\n"
328 		"<body>\r\n";
329 
330 	SMTPClient sclient;
331 	sclient.SetFrom(CURLEncode::URLDecode(EmailFrom.c_str()));
332 	sclient.SetTo(CURLEncode::URLDecode(EmailTo.c_str()));
333 	sclient.SetCredentials(base64_decode(EmailUsername), base64_decode(EmailPassword));
334 	sclient.SetServer(CURLEncode::URLDecode(EmailServer.c_str()), EmailPort);
335 	sclient.SetSubject(CURLEncode::URLDecode(subject));
336 
337 	for (const auto & camIt : splitresults)
338 	{
339 		std::vector<unsigned char> camimage;
340 
341 		if (!TakeSnapshot(camIt, camimage))
342 			return false;
343 
344 		std::vector<char> filedata;
345 		filedata.insert(filedata.begin(), camimage.begin(), camimage.end());
346 		std::string imgstring;
347 		imgstring.insert(imgstring.end(), filedata.begin(), filedata.end());
348 		imgstring = base64_encode(imgstring);
349 		imgstring = WrapBase64(imgstring);
350 
351 		htmlMsg +=
352 			"<img src=\"data:image/jpeg;base64,";
353 		htmlMsg +=
354 			imgstring +
355 			"\">\r\n";
356 		if (EmailAsAttachment != 0)
357 			sclient.AddAttachment(imgstring, "snapshot" + camIt + ".jpg");
358 	}
359 	if (EmailAsAttachment == 0)
360 		sclient.SetHTMLBody(htmlMsg);
361 	bool bRet = sclient.SendEmail();
362 	return bRet;
363 }
364 
365 //Webserver helpers
366 namespace http {
367 	namespace server {
RType_Cameras(WebEmSession & session,const request & req,Json::Value & root)368 		void CWebServer::RType_Cameras(WebEmSession & session, const request& req, Json::Value &root)
369 		{
370 			if (session.rights < 2)
371 			{
372 				session.reply_status = reply::forbidden;
373 				return; //Only admin user allowed
374 			}
375 
376 			std::string rused = request::findValue(&req, "used");
377 
378 			root["status"] = "OK";
379 			root["title"] = "Cameras";
380 
381 			std::vector<std::vector<std::string> > result;
382 			if (rused == "true") {
383 				result = m_sql.safe_query("SELECT ID, Name, Enabled, Address, Port, Username, Password, ImageURL, Protocol FROM Cameras WHERE (Enabled=='1') ORDER BY ID ASC");
384 			}
385 			else {
386 				result = m_sql.safe_query("SELECT ID, Name, Enabled, Address, Port, Username, Password, ImageURL, Protocol FROM Cameras ORDER BY ID ASC");
387 			}
388 			if (!result.empty())
389 			{
390 				int ii = 0;
391 				for (const auto & itt : result)
392 				{
393 					std::vector<std::string> sd = itt;
394 
395 					root["result"][ii]["idx"] = sd[0];
396 					root["result"][ii]["Name"] = sd[1];
397 					root["result"][ii]["Enabled"] = (sd[2] == "1") ? "true" : "false";
398 					root["result"][ii]["Address"] = sd[3];
399 					root["result"][ii]["Port"] = atoi(sd[4].c_str());
400 					root["result"][ii]["Username"] = base64_decode(sd[5]);
401 					root["result"][ii]["Password"] = base64_decode(sd[6]);
402 					root["result"][ii]["ImageURL"] = sd[7];
403 					root["result"][ii]["Protocol"] = atoi(sd[8].c_str());
404 					ii++;
405 				}
406 			}
407 		}
RType_CamerasUser(WebEmSession & session,const request & req,Json::Value & root)408 		void CWebServer::RType_CamerasUser(WebEmSession& session, const request& req, Json::Value& root)
409 		{
410 			root["status"] = "OK";
411 			root["title"] = "Cameras";
412 
413 			std::vector<std::vector<std::string> > result;
414 			result = m_sql.safe_query("SELECT ID, Name FROM Cameras WHERE (Enabled=='1') ORDER BY ID ASC");
415 			if (!result.empty())
416 			{
417 				int ii = 0;
418 				for (const auto& itt : result)
419 				{
420 					std::vector<std::string> sd = itt;
421 
422 					root["result"][ii]["idx"] = sd[0];
423 					root["result"][ii]["Name"] = sd[1];
424 					ii++;
425 				}
426 			}
427 		}
GetInternalCameraSnapshot(WebEmSession & session,const request & req,reply & rep)428 		void CWebServer::GetInternalCameraSnapshot(WebEmSession & session, const request& req, reply & rep)
429 		{
430 			std::string request_path;
431 			request_handler::url_decode(req.uri, request_path);
432 
433 			std::vector<unsigned char> camimage;
434 			if (request_path.find("raspberry") != std::string::npos)
435 			{
436 				if (!m_mainworker.m_cameras.TakeRaspberrySnapshot(camimage)) {
437 					return;
438 				}
439 			}
440 			else
441 			{
442 				if (!m_mainworker.m_cameras.TakeUVCSnapshot("", camimage)) {
443 					return;
444 				}
445 			}
446 			reply::set_content(&rep, camimage.begin(), camimage.end());
447 			reply::add_header_attachment(&rep, "snapshot.jpg");
448 		}
449 
GetCameraSnapshot(WebEmSession & session,const request & req,reply & rep)450 		void CWebServer::GetCameraSnapshot(WebEmSession & session, const request& req, reply & rep)
451 		{
452 			std::vector<unsigned char> camimage;
453 			std::string idx = request::findValue(&req, "idx");
454 			if (idx == "") {
455 				return;
456 			}
457 			if (!m_mainworker.m_cameras.TakeSnapshot(idx, camimage)) {
458 				return;
459 			}
460 			reply::set_content(&rep, camimage.begin(), camimage.end());
461 			reply::add_header_attachment(&rep, "snapshot.jpg");
462 		}
463 
Cmd_AddCamera(WebEmSession & session,const request & req,Json::Value & root)464 		void CWebServer::Cmd_AddCamera(WebEmSession & session, const request& req, Json::Value &root)
465 		{
466 			if (session.rights < 2)
467 			{
468 				session.reply_status = reply::forbidden;
469 				return; //Only admin user allowed
470 			}
471 
472 			std::string name = HTMLSanitizer::Sanitize(request::findValue(&req, "name"));
473 			std::string senabled = request::findValue(&req, "enabled");
474 			std::string address = HTMLSanitizer::Sanitize(request::findValue(&req, "address"));
475 			std::string sport = request::findValue(&req, "port");
476 			std::string username = HTMLSanitizer::Sanitize(request::findValue(&req, "username"));
477 			std::string password = request::findValue(&req, "password");
478 			std::string timageurl = HTMLSanitizer::Sanitize(request::findValue(&req, "imageurl"));
479 			int cprotocol = atoi(request::findValue(&req, "protocol").c_str());
480 			if (
481 				(name == "") ||
482 				(address == "") ||
483 				(timageurl == "")
484 				)
485 				return;
486 
487 			std::string imageurl;
488 			if (request_handler::url_decode(timageurl, imageurl))
489 			{
490 				imageurl = base64_decode(imageurl);
491 
492 				int port = atoi(sport.c_str());
493 				root["status"] = "OK";
494 				root["title"] = "AddCamera";
495 				m_sql.safe_query(
496 					"INSERT INTO Cameras (Name, Enabled, Address, Port, Username, Password, ImageURL, Protocol) VALUES ('%q',%d,'%q',%d,'%q','%q','%q',%d)",
497 					name.c_str(),
498 					(senabled == "true") ? 1 : 0,
499 					address.c_str(),
500 					port,
501 					base64_encode(username).c_str(),
502 					base64_encode(password).c_str(),
503 					imageurl.c_str(),
504 					cprotocol
505 				);
506 				m_mainworker.m_cameras.ReloadCameras();
507 			}
508 		}
509 
Cmd_UpdateCamera(WebEmSession & session,const request & req,Json::Value & root)510 		void CWebServer::Cmd_UpdateCamera(WebEmSession & session, const request& req, Json::Value &root)
511 		{
512 			if (session.rights < 2)
513 			{
514 				session.reply_status = reply::forbidden;
515 				return; //Only admin user allowed
516 			}
517 
518 			std::string idx = request::findValue(&req, "idx");
519 			if (idx == "")
520 				return;
521 			std::string name = HTMLSanitizer::Sanitize(request::findValue(&req, "name"));
522 			std::string senabled = request::findValue(&req, "enabled");
523 			std::string address = HTMLSanitizer::Sanitize(request::findValue(&req, "address"));
524 			std::string sport = request::findValue(&req, "port");
525 			std::string username = HTMLSanitizer::Sanitize(request::findValue(&req, "username"));
526 			std::string password = request::findValue(&req, "password");
527 			std::string timageurl = HTMLSanitizer::Sanitize(request::findValue(&req, "imageurl"));
528 			int cprotocol = atoi(request::findValue(&req, "protocol").c_str());
529 			if (
530 				(name == "") ||
531 				(senabled == "") ||
532 				(address == "") ||
533 				(timageurl == "")
534 				)
535 				return;
536 
537 			std::string imageurl;
538 			if (request_handler::url_decode(timageurl, imageurl))
539 			{
540 				imageurl = base64_decode(imageurl);
541 
542 				int port = atoi(sport.c_str());
543 
544 				root["status"] = "OK";
545 				root["title"] = "UpdateCamera";
546 
547 				m_sql.safe_query(
548 					"UPDATE Cameras SET Name='%q', Enabled=%d, Address='%q', Port=%d, Username='%q', Password='%q', ImageURL='%q', Protocol=%d WHERE (ID == '%q')",
549 					name.c_str(),
550 					(senabled == "true") ? 1 : 0,
551 					address.c_str(),
552 					port,
553 					base64_encode(username).c_str(),
554 					base64_encode(password).c_str(),
555 					imageurl.c_str(),
556 					cprotocol,
557 					idx.c_str()
558 				);
559 				m_mainworker.m_cameras.ReloadCameras();
560 			}
561 		}
562 
Cmd_DeleteCamera(WebEmSession & session,const request & req,Json::Value & root)563 		void CWebServer::Cmd_DeleteCamera(WebEmSession & session, const request& req, Json::Value &root)
564 		{
565 			if (session.rights < 2)
566 			{
567 				session.reply_status = reply::forbidden;
568 				return; //Only admin user allowed
569 			}
570 
571 			std::string idx = request::findValue(&req, "idx");
572 			if (idx == "")
573 				return;
574 			root["status"] = "OK";
575 			root["title"] = "DeleteCamera";
576 
577 			m_sql.DeleteCamera(idx);
578 			m_mainworker.m_cameras.ReloadCameras();
579 		}
580 	}
581 }
582