1 /*
2 * Copyright 2009 Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Author(s):
6 * Ma Jie, china.majie at gmail
7 */
8
9 #include "PoorManServer.h"
10
11 #include <errno.h>
12 #include <pthread.h>
13 #include <string.h>
14 #include <stdlib.h>
15 #include <time.h> //for struct timeval
16 #include <sys/socket.h>
17 #include <netinet/in.h>
18 #include <arpa/inet.h>
19 #include <poll.h>
20
21 #include <File.h>
22 #include <Debug.h>
23 #include <OS.h>
24 #include <String.h>
25 #include <StorageDefs.h>
26 #include <SupportDefs.h>
27
28 #include "PoorManApplication.h"
29 #include "PoorManLogger.h"
30 #include "PoorManWindow.h"
31 #include "libhttpd/libhttpd.h"
32
33
PoorManServer(const char * webDir,int32 maxConns,bool listDir,const char * idxName)34 PoorManServer::PoorManServer(const char* webDir,
35 int32 maxConns, bool listDir,const char* idxName)
36 :fIsRunning(false),
37 fMaxConns(maxConns),
38 fIndexName(new char[strlen(idxName) + 1]),
39 fCurConns(0)
40 {
41 fHttpdServer = httpd_initialize(
42 (char*)0,//hostname
43 (httpd_sockaddr*)0,//sa4P
44 (httpd_sockaddr*)0,//sa6P
45 (unsigned short)80,//port
46 (char*)0,//cgi pattern
47 0,//cgi_limit
48 (char*)"utf-8",//charset
49 (char *)"",//p3p
50 -1,//max_age
51 const_cast<char*>(webDir),//cwd
52 1,//no_log
53 (FILE*)0,//logfp
54 0,//no_symlink_check
55 0,//vhost
56 0,//global_passwd
57 (char*)0,//url_pattern
58 (char*)0,//local_pattern
59 0//no_empty_referers
60 );
61
62 strcpy(fIndexName, idxName);
63
64 size_t cwdLen = strlen(fHttpdServer->cwd);
65 if (fHttpdServer->cwd[cwdLen-1] == '/') {
66 fHttpdServer->cwd[cwdLen-1] = '\0';
67 }
68
69 fHttpdServer->do_list_dir = (listDir ? 1 : 0);
70 fHttpdServer->index_name = fIndexName;
71
72 pthread_rwlock_init(&fWebDirLock, NULL);
73 pthread_rwlock_init(&fIndexNameLock, NULL);
74 }
75
76
~PoorManServer()77 PoorManServer::~PoorManServer()
78 {
79 Stop();
80 httpd_terminate(fHttpdServer);
81 delete[] fIndexName;
82 pthread_rwlock_destroy(&fWebDirLock);
83 pthread_rwlock_destroy(&fIndexNameLock);
84 }
85
86
Run()87 status_t PoorManServer::Run()
88 {
89 if (chdir(fHttpdServer->cwd) == -1) {
90 poorman_log("no web directory, can't start up.\n", false, NULL, RED);
91 return B_ERROR;
92 }
93
94 httpd_sockaddr sa4;
95 memset(&sa4, 0, sizeof(httpd_sockaddr));
96 sa4.sa_in.sin_family = AF_INET;
97 sa4.sa_in.sin_port = htons(80);
98 sa4.sa_in.sin_addr.s_addr = htonl(INADDR_ANY);
99 fHttpdServer->listen4_fd = httpd_initialize_listen_socket(&sa4);
100
101 httpd_sockaddr sa6;
102 memset(&sa6, 0, sizeof(httpd_sockaddr));
103 sa6.sa_in.sin_family = AF_INET6;
104 sa6.sa_in.sin_port = htons(80);
105 sa6.sa_in.sin_addr.s_addr = htonl(INADDR_ANY);
106 fHttpdServer->listen6_fd = httpd_initialize_listen_socket(&sa6);
107
108 if (fHttpdServer->listen4_fd == -1 && fHttpdServer->listen6_fd == -1)
109 return B_ERROR;
110
111 fListenerTid = spawn_thread(
112 PoorManServer::_Listener,
113 "www listener",
114 B_NORMAL_PRIORITY,
115 static_cast<void*>(this)
116 );
117 if (fListenerTid < B_OK) {
118 poorman_log("can't create listener thread.\n", false, NULL, RED);
119 return B_ERROR;
120 }
121 fIsRunning = true;
122 if (resume_thread(fListenerTid) != B_OK) {
123 fIsRunning = false;
124 return B_ERROR;
125 }
126
127 //our server is up and running
128 return B_OK;
129 }
130
131
Stop()132 status_t PoorManServer::Stop()
133 {
134 if (fIsRunning) {
135 fIsRunning = false;
136 httpd_unlisten(fHttpdServer);
137 }
138 return B_OK;
139 }
140
141
142 /*The Web Dir is not changed if an error occured.
143 */
SetWebDir(const char * webDir)144 status_t PoorManServer::SetWebDir(const char* webDir)
145 {
146 if (chdir(webDir) == -1) {
147 //log it
148 return B_ERROR;
149 }
150
151 char* tmp = strdup(webDir);
152 if (tmp == NULL)
153 return B_ERROR;
154
155 if (pthread_rwlock_wrlock(&fWebDirLock) == 0) {
156 free(fHttpdServer->cwd);
157 fHttpdServer->cwd = tmp;
158 if (tmp[strlen(tmp) - 1] == '/') {
159 tmp[strlen(tmp) - 1] = '\0';
160 }
161 pthread_rwlock_unlock(&fWebDirLock);
162 } else {
163 free(tmp);
164 return B_ERROR;
165 }
166
167 return B_OK;
168 }
169
170
SetMaxConns(int32 count)171 status_t PoorManServer::SetMaxConns(int32 count)
172 {
173 fMaxConns = count;
174 return B_OK;
175 }
176
177
SetListDir(bool listDir)178 status_t PoorManServer::SetListDir(bool listDir)
179 {
180 fHttpdServer->do_list_dir = (listDir ? 1 : 0);
181 return B_OK;
182 }
183
184
SetIndexName(const char * idxName)185 status_t PoorManServer::SetIndexName(const char* idxName)
186 {
187 size_t length = strlen(idxName);
188 if (length > B_PATH_NAME_LENGTH + 1)
189 return B_ERROR;
190
191 char* tmp = new char[length + 1];
192 if (tmp == NULL)
193 return B_ERROR;
194
195 strcpy(tmp, idxName);
196 if (pthread_rwlock_wrlock(&fIndexNameLock) == 0) {
197 delete[] fIndexName;
198 fIndexName = tmp;
199 fHttpdServer->index_name = fIndexName;
200 pthread_rwlock_unlock(&fIndexNameLock);
201 } else {
202 delete[] tmp;
203 return B_ERROR;
204 }
205
206 return B_OK;
207 }
208
209
_Listener(void * data)210 int32 PoorManServer::_Listener(void* data)
211 {
212 PRINT(("The listener thread is working.\n"));
213 int retval;
214 thread_id tid;
215 httpd_conn* hc;
216 PoorManServer* s = static_cast<PoorManServer*>(data);
217 const int nfds = 2;
218 pollfd fds[nfds];
219
220 // N.B. these fds could be -1, which poll() should skip
221 memset(&fds, 0, sizeof(fds));
222 fds[0].fd = s->fHttpdServer->listen4_fd;
223 fds[0].events = POLLIN;
224 fds[1].fd = s->fHttpdServer->listen6_fd;
225 fds[1].events = POLLIN;
226
227 while (s->fIsRunning) {
228 // Wait for listen4_fd or listen6_fd (or both!) to become ready:
229 retval = poll(fds, nfds, -1);
230 if (retval == -1 && errno == EINTR)
231 continue;
232 if (retval < 1) {
233 return -1; // fds no longer available
234 }
235
236 for (int fdi = 0; fdi < nfds; fdi++) {
237 if (fds[fdi].fd < 0) {
238 continue; // fd is disabled, e.g. ipv4-only
239 }
240 if ((fds[fdi].revents & POLLIN) != POLLIN) {
241 continue; // fd is unavailable, try next fd
242 }
243
244 hc = new httpd_conn;
245 hc->initialized = 0;
246
247 PRINT(("calling httpd_get_conn()\n"));
248 retval = httpd_get_conn(s->fHttpdServer, fds[fdi].fd, hc);
249 switch (retval) {
250 case GC_OK:
251 break;
252 case GC_FAIL:
253 httpd_destroy_conn(hc);
254 delete hc;
255 s->fIsRunning = false;
256 return -1;
257 case GC_NO_MORE:
258 //should not happen, since we have a blocking socket
259 httpd_destroy_conn(hc);
260 continue;
261 break;
262 default:
263 //shouldn't happen
264 continue;
265 break;
266 }
267
268 if (s->fCurConns > s->fMaxConns) {
269 httpd_send_err(hc, 503,
270 httpd_err503title, (char *)"", httpd_err503form, (char *)"");
271 httpd_write_response(hc);
272 continue;
273 }
274
275 tid = spawn_thread(
276 PoorManServer::_Worker,
277 "www connection",
278 B_NORMAL_PRIORITY,
279 static_cast<void*>(s)
280 );
281 if (tid < B_OK) {
282 continue;
283 }
284 /*We don't check the return code here.
285 *As we can't kill a thread that doesn't receive the
286 *httpd_conn, we simply let it die itself.
287 */
288 send_data(tid, 512, &hc, sizeof(httpd_conn*));
289 atomic_add(&s->fCurConns, 1);
290 resume_thread(tid);
291 }//for
292 }//while
293 return 0;
294 }
295
296
_Worker(void * data)297 int32 PoorManServer::_Worker(void* data)
298 {
299 static const struct timeval kTimeVal = {60, 0};
300 PoorManServer* s = static_cast<PoorManServer*>(data);
301 httpd_conn* hc;
302 int retval;
303
304 if (has_data(find_thread(NULL))) {
305 thread_id sender;
306 if (receive_data(&sender, &hc, sizeof(httpd_conn*)) != 512)
307 goto cleanup;
308 } else {
309 // No need to go throught the whole cleanup, as we haven't open
310 // nor allocated ht yet.
311 atomic_add(&s->fCurConns, -1);
312 return 0;
313 }
314
315 PRINT(("A worker thread starts to work.\n"));
316
317 setsockopt(hc->conn_fd, SOL_SOCKET, SO_RCVTIMEO, &kTimeVal,
318 sizeof(struct timeval));
319 retval = recv(
320 hc->conn_fd,
321 &(hc->read_buf[hc->read_idx]),
322 hc->read_size - hc->read_idx,
323 0
324 );
325 if (retval < 0)
326 goto cleanup;
327
328 hc->read_idx += retval;
329 switch(httpd_got_request(hc)) {
330 case GR_GOT_REQUEST:
331 break;
332 case GR_BAD_REQUEST:
333 httpd_send_err(hc, 400,
334 httpd_err400title, (char *)"", httpd_err400form, (char *)"");
335 httpd_write_response(hc);//fall through
336 case GR_NO_REQUEST: //fall through
337 default: //won't happen
338 goto cleanup;
339 break;
340 }
341
342 if (httpd_parse_request(hc) < 0) {
343 httpd_write_response(hc);
344 goto cleanup;
345 }
346
347 retval = httpd_start_request(hc, (struct timeval*)0);
348 if (retval < 0) {
349 httpd_write_response(hc);
350 goto cleanup;
351 }
352
353 /*true means the connection is already handled
354 *by the directory index generator in httpd_start_request().
355 */
356 if (hc->processed_directory_index == 1) {
357 if (hc->method == METHOD_GET) {
358 static_cast<PoorManApplication*>(be_app)->GetPoorManWindow()->SetHits(
359 static_cast<PoorManApplication*>(be_app)->GetPoorManWindow()->GetHits() + 1
360 );
361 }
362 goto cleanup;
363 }
364
365 switch (hc->method) {
366 case METHOD_GET:
367 s->_HandleGet(hc);
368 break;
369 case METHOD_HEAD:
370 s->_HandleHead(hc);
371 break;
372 case METHOD_POST:
373 s->_HandlePost(hc);
374 break;
375 }
376
377 cleanup: ;
378 httpd_close_conn(hc, (struct timeval*)0);
379 httpd_destroy_conn(hc);
380
381 delete hc;
382 atomic_add(&s->fCurConns, -1);
383 return 0;
384 }
385
386
_HandleGet(httpd_conn * hc)387 status_t PoorManServer::_HandleGet(httpd_conn* hc)
388 {
389 PRINT(("HandleGet() called\n"));
390
391 ssize_t bytesRead;
392 uint8* buf;
393 BString log;
394
395 BFile file(hc->expnfilename, B_READ_ONLY);
396 if (file.InitCheck() != B_OK)
397 return B_ERROR;
398
399 buf = new uint8[POOR_MAN_BUF_SIZE];
400 if (buf == NULL)
401 return B_ERROR;
402
403 static_cast<PoorManApplication*>(be_app)->GetPoorManWindow()->SetHits(
404 static_cast<PoorManApplication*>(be_app)->
405 GetPoorManWindow()->GetHits() + 1);
406
407 log.SetTo("Sending file: ");
408 if (pthread_rwlock_rdlock(&fWebDirLock) == 0) {
409 log << hc->hs->cwd;
410 pthread_rwlock_unlock(&fWebDirLock);
411 }
412 log << '/' << hc->expnfilename << '\n';
413 poorman_log(log.String(), true, &hc->client_addr);
414
415 //send mime headers
416 if (send(hc->conn_fd, hc->response, hc->responselen, 0) < 0) {
417 delete [] buf;
418 return B_ERROR;
419 }
420
421 file.Seek(hc->first_byte_index, SEEK_SET);
422 while (true) {
423 bytesRead = file.Read(buf, POOR_MAN_BUF_SIZE);
424 if (bytesRead == 0)
425 break;
426 else if (bytesRead < 0) {
427 delete [] buf;
428 return B_ERROR;
429 }
430 if (send(hc->conn_fd, (void*)buf, bytesRead, 0) < 0) {
431 log.SetTo("Error sending file: ");
432 if (pthread_rwlock_rdlock(&fWebDirLock) == 0) {
433 log << hc->hs->cwd;
434 pthread_rwlock_unlock(&fWebDirLock);
435 }
436 log << '/' << hc->expnfilename << '\n';
437 poorman_log(log.String(), true, &hc->client_addr, RED);
438 delete [] buf;
439 return B_ERROR;
440 }
441 }
442
443 delete [] buf;
444 return B_OK;
445 }
446
447
_HandleHead(httpd_conn * hc)448 status_t PoorManServer::_HandleHead(httpd_conn* hc)
449 {
450 int retval = send(hc->conn_fd, hc->response, hc->responselen, 0);
451 if (retval == -1)
452 return B_ERROR;
453 return B_OK;
454 }
455
456
_HandlePost(httpd_conn * hc)457 status_t PoorManServer::_HandlePost(httpd_conn* hc)
458 {
459 //not implemented
460 return B_OK;
461 }
462
463
get_web_dir_lock()464 pthread_rwlock_t* get_web_dir_lock()
465 {
466 return static_cast<PoorManApplication*>(be_app)->
467 GetPoorManWindow()->GetServer()->GetWebDirLock();
468 }
469
470
get_index_name_lock()471 pthread_rwlock_t* get_index_name_lock()
472 {
473 return static_cast<PoorManApplication*>(be_app)->
474 GetPoorManWindow()->GetServer()->GetIndexNameLock();
475 }
476