CVS update: php31/sapi

From: Date: Tue, 25 Aug 1998 18:35:55 +0000
Subject: CVS update: php31/sapi
Groups: php.dev 
Request: Send a blank email to php-dev+get-568@lists.php.net to get a copy of this message
Date: Tuesday August 25, 1998 @ 14:35 Author: shane Update of /repository/php31/sapi In directory asf:/u2/tmp/cvs-serv24679/sapi Modified Files: isapi_sapi.c nsapi-readme.txt nsapi_sapi.c sapi.h Log Message: NSAPI now on par with ISAPI. Authentication and redirection working now. Index: php31/sapi/isapi_sapi.c diff -c php31/sapi/isapi_sapi.c:2.16 php31/sapi/isapi_sapi.c:2.17 *** php31/sapi/isapi_sapi.c:2.16 Sun Aug 23 02:29:14 1998 --- php31/sapi/isapi_sapi.c Tue Aug 25 14:35:53 1998 *************** *** 132,138 **** if (!strcasecmp(header, "HTTP")) { *r = '/'; r = strchr(header, ' '); ! sapi_info->http_status = estrdup(r + 1); return SUCCESS; } *r = '/'; --- 132,138 ---- if (!strcasecmp(header, "HTTP")) { *r = '/'; r = strchr(header, ' '); ! sapi_info->httpstatus = estrdup(r + 1); return SUCCESS; } *r = '/'; *************** *** 150,156 **** } else { /*local redirect */ sapi_info->http_redirect = 2; } ! sapi_info->http_status = estrdup(r + 2); return SUCCESS; } *r = ':'; --- 150,156 ---- } else { /*local redirect */ sapi_info->http_redirect = 2; } ! sapi_info->httpstatus = estrdup(r + 2); return SUCCESS; } *r = ':'; *************** *** 182,195 **** switch (sapi_info->http_redirect) { case 1: req = HSE_REQ_SEND_URL_REDIRECT_RESP; ! headlen = strlen(sapi_info->http_status); /*for some reason, if we actualy print something on a redirection, iis does very funky stuff*/ php3_globals->client_alive=0; break; case 2: req = HSE_REQ_SEND_URL; ! headlen = strlen(sapi_info->http_status); php3_globals->client_alive=0; break; default: --- 182,195 ---- switch (sapi_info->http_redirect) { case 1: req = HSE_REQ_SEND_URL_REDIRECT_RESP; ! headlen = strlen(sapi_info->httpstatus); /*for some reason, if we actualy print something on a redirection, iis does very funky stuff*/ php3_globals->client_alive=0; break; case 2: req = HSE_REQ_SEND_URL; ! headlen = strlen(sapi_info->httpstatus); php3_globals->client_alive=0; break; default: *************** *** 209,215 **** efree(sapi_info->headers); } if (!lpEcb->ServerSupportFunction(lpEcb->ConnID, ! req, sapi_info->http_status, &headlen, (LPDWORD) tempstr)) { /*FIXME request_shutdown on failure! */ if (tempstr) --- 209,215 ---- efree(sapi_info->headers); } if (!lpEcb->ServerSupportFunction(lpEcb->ConnID, ! req, sapi_info->httpstatus, &headlen, (LPDWORD) tempstr)) { /*FIXME request_shutdown on failure! */ if (tempstr) *************** *** 373,379 **** sapi_info->insert_header = sapi_insert_header; sapi_info->send_header = sapi_send_header; sapi_info->headers = NULL; ! sapi_info->http_status = NULL; sapi_info->http_redirect = 0; sapi_info->_php3_hash_server_env = sapi__php3_hash_server_env; --- 373,379 ---- sapi_info->insert_header = sapi_insert_header; sapi_info->send_header = sapi_send_header; sapi_info->headers = NULL; ! sapi_info->httpstatus = NULL; sapi_info->http_redirect = 0; sapi_info->_php3_hash_server_env = sapi__php3_hash_server_env; Index: php31/sapi/nsapi-readme.txt diff -c php31/sapi/nsapi-readme.txt:2.1 php31/sapi/nsapi-readme.txt:2.2 *** php31/sapi/nsapi-readme.txt:2.1 Sat Aug 22 19:33:12 1998 --- php31/sapi/nsapi-readme.txt Tue Aug 25 14:35:53 1998 *************** *** 11,17 **** Add the following to obj.conf #note place following two lines after mime types init! ! Init fn="load-modules" funcs="init_nsapi_mod,close_nsapi_mod,nsapi_php_main" shlib="/php31/phpsapi_nsapi.dll" Init fn=init_nsapi_mod errorString="Failed to initialize PHP!" <Object name="default"> --- 11,17 ---- Add the following to obj.conf #note place following two lines after mime types init! ! Init fn="load-modules" funcs="init_nsapi_mod,close_nsapi_mod,nsapi_php_main,php3_nsapi_auth_trans" shlib="/php31/phpsapi_nsapi.dll" Init fn=init_nsapi_mod errorString="Failed to initialize PHP!" <Object name="default"> *************** *** 28,31 **** --- 28,52 ---- <Object name="x-httpd-php31"> ObjectType fn="force-type" type="magnus-internal/x-httpd-php31" Service fn=nsapi_php_main + </Object> + + + Authentication configuration + + PHP authentication cannot be used with any other authentication. ALL AUTHENTICATION IS + PASSED TO YOUR PHP SCRIPT. To configure PHP Authentication for the entire server, add + the following line: + + <Object name="default"> + AuthTrans fn=php3_nsapi_auth_trans + . + . + . + . + </Object> + + To use PHP Authentication on a single directory, add the following: + + <Object ppath="d:\path\to\authenticated\dir\*"> + AuthTrans fn=php3_nsapi_auth_trans </Object> Index: php31/sapi/nsapi_sapi.c diff -c php31/sapi/nsapi_sapi.c:2.5 php31/sapi/nsapi_sapi.c:2.6 *** php31/sapi/nsapi_sapi.c:2.5 Sun Aug 23 02:29:14 1998 --- php31/sapi/nsapi_sapi.c Tue Aug 25 14:35:54 1998 *************** *** 67,73 **** #ifndef XP_WIN32 #include <unistd.h> /* sleep */ ! #define DLLEXPORT #else /* XP_WIN32 */ #define WIN32_LEAN_AND_MEAN #define EXPORT __declspec(dllexport) --- 67,73 ---- #ifndef XP_WIN32 #include <unistd.h> /* sleep */ ! #define EXPORT #else /* XP_WIN32 */ #define WIN32_LEAN_AND_MEAN #define EXPORT __declspec(dllexport) *************** *** 75,92 **** #include <stdio.h> #endif /* XP_WIN32 */ ! #define DEBUG_MESSAGE(debugmessage) OutputDebugString(debugmessage) ! /* { \ ! char logmessage[1024]; \ ! snprintf(logmessage, 1024, "%d:debugmessage\n", GetCurrentThreadId()); \ ! OutputDebugString(logmessage); \ ! } ! */ struct ns_request_info { pblock *pb; Session *sn; Request *rq; - pblock *cookie_recv; }; #define SAPI_FUNC_VARS \ --- 75,87 ---- #include <stdio.h> #endif /* XP_WIN32 */ ! #define empty_string "" ! #define NSAPI_DEBUG_MESSAGE(debugmessage) OutputDebugString(debugmessage) ! struct ns_request_info { pblock *pb; Session *sn; Request *rq; }; #define SAPI_FUNC_VARS \ *************** *** 166,172 **** int i = 0; #ifdef DEBUG ! DEBUG_MESSAGE("enter _php3_nsapi_env_get\n"); #endif /* we need to go through the table above, and see if we can get what is being looked for from netscape, otherwise, try the regular --- 161,167 ---- int i = 0; #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("enter _php3_nsapi_env_get\n"); #endif /* we need to go through the table above, and see if we can get what is being looked for from netscape, otherwise, try the regular *************** *** 242,248 **** { SAPI_FUNC_VARS; #ifdef DEBUG ! DEBUG_MESSAGE("called sapi_getenv\n"); #endif return _php3_nsapi_env_get(ns_rqst, var); } --- 237,243 ---- { SAPI_FUNC_VARS; #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("called sapi_getenv\n"); #endif return _php3_nsapi_env_get(ns_rqst, var); } *************** *** 262,275 **** char *tempstr = NULL,*r; SAPI_FUNC_VARS; #ifdef DEBUG ! DEBUG_MESSAGE("enter sapi_insert_header\n"); #endif /*inserts header into a list of headers */ /* header is in form of "headername: info" need to split into two strings and handle from there */ ! if (!rq->senthdrs && header) { /*lets check to see if this is a HTTP/1.x response */ r = strchr(header, '/'); if (r) { --- 257,270 ---- char *tempstr = NULL,*r; SAPI_FUNC_VARS; #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("enter sapi_insert_header\n"); #endif /*inserts header into a list of headers */ /* header is in form of "headername: info" need to split into two strings and handle from there */ ! if (!rq->senthdrs && !sapi_info->http_redirect && header) { /*lets check to see if this is a HTTP/1.x response */ r = strchr(header, '/'); if (r) { *************** *** 277,283 **** if (!strcasecmp(header, "HTTP")) { *r = '/'; r = strchr(header, ' '); ! // sapi_info->http_status = estrdup(r + 1); return SUCCESS; } *r = '/'; --- 272,278 ---- if (!strcasecmp(header, "HTTP")) { *r = '/'; r = strchr(header, ' '); ! sapi_info->httpstatus = safe_estrdup(r+1); return SUCCESS; } *r = '/'; *************** *** 286,334 **** r = strchr(header, ':'); if (r) { *r = '\0'; ! if (!strcasecmp(header, "Location")) { *r = ':'; ! if (strspn((r+2),"://")) {/*redirect to remote system */ ! sapi_info->http_redirect = 1; ! } else { /*local redirect */ ! sapi_info->http_redirect = 2; ! } ! // sapi_info->http_status = estrdup(r + 2); return SUCCESS; }else if(!strcasecmp(header, "Set-cookie")){ ! pblock_nvinsert(header, r+2, rq->srvhdrs); }else{ ! pblock_remove(header, rq->srvhdrs); ! pblock_nvinsert(header, r+2, rq->srvhdrs); } *r = ':'; } } #ifdef DEBUG ! DEBUG_MESSAGE("exit sapi_insert_header\n"); #endif return SUCCESS; } int sapi_send_header(SAPI_GLOBAL_INCLUDE_V) { SAPI_FUNC_VARS; /* this is sending ok on every call. need to figure out how we can handle other status codes */ #ifdef DEBUG ! DEBUG_MESSAGE("enter sapi_send_header\n"); #endif if (!rq->senthdrs) { /*just verify headers not already sent */ ! protocol_status(sn, rq, PROTOCOL_OK, NULL); if (protocol_start_response(sn, rq) == REQ_NOACTION){ #ifdef DEBUG ! DEBUG_MESSAGE("exit sapi_send_header, failed\n"); #endif return REQ_PROCEED; } } #ifdef DEBUG ! DEBUG_MESSAGE("exit sapi_send_header, success\n"); #endif return SUCCESS; } --- 281,357 ---- r = strchr(header, ':'); if (r) { *r = '\0'; ! if (!strcasecmp(header, "WWW-authenticate")){ ! pblock_nvinsert("WWW-authenticate", (r+2), rq->srvhdrs); ! }else if (!strcasecmp(header, "Location")) { *r = ':'; ! param_free(pblock_remove("Location", rq->srvhdrs)); ! pblock_nvinsert( "Location", (r+2), rq->srvhdrs ); ! sapi_info->http_redirect = 1; return SUCCESS; }else if(!strcasecmp(header, "Set-cookie")){ ! pblock_nvinsert(header, (r+2), rq->srvhdrs); }else{ ! char *theader=_php3_strtolower(header); ! char *temp=(r+2); ! param_free( pblock_remove(theader, rq->srvhdrs)); ! pblock_nvinsert(theader, temp, rq->srvhdrs); } *r = ':'; } } #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("exit sapi_insert_header\n"); #endif return SUCCESS; } + int _nsapi_get_status(struct sapi_request_info *sapi_info){ + int status; + char *r; + + if(!sapi_info->httpstatus)return 200; + r = strchr(sapi_info->httpstatus, ' '); + if(r){ + *r='\0'; + status=atoi(sapi_info->httpstatus); + *r=' '; + return status; + } + return 200; + } + int sapi_send_header(SAPI_GLOBAL_INCLUDE_V) { + int status; SAPI_FUNC_VARS; /* this is sending ok on every call. need to figure out how we can handle other status codes */ #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("enter sapi_send_header\n"); #endif if (!rq->senthdrs) { /*just verify headers not already sent */ ! if(sapi_info->http_redirect){ ! protocol_status( sn, rq, PROTOCOL_REDIRECT, NULL ); ! /* no point in printing anything to client on a redirect */ ! GLOBAL(client_alive)=0; ! } else if(sapi_info->httpstatus) { ! status=_nsapi_get_status(sapi_info); ! protocol_status(sn, rq, status, NULL); ! /* no point in printing anything to client on a redirect */ ! if(status>299)GLOBAL(client_alive)=0; ! }else { ! protocol_status(sn, rq, PROTOCOL_OK, NULL); ! } if (protocol_start_response(sn, rq) == REQ_NOACTION){ #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("exit sapi_send_header, failed\n"); #endif return REQ_PROCEED; } } #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("exit sapi_send_header, success\n"); #endif return SUCCESS; } *************** *** 340,362 **** / will pass authentication through to php, and allow us to / check authentication with our scripts. / - / to do this, we will have to figure out some way of telling - / this authentication module wether or not we want php to - / be able to authenticate this or to pass it through to the - / next authentication module. One idea is to stat the directory - / for some special file, such as a .phpauth file, which would - / just be an empty file. Then, if that file exists, we tell - / netscape that the user is authenticated. Otherwise, we tell - / the server not to proceed, but to pass the authentication - / to the next available authentication module. - / - / however, stat'ing a file for each request is a lot of overhead - / so I am completely open to ideas. Another idea is to use - / ppath which is what I am doing now. This does require however, - / editing obj.conf for EACH directory that needs to be authenticated - / with php scripts. If we can, we'll come up with some good way - / of doing this. Maybe a registry setting in windows, but unix? - / / php3_nsapi_auth_trans / main function called from netscape server to authenticate / a line in obj.conf: --- 363,368 ---- *************** *** 365,385 **** / <Object ppath="path/to/be/authenticated/by/php/*"> / AuthTrans fn="php3_nsapi_auth_trans" /*********************************************************/ - int php3_nsapi_has_auth(char *string, Request * rq) - { - char *authheader = pblock_findval("authorization", rq->headers); - *string = '\0'; - if (authheader) - strcpy(string, authheader); - return strlen(authheader); - } - int EXPORT php3_nsapi_auth_trans(pblock * pb, Session * sn, Request * rq) { ! char AuthString[MAX_PATH]; ! return (php3_nsapi_has_auth(AuthString, rq) && ! !strncmp("Basic ", AuthString, 6)) ! ? REQ_PROCEED : REQ_ABORTED; } --- 371,381 ---- / <Object ppath="path/to/be/authenticated/by/php/*"> / AuthTrans fn="php3_nsapi_auth_trans" /*********************************************************/ int EXPORT php3_nsapi_auth_trans(pblock * pb, Session * sn, Request * rq) { ! /*This is a DO NOTHING function that allows authentication information ! to be passed through to PHP scripts.*/ ! return REQ_PROCEED; } *************** *** 392,398 **** int i=0; while(i<len){ buf[i]=netbuf_getc(sn->inbuf); ! if(buf[i]==IO_ERROR || buf[i]==IO_EOF)break; i++; } buf[i]='\0'; --- 388,397 ---- int i=0; while(i<len){ buf[i]=netbuf_getc(sn->inbuf); ! if(buf[i]==IO_ERROR || buf[i]==IO_EOF){ ! buf[i]='\0'; ! return FAILURE; ! } i++; } buf[i]='\0'; *************** *** 424,430 **** SAPI_FUNC_VARS; #ifdef DEBUG ! DEBUG_MESSAGE("enter sapi_print_info\n"); #endif PUTS("<center><h2>NSAPI Environment</h2></center>"); --- 423,429 ---- SAPI_FUNC_VARS; #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("enter sapi_print_info\n"); #endif PUTS("<center><h2>NSAPI Environment</h2></center>"); *************** *** 446,452 **** PUTS(SAPI_GLOBAL_PASS "</table>\n"); #ifdef DEBUG ! DEBUG_MESSAGE("exit sapi_print_info\n"); #endif } --- 445,451 ---- PUTS(SAPI_GLOBAL_PASS "</table>\n"); #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("exit sapi_print_info\n"); #endif } *************** *** 483,489 **** int l = 0; #ifdef DEBUG ! DEBUG_MESSAGE("enter sapi__php3_hash_server_env\n"); #endif phpsn = sapi_info->script_name; pi = sapi_info->path_info; --- 482,488 ---- int l = 0; #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("enter sapi__php3_hash_server_env\n"); #endif phpsn = sapi_info->script_name; pi = sapi_info->path_info; *************** *** 500,506 **** tmp.type = IS_STRING; _php3_hash_update(&php3_globals->symbol_table, "PHP_SELF", sizeof("PHP_SELF"), (void *) & tmp, sizeof(pval), NULL); #ifdef DEBUG ! DEBUG_MESSAGE("exit sapi__php3_hash_server_env\n"); #endif } --- 499,505 ---- tmp.type = IS_STRING; _php3_hash_update(&php3_globals->symbol_table, "PHP_SELF", sizeof("PHP_SELF"), (void *) & tmp, sizeof(pval), NULL); #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("exit sapi__php3_hash_server_env\n"); #endif } *************** *** 556,561 **** --- 555,562 ---- sapi_info->insert_header = sapi_insert_header; sapi_info->send_header = sapi_send_header; sapi_info->headers = NULL; + sapi_info->httpstatus = NULL; + sapi_info->http_redirect=0; sapi_info->_php3_hash_server_env = sapi__php3_hash_server_env; sapi_info->register_clean = sapi_reqister_cleanup; *************** *** 585,591 **** //do we use dllmain or this? What about thread start/end? //_php3_sapi_process_end(); #ifdef DEBUG ! DEBUG_MESSAGE("close_nsapi_mod"); #endif } --- 586,592 ---- //do we use dllmain or this? What about thread start/end? //_php3_sapi_process_end(); #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("close_nsapi_mod"); #endif } *************** *** 597,603 **** //_php3_sapi_process_start(module_info); daemon_atrestart(close_nsapi_mod, NULL); #ifdef DEBUG ! DEBUG_MESSAGE("init_nsapi_mod"); #endif return REQ_PROCEED; } --- 598,604 ---- //_php3_sapi_process_start(module_info); daemon_atrestart(close_nsapi_mod, NULL); #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("init_nsapi_mod"); #endif return REQ_PROCEED; } *************** *** 609,628 **** struct sapi_request_info *sapi_info; int ret; #ifdef DEBUG ! DEBUG_MESSAGE("nsapi entry\n"); #endif ns_rqst = malloc(sizeof(struct ns_request_info)); sapi_info = malloc(sizeof(struct sapi_request_info)); #ifdef DEBUG ! DEBUG_MESSAGE("memory alloced\n"); #endif // protocol_status(sn,rq,PROTOCOL_OK,NULL); // if(protocol_start_response(sn,rq)==REQ_NOACTION)return REQ_PROCEED; #ifdef DEBUG ! DEBUG_MESSAGE("response started\n"); #endif ns_rqst->pb = pb; ns_rqst->sn = sn; --- 610,629 ---- struct sapi_request_info *sapi_info; int ret; #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("nsapi entry\n"); #endif ns_rqst = malloc(sizeof(struct ns_request_info)); sapi_info = malloc(sizeof(struct sapi_request_info)); #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("memory alloced\n"); #endif // protocol_status(sn,rq,PROTOCOL_OK,NULL); // if(protocol_start_response(sn,rq)==REQ_NOACTION)return REQ_PROCEED; #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("response started\n"); #endif ns_rqst->pb = pb; ns_rqst->sn = sn; *************** *** 632,642 **** // char *cookiename=pblock_findval("cookiename", cookie_recv); #ifdef DEBUG ! DEBUG_MESSAGE("nsapi starting sapi init\n"); #endif sapi_init(ns_rqst, sapi_info); #ifdef DEBUG ! DEBUG_MESSAGE("nsapi sapi init done"); #endif ret=php3_sapi_main(sapi_info, module_info); --- 633,643 ---- // char *cookiename=pblock_findval("cookiename", cookie_recv); #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("nsapi starting sapi init\n"); #endif sapi_init(ns_rqst, sapi_info); #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("nsapi sapi init done"); #endif ret=php3_sapi_main(sapi_info, module_info); *************** *** 645,656 **** free(ns_rqst); if (ret == SUCCESS){ #ifdef DEBUG ! DEBUG_MESSAGE("nsapi success"); #endif return REQ_PROCEED; /*SUCCESS */ } else { #ifdef DEBUG ! DEBUG_MESSAGE("nsapi failed"); #endif return REQ_ABORTED; /*FAILURE */ } --- 646,657 ---- free(ns_rqst); if (ret == SUCCESS){ #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("nsapi success"); #endif return REQ_PROCEED; /*SUCCESS */ } else { #ifdef DEBUG ! NSAPI_DEBUG_MESSAGE("nsapi failed"); #endif return REQ_ABORTED; /*FAILURE */ } *************** *** 670,695 **** switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: return _php3_sapi_process_start(module_info); - #ifdef DEBUG - DEBUG_MESSAGE("nsapi DLL_PROCESS_ATTACH"); - #endif break; case DLL_THREAD_ATTACH: - #ifdef DEBUG - DEBUG_MESSAGE("nsapi DLL_THREAD_ATTACH"); - #endif return _php3_sapi_thread_start(module_info); break; case DLL_THREAD_DETACH: - #ifdef DEBUG - DEBUG_MESSAGE("nsapi DLL_THREAD_DETACH"); - #endif return _php3_sapi_thread_end(); break; case DLL_PROCESS_DETACH: - #ifdef DEBUG - DEBUG_MESSAGE("nsapi DLL_PROCESS_DETACH"); - #endif return _php3_sapi_process_end(); break; } --- 671,684 ---- Index: php31/sapi/sapi.h diff -c php31/sapi/sapi.h:2.13 php31/sapi/sapi.h:2.14 *** php31/sapi/sapi.h:2.13 Sun Aug 23 02:29:14 1998 --- php31/sapi/sapi.h Tue Aug 25 14:35:54 1998 *************** *** 61,67 **** /*handling headers */ unsigned char *headers; /*pointer to header list */ ! unsigned char *http_status; /* such as 401 Access Denied, needed for isapi */ int http_redirect; /*used to signal redirect for isapi */ int (*insert_header) (SAPI_GLOBAL_INCLUDE unsigned char *); /*insert header into a list */ int (*send_header) (SAPI_GLOBAL_INCLUDE_V); /*write headers to client */ --- 61,67 ---- /*handling headers */ unsigned char *headers; /*pointer to header list */ ! unsigned char *httpstatus; /* such as 401 Access Denied, needed for isapi */ int http_redirect; /*used to signal redirect for isapi */ int (*insert_header) (SAPI_GLOBAL_INCLUDE unsigned char *); /*insert header into a list */ int (*send_header) (SAPI_GLOBAL_INCLUDE_V); /*write headers to client */ -- PHP Development Mailing List http://www.php.net/ To unsubscribe send an empty message to php-dev-unsubscribe@lists.php.net For help: php-dev-help@lists.php.net

« previous php.dev (#568) next »