Fossil SCM
worked around a weird cgi_parameter() bug. We are now not processing the name/password params with the precedence i would like, but it works now in server/cgi modes with GET and POST.
Commit
b0885e864c3a4f9242a11cb139ef4642499b4cd2
Parent
7e3902d14f3e31b…
1 file changed
+86
-41
+86
-41
| --- src/json.c | ||
| +++ src/json.c | ||
| @@ -53,15 +53,18 @@ | ||
| 53 | 53 | FSL_JSON_E_ASSERT = FSL_JSON_E_GENERIC + 6, |
| 54 | 54 | FSL_JSON_E_ALLOC = FSL_JSON_E_GENERIC + 7, |
| 55 | 55 | FSL_JSON_E_NYI = FSL_JSON_E_GENERIC + 8, |
| 56 | 56 | |
| 57 | 57 | FSL_JSON_E_AUTH = 2000, |
| 58 | -FSL_JSON_E_LOGIN_FAILED = FSL_JSON_E_AUTH + 1, | |
| 59 | 58 | FSL_JSON_E_MISSING_AUTH = FSL_JSON_E_AUTH + 2, |
| 60 | 59 | FSL_JSON_E_DENIED = FSL_JSON_E_AUTH + 3, |
| 61 | 60 | FSL_JSON_E_WRONG_MODE = FSL_JSON_E_AUTH + 4, |
| 62 | 61 | |
| 62 | +FSL_JSON_E_LOGIN_FAILED = FSL_JSON_E_AUTH + 100, | |
| 63 | +FSL_JSON_E_LOGIN_FAILED_NONAME = FSL_JSON_E_LOGIN_FAILED + 1, | |
| 64 | +FSL_JSON_E_LOGIN_FAILED_NOPW = FSL_JSON_E_LOGIN_FAILED + 2, | |
| 65 | +FSL_JSON_E_LOGIN_FAILED_NOTFOUND = FSL_JSON_E_LOGIN_FAILED + 3, | |
| 63 | 66 | |
| 64 | 67 | FSL_JSON_E_USAGE = 3000, |
| 65 | 68 | FSL_JSON_E_INVALID_ARGS = FSL_JSON_E_USAGE + 1, |
| 66 | 69 | FSL_JSON_E_MISSING_ARGS = FSL_JSON_E_USAGE + 2, |
| 67 | 70 | |
| @@ -103,10 +106,13 @@ | ||
| 103 | 106 | C(ASSERT,"Assertion failed"); |
| 104 | 107 | C(ALLOC,"Resource allocation failed"); |
| 105 | 108 | C(NYI,"Not yet implemented."); |
| 106 | 109 | C(AUTH,"Authentication error"); |
| 107 | 110 | C(LOGIN_FAILED,"Login failed"); |
| 111 | + C(LOGIN_FAILED_NONAME,"Login failed - name not supplied"); | |
| 112 | + C(LOGIN_FAILED_NOPW,"Login failed - password not supplied"); | |
| 113 | + C(LOGIN_FAILED_NOTFOUND,"Login failed - no match found"); | |
| 108 | 114 | C(MISSING_AUTH,"Authentication info missing from request"); |
| 109 | 115 | C(DENIED,"Access denied"); |
| 110 | 116 | C(WRONG_MODE,"Request not allowed (wrong operation mode)"); |
| 111 | 117 | |
| 112 | 118 | C(USAGE,"Usage error"); |
| @@ -499,10 +505,16 @@ | ||
| 499 | 505 | if( modulo ) code = code - (code % modulo); |
| 500 | 506 | return code; |
| 501 | 507 | } |
| 502 | 508 | } |
| 503 | 509 | |
| 510 | +#if 0 | |
| 511 | +static unsigned int json_timestamp(){ | |
| 512 | + | |
| 513 | +} | |
| 514 | +#endif | |
| 515 | + | |
| 504 | 516 | /* |
| 505 | 517 | ** Creates a new Fossil/REST response envelope skeleton. It is owned |
| 506 | 518 | ** by the caller, who must eventually free it using cson_value_free(), |
| 507 | 519 | ** or add it to a cson container to transfer ownership. Returns NULL |
| 508 | 520 | ** on error. |
| @@ -664,16 +676,25 @@ | ||
| 664 | 676 | cson_object_set( jobj, "releaseVersionNumber", |
| 665 | 677 | cson_value_new_integer(RELEASE_VERSION_NUMBER) ); |
| 666 | 678 | return jval; |
| 667 | 679 | } |
| 668 | 680 | |
| 681 | +#if 0 | |
| 682 | +/* we have a disconnect here between fossil's server-mode QUERY_STRING | |
| 683 | + handling and cson_cgi's. | |
| 684 | +*/ | |
| 669 | 685 | static cson_value * json_getenv( char const *zWhichEnv, char const * zKey ){ |
| 670 | 686 | return cson_cgi_getenv(&g.json.cgiCx, zWhichEnv, zKey); |
| 671 | 687 | } |
| 672 | 688 | static char const * json_getenv_cstr( char const *zWhichEnv, char const * zKey ){ |
| 673 | - return cson_value_get_cstr( json_getenv(zWhichEnv, zKey) ); | |
| 689 | + char const * rc = cson_value_get_cstr( json_getenv(zWhichEnv, zKey) ); | |
| 690 | + if( !rc && zWhichEnv && (NULL!=strstr(zWhichEnv,"g")) ){ | |
| 691 | + rc = PD(zKey,NULL); | |
| 692 | + } | |
| 693 | + return rc; | |
| 674 | 694 | } |
| 695 | +#endif | |
| 675 | 696 | |
| 676 | 697 | /* |
| 677 | 698 | ** Implementation for /json/cap |
| 678 | 699 | ** |
| 679 | 700 | ** Returned object contains details about the "capabilities" of the |
| @@ -738,55 +759,79 @@ | ||
| 738 | 759 | ** TODOs: |
| 739 | 760 | ** |
| 740 | 761 | ** - anonymous user login (requires separate handling |
| 741 | 762 | ** due to random password). |
| 742 | 763 | ** |
| 743 | -** - more testing | |
| 764 | +** - more testing with ONLY the JSON-specified authToken | |
| 765 | +** (no cookie). In theory that works but we don't yet have | |
| 766 | +** a non-browser client to play with. | |
| 767 | +** | |
| 744 | 768 | */ |
| 745 | 769 | cson_value * json_page_login(void){ |
| 746 | - char const * name = cson_value_get_cstr(json_payload_property("name")); | |
| 747 | - char const * pw; | |
| 748 | - cson_value * payload = NULL; | |
| 749 | - /*cson_object * pObj;*/ | |
| 750 | - int uid = 0; | |
| 751 | - /*return cson_cgi_env_get_val(&g.json.cgiCx,'g',0);*/ | |
| 752 | - | |
| 753 | - if( !name ){ | |
| 754 | - name = json_getenv_cstr( "g", "name" ) | |
| 755 | - /* When i use P("n") i'm not getting the result i expect! */ | |
| 756 | - ; | |
| 757 | - if( !name ){ | |
| 758 | - name = json_getenv_cstr( "g", "n" ); | |
| 759 | - } | |
| 760 | - } | |
| 761 | - if( !name ){ | |
| 762 | - g.json.resultCode = FSL_JSON_E_LOGIN_FAILED; | |
| 763 | - return NULL; | |
| 770 | + /* | |
| 771 | + FIXME: we want to check the GET/POST args in this order: | |
| 772 | + | |
| 773 | + - GET: name, n, password, p | |
| 774 | + - POST: name, password | |
| 775 | + | |
| 776 | + but a bug in cgi_parameter() is breaking that, causing PD() to | |
| 777 | + return the last element of the PATH_INFO instead. | |
| 778 | + | |
| 779 | + Summary: If we check for P("name") first, then P("n"), | |
| 780 | + then ONLY a GET param of "name" will match ("n" | |
| 781 | + is not recognized). If we reverse the order of the | |
| 782 | + checks then both forms work. Strangely enough, the | |
| 783 | + "p"/"password" check is not affected by this. | |
| 784 | + */ | |
| 785 | + char const * name = cson_value_get_cstr(json_payload_property("name")); | |
| 786 | + char const * pw = NULL; | |
| 787 | + if( !name ){ | |
| 788 | + name = PD("n",NULL); | |
| 789 | + if( !name ){ | |
| 790 | + name = PD("name",NULL); | |
| 791 | + if( !name ){ | |
| 792 | + g.json.resultCode = FSL_JSON_E_LOGIN_FAILED_NONAME; | |
| 793 | + return NULL; | |
| 794 | + } | |
| 795 | + } | |
| 764 | 796 | } |
| 765 | 797 | |
| 766 | 798 | pw = cson_value_get_cstr(json_payload_property("password")); |
| 767 | 799 | if( !pw ){ |
| 768 | - pw = json_getenv_cstr( "g", "password" ); | |
| 769 | - if( !pw ){ | |
| 770 | - pw = json_getenv_cstr( "g", "p" ); | |
| 771 | - } | |
| 772 | - } | |
| 773 | - | |
| 774 | - if(!pw){ | |
| 775 | - g.json.resultCode = FSL_JSON_E_LOGIN_FAILED; | |
| 776 | - }else{ | |
| 777 | - uid = login_search_uid( name, pw ); | |
| 778 | - } | |
| 779 | - if( !uid ){ | |
| 780 | - g.json.resultCode = FSL_JSON_E_LOGIN_FAILED; | |
| 781 | - }else{ | |
| 782 | - char * cookie = NULL; | |
| 783 | - login_set_user_cookie(name, uid, &cookie); | |
| 784 | - payload = cson_value_new_string( cookie, strlen(cookie) ); | |
| 785 | - free(cookie); | |
| 786 | - } | |
| 787 | - return payload; | |
| 800 | + pw = PD("p",NULL); | |
| 801 | + if( !pw ){ | |
| 802 | + pw = PD("password",NULL); | |
| 803 | + } | |
| 804 | + } | |
| 805 | + if(!pw){ | |
| 806 | + g.json.resultCode = FSL_JSON_E_LOGIN_FAILED_NOPW; | |
| 807 | + return NULL; | |
| 808 | + }else{ | |
| 809 | + cson_value * payload = NULL; | |
| 810 | + int uid = 0; | |
| 811 | +#if 0 | |
| 812 | + /* only for debugging the PD()-incorrect-result problem */ | |
| 813 | + cson_object * o = NULL; | |
| 814 | + uid = login_search_uid( name, pw ); | |
| 815 | + payload = cson_value_new_object(); | |
| 816 | + o = cson_value_get_object(payload); | |
| 817 | + cson_object_set( o, "n", cson_value_new_string(name,strlen(name))); | |
| 818 | + cson_object_set( o, "p", cson_value_new_string(pw,strlen(pw))); | |
| 819 | + return payload; | |
| 820 | +#else | |
| 821 | + uid = login_search_uid( name, pw ); | |
| 822 | + if( !uid ){ | |
| 823 | + g.json.resultCode = FSL_JSON_E_LOGIN_FAILED_NOTFOUND; | |
| 824 | + }else{ | |
| 825 | + char * cookie = NULL; | |
| 826 | + login_set_user_cookie(name, uid, &cookie); | |
| 827 | + payload = cson_value_new_string( cookie, strlen(cookie) ); | |
| 828 | + free(cookie); | |
| 829 | + } | |
| 830 | + return payload; | |
| 831 | +#endif | |
| 832 | + } | |
| 788 | 833 | } |
| 789 | 834 | |
| 790 | 835 | /* |
| 791 | 836 | ** Impl of /json/logout. |
| 792 | 837 | ** |
| 793 | 838 |
| --- src/json.c | |
| +++ src/json.c | |
| @@ -53,15 +53,18 @@ | |
| 53 | FSL_JSON_E_ASSERT = FSL_JSON_E_GENERIC + 6, |
| 54 | FSL_JSON_E_ALLOC = FSL_JSON_E_GENERIC + 7, |
| 55 | FSL_JSON_E_NYI = FSL_JSON_E_GENERIC + 8, |
| 56 | |
| 57 | FSL_JSON_E_AUTH = 2000, |
| 58 | FSL_JSON_E_LOGIN_FAILED = FSL_JSON_E_AUTH + 1, |
| 59 | FSL_JSON_E_MISSING_AUTH = FSL_JSON_E_AUTH + 2, |
| 60 | FSL_JSON_E_DENIED = FSL_JSON_E_AUTH + 3, |
| 61 | FSL_JSON_E_WRONG_MODE = FSL_JSON_E_AUTH + 4, |
| 62 | |
| 63 | |
| 64 | FSL_JSON_E_USAGE = 3000, |
| 65 | FSL_JSON_E_INVALID_ARGS = FSL_JSON_E_USAGE + 1, |
| 66 | FSL_JSON_E_MISSING_ARGS = FSL_JSON_E_USAGE + 2, |
| 67 | |
| @@ -103,10 +106,13 @@ | |
| 103 | C(ASSERT,"Assertion failed"); |
| 104 | C(ALLOC,"Resource allocation failed"); |
| 105 | C(NYI,"Not yet implemented."); |
| 106 | C(AUTH,"Authentication error"); |
| 107 | C(LOGIN_FAILED,"Login failed"); |
| 108 | C(MISSING_AUTH,"Authentication info missing from request"); |
| 109 | C(DENIED,"Access denied"); |
| 110 | C(WRONG_MODE,"Request not allowed (wrong operation mode)"); |
| 111 | |
| 112 | C(USAGE,"Usage error"); |
| @@ -499,10 +505,16 @@ | |
| 499 | if( modulo ) code = code - (code % modulo); |
| 500 | return code; |
| 501 | } |
| 502 | } |
| 503 | |
| 504 | /* |
| 505 | ** Creates a new Fossil/REST response envelope skeleton. It is owned |
| 506 | ** by the caller, who must eventually free it using cson_value_free(), |
| 507 | ** or add it to a cson container to transfer ownership. Returns NULL |
| 508 | ** on error. |
| @@ -664,16 +676,25 @@ | |
| 664 | cson_object_set( jobj, "releaseVersionNumber", |
| 665 | cson_value_new_integer(RELEASE_VERSION_NUMBER) ); |
| 666 | return jval; |
| 667 | } |
| 668 | |
| 669 | static cson_value * json_getenv( char const *zWhichEnv, char const * zKey ){ |
| 670 | return cson_cgi_getenv(&g.json.cgiCx, zWhichEnv, zKey); |
| 671 | } |
| 672 | static char const * json_getenv_cstr( char const *zWhichEnv, char const * zKey ){ |
| 673 | return cson_value_get_cstr( json_getenv(zWhichEnv, zKey) ); |
| 674 | } |
| 675 | |
| 676 | /* |
| 677 | ** Implementation for /json/cap |
| 678 | ** |
| 679 | ** Returned object contains details about the "capabilities" of the |
| @@ -738,55 +759,79 @@ | |
| 738 | ** TODOs: |
| 739 | ** |
| 740 | ** - anonymous user login (requires separate handling |
| 741 | ** due to random password). |
| 742 | ** |
| 743 | ** - more testing |
| 744 | */ |
| 745 | cson_value * json_page_login(void){ |
| 746 | char const * name = cson_value_get_cstr(json_payload_property("name")); |
| 747 | char const * pw; |
| 748 | cson_value * payload = NULL; |
| 749 | /*cson_object * pObj;*/ |
| 750 | int uid = 0; |
| 751 | /*return cson_cgi_env_get_val(&g.json.cgiCx,'g',0);*/ |
| 752 | |
| 753 | if( !name ){ |
| 754 | name = json_getenv_cstr( "g", "name" ) |
| 755 | /* When i use P("n") i'm not getting the result i expect! */ |
| 756 | ; |
| 757 | if( !name ){ |
| 758 | name = json_getenv_cstr( "g", "n" ); |
| 759 | } |
| 760 | } |
| 761 | if( !name ){ |
| 762 | g.json.resultCode = FSL_JSON_E_LOGIN_FAILED; |
| 763 | return NULL; |
| 764 | } |
| 765 | |
| 766 | pw = cson_value_get_cstr(json_payload_property("password")); |
| 767 | if( !pw ){ |
| 768 | pw = json_getenv_cstr( "g", "password" ); |
| 769 | if( !pw ){ |
| 770 | pw = json_getenv_cstr( "g", "p" ); |
| 771 | } |
| 772 | } |
| 773 | |
| 774 | if(!pw){ |
| 775 | g.json.resultCode = FSL_JSON_E_LOGIN_FAILED; |
| 776 | }else{ |
| 777 | uid = login_search_uid( name, pw ); |
| 778 | } |
| 779 | if( !uid ){ |
| 780 | g.json.resultCode = FSL_JSON_E_LOGIN_FAILED; |
| 781 | }else{ |
| 782 | char * cookie = NULL; |
| 783 | login_set_user_cookie(name, uid, &cookie); |
| 784 | payload = cson_value_new_string( cookie, strlen(cookie) ); |
| 785 | free(cookie); |
| 786 | } |
| 787 | return payload; |
| 788 | } |
| 789 | |
| 790 | /* |
| 791 | ** Impl of /json/logout. |
| 792 | ** |
| 793 |
| --- src/json.c | |
| +++ src/json.c | |
| @@ -53,15 +53,18 @@ | |
| 53 | FSL_JSON_E_ASSERT = FSL_JSON_E_GENERIC + 6, |
| 54 | FSL_JSON_E_ALLOC = FSL_JSON_E_GENERIC + 7, |
| 55 | FSL_JSON_E_NYI = FSL_JSON_E_GENERIC + 8, |
| 56 | |
| 57 | FSL_JSON_E_AUTH = 2000, |
| 58 | FSL_JSON_E_MISSING_AUTH = FSL_JSON_E_AUTH + 2, |
| 59 | FSL_JSON_E_DENIED = FSL_JSON_E_AUTH + 3, |
| 60 | FSL_JSON_E_WRONG_MODE = FSL_JSON_E_AUTH + 4, |
| 61 | |
| 62 | FSL_JSON_E_LOGIN_FAILED = FSL_JSON_E_AUTH + 100, |
| 63 | FSL_JSON_E_LOGIN_FAILED_NONAME = FSL_JSON_E_LOGIN_FAILED + 1, |
| 64 | FSL_JSON_E_LOGIN_FAILED_NOPW = FSL_JSON_E_LOGIN_FAILED + 2, |
| 65 | FSL_JSON_E_LOGIN_FAILED_NOTFOUND = FSL_JSON_E_LOGIN_FAILED + 3, |
| 66 | |
| 67 | FSL_JSON_E_USAGE = 3000, |
| 68 | FSL_JSON_E_INVALID_ARGS = FSL_JSON_E_USAGE + 1, |
| 69 | FSL_JSON_E_MISSING_ARGS = FSL_JSON_E_USAGE + 2, |
| 70 | |
| @@ -103,10 +106,13 @@ | |
| 106 | C(ASSERT,"Assertion failed"); |
| 107 | C(ALLOC,"Resource allocation failed"); |
| 108 | C(NYI,"Not yet implemented."); |
| 109 | C(AUTH,"Authentication error"); |
| 110 | C(LOGIN_FAILED,"Login failed"); |
| 111 | C(LOGIN_FAILED_NONAME,"Login failed - name not supplied"); |
| 112 | C(LOGIN_FAILED_NOPW,"Login failed - password not supplied"); |
| 113 | C(LOGIN_FAILED_NOTFOUND,"Login failed - no match found"); |
| 114 | C(MISSING_AUTH,"Authentication info missing from request"); |
| 115 | C(DENIED,"Access denied"); |
| 116 | C(WRONG_MODE,"Request not allowed (wrong operation mode)"); |
| 117 | |
| 118 | C(USAGE,"Usage error"); |
| @@ -499,10 +505,16 @@ | |
| 505 | if( modulo ) code = code - (code % modulo); |
| 506 | return code; |
| 507 | } |
| 508 | } |
| 509 | |
| 510 | #if 0 |
| 511 | static unsigned int json_timestamp(){ |
| 512 | |
| 513 | } |
| 514 | #endif |
| 515 | |
| 516 | /* |
| 517 | ** Creates a new Fossil/REST response envelope skeleton. It is owned |
| 518 | ** by the caller, who must eventually free it using cson_value_free(), |
| 519 | ** or add it to a cson container to transfer ownership. Returns NULL |
| 520 | ** on error. |
| @@ -664,16 +676,25 @@ | |
| 676 | cson_object_set( jobj, "releaseVersionNumber", |
| 677 | cson_value_new_integer(RELEASE_VERSION_NUMBER) ); |
| 678 | return jval; |
| 679 | } |
| 680 | |
| 681 | #if 0 |
| 682 | /* we have a disconnect here between fossil's server-mode QUERY_STRING |
| 683 | handling and cson_cgi's. |
| 684 | */ |
| 685 | static cson_value * json_getenv( char const *zWhichEnv, char const * zKey ){ |
| 686 | return cson_cgi_getenv(&g.json.cgiCx, zWhichEnv, zKey); |
| 687 | } |
| 688 | static char const * json_getenv_cstr( char const *zWhichEnv, char const * zKey ){ |
| 689 | char const * rc = cson_value_get_cstr( json_getenv(zWhichEnv, zKey) ); |
| 690 | if( !rc && zWhichEnv && (NULL!=strstr(zWhichEnv,"g")) ){ |
| 691 | rc = PD(zKey,NULL); |
| 692 | } |
| 693 | return rc; |
| 694 | } |
| 695 | #endif |
| 696 | |
| 697 | /* |
| 698 | ** Implementation for /json/cap |
| 699 | ** |
| 700 | ** Returned object contains details about the "capabilities" of the |
| @@ -738,55 +759,79 @@ | |
| 759 | ** TODOs: |
| 760 | ** |
| 761 | ** - anonymous user login (requires separate handling |
| 762 | ** due to random password). |
| 763 | ** |
| 764 | ** - more testing with ONLY the JSON-specified authToken |
| 765 | ** (no cookie). In theory that works but we don't yet have |
| 766 | ** a non-browser client to play with. |
| 767 | ** |
| 768 | */ |
| 769 | cson_value * json_page_login(void){ |
| 770 | /* |
| 771 | FIXME: we want to check the GET/POST args in this order: |
| 772 | |
| 773 | - GET: name, n, password, p |
| 774 | - POST: name, password |
| 775 | |
| 776 | but a bug in cgi_parameter() is breaking that, causing PD() to |
| 777 | return the last element of the PATH_INFO instead. |
| 778 | |
| 779 | Summary: If we check for P("name") first, then P("n"), |
| 780 | then ONLY a GET param of "name" will match ("n" |
| 781 | is not recognized). If we reverse the order of the |
| 782 | checks then both forms work. Strangely enough, the |
| 783 | "p"/"password" check is not affected by this. |
| 784 | */ |
| 785 | char const * name = cson_value_get_cstr(json_payload_property("name")); |
| 786 | char const * pw = NULL; |
| 787 | if( !name ){ |
| 788 | name = PD("n",NULL); |
| 789 | if( !name ){ |
| 790 | name = PD("name",NULL); |
| 791 | if( !name ){ |
| 792 | g.json.resultCode = FSL_JSON_E_LOGIN_FAILED_NONAME; |
| 793 | return NULL; |
| 794 | } |
| 795 | } |
| 796 | } |
| 797 | |
| 798 | pw = cson_value_get_cstr(json_payload_property("password")); |
| 799 | if( !pw ){ |
| 800 | pw = PD("p",NULL); |
| 801 | if( !pw ){ |
| 802 | pw = PD("password",NULL); |
| 803 | } |
| 804 | } |
| 805 | if(!pw){ |
| 806 | g.json.resultCode = FSL_JSON_E_LOGIN_FAILED_NOPW; |
| 807 | return NULL; |
| 808 | }else{ |
| 809 | cson_value * payload = NULL; |
| 810 | int uid = 0; |
| 811 | #if 0 |
| 812 | /* only for debugging the PD()-incorrect-result problem */ |
| 813 | cson_object * o = NULL; |
| 814 | uid = login_search_uid( name, pw ); |
| 815 | payload = cson_value_new_object(); |
| 816 | o = cson_value_get_object(payload); |
| 817 | cson_object_set( o, "n", cson_value_new_string(name,strlen(name))); |
| 818 | cson_object_set( o, "p", cson_value_new_string(pw,strlen(pw))); |
| 819 | return payload; |
| 820 | #else |
| 821 | uid = login_search_uid( name, pw ); |
| 822 | if( !uid ){ |
| 823 | g.json.resultCode = FSL_JSON_E_LOGIN_FAILED_NOTFOUND; |
| 824 | }else{ |
| 825 | char * cookie = NULL; |
| 826 | login_set_user_cookie(name, uid, &cookie); |
| 827 | payload = cson_value_new_string( cookie, strlen(cookie) ); |
| 828 | free(cookie); |
| 829 | } |
| 830 | return payload; |
| 831 | #endif |
| 832 | } |
| 833 | } |
| 834 | |
| 835 | /* |
| 836 | ** Impl of /json/logout. |
| 837 | ** |
| 838 |