HttpsServer.cpp 7.65 KB
Newer Older
1
2
3
4
5
6
7
8
/*
 * HttpsServer.cpp
 *
 *  Created on: 25.05.2018
 *      Author: Micha Mueller
 */

#include "HttpsServer.h"
9
#include "timestamp.h"
10

11
#include <iostream>
12
13
#include <memory>
#include <functional>
14

Micha Mueller's avatar
Micha Mueller committed
15
16
#include <boost/algorithm/string/split.hpp>

17
18
HttpsServer::requestHandler::requestHandler(HttpsServer& httpsServer) :	_httpsServer(httpsServer) {}

19
void HttpsServer::requestHandler::operator()(server::request const &request, server::connection_ptr connection) {
20
	//first log some info about client
21
22
23
	server::string_type ip = source(request);
	unsigned int port = request.source_port;

24
25
	LOG(info) << "HttpsServer: " << ip << ":" << port << " connected";

Micha Mueller's avatar
Micha Mueller committed
26
27
28
29
	connection->set_status(server::connection::internal_server_error);

	std::ostringstream data;
	server::string_type method = request.method;
30

Micha Mueller's avatar
Micha Mueller committed
31
32
33
34
35
36
	//first check if request is supported at all
	if (method != "GET" && method != "PUT") {
		LOG(info) << "HttpsServer: Unsupported " << method << " request was made";
		connection->set_status(server::connection::not_supported);
	} else {
		LOG(info) << "HttpsServer: " << method << " request of " << request.destination << " was made";
37
38
39

		std::string response = "";

Micha Mueller's avatar
Micha Mueller committed
40
41
42
		boost::network::uri::uri uri("https://" + request.destination);
		server::string_type path = uri.path();
		server::string_type query = uri.query();
43
44

		//do some string processing
Micha Mueller's avatar
Micha Mueller committed
45
46
47
48
49
50
51
		std::vector<std::string> pathStrs;
		std::vector<std::pair<std::string, std::string>> queries;

		//split path into its hierarchical parts
		if (path.size() >= 2) {
			if (path[0] == '/') {
				path.erase(0,1);
52
			}
Micha Mueller's avatar
Micha Mueller committed
53
54
			if (path[path.size() -1] == '/') {
				path.erase(path.size() -1);
55
56
			}

Micha Mueller's avatar
Micha Mueller committed
57
58
			boost::split(pathStrs, path, boost::is_any_of("/"), boost::token_compress_off);
		}
59

Micha Mueller's avatar
Micha Mueller committed
60
61
62
63
64
		//split query part into the individual queries
		std::vector<std::string> queryStrs;
		boost::split(queryStrs, query, boost::is_any_of(";"), boost::token_compress_on);
		for(auto& key : queryStrs) {
			size_t pos = key.find("=");
65
			if (pos != std::string::npos) {
Micha Mueller's avatar
Micha Mueller committed
66
67
68
69
				std::string value;
				value = key.substr(pos+1);
				key.erase(pos);
				queries.push_back(std::make_pair(key, value));
70
71
72
73
			}
		}
		//finished string processing

74
		std::string auth_value = "";
Micha Mueller's avatar
Micha Mueller committed
75
76
77
78
		for (auto& p : queries) {
			if (p.first == "authkey") {
				auth_value = p.second;
				break;
79
80
81
			}
		}

Micha Mueller's avatar
Micha Mueller committed
82
83
84
85
86
87
88
		if (pathStrs.size() < 2 || auth_value == "") {
			LOG(error) << "Received malformed request";
			connection->set_status(server::connection::bad_request);
		} else {
			std::string plugin = pathStrs[0];

			//select code depending on request
89
/* ##################################################### GET ##################################################### */
Micha Mueller's avatar
Micha Mueller committed
90
91
92
93
94
95
96
97
98
99
			if (method == "GET") {

				//do some prior checks
				if (pathStrs.size() < 3 || pathStrs[2] != "avg") {
					response = "GET requests should be of the form \"host:port/[plugin]/[sensor]/[action]?authkey=[token]\"\n"
											"Where [plugin] names a plugin, [sensor] a sensor within this plugin, [action] is 'avg' and [token] is your authentication key";
					connection->set_status(server::connection::bad_request);
				} else {
					std::string sensor = pathStrs[1];
					std::string action = pathStrs[2];
100
101
102
103
104
105
106
107
108
					uint64_t time = 0;

					for (auto& p : queries) {
						if (p.first == "interval") {
							time = getTimestamp();
							time -= S_TO_NS(std::stoul(p.second));
						}
					}

Micha Mueller's avatar
Micha Mueller committed
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
					//process actual request
					//check if query and action are valid values
					//check if authkey is valid
					if (_httpsServer.check_authkey(auth_value, permission::GETReq)) {
						response = "Plugin not found!";
						connection->set_status(server::connection::not_found);

						for(auto& p : _httpsServer._plugins) {
							if (p.id == plugin) {
								response = "Sensor not found!";
								for(auto s : p.configurator->getSensors()) {
									if (s->getName() == sensor) {
										uint64_t avg = 0;
										const reading_t * const cache = s->getCache();
										unsigned size = s->getCacheSize();

125
										unsigned count = 0;
Micha Mueller's avatar
Micha Mueller committed
126
										for(unsigned i = 0; i < size; i++) {
127
128
129
130
											if (cache[i].timestamp > time) {
												avg += cache[i].value;
												count++;
											}
Micha Mueller's avatar
Micha Mueller committed
131
										}
132
133
										if (count > 0)
											avg /= count;
Micha Mueller's avatar
Micha Mueller committed
134

135
										response = plugin + "::" + sensor + " Average of last " + std::to_string(count) + " values is " + std::to_string(avg);
Micha Mueller's avatar
Micha Mueller committed
136
137
138
										connection->set_status(server::connection::ok);
									}
								}
139
							}
Micha Mueller's avatar
Micha Mueller committed
140
141
142
143
144
145
						}
					} else {
						response = "Invalid authentication key!";
						connection->set_status(server::connection::unauthorized);
					}
				}
146
/* ##################################################### PUT ##################################################### */
Micha Mueller's avatar
Micha Mueller committed
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
			} else if (method == "PUT") {
				std::string action = pathStrs[1];

				//process actual request
				//check if query and action are valid values
				if ((action == "start") || (action == "stop")) {

					//check if authkey is valid
					if (_httpsServer.check_authkey(auth_value, permission::PUTReq)) {
						response = "Plugin not found!";
						connection->set_status(server::connection::not_found);

						for(auto& p : _httpsServer._plugins) {
							if (p.id == plugin) {
								if (action == "start") {
									for(auto s : p.configurator->getSensors()) {
										s->startPolling();
									}
									response = "Plugin " + plugin + ": Sensors started";
									connection->set_status(server::connection::ok);
								} else if (action == "stop") {
									for(auto s : p.configurator->getSensors()) {
										s->stopPolling();
									}
									response = "Plugin " + plugin + ": Sensors stopped";
									connection->set_status(server::connection::ok);
								}
174
175
							}
						}
Micha Mueller's avatar
Micha Mueller committed
176
177
178
					} else {
						response = "Invalid authentication key!";
						connection->set_status(server::connection::unauthorized);
179
					}
Micha Mueller's avatar
Micha Mueller committed
180
181
182
183
184

				} else {
					response = "PUT requests should be of the form \"host:port/[plugin]/[action]?authkey=[token]\"\n"
							"Where [plugin] names a plugin, [action] is either \"start\" or \"stop\" and [token] is your authentication key";
					connection->set_status(server::connection::bad_request);
185
186
				}
			}
Micha Mueller's avatar
Micha Mueller committed
187
188
			LOG(info) << "HttpsServer: Responding: " << response;
			data << response << std::endl;
189
190
191
192
193
		}
	}

	//send response
	server::response_header headers[] = { {"Connection", "close"}, {"Content-Type", "text/plain"} };
194
195
196
197
198
199
200
	connection->set_headers(boost::make_iterator_range(headers, headers + 2));
	connection->write(data.str());
}

