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