Changeset View
Changeset View
Standalone View
Standalone View
plugins/htmlinterface/webserver.cpp
Show All 15 Lines | |||||
16 | * along with this program; if not, write to the * | 16 | * along with this program; if not, write to the * | ||
17 | * Free Software Foundation, Inc., * | 17 | * Free Software Foundation, Inc., * | ||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * | 18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * | ||
19 | ***************************************************************************/ | 19 | ***************************************************************************/ | ||
20 | 20 | | |||
21 | #include <iostream> | 21 | #include <iostream> | ||
22 | #include "webserver.h" | 22 | #include "webserver.h" | ||
23 | 23 | | |||
24 | using namespace std; | | |||
25 | | ||||
26 | #include <stdio.h> | 24 | #include <stdio.h> | ||
25 | #include <sys/stat.h> | ||||
26 | #include <fcntl.h> | ||||
27 | #include <string.h> | 27 | #include <string.h> | ||
28 | #include <QString> | 28 | #include <QString> | ||
29 | #include <QRegExp> | 29 | #include <QRegExp> | ||
30 | #include <QStringList> | 30 | #include <QStringList> | ||
31 | #include <QMimeDatabase> | ||||
32 | #include <QUuid> | ||||
pino: Twice? | |||||
33 | #define GET 0 | ||||
34 | #define POST 1 | ||||
35 | #define POSTBUFFERSIZE 65536 | ||||
31 | 36 | | |||
32 | using namespace std; | 37 | using namespace std; | ||
33 | 38 | | |||
34 | namespace kt { | 39 | namespace kt { | ||
35 | WebServer::WebServer(CoreInterface * core): core(core) | 40 | WebServer::WebServer(CoreInterface * core): core(core) | ||
36 | { | 41 | { | ||
37 | 42 | | |||
38 | } | 43 | } | ||
39 | 44 | | |||
40 | WebServer::~WebServer() | 45 | WebServer::~WebServer() | ||
41 | { | 46 | { | ||
47 | MHD_stop_daemon (daemon); | ||||
48 | delete listGenerator; | ||||
42 | } | 49 | } | ||
43 | 50 | | |||
51 | static QMimeDatabase mimeDatabase; | ||||
52 | | ||||
pino: The hardcoded port is a no-go. | |||||
53 | static QString BASE_DIRECTORY = QString::fromLatin1("/usr/share/ktorrent/html/"); | ||||
54 | | ||||
44 | TorrentListGenerator * WebServer::listGenerator; | 55 | TorrentListGenerator * WebServer::listGenerator; | ||
45 | 56 | | |||
pino: Forever loop? How does it exit cleanly, then? | |||||
46 | void WebServer::process() | 57 | QString WebServer::sessionToken; | ||
58 | | ||||
59 | const char * WebServer::cookie; | ||||
60 | | ||||
61 | static int respond404(MHD_Connection * connection) | ||||
47 | { | 62 | { | ||
48 | listGenerator = new TorrentListGenerator(core); | 63 | int ret; | ||
49 | struct mg_connection *c; | 64 | struct MHD_Response *response; | ||
50 | 65 | | |||
pino: Hardcoded path. | |||||
51 | mg_mgr_init(&mgr, NULL); | 66 | const char * message = "<html><body>404: The requested resource was not found.</body></html>"; | ||
52 | c = mg_bind(&mgr, "8880", WebServer::httpeventhandler); | 67 | | ||
53 | mg_set_protocol_http_websocket(c); | 68 | response = MHD_create_response_from_buffer (strlen(message), (void *) message, MHD_RESPMEM_PERSISTENT); | ||
54 | while (true) { | 69 | ret = MHD_queue_response (connection, MHD_HTTP_NOT_FOUND, response); | ||
55 | mg_mgr_poll(&mgr, 1000); | 70 | MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_ENCODING, "text/html"); | ||
56 | } | 71 | MHD_destroy_response (response); | ||
method is leaked; considering its only use is to compare it to "GET", then just use memcmp with message->method.len instead. pino: `method` is leaked; considering its only use is to compare it to `"GET"`, then just use… | |||||
72 | return ret; | ||||
57 | } | 73 | } | ||
58 | 74 | | |||
59 | void WebServer::httpeventhandler(struct mg_connection * c, int ev, void * ev_data) { | 75 | static int respond500(MHD_Connection * connection) | ||
pino: Ditto (same as `method` for `uri`). | |||||
60 | if (ev == MG_EV_HTTP_REQUEST) | | |||
61 | { | 76 | { | ||
62 | struct mg_serve_http_opts opts; | 77 | int ret; | ||
78 | struct MHD_Response *response; | ||||
63 | 79 | | |||
64 | memset(&opts, 0, sizeof(opts)); // Reset all options to defaults | 80 | const char * message = "<html><body>500: An internal server error has occured.</body></html>"; | ||
size is not used. Considering my suggestion earlier regarding the return value of TorrentListGenerator::get, then that would solve this issue too. pino: `size` is not used. Considering my suggestion earlier regarding the return value of… | |||||
65 | opts.document_root = "/usr/share/ktorrent/html/"; | | |||
66 | 81 | | |||
67 | struct http_message * message = (struct http_message *) ev_data; | 82 | response = MHD_create_response_from_buffer (strlen(message), (void *) message, MHD_RESPMEM_PERSISTENT); | ||
83 | ret = MHD_queue_response (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response); | ||||
84 | MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_ENCODING, "text/html"); | ||||
85 | MHD_destroy_response (response); | ||||
86 | return ret; | ||||
87 | } | ||||
68 | 88 | | |||
69 | char * method = new char[message->method.len + 1]; | 89 | struct Json | ||
70 | strncpy(method, message->method.p, message->method.len); | 90 | { | ||
body is leaked, and it is even used only in a specific situation (so no need to create it always). pino: `body` is leaked, and it is even used only in a specific situation (so no need to create it… | |||||
71 | method[message->method.len] = '\0'; | 91 | QString json; | ||
92 | bool firstPass; | ||||
93 | }; | ||||
72 | 94 | | |||
73 | char * uri = new char[message->uri.len + 1]; | 95 | int WebServer::answer_to_connection(void * cls, struct MHD_Connection * connection, | ||
74 | strncpy(uri, message->uri.p, message->uri.len); | 96 | const char * url, const char * method, | ||
pino: `len` is not used. | |||||
75 | uri[message->uri.len] = '\0'; | 97 | const char * version, const char * upload_data, | ||
98 | size_t * upload_data_size, void ** con_cls) | ||||
99 | { | ||||
100 | int ret; | ||||
101 | struct MHD_Response *response; | ||||
76 | 102 | | |||
77 | if (strcmp(uri, "/ktorrentdata") == 0 && strcmp(method, "GET") == 0) | 103 | if (strcmp("/ktorrentdata", url) == 0 && strcmp("GET", method) == 0) | ||
104 | { | ||||
105 | QByteArray json = listGenerator->get(); | ||||
106 | response = MHD_create_response_from_buffer (json.size(), (void *) json.data(), | ||||
107 | MHD_RESPMEM_PERSISTENT); | ||||
108 | ret = MHD_queue_response (connection, MHD_HTTP_OK, response); | ||||
109 | MHD_destroy_response (response); | ||||
110 | return ret; | ||||
111 | } | ||||
112 | if (strcmp("/ktorrentaction", url) == 0 && strcmp("POST", method) == 0) | ||||
113 | { | ||||
114 | Json * json; | ||||
115 | if (*con_cls == NULL) | ||||
78 | { | 116 | { | ||
79 | char * json = NULL; | 117 | json = new Json(); | ||
80 | int size; | 118 | *con_cls = (void *) json; | ||
81 | listGenerator->get(&json, &size); | 119 | json->firstPass = true; | ||
82 | mg_send_head(c, 200, size, "Content-Type: application/json\nAccess-Control-Allow-Origin: *"); | | |||
83 | mg_printf(c, "%s", json); | | |||
84 | return; | | |||
85 | } | 120 | } | ||
86 | if (strcmp(uri, "/ktorrentaction") == 0) | 121 | else | ||
87 | { | 122 | { | ||
88 | char * body = new char[message->body.len + 1]; | 123 | json = (Json *) *con_cls; | ||
89 | strncpy(body, message->body.p, message->body.len); | 124 | } | ||
90 | body[message->body.len] = '\0'; | | |||
91 | 125 | | |||
92 | if (strcmp(method, "POST") == 0) { | 126 | if (json->firstPass) { | ||
93 | listGenerator->post(body); | 127 | json->firstPass = false; | ||
128 | return MHD_YES; | ||||
129 | } | ||||
130 | if (*upload_data_size > 0 && !json->firstPass) | ||||
131 | { | ||||
132 | QString qJson = QString::fromLatin1(upload_data, *upload_data_size); | ||||
133 | json->json.append(qJson); | ||||
134 | if (checkForToken(connection)) | ||||
135 | { | ||||
136 | listGenerator->post(json->json); | ||||
137 | } | ||||
138 | *upload_data_size = 0; | ||||
139 | return MHD_YES; | ||||
94 | } | 140 | } | ||
95 | const char * validation = "{\"status\":\"Request received.\"}"; | 141 | const char * validation = "{\"status\":\"Request received.\"}"; | ||
96 | int len = strlen(validation); | 142 | response = MHD_create_response_from_buffer(strlen(validation), (void *) validation, | ||
97 | mg_send_head(c, 200, len, "Content-Type: application/json\nAccess-Control-Allow-Origin: *\nAccess-Control-Allow-Headers: X-ACCESS_TOKEN, Access-Control-Allow-Origin, Authorization, Origin, x-requested-with, Content-Type, Content-Range, Content-Disposition, Content-Description\nAccess-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS"); | 143 | MHD_RESPMEM_PERSISTENT); | ||
98 | mg_printf(c, "%s", validation); | 144 | MHD_add_response_header(response, "Content-Type", "application/json"); | ||
99 | return; | 145 | ret = MHD_queue_response(connection, MHD_HTTP_OK, response); | ||
146 | MHD_destroy_response(response); | ||||
147 | return ret; | ||||
148 | } | ||||
149 | struct stat sbuf; | ||||
150 | int fd; | ||||
151 | if (0 == strcmp(url, "/") || 0 == strcmp(url, "/index")) | ||||
152 | { | ||||
153 | url = "index.html"; | ||||
154 | } | ||||
155 | QString qUrl = QString::fromLatin1(url); | ||||
156 | const char * file = (BASE_DIRECTORY + qUrl).toLatin1().data(); | ||||
157 | if ((-1 == (fd = open(file, O_RDONLY))) || (0 != fstat(fd, &sbuf))) | ||||
158 | { | ||||
159 | return respond404(connection); | ||||
160 | } | ||||
161 | response = MHD_create_response_from_fd_at_offset64(sbuf.st_size, fd, 0); | ||||
162 | const char * mimeType = mimeDatabase.mimeTypeForFile(qUrl).name().toLatin1().data(); | ||||
163 | MHD_add_response_header(response, "Content-Type", mimeType); | ||||
164 | MHD_add_response_header(response, "Set-Cookie", makeCookie()); | ||||
165 | ret = MHD_queue_response(connection, MHD_HTTP_OK, response); | ||||
166 | MHD_destroy_response (response); | ||||
167 | return ret; | ||||
168 | } | ||||
169 | | ||||
170 | static void request_completed (void *cls, struct MHD_Connection *connection, | ||||
171 | void **con_cls, enum MHD_RequestTerminationCode toe) | ||||
172 | { | ||||
173 | Json * json = (Json *) *con_cls; | ||||
174 | | ||||
175 | delete json; | ||||
176 | } | ||||
177 | | ||||
178 | void WebServer::process() | ||||
179 | { | ||||
180 | listGenerator = new TorrentListGenerator(core); | ||||
181 | sessionToken = QUuid::createUuid().toString(); | ||||
182 | cookie = makeCookie(); | ||||
183 | | ||||
184 | daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, 8880, NULL, NULL, | ||||
185 | &answer_to_connection, NULL, | ||||
186 | MHD_OPTION_NOTIFY_COMPLETED, request_completed, | ||||
187 | NULL, MHD_OPTION_END); | ||||
188 | } | ||||
189 | | ||||
190 | const char * WebServer::makeCookie() | ||||
191 | { | ||||
192 | QString tempString = QString::fromLatin1("token=") + sessionToken; | ||||
193 | return tempString.toLatin1().data(); | ||||
100 | } | 194 | } | ||
101 | mg_serve_http(c, (struct http_message *) ev_data, opts); | 195 | | ||
196 | bool WebServer::checkForToken(MHD_Connection * connection) | ||||
197 | { | ||||
198 | const char * token = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "session-Token"); | ||||
199 | if (token == NULL) | ||||
200 | { | ||||
201 | return false; | ||||
202 | } | ||||
203 | if (strcmp(token, sessionToken.toLatin1().data()) == 0) | ||||
204 | { | ||||
205 | return true; | ||||
102 | } | 206 | } | ||
207 | return false; | ||||
103 | } | 208 | } | ||
104 | } | 209 | } |
Twice?