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
34PoorManServer::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
77PoorManServer::~PoorManServer()
78{
79	Stop();
80	httpd_terminate(fHttpdServer);
81	delete[] fIndexName;
82	pthread_rwlock_destroy(&fWebDirLock);
83	pthread_rwlock_destroy(&fIndexNameLock);
84}
85
86
87status_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
132status_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 */
144status_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
171status_t PoorManServer::SetMaxConns(int32 count)
172{
173	fMaxConns = count;
174	return B_OK;
175}
176
177
178status_t PoorManServer::SetListDir(bool listDir)
179{
180	fHttpdServer->do_list_dir = (listDir ? 1 : 0);
181	return B_OK;
182}
183
184
185status_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
210int32 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
297int32 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
377cleanup: ;
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
387status_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
448status_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
457status_t PoorManServer::_HandlePost(httpd_conn* hc)
458{
459	//not implemented
460	return B_OK;
461}
462
463
464pthread_rwlock_t* get_web_dir_lock()
465{
466	return static_cast<PoorManApplication*>(be_app)->
467		GetPoorManWindow()->GetServer()->GetWebDirLock();
468}
469
470
471pthread_rwlock_t* get_index_name_lock()
472{
473	return static_cast<PoorManApplication*>(be_app)->
474		GetPoorManWindow()->GetServer()->GetIndexNameLock();
475}
476