示例#1
0
void t01_url() {
  INIT_LOCAL();

  onion_url *url = onion_url_new();
  onion_url_add_handler(url, "^handler1.$",
                        onion_handler_new((onion_handler_handler) handler1,
                                          NULL, NULL));
  onion_url_add(url, "handler2/", handler2);
  onion_url_add_with_data(url, "^handler(3|4)/", handler3, NULL, NULL);

  onion_set_root_handler(server, onion_url_to_handler(url));

  onion_request *req = onion_request_new(onion_get_listen_point(server, 0));

#define R "GET /handler1/ HTTP/1.1\n\n"
  onion_request_write(req, R, sizeof(R));
  onion_request_process(req);
  FAIL_IF_NOT_EQUAL_INT(handler_called, 1);
  FAIL_IF_NOT_EQUAL_STR(urltxt, "");
  free(urltxt);
  urltxt = NULL;

  onion_request_clean(req);
  onion_request_write(req, "GET /handler2/ HTTP/1.1\n\n", sizeof(R));
  onion_request_process(req);
  FAIL_IF_NOT_EQUAL_INT(handler_called, 2);
  ONION_DEBUG("%s", urltxt);
  FAIL_IF_NOT_EQUAL_STR(urltxt, "");
  free(urltxt);
  urltxt = NULL;

  onion_request_clean(req);
  onion_request_write(req, "GET /handler3/hello HTTP/1.1\n\n", sizeof(R) + 5);
  onion_request_process(req);
  FAIL_IF_NOT_EQUAL_INT(handler_called, 3);
  FAIL_IF_NOT_EQUAL_STR(urltxt, "hello");
  free(urltxt);
  urltxt = NULL;

  handler_called = 0;
  onion_request_clean(req);
  onion_request_write(req, "GET /handler2/hello HTTP/1.1\n\n", sizeof(R) + 5);
  onion_request_process(req);
  FAIL_IF_NOT_EQUAL_INT(handler_called, 0);
  FAIL_IF_EQUAL_STR(urltxt, "");
  free(urltxt);
  urltxt = NULL;

  onion_request_free(req);
  onion_url_free(url);
  onion_set_root_handler(server, NULL);

  END_LOCAL();
}
示例#2
0
文件: server.c 项目: cemonds/onion
/**
 * @short  Performs the processing of the request.
 * @memberof onion_server_t
 * 
 * Returns the OCS_KEEP_ALIVE or OCS_CLOSE_CONNECTION value. If on keep alive the struct is already reinitialized.
 * 
 * It calls the root_handler and if it returns an error of any type (or not processed), it calls the
 * default internal_error_handler.
 * 
 * Normally it could get the server from the request, but it is passed for homogenity, and
 * to allow unforseen posibilities.
 * 
 * @see onion_connection_status
 */
