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