int main(int argc, char **argv) { if (argc > 1) while (++argv, --argc) { if (!strcmp("-c", argv[0])) color_on = 1; else if (!strcmp("-h", argv[0])) printf("liblisp unit tests\n\tusage ./%s (-c)? (-h)?\n", argv[0]); else printf("unknown argument '%s'\n", argv[0]); } unit_test_start("liblisp"); { print_note("util.c"); test(is_number("0xfAb")); test(is_number("-01234567")); test(is_number("+1000000000000000000000000000003")); test(!is_number("")); test(!is_number("+")); test(!is_number("-")); test(unbalanced("(((", '(', ')') > 0); test(unbalanced("))", '(', ')') < 0); test(unbalanced("", '(', ')') == 0); test(unbalanced("\"(", '(', ')') == 0); test(unbalanced("( \"))))(()()()(()\\\"())\")", '(', ')') == 0); test(unbalanced("(a (b) c (d (e (f) \")\" g)))", '(', ')') == 0); test(unbalanced("((a b) c", '(', ')') > 0); test(!is_fnumber("")); test(!is_fnumber("1e")); test(!is_fnumber("-1e")); test(!is_fnumber("1-e")); test(is_fnumber("+0.")); test(is_fnumber("123")); /*this passes, see header */ test(is_fnumber("1e-3")); test(is_fnumber("1.003e+34")); test(is_fnumber("1e34")); test(is_fnumber("93.04")); test(match("", "")); test(match("abc", "abc")); test(!match("abC", "abc")); test(match("aaa*", "aaaXX")); test(!match("aaa*", "XXaaaXX")); test(match(".bc", "abc")); test(match("a.c", "aXc")); test(!match("a\\.c", "aXc")); test(match("a\\.c", "a.c")); char *s = NULL; state(s = vstrcatsep(",", "a", "b", "c", "", "foo", "bar", NULL)); test(!sstrcmp("a,b,c,,foo,bar", s)); free(s); char *t = NULL, *s1 = "Hello,", *s2 = " World!"; state(t = calloc(16, 1)); state(strcpy(t, s1)); test(((size_t) (lstrcatend(t, s2) - t)) == (strlen(s1) + strlen(s2))); free(t); /*test tr, or translate, functionality */ size_t trinsz = 0; uint8_t trout[128] = { 0 }, *trin = (uint8_t *) "aaabbbcdaacccdeeefxxxa"; tr_state_t *tr1; state(tr1 = tr_new()); state(trinsz = strlen((char *)trin)); test(tr_init(tr1, "", (uint8_t *) "abc", (uint8_t *) "def") == TR_OK); test(tr_block(tr1, trin, trout, trinsz) == trinsz); test(!strcmp((char *)trout, "dddeeefdddfffdeeefxxxd")); test(tr_init(tr1, "s", (uint8_t *) "abc", (uint8_t *) "def") == TR_OK); state(memset(trout, 0, 128)); test(tr_block(tr1, trin, trout, trinsz) <= trinsz); test(!strcmp((char *)trout, "defddfdeeefxxxd")); state(tr_delete(tr1)); /*know collisions for the djb2 hash algorithm */ test(djb2("heliotropes", strlen("heliotropes")) == djb2("neurospora", strlen("neurospora"))); test(djb2("depravement", strlen("depravement")) == djb2("serafins", strlen("serafins"))); /*should not collide */ test(djb2("heliotropes", strlen("heliotropes")) != djb2("serafins", strlen("serafins"))); } { /*io.c test */ io_t *in, *out; print_note("io.c"); /*string input */ static const char hello[] = "Hello\n"; /**@note io_sin currently duplicates "hello" internally*/ state(in = io_sin(hello, strlen(hello))); test(io_is_in(in)); test(io_getc(in) == 'H'); test(io_getc(in) == 'e'); test(io_getc(in) == 'l'); test(io_getc(in) == 'l'); test(io_getc(in) == 'o'); test(io_getc(in) == '\n'); test(io_getc(in) == EOF); test(io_getc(in) == EOF); test(!io_error(in)); test(io_seek(in, 0, SEEK_SET) >= 0); test(io_getc(in) == 'H'); test(io_seek(in, 3, SEEK_SET) >= 0); test(io_getc(in) == 'l'); test(io_ungetc('x', in) == 'x'); test(io_getc(in) == 'x'); test(io_getc(in) == 'o'); state(io_close(in)); /*string output */ char *s = NULL; static const char hello_world[] = "Hello,\n\tWorld!\n"; /**@note io_sin currently duplicates hello_world internally*/ state(in = io_sin(hello_world, strlen(hello_world))); test(!strcmp(s = io_getline(in), "Hello,")); s = (free(s), NULL); test(!strcmp(s = io_getline(in), "\tWorld!")); s = (free(s), NULL); test(!io_getline(in)); test(io_seek(in, 0, SEEK_SET) >= 0); test(!strcmp(s = io_getdelim(in, EOF), "Hello,\n\tWorld!\n")); s = (free(s), NULL); state(io_close(in)); state(out = io_sout(1)); test(io_puts("Hello, World", out) != EOF); test(!strcmp("Hello, World", io_get_string(out))); test(io_putc('\n', out) != EOF); test(!strcmp("Hello, World\n", io_get_string(out))); test(io_seek(out, -6, SEEK_CUR) >= 0); test(io_puts("Mars\n", out) != EOF); test(!strcmp("Hello, Mars\n\n", io_get_string(out))); free(io_get_string(out)); state(io_close(out)); static const char block_in[16] = {1, 3, 4, 6}; static char block_out[16] = {0}; state((in = io_sin(block_in, 16))); test(io_getc(in) == 1); test(io_read(block_out, 15, in) == 15); test(!memcmp(block_out, block_in+1, 15)); state(io_close(in)); } { /* hash.c hash table tests */ hash_table_t *h = NULL; print_note("hash.c"); state(h = hash_create(1)); return_if(!h); test(!hash_insert(h, "key1", "val1")); test(!hash_insert(h, "key2", "val2")); /* assuming the hash algorithm is djb2, then * "heliotropes" collides with "neurospora" * "depravement" collides with "serafins" * "playwright" collides with "snush" (for djb2a) * See: * <https://programmers.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed> */ test(!hash_insert(h, "heliotropes", "val3")); test(!hash_insert(h, "neurospora", "val4")); test(!hash_insert(h, "depravement", "val5")); test(!hash_insert(h, "serafins", "val6")); test(!hash_insert(h, "playwright", "val7")); test(!hash_insert(h, "snush", "val8")); test(!hash_insert(h, "", "val9")); test(!hash_insert(h, "nil", "")); test(!hash_insert(h, "a", "x")); test(!hash_insert(h, "a", "y")); test(!hash_insert(h, "a", "z")); test(!sstrcmp("val1", hash_lookup(h, "key1"))); test(!sstrcmp("val2", hash_lookup(h, "key2"))); test(!sstrcmp("val3", hash_lookup(h, "heliotropes"))); test(!sstrcmp("val4", hash_lookup(h, "neurospora"))); test(!sstrcmp("val5", hash_lookup(h, "depravement"))); test(!sstrcmp("val6", hash_lookup(h, "serafins"))); test(!sstrcmp("val7", hash_lookup(h, "playwright"))); test(!sstrcmp("val8", hash_lookup(h, "snush"))); test(!sstrcmp("val9", hash_lookup(h, ""))); test(!sstrcmp("", hash_lookup(h, "nil"))); test(!sstrcmp("z", hash_lookup(h, "a"))); test(hash_get_load_factor(h) <= 0.75f); state(hash_destroy(h)); } { /* lisp.c (and the lisp interpreter in general) */ lisp_t *l; print_note("lisp.c"); /*while unit testing eschews state being held across tests it is makes *little sense in this case*/ state(l = lisp_init()); state(io_close(lisp_get_logging(l))); test(!lisp_set_logging(l, io_nout())); return_if(!l); test(!lisp_eval_string(l, "")); test(is_int(lisp_eval_string(l, "2"))); test(get_int(lisp_eval_string(l, "(+ 2 2)")) == 4); test(get_int(lisp_eval_string(l, "(* 3 2)")) == 6); lisp_cell_t *x = NULL, *y = NULL, *z = NULL; char *t = NULL; state(x = lisp_intern(l, lstrdup_or_abort("foo"))); state(y = lisp_intern(l, t = lstrdup_or_abort("foo"))); /*this one needs freeing! */ state(z = lisp_intern(l, lstrdup_or_abort("bar"))); test(x == y && x != NULL); test(x != z); free(t); /*free the non-interned string */ test(is_proc(lisp_eval_string(l, "(define square (lambda (x) (* x x)))"))); test(get_int(lisp_eval_string(l, "(square 4)")) == 16); test(!is_list(cons(l, gsym_tee(), gsym_tee()))); test(is_list(cons(l, gsym_tee(), gsym_nil()))); test(!is_list(cons(l, gsym_nil(), cons(l, gsym_tee(), gsym_tee())))); test(is_list(mk_list(l, gsym_tee(), gsym_nil(), gsym_tee(), NULL))); test(gsym_error() == lisp_eval_string(l, "(> 'a 1)")); test(is_sym(x)); test(is_asciiz(x)); test(!is_str(x)); test(gsym_error() == lisp_eval_string(l, "(eval (cons quote 0))")); char *serial = NULL; test(!strcmp((serial = lisp_serialize(l, cons(l, gsym_tee(), gsym_error()))), "(t . error)")); state(free(serial)); state(lisp_destroy(l)); } return unit_test_end("liblisp"); /*should be zero! */ }
status_t handle_request( RequestPB *pb ) { HTTPResponse response; pb->closeConnection = false; const char *sPtr; // General purpose string pointer // Temporary buffers int32 fieldSize = 1024; char fieldValue[1024]; char headBuffer[1024]; int32 contentLength = 0; // **** // Get PATH_INFO and SCRIPT_NAME from path; Setup absPath of CGI // **** char PATH_INFO[1024]; char SCRIPT_NAME[256]; // Get SCRIPT_NAME strxcpy( SCRIPT_NAME, pb->resourcePath->Path(), 255 ); strxcpy( PATH_INFO, pb->brURI->path+strlen( pb->resourcePath->Path()+1 ), 1023 ); // Make absolute CGI path from web-directory and requested path BPath absPath( pb->webDirectory->Path(), pb->resourcePath->Path()+1 ); // **** // Make sure CGI exists and Check Permission // **** if( pb->authenticate && !pb->realms->Authenticate( pb->request, &response, pb->brURI->path, absPath.Path(), S_IXOTH ) ) { pb->Logprintf( "%ld Status-Line: %s\n", pb->sn, response.GetStatusLine() ); return B_OK; } // **** // Setup meta-variables in new environment // **** char params[2048]; // Should we use the CGI script command line? // This should be done on a GET or HEAD where the URL query string // does not contain any unencoded '=' characters. if( *pb->brURI->query && ((pb->request->GetMethod() == METHOD_GET)||(pb->request->GetMethod() == METHOD_HEAD))&& !strchr( pb->brURI->query, '=' ) ) { uri_unescape_str( params, pb->brURI->query, 2048 ); } else uri_unescape_str( params, pb->brURI->params, 2048 ); // Environment to be used by the CGI Environment env( pb->environ ); // AUTH_TYPE if( pb->request->FindHeader( kHEAD_AUTHORIZATION, fieldValue, fieldSize ) ) { sPtr = fieldValue; sPtr = get_next_token( headBuffer, sPtr, fieldSize ); env.PutEnv( "AUTH_TYPE", headBuffer ); if( strcasecmp( headBuffer, "Basic" ) == 0 ) { // REMOTE_USER sPtr = get_next_token( headBuffer, sPtr, fieldSize ); decode_base64( headBuffer, headBuffer, fieldSize ); sPtr = get_next_token( fieldValue, headBuffer, fieldSize, ":" ); env.PutEnv( "REMOTE_USER", fieldValue ); } } // CONTENT_LENGTH if( pb->request->FindHeader( kHEAD_LENGTH, fieldValue, fieldSize ) ) env.PutEnv( "CONTENT_LENGTH", fieldValue ); // CONTENT_TYPE if( pb->request->FindHeader( kHEAD_TYPE, fieldValue, fieldSize ) ) env.PutEnv( "CONTENT_TYPE", fieldValue ); // GATEWAY_INTERFACE env.PutEnv( "GATEWAY_INTERFACE", "CGI/1.1" ); // HTTP_* for( int i=0; (sPtr = pb->request->HeaderAt( i )); i++ ) { sPtr = get_next_token( fieldValue, sPtr, fieldSize, ":" ); sprintf( headBuffer, "HTTP_%s", http_to_cgi_header( fieldValue ) ); sPtr = get_next_token( fieldValue, sPtr, fieldSize, ":" ); env.PutEnv( headBuffer, fieldValue ); } // PATH_INFO env.PutEnv( "PATH_INFO", PATH_INFO ); // PATH_TRANSLATED if( *PATH_INFO ) { BPath pathTrans( pb->webDirectory->Path(), PATH_INFO+1 ); if( pathTrans.Path() ) env.PutEnv( "PATH_TRANSLATED", pathTrans.Path() ); } // QUERY_STRING env.PutEnv( "QUERY_STRING", pb->brURI->query ); // REMOTE_ADDR env.PutEnv( "REMOTE_ADDR", pb->request->GetRemoteHost() ); // REMOTE_HOST // Ya, right... like were going to waste valuable time with a DNS lookup! env.PutEnv( "REMOTE_HOST", "" ); // REMOTE_IDENT // Ha! Perform an Ident lookup... I don't think so. // REQUEST_METHOD env.PutEnv( "REQUEST_METHOD", http_find_method( pb->request->GetMethod() ) ); // SCRIPT_NAME env.PutEnv( "SCRIPT_NAME", SCRIPT_NAME ); // SERVER_NAME env.PutEnv( "SERVER_NAME", pb->brURI->host ); // SERVER_PORT sprintf( fieldValue, "%u", pb->request->GetPort() ); env.PutEnv( "SERVER_PORT", fieldValue ); // SERVER_PROTOCOL env.PutEnv( "SERVER_PROTOCOL", pb->request->GetVersion() ); // SERVER_SOFTWARE env.PutEnv( "SERVER_SOFTWARE", "RobinHood" ); // PWD BPath PWD( absPath ); PWD.GetParent( &PWD ); env.PutEnv( "PWD", PWD.Path() ); // **** // Create pipes // **** pid_t pid; int ipipe[2], opipe[2]; if( pipe(ipipe) < 0 ) { response.SetHTMLMessage( 500, "Pipe creation failed!" ); pb->request->SendReply( &response ); return B_OK; } if( pipe(opipe) < 0 ) { close( ipipe[0] ); close( ipipe[1] ); response.SetHTMLMessage( 500, "Pipe creation failed!" ); pb->request->SendReply( &response ); return B_OK; } // **** // Setup args for execve() // **** // Setup command string; copy CGI path and append params char command[4096]; sPtr = strxcpy( command, absPath.Path(), 4095 ); // Disabled because of security risk /* if( *params && !strpbrk( params, ";&" ) ) { sPtr = strxcpy( (char *)sPtr, " ", command+4095-sPtr ); strxcpy( (char *)sPtr, params, command+4095-sPtr ); // Append params }*/ char *args[4]; args[0] = "/bin/sh"; args[1] = "-c"; args[2] = command; args[3] = NULL; pb->Logprintf( "%ld Exec: %s\n", pb->sn, command ); // **** // Start sub-process using fork() dup2() and exec() // **** pid = fork(); if( pid == (pid_t)0 ) // If we are the child process... { // Make this process the process group leader setpgid( 0, 0 ); fflush(stdout); // sync stdout... // Set pipes to stdin and stdout if( dup2( opipe[0], STDIN_FILENO ) < 0 ) exit( 0 ); if( dup2( ipipe[1], STDOUT_FILENO ) < 0 ) exit( 0 ); // Close unused ends of pipes close( opipe[1] ); close( ipipe[0] ); // Set Current Working Directory to that of script chdir( PWD.Path() ); // Execute CGI in this process by means of /bin/sh execve( args[0], args, env.GetEnvironment() ); exit( 0 ); // If for some reason execve() fails... } else if( pid < (pid_t)0 ) // Something Bad happened! { close( opipe[0] ); close( opipe[1] ); close( ipipe[0] ); close( ipipe[1] ); response.SetHTMLMessage( 500, "Fork failed!" ); pb->request->SendReply( &response ); return true; } // Close unused ends of pipes close( opipe[0] ); close( ipipe[1] ); // **** // Talk to CGI // **** bool persistant = true; // Defined to make code easier to read int inDes = ipipe[0]; // input file descripter int outDes = opipe[1]; // output file descripter // Make a BDataIO wrapper for the in and out pipes DesIO pipeIO( inDes, outDes ); // If the request contains a content body, feed it into stdin of the CGI script if( pb->request->GetContentLength() > 0 ) pb->request->SendBody( &pipeIO ); // Buffer the response body for better performance response.SetBodyBuffering( true ); // Read first line to detect use of Non-Parsed Header Output io_getline( &pipeIO, headBuffer, fieldSize ); // Strip the '\r' character if there is one int32 size; size = strlen( headBuffer )-1; if( headBuffer[size] == '\r' ) headBuffer[size] = 0; // Is NPH Output? if( strncmp( "HTTP/", headBuffer, 5 ) == 0 ) { DataIOPump ioPump; BufferedIO bufio( pb->request->GetReplyIO() ); bufio.DoAllocate(); io_printf( &bufio, "%s\r\n", headBuffer ); ioPump.StartPump( &pipeIO, &bufio, contentLength ); bufio.Flush(); persistant = false; } else // using Parsed Header Output { // Add Date header time_t now; struct tm *brokentime; now = time( NULL ); brTimeLock.Lock(); brokentime = gmtime( &now ); strftime (fieldValue, 256, kHTTP_DATE, brokentime); brTimeLock.Unlock(); response.AddHeader( kHEAD_DATE, fieldValue ); // Add line used to detect NPH as CGI header response.AddHeader( headBuffer ); // Receive the CGI headers response.ReceiveHeaders( &pipeIO ); // If Location header, don't expect any more headers if( (sPtr = response.FindHeader( "Location", fieldValue, fieldSize )) ) { response.SetStatusLine( 302 ); // 302 Moved Temporarily } else { if( (sPtr = response.FindHeader( "Status", fieldValue, fieldSize )) ) { response.RemoveHeader( (char *)sPtr ); // Don't forward to client response.SetStatusLine( fieldValue ); } else response.SetStatusLine( 200 ); } // Don't cache the response if( !response.FindHeader( "Cache-Control", fieldValue, fieldSize ) ) response.AddHeader( "Cache-Control: no-cache" ); if( !response.FindHeader( "Pragma", fieldValue, fieldSize ) ) response.AddHeader( "Pragma: no-cache" ); // Content-Length header? int32 contentLength = 0; if( (sPtr = response.FindHeader( kHEAD_LENGTH, fieldValue, fieldSize )) ) { contentLength = strtol( fieldValue, (char **)&headBuffer, 10 ); response.SetContentLength( contentLength ); } else // No content-length provided; close connection on return { response.AddHeader( "Connection: close" ); persistant = false; } pb->Logprintf( "%ld Status-Line: %s\n", pb->sn, response.GetStatusLine() ); if( pb->request->GetMethod() != METHOD_HEAD ) response.SetMessageBody( &pipeIO ); pb->request->SendReply( &response ); } // Close remaining ends of pipes close( ipipe[0] ); close( opipe[1] ); pb->closeConnection = !persistant; return B_OK; }