xref: /haiku/src/apps/poorman/PoorManServer.cpp (revision 445d4fd926c569e7b9ae28017da86280aaecbae2)
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 
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 
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 
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 
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  */
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 
171 status_t PoorManServer::SetMaxConns(int32 count)
172 {
173 	fMaxConns = count;
174 	return B_OK;
175 }
176 
177 
178 status_t PoorManServer::SetListDir(bool listDir)
179 {
180 	fHttpdServer->do_list_dir = (listDir ? 1 : 0);
181 	return B_OK;
182 }
183 
184 
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 
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 
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 
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 
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 
457 status_t PoorManServer::_HandlePost(httpd_conn* hc)
458 {
459 	//not implemented
460 	return B_OK;
461 }
462 
463 
464 pthread_rwlock_t* get_web_dir_lock()
465 {
466 	return static_cast<PoorManApplication*>(be_app)->
467 		GetPoorManWindow()->GetServer()->GetWebDirLock();
468 }
469 
470 
471 pthread_rwlock_t* get_index_name_lock()
472 {
473 	return static_cast<PoorManApplication*>(be_app)->
474 		GetPoorManWindow()->GetServer()->GetIndexNameLock();
475 }
476