void HttpsServer::requestHandler::log(const server::string_type& message) {
	LOG(error) << message;
}
201

202
HttpsServer::HttpsServer(restAPISettings_t restAPISettings, pluginVector_t& plugins) :
203
		_plugins(plugins), _handler(*this) {
204

205
206
207
	std::shared_ptr<asio::ssl::context> ctx = std::make_shared<asio::ssl::context>(asio::ssl::context::sslv23);
	ctx->set_options(asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv3 | asio::ssl::context::single_dh_use);

208
	// Set certificate, private key and DH parameters
209
	//ctx->set_password_callback(HttpsServer::password_callback);
210
211
212
	ctx->use_certificate_chain_file(restAPISettings.certificate);
	ctx->use_private_key_file(restAPISettings.privateKey, asio::ssl::context::pem);
	ctx->use_tmp_dh_file(restAPISettings.dhFile);
213

214
	server::options options(_handler);
215
	_server = new server(options.address(restAPISettings.restHost).port(restAPISettings.restPort).context(ctx));
216
217
218
219
220
221
}

HttpsServer::~HttpsServer() {
	delete _server;
}

222
223
224
225
226
227
228
229
bool HttpsServer::check_authkey(const std::string& authkey, permission requiredPerm) {
	authkeyMap_t::iterator it = _authkeys.find(authkey);
	if (it != _authkeys.end()) {
		return it->second[requiredPerm];
	}
	return false;
}

230
231
232
233
234
/*
std::string HttpsServer::password_callback(std::size_t max_length, asio::ssl::context_base::password_purpose purpose) {
    return std::string("pwd");
}
*/