HttpsServer.cpp 7.09 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

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

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

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

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

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

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

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

Micha Mueller's avatar
Micha Mueller committed
30
31
32
33
34
35
	//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";
36
37
38

		std::string response = "";

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

		//do some string processing
Micha Mueller's avatar
Micha Mueller committed
44
45
46
47
48
49
50
		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);
51
			}
Micha Mueller's avatar
Micha Mueller committed
52
53
			if (path[path.size() -1] == '/') {
				path.erase(path.size() -1);
54
55
			}

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

Micha Mueller's avatar
Micha Mueller committed
59
60
61
62
63
		//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("=");
64
			if (pos != std::string::npos) {
Micha Mueller's avatar
Micha Mueller committed
65
66
67
68
				std::string value;
				value = key.substr(pos+1);
				key.erase(pos);
				queries.push_back(std::make_pair(key, value));
69
70
71
72
			}
		}
		//finished string processing

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

Micha Mueller's avatar
Micha Mueller committed
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
		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
			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];
					//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();

										for(unsigned i = 0; i < size; i++) {
											avg += cache[i].value;
										}
										avg /= size;

										response = plugin + "::" + sensor + " Average of last " + std::to_string(size) + " values is " + std::to_string(avg);
										connection->set_status(server::connection::ok);
									}
								}
123
							}
Micha Mueller's avatar
Micha Mueller committed
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
						}
					} else {
						response = "Invalid authentication key!";
						connection->set_status(server::connection::unauthorized);
					}
				}
			} 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);
								}
157
158
							}
						}
Micha Mueller's avatar
Micha Mueller committed
159
160
161
					} else {
						response = "Invalid authentication key!";
						connection->set_status(server::connection::unauthorized);
162
					}
Micha Mueller's avatar
Micha Mueller committed
163
164
165
166
167

				} 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);
168
169
				}
			}
Micha Mueller's avatar
Micha Mueller committed
170
171
			LOG(info) << "HttpsServer: Responding: " << response;
			data << response << std::endl;
172
173
174
175
176
		}
	}

	//send response
	server::response_header headers[] = { {"Connection", "close"}, {"Content-Type", "text/plain"} };
177
178
179
180
181
182
183
	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;
}
184

185
HttpsServer::HttpsServer(restAPISettings_t restAPISettings, pluginVector_t& plugins) :
186
		_plugins(plugins), _handler(*this) {
187

188
189
190
	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);

191
	// Set certificate, private key and DH parameters
192
	//ctx->set_password_callback(HttpsServer::password_callback);
193
194
195
	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);
196

197
	server::options options(_handler);
198
	_server = new server(options.address(restAPISettings.restHost).port(restAPISettings.restPort).context(ctx));
199
200
201
202
203
204
}

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

205
206
207
208
209
210
211
212
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;
}

213
214
215
216
217
/*
std::string HttpsServer::password_callback(std::size_t max_length, asio::ssl::context_base::password_purpose purpose) {
    return std::string("pwd");
}
*/