onion_connection_status onion_server_handle_request(onion_server *server, onion_request *req){
	onion_response *res=onion_response_new(req);
	
	// Call the main handler.
	onion_connection_status hs=onion_handler_handle(server->root_handler, req, res);

	if (hs==OCS_INTERNAL_ERROR || 
		hs==OCS_NOT_IMPLEMENTED || 
		hs==OCS_NOT_PROCESSED){
		if (hs==OCS_INTERNAL_ERROR)
			req->flags|=OR_INTERNAL_ERROR;
		if (hs==OCS_NOT_IMPLEMENTED)
			req->flags|=OR_NOT_IMPLEMENTED;
		if (hs==OCS_NOT_PROCESSED)
			req->flags|=OR_NOT_FOUND;
    if (hs==OCS_FORBIDDEN)
      req->flags|=OR_FORBIDDEN;
		
		hs=onion_handler_handle(server->internal_error_handler, req, res);
	}

	
	int rs=onion_response_free(res);
	if (hs>=0 && rs==OCS_KEEP_ALIVE) // if keep alive, reset struct to get the new petition.
		onion_request_clean(req);
	
	return hs>0 ? rs : hs;
}
示例#3
0
void t06_create_add_free_POST_toobig(){
	INIT_LOCAL();
	
	onion_request *req;
	int ok;
	
	req=onion_request_new(custom_io);
	FAIL_IF_EQUAL(req,NULL);
	FAIL_IF_NOT_EQUAL(req->connection.fd, -1);
	
	onion_set_max_post_size(server, 10); // Very small limit
	
	const char *query="POST /myurl%20/is/very/deeply/nested?test=test&query2=query%202&more_query=%20more%20query+10 HTTP/1.0\n"
													"Host: 127.0.0.1\n\rContent-Length: 24\n"
													"Other-Header: My header is very long and with spaces...\r\n\r\npost_data=1&post_data2=2&post_data=1&post_data2=2";
	
	int i; // Straight write, with clean (keep alive like)
	for (i=0;i<10;i++){
		FAIL_IF_NOT_EQUAL(req->flags,0);
		ok=REQ_WRITE(req,query);
		
		FAIL_IF_NOT_EQUAL(ok,OCS_INTERNAL_ERROR);

		onion_request_clean(req);
		FAIL_IF_NOT_EQUAL(req->GET,NULL);
	}
	
	onion_request_free(req);
	
	END_LOCAL();
}
示例#4
0
void t02_create_add_free_overflow(){
	INIT_LOCAL();
	
	onion_request *req;
	int ok, i;
	
	req=onion_request_new(custom_io);
	FAIL_IF_NOT_EQUAL(req->connection.fd, -1);

	FAIL_IF_EQUAL(req,NULL);
	
	char of[14096];
	for (i=0;i<sizeof(of);i++)
		of[i]='a'+i%26;
	of[i-1]='\0';
	char get[14096*4];
	
	sprintf(get,"%s %s %s",of,of,of);
	ok=REQ_WRITE(req,get);
	FAIL_IF_NOT_EQUAL(ok,OCS_INTERNAL_ERROR); 
	onion_request_clean(req);
	
	sprintf(get,"%s %s %s\n",of,of,of);
	ok=REQ_WRITE(req,get);
	FAIL_IF_NOT_EQUAL(ok,OCS_INTERNAL_ERROR); 
	onion_request_clean(req);

	
	sprintf(get,"GET %s %s\n",of,of);
	ok=REQ_WRITE(req,get);
	printf("%d\n",ok);
	FAIL_IF_NOT_EQUAL(ok,OCS_INTERNAL_ERROR); 
	
	onion_request_free(req);
	
	END_LOCAL();
}
示例#5
0
void t05_create_add_free_POST(){
	INIT_LOCAL();
	
	onion_request *req;
	int ok;
	
	req=onion_request_new(custom_io);
	FAIL_IF_EQUAL(req,NULL);
	FAIL_IF_NOT_EQUAL(req->connection.fd, -1);
	
	const char *query="POST /myurl%20/is/very/deeply/nested?test=test&query2=query%202&more_query=%20more%20query+10 HTTP/1.0\n"
													"Host: 127.0.0.1\n\rContent-Length: 50\n"
													"Other-Header: My header is very long and with spaces...\r\n\r\nempty_post=&post_data=1&post_data2=2&empty_post_2=\n";
	
	int i; // Straight write, with clean (keep alive like)
	for (i=0;i<10;i++){
		FAIL_IF_NOT_EQUAL(req->flags,0);
		ok=REQ_WRITE(req,query);
		
		FAIL_IF_NOT_EQUAL(ok, OCS_CLOSE_CONNECTION);
		FAIL_IF_EQUAL(req->flags,OR_GET|OR_HTTP11);
		
		FAIL_IF_EQUAL(req->headers, NULL);
		FAIL_IF_NOT_EQUAL_STR( onion_dict_get(req->headers,"Host"), "127.0.0.1");
		FAIL_IF_NOT_EQUAL_STR( onion_dict_get(req->headers,"Other-Header"), "My header is very long and with spaces...");

		FAIL_IF_NOT_EQUAL_STR(req->fullpath,"/myurl /is/very/deeply/nested");
		FAIL_IF_NOT_EQUAL_STR(req->path,"myurl /is/very/deeply/nested");

		FAIL_IF_EQUAL(req->GET,NULL);
		FAIL_IF_NOT_EQUAL_STR( onion_dict_get(req->GET,"test"), "test");
		FAIL_IF_NOT_EQUAL_STR( onion_dict_get(req->GET,"query2"), "query 2");
		FAIL_IF_NOT_EQUAL_STR( onion_dict_get(req->GET,"more_query"), " more query 10");

		const onion_dict *post=onion_request_get_post_dict(req);
		FAIL_IF_EQUAL(post,NULL);
		FAIL_IF_NOT_EQUAL_STR( onion_dict_get(post,"post_data"), "1");
		FAIL_IF_NOT_EQUAL_STR( onion_dict_get(post,"post_data2"), "2");
		FAIL_IF_NOT_EQUAL_STR( onion_request_get_post(req, "empty_post"), "");
		FAIL_IF_NOT_EQUAL_STR( onion_request_get_post(req, "empty_post_2"), "");

		onion_request_clean(req);
		FAIL_IF_NOT_EQUAL(req->GET,NULL);
	}
	
	onion_request_free(req);
	
	END_LOCAL();
}
示例#6
0
/// A BUG were detected: transformed \n to \r\n on files.
void t03_post_carriage_return_new_lines_file(){
	INIT_LOCAL();
	
	buffer *b=buffer_new(1024);
	
	expected_post post={};;
	post.filename="file.dat";
	post.test_ok=0; // Not ok as not called processor yet
	post.tmpfilename=NULL;
	post.size=3;
	
	onion_server *server=onion_server_new();
	onion_server_set_write(server, (void*)&buffer_append);
	onion_server_set_root_handler(server, onion_handler_new((void*)&post_check,&post,NULL));
	
	onion_request *req=onion_request_new(server,b,"test");

#define POST_EMPTY "POST / HTTP/1.1\nContent-Type: multipart/form-data; boundary=end\nContent-Length:81\n\n--end\nContent-Disposition: text/plain; name=\"file\"; filename=\"file.dat\"\n\n\n\r\n\n--end--"
	//ONION_DEBUG("%s",POST_EMPTY);
	onion_request_write(req,POST_EMPTY,sizeof(POST_EMPTY));
	FAIL_IF_NOT_EQUAL(post.test_ok,1);
#undef POST_EMPTY

	onion_request_clean(req);
	post.test_ok=0; // Not ok as not called processor yet

#define POST_EMPTYR "POST / HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=end\r\nContent-Length:85\r\n\r\n--end\r\nContent-Disposition: text/plain; name=\"file\"; filename=\"file.dat\"\r\n\r\n\n\r\n\r\n--end--"
	onion_request_write(req,POST_EMPTYR,sizeof(POST_EMPTYR));
	FAIL_IF_NOT_EQUAL(post.test_ok,1);
#undef POST_EMPTYR

	onion_request_free(req);

	if (post.tmpfilename){
		struct stat st;
		FAIL_IF_EQUAL(stat(post.tmpfilename,&st), 0); // Should not exist
		free(post.tmpfilename);
	}
	if (post.tmplink)
		free(post.tmplink);
	
	onion_server_free(server);
	buffer_free(b);
	END_LOCAL();
}
示例#7
0
文件: 08-post.c 项目: Andrepuel/onion
/// A BUG were detected: transformed \n to \r\n on files.
void t02_post_new_lines_file(){
	INIT_LOCAL();
	
	expected_post post={};;
	post.filename="file.dat";
	post.test_ok=0; // Not ok as not called processor yet
	post.tmpfilename=NULL;
	post.size=1;
	
	onion *server=onion_new(0);
	onion_listen_point *lp=onion_buffer_listen_point_new();
	onion_add_listen_point(server,NULL,NULL,lp);
	onion_set_root_handler(server, onion_handler_new((void*)&post_check,&post,NULL));
	
	onion_request *req=onion_request_new(lp);

#define POST_EMPTY "POST / HTTP/1.1\nContent-Type: multipart/form-data; boundary=end\nContent-Length:81\n\n--end\nContent-Disposition: text/plain; name=\"file\"; filename=\"file.dat\"\n\n\n\n--end--"
	onion_request_write(req,POST_EMPTY,sizeof(POST_EMPTY));
	FAIL_IF_NOT_EQUAL(post.test_ok,1);
#undef POST_EMPTY

	onion_request_clean(req);
	post.test_ok=0; // Not ok as not called processor yet

#define POST_EMPTYR "POST / HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=end\r\nContent-Length:85\r\n\r\n--end\r\nContent-Disposition: text/plain; name=\"file\"; filename=\"file.dat\"\r\n\r\n\n\r\n--end--"
	onion_request_write(req,POST_EMPTYR,sizeof(POST_EMPTYR));
	FAIL_IF_NOT_EQUAL(post.test_ok,1);
#undef POST_EMPTYR

	onion_request_free(req);

	if (post.tmpfilename){
		struct stat st;
		FAIL_IF_EQUAL(stat(post.tmpfilename,&st), 0); // Should not exist
		free(post.tmpfilename);
	}
	if (post.tmplink)
		free(post.tmplink);
	
	onion_free(server);
	END_LOCAL();
}
示例#8
0
void t07_multiline_headers(){
	INIT_LOCAL();
	
	onion_request *req;
	int ok;
	
	onion_set_max_post_size(server, 1024);
	req=onion_request_new(custom_io);
	FAIL_IF_EQUAL(req,NULL);
	FAIL_IF_NOT_EQUAL(req->connection.fd, -1);
	
	{
		const char *query="GET / HTTP/1.0\n"
											"Host: 127.0.0.1\n\rContent-Length: 24\n"
											"Other-Header: My header is very long and with several\n lines\n"
											"Extra-Other-Header: My header is very long and with several\n \n lines\n"
											"My-Other-Header: My header is very long and with several\n\tlines\n\n";
		
		ok=onion_request_write(req,query,strlen(query));
	}
	FAIL_IF_EQUAL(ok,OCS_INTERNAL_ERROR);
	FAIL_IF_NOT_EQUAL_STR(onion_request_get_header(req,"other-header"),"My header is very long and with several lines");
	FAIL_IF_NOT_EQUAL_STR(onion_request_get_header(req,"extra-other-header"),"My header is very long and with several lines");
	FAIL_IF_NOT_EQUAL_STR(onion_request_get_header(req,"My-other-header"),"My header is very long and with several lines");
	onion_request_clean(req);

	{
		const char *query="GET / HTTP/1.0\n"
											"Host: 127.0.0.1\n\rContent-Length: 24\n"
											"Other-Header: My header is very long and with several\n lines\n"
											"My-Other-Header: My header is very long and with several\nlines\n\n";
		
		ok=onion_request_write(req,query,strlen(query));
	}
	FAIL_IF_NOT_EQUAL(ok,OCS_INTERNAL_ERROR); // No \t at my-other-header
	
	onion_request_free(req);
	
	
	END_LOCAL();
}
示例#9
0
/**
 * @short Launches one handler for the given request
 * @ingroup request
 *
 * Once the request is ready, launch it.
 *
 * @returns The connection status: if it should be closed, error codes...
 */
onion_connection_status onion_request_process(onion_request *req){
	onion_response *res=onion_response_new(req);
	if (!req->path){
		onion_request_polish(req);
	}
	// Call the main handler.
	onion_connection_status hs=onion_handler_handle(req->connection.listen_point->server->root_handler, req, res);

	if (hs==OCS_INTERNAL_ERROR ||
		hs==OCS_NOT_IMPLEMENTED ||
		hs==OCS_NOT_PROCESSED){
		if (hs==OCS_INTERNAL_ERROR)
			req->flags|=OR_INTERNAL_ERROR;
		if (hs==OCS_NOT_IMPLEMENTED)
			req->flags|=OR_NOT_IMPLEMENTED;
		if (hs==OCS_NOT_PROCESSED)
			req->flags|=OR_NOT_FOUND;
		if (hs==OCS_FORBIDDEN)
			req->flags|=OR_FORBIDDEN;

		hs=onion_handler_handle(req->connection.listen_point->server->internal_error_handler, req, res);
	}

	if (hs==OCS_YIELD){
		// Remove from the poller, and yield thread to poller. From now on it will be processed somewhere else (longpoll thread).
		onion_poller *poller=onion_get_poller(req->connection.listen_point->server);
		onion_poller_slot *slot=onion_poller_get(poller, req->connection.fd);
		onion_poller_slot_set_shutdown(slot, NULL, NULL);

		return hs;
	}
	int rs=onion_response_free(res);
	if (hs>=0 && rs==OCS_KEEP_ALIVE) // if keep alive, reset struct to get the new petition.
		onion_request_clean(req);
	return hs>0 ? rs : hs;
}
示例#10
0
文件: 08-post.c 项目: Andrepuel/onion
/// There is a bug when posting large files. Introduced when change write 1 by 1, to write by blocks on the FILE parser
void t04_post_largefile(){
	INIT_LOCAL();
	
	int postfd=open(BIG_FILE, O_RDONLY);
	off_t filesize=lseek(postfd, 0, SEEK_END);
	lseek(postfd, 0, SEEK_SET);
	
	expected_post post={};;
	post.filename=BIG_FILE_BASE;
	post.test_ok=0; // Not ok as not called processor yet
	post.tmpfilename=NULL;
	post.size=filesize;
	
	onion *server=onion_new(0);
	onion_listen_point *lp=onion_buffer_listen_point_new();
	onion_add_listen_point(server,NULL,NULL,lp);
	onion_set_root_handler(server, onion_handler_new((void*)&post_check,&post,NULL));
	
	onion_request *req=onion_request_new(lp);

#define POST_HEADER "POST / HTTP/1.1\nContent-Type: multipart/form-data; boundary=end\nContent-Length: %d\n\n--end\nContent-Disposition: text/plain; name=\"file\"; filename=\"" BIG_FILE_BASE "\"\n\n"
	char tmp[1024];
	ONION_DEBUG("Post size is about %d",filesize+73);
	snprintf(tmp, sizeof(tmp), POST_HEADER, (int)filesize+73);
	ONION_DEBUG("%s",tmp);
	onion_request_write(req,tmp,strlen(tmp));
	
	int r=read(postfd, tmp, sizeof(tmp));
	while ( r>0 ){
		onion_request_write(req, tmp, r);
		r=read(postfd, tmp, sizeof(tmp));
	}
	
	onion_request_write(req,"\n--end--",8);
	FAIL_IF_NOT_EQUAL_INT(post.test_ok,1);
#undef POST_HEADER
	onion_request_clean(req);


	//post.test_ok=0; // Not ok as not called processor yet

	lseek(postfd, 0, SEEK_SET);

	int difffd=open(post.tmpfilename, O_RDONLY);
	FAIL_IF_NOT_EQUAL_INT(difffd,-1); // Orig file is removed at handler returns. But i have a copy
	difffd=open(post.tmplink, O_RDONLY);
	FAIL_IF_EQUAL_INT(difffd,-1);
	ONION_DEBUG("tmp filename %s",post.tmpfilename);
	int r1=1, r2=1;
	char c1=0, c2=0;
	int p=0;
	while ( r1 && r2 && c1==c2){
		r1=read(difffd, &c1, 1);
		r2=read(postfd, &c2, 1);
		//ONION_DEBUG("%d %d",c1,c2);
		FAIL_IF_NOT_EQUAL_INT(c1,c2);
		p++;
	}
	if ( r1 || r2 ){
		ONION_ERROR("At %d",p);
		FAIL_IF_NOT_EQUAL_INT(r1,0);
		FAIL_IF_NOT_EQUAL_INT(r2,0);
		FAIL_IF_NOT_EQUAL_INT(c1,c2);
		FAIL("Files are different");
	}
	else
		ONION_DEBUG("Files are ok");
	
	close(difffd);
	close(postfd);

	onion_request_free(req);

	if (post.tmpfilename){
		struct stat st;
		FAIL_IF_EQUAL(stat(post.tmpfilename,&st), 0); // Should not exist
	}
	
	onion_free(server);
	if (post.tmpfilename)
		free(post.tmpfilename);
	if (post.tmplink)
		free(post.tmplink);
	
	END_LOCAL();
}
示例#11
0
/**
 * @short Opens a script file, and executes it.
 */
void prerecorded(const char *oscript, int do_r){
	INIT_LOCAL();
	FILE *fd=fopen(oscript, "r");
	if (!fd){
		FAIL("Could not open script file");
		END_LOCAL();
		return;
	}
	const char *script=basename((char*)oscript);
	
	buffer *buffer=buffer_new(1024*1024);
	onion_request *req=onion_request_new(server, buffer, "test");
	
	ssize_t r;
	const size_t LINE_SIZE=1024;
	char *line=malloc(LINE_SIZE);
	size_t len=LINE_SIZE;
	onion_connection_status ret;
	int ntest=0;
	int linen=0;
	while (!feof(fd)){
		ntest++;
		ret=OCS_NEED_MORE_DATA;
		ONION_DEBUG("Test %d",ntest);
		// Read request
		while ( (r=getline(&line, &len, fd)) != -1 ){
			linen++;
			if (strcmp(line,"-- --\n")==0){
				break;
			}
			if (do_r){
				line[r-1]='\r';
				line[r]='\n';
				r++;
			}
			if (ret==OCS_NEED_MORE_DATA)
				ret=onion_request_write(req, line, r);
			//line[r]='\0';
			//ONION_DEBUG0("Write: %s\\n (%d). Ret %d",line,r,ret);
			len=LINE_SIZE;
		}
		if (r<0){
			buffer_free(buffer);
			fclose(fd);
			onion_request_free(req);
			free(line);
			END_LOCAL();
			return;
		}
		
		if (r==0){
			FAIL_IF("Found end of file before end of request");
			buffer_free(buffer);
			fclose(fd);
			onion_request_free(req);
			free(line);
			END_LOCAL();
			return;
		}
		
		// Check response
		buffer->data[buffer->pos]='\0';
		if (buffer->pos==0){
			ONION_DEBUG("Empty response");
		}
		else{
			ONION_DEBUG0("Response: %s",buffer->data);
		}

		while ( (r=getline(&line, &len, fd)) != -1 ){
			linen++;
			if (strcmp(line,"++ ++\n")==0){
				break;
			}
			line[strlen(line)-1]='\0';
			if (strcmp(line,"INTERNAL_ERROR")==0){ // Checks its an internal error
				ONION_DEBUG("%s:%d Check INTERNAL_ERROR",script,linen);
				ONION_DEBUG0("Returned %d",ret);
				FAIL_IF_NOT_EQUAL(ret, OCS_INTERNAL_ERROR);
			}
			else if (strcmp(line,"NOT_IMPLEMENTED")==0){ // Checks its an internal error
				ONION_DEBUG("Check NOT_IMPLEMENTED");
				ONION_DEBUG0("Returned %d",ret);
				FAIL_IF_NOT_EQUAL(ret, OCS_NOT_IMPLEMENTED);
			}
			else{
				regex_t re;
				regmatch_t match[1];
				
				int l=strlen(line)-1;

				int _not=line[0]=='!';
				if (_not){
					ONION_DEBUG("Oposite regexp");
					memmove(line, line+1, l);
					l--;
				}

				memmove(line+1,line,l+1);
				line[0]='^';
				line[l+2]='$';
				line[l+3]='\0';
				ONION_DEBUG("%s:%d Check regexp: '%s'",script, linen, line);
				int r;
				r=regcomp(&re, line, REG_EXTENDED);
				if ( r !=0 ){
					char error[1024];
					regerror(r, &re, error, sizeof(error));
					ONION_ERROR("%s:%d Error compiling regular expression %s: %s",script, linen, line, error);
					FAIL(line);
				}
				else{
					int _match=regexec_multiline(&re, buffer->data, 1, match, 0);
					if ( (_not && _match==0) || (!_not && _match!=0) ){
						ONION_ERROR("%s:%d cant find %s",script, linen, line);
						FAIL(line);
					}
					else{
						ONION_DEBUG0("Found at %d-%d",match[0].rm_so, match[0].rm_eo);
						FAIL_IF(0); // To mark a passed test
					}
				}
				regfree(&re);
			}
			len=1024;
		}

		
		buffer_clean(buffer);
		onion_request_clean(req);
	}
	
	free(line);
	
	onion_request_free(req);
	
	buffer_free(buffer);
	
	fclose(fd);
	END_LOCAL();
}
示例#12
0
void t01_call_otemplate(){
  INIT_LOCAL();


  onion *s=onion_new(0);

  onion_set_root_handler(s, onion_handler_new((void*)_13_otemplate_html_handler_page, NULL, NULL));
	onion_listen_point *lp=onion_buffer_listen_point_new();
	onion_add_listen_point(s,NULL,NULL,lp);

	struct tests_call_otemplate tests;

  onion_request *req=onion_request_new(lp);
  FAIL_IF_NOT_EQUAL_INT(onion_request_write0(req, "GET /\n\n"), OCS_REQUEST_READY);
  FAIL_IF_NOT_EQUAL_INT(onion_request_process(req), OCS_CLOSE_CONNECTION);

  ONION_INFO("Got %s",onion_buffer_listen_point_get_buffer_data(req));
  check_tests(onion_buffer_listen_point_get_buffer(req), &tests);

  FAIL_IF_NOT_EQUAL_INT(tests.ok_hello,1);
  FAIL_IF_NOT_EQUAL_INT(tests.ok_list,0);
  FAIL_IF_NOT_EQUAL_INT(tests.ok_title,0);
  FAIL_IF_NOT_EQUAL_INT(tests.ok_title_title,0);
  FAIL_IF_NOT_EQUAL_INT(tests.ok_encoding,0);


  onion_dict *d=onion_dict_new();
  onion_dict_add(d, "title", "TITLE",0);
  onion_dict_add(d, "hello", "SHOULD NOT APPEAR",0);
	onion_dict_add(d, "quoted", "<\"Hello>",0);

  onion_request_clean(req);
	onion_handler_free(onion_get_root_handler(s));
  onion_set_root_handler(s, onion_handler_new((void*)_13_otemplate_html_handler_page, d, NULL));
  FAIL_IF_NOT_EQUAL_INT(onion_request_write0(req, "GET /\n\n"), OCS_REQUEST_READY);
  FAIL_IF_NOT_EQUAL_INT(onion_request_process(req), OCS_CLOSE_CONNECTION);

  ONION_INFO("Got %s",onion_buffer_listen_point_get_buffer_data(req));
  check_tests(onion_buffer_listen_point_get_buffer(req), &tests);

  FAIL_IF_NOT_EQUAL_INT(tests.ok_hello,1);
  FAIL_IF_NOT_EQUAL_INT(tests.ok_list,0);
  FAIL_IF_NOT_EQUAL_INT(tests.ok_title,1);
  FAIL_IF_NOT_EQUAL_INT(tests.ok_title_title,1);
  FAIL_IF_NOT_EQUAL_INT(tests.ok_encoding,1);


  onion_dict *d2=onion_dict_new();
  onion_dict_add(d2,"0","LIST 1",0);
  onion_dict_add(d2,"1","LIST 2",0);
  onion_dict_add(d2,"2","LIST 3",0);
  onion_dict_add(d,"list",d2, OD_DICT|OD_FREE_VALUE);

	onion_dict *f1=onion_dict_new();
	onion_dict *f2=onion_dict_new();
	onion_dict_add(f2, "0", "internal",0);
	onion_dict_add(f2, "1", "loop",0);
	onion_dict_add(f1, "loop", f2, OD_DICT|OD_FREE_VALUE);

	onion_dict_add(d, "loop", f1, OD_DICT|OD_FREE_VALUE);

  onion_request_clean(req);
	onion_handler_free(onion_get_root_handler(s));
  onion_set_root_handler(s, onion_handler_new((void*)_13_otemplate_html_handler_page, d, (void*)onion_dict_free));
    FAIL_IF_NOT_EQUAL_INT(onion_request_write0(req, "GET /\n\n"), OCS_REQUEST_READY);
  FAIL_IF_NOT_EQUAL_INT(onion_request_process(req), OCS_CLOSE_CONNECTION);
  check_tests(onion_buffer_listen_point_get_buffer(req), &tests);
  ONION_INFO("Got %s",onion_buffer_listen_point_get_buffer_data(req));

	FAIL_IF_NOT_EQUAL_INT(tests.ok_hello,1);
  FAIL_IF_NOT_EQUAL_INT(tests.ok_list,1);
  FAIL_IF_NOT_EQUAL_INT(tests.ok_title,1);
  FAIL_IF_NOT_EQUAL_INT(tests.ok_title_title,1);
  FAIL_IF_NOT_EQUAL_INT(tests.ok_internal_loop,1);


  onion_request_free(req);
  onion_free(s);

  END_LOCAL();
}