Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Initial code to implement synchronization via ssh. |
|---|---|
| Timelines: | family | ancestors | descendants | both | experimental |
| Files: | files | file ages | folders |
| SHA1: |
b19f25fe87273e1666255d9a6053020f |
| User & Date: | drh 2010-08-25 14:03:01.000 |
Context
|
2010-08-25
| ||
| 16:03 | Continuing work on the ssh:// sync protocol. check-in: 958f596637 user: drh tags: experimental | |
| 14:03 | Initial code to implement synchronization via ssh. check-in: b19f25fe87 user: drh tags: experimental | |
|
2010-08-24
| ||
| 01:24 | Fix a potential sigfault that can occur in the graph generator if the child is older than its parent. check-in: 7503f98779 user: drh tags: trunk | |
Changes
Changes to src/cgi.c.
| ︙ | ︙ | |||
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 |
cgi_set_status(501, "Not Implemented");
cgi_printf(
"<html><body>Unrecognized HTTP Request</body></html>\n"
);
cgi_reply();
fossil_exit(0);
}
/*
** Panic and die while processing a webpage.
*/
void cgi_panic(const char *zFormat, ...){
va_list ap;
cgi_reset_content();
| > > > > > > > > > > > > | 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 |
cgi_set_status(501, "Not Implemented");
cgi_printf(
"<html><body>Unrecognized HTTP Request</body></html>\n"
);
cgi_reply();
fossil_exit(0);
}
/*
** Send a reply indicating that the HTTP request is forbidden
*/
static void forbidden_request(void){
cgi_set_status(403, "Forbidden");
cgi_printf(
"<html><body>Access Denied</body></html>\n"
);
cgi_reply();
fossil_exit(0);
}
/*
** Panic and die while processing a webpage.
*/
void cgi_panic(const char *zFormat, ...){
va_list ap;
cgi_reset_content();
|
| ︙ | ︙ | |||
1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 |
** and subsequent code handles the actual generation of the webpage.
*/
void cgi_handle_http_request(const char *zIpAddr){
char *z, *zToken;
int i;
struct sockaddr_in remoteName;
size_t size = sizeof(struct sockaddr_in);
char zLine[2000]; /* A single line of input. */
g.fullHttpReply = 1;
if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
malformed_request();
}
zToken = extract_token(zLine, &z);
| > | 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 |
** and subsequent code handles the actual generation of the webpage.
*/
void cgi_handle_http_request(const char *zIpAddr){
char *z, *zToken;
int i;
struct sockaddr_in remoteName;
size_t size = sizeof(struct sockaddr_in);
int accessTokenSeen = 0;
char zLine[2000]; /* A single line of input. */
g.fullHttpReply = 1;
if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
malformed_request();
}
zToken = extract_token(zLine, &z);
|
| ︙ | ︙ | |||
1120 1121 1122 1123 1124 1125 1126 |
zFieldName = extract_token(zLine,&zVal);
if( zFieldName==0 || *zFieldName==0 ) break;
while( isspace(*zVal) ){ zVal++; }
i = strlen(zVal);
while( i>0 && isspace(zVal[i-1]) ){ i--; }
zVal[i] = 0;
for(i=0; zFieldName[i]; i++){ zFieldName[i] = tolower(zFieldName[i]); }
| < < | < < < < > > > > > > > > > > > > > > | | > > > > > | 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 |
zFieldName = extract_token(zLine,&zVal);
if( zFieldName==0 || *zFieldName==0 ) break;
while( isspace(*zVal) ){ zVal++; }
i = strlen(zVal);
while( i>0 && isspace(zVal[i-1]) ){ i--; }
zVal[i] = 0;
for(i=0; zFieldName[i]; i++){ zFieldName[i] = tolower(zFieldName[i]); }
if( strcmp(zFieldName,"content-length:")==0 ){
cgi_setenv("CONTENT_LENGTH", zVal);
}else if( strcmp(zFieldName,"content-type:")==0 ){
cgi_setenv("CONTENT_TYPE", zVal);
}else if( strcmp(zFieldName,"cookie:")==0 ){
cgi_setenv("HTTP_COOKIE", zVal);
}else if( strcmp(zFieldName,"https:")==0 ){
cgi_setenv("HTTPS", zVal);
}else if( strcmp(zFieldName,"host:")==0 ){
cgi_setenv("HTTP_HOST", zVal);
}else if( strcmp(zFieldName,"if-none-match:")==0 ){
cgi_setenv("HTTP_IF_NONE_MATCH", zVal);
}else if( strcmp(zFieldName,"if-modified-since:")==0 ){
cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal);
}else if( strcmp(zFieldName,"x-fossil-security-token:")==0 ){
if( g.zAccessToken ){
if( strcmp(zVal,g.zAccessToken)==0 ){
accessTokenSeen = 1;
}
}
}
#if 0
else if( strcmp(zFieldName,"referer:")==0 ){
cgi_setenv("HTTP_REFERER", zVal);
}else if( strcmp(zFieldName,"user-agent:")==0 ){
cgi_setenv("HTTP_USER_AGENT", zVal);
}
#endif
}
if( g.zAccessToken && !accessTokenSeen ){
forbidden_request();
}
cgi_init();
}
/*
** Maximum number of child processes that we can have running
|
| ︙ | ︙ |
Changes to src/http.c.
| ︙ | ︙ | |||
109 110 111 112 113 114 115 |
blob_appendf(pHdr, "Host: %s\r\n", g.urlHostname);
blob_appendf(pHdr, "User-Agent: Fossil/" MANIFEST_VERSION "\r\n");
if( g.fHttpTrace ){
blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n");
}else{
blob_appendf(pHdr, "Content-Type: application/x-fossil\r\n");
}
| | > > > > | 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
blob_appendf(pHdr, "Host: %s\r\n", g.urlHostname);
blob_appendf(pHdr, "User-Agent: Fossil/" MANIFEST_VERSION "\r\n");
if( g.fHttpTrace ){
blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n");
}else{
blob_appendf(pHdr, "Content-Type: application/x-fossil\r\n");
}
blob_appendf(pHdr, "Content-Length: %d\r\n", blob_size(pPayload));
if( g.zAccessToken ){
blob_appendf(pHdr, "X-Fossil-Access-Token: %s\r\n", g.zAccessToken);
}
blob_appendf(pHdr, "\r\n");
}
/*
** Sign the content in pSend, compress it, and send it to the server
** via HTTP or HTTPS. Get a reply, uncompress the reply, and store the reply
** in pRecv. pRecv is assumed to be uninitialized when
** this routine is called - this routine will initialize it.
|
| ︙ | ︙ |
Changes to src/http_transport.c.
| ︙ | ︙ | |||
61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
if( pnSent ) *pnSent = transport.nSent;
if( pnRcvd ) *pnRcvd = transport.nRcvd;
if( resetFlag ){
transport.nSent = 0;
transport.nRcvd = 0;
}
}
/*
** Open a connection to the server. The server is defined by the following
** global variables:
**
** g.urlName Name of the server. Ex: www.fossil-scm.org
** g.urlPort TCP/IP port. Ex: 80
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
if( pnSent ) *pnSent = transport.nSent;
if( pnRcvd ) *pnRcvd = transport.nRcvd;
if( resetFlag ){
transport.nSent = 0;
transport.nRcvd = 0;
}
}
/*
** Global initialization of the transport layer
*/
void transport_global_startup(void){
if( g.urlIsSsh ){
char *zCmd;
int i, j;
char zReply[200];
if( g.urlUser && g.urlUser[0] ){
zCmd = mprintf(
"ssh -L127.0.0.1:%d:127.0.0.1:%d %s@%s \"fossil sshd -P %d '%s'\"",
g.urlPort, g.urlPort, g.urlUser, g.urlSshHost, g.urlPort, g.urlPath
);
}else{
zCmd = mprintf(
"ssh -L127.0.0.1:%d:127.0.0.1:%d %s \"fossil sshd -P %d '%s'\"",
g.urlPort, g.urlPort, g.urlSshHost, g.urlPort, g.urlPath
);
}
printf("%s\n", zCmd);
g.sshIn = popen(zCmd, "r");
if( g.sshIn==0 ){
fossil_fatal("cannot start ssh tunnel using [%s]", zCmd);
}
free(zCmd);
zReply[0] = 0;
fgets(zReply, sizeof(zReply), g.sshIn);
if( zReply[0]==0 ){
pclose(g.sshIn);
fossil_fatal("unable to set up ssh tunnel");
}
if( memcmp(zReply, "Access-Token: ", 14)!=0 ){
pclose(g.sshIn);
fossil_fatal("ssh tunnel did not send back an access token");
}
for(i=14; isspace(zReply[i]); i++);
for(j=i; isalnum(zReply[j]); j++);
g.zAccessToken = mprintf("%.*s", j-i, &zReply[i]);
}
}
/*
** Open a connection to the server. The server is defined by the following
** global variables:
**
** g.urlName Name of the server. Ex: www.fossil-scm.org
** g.urlPort TCP/IP port. Ex: 80
|
| ︙ | ︙ | |||
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 |
i++;
}
/* printf("Got line: [%s]\n", &transport.pBuf[iStart]); */
return &transport.pBuf[iStart];
}
void transport_global_shutdown(void){
if( g.urlIsHttps ){
#ifdef FOSSIL_ENABLE_SSL
ssl_global_shutdown();
#endif
}else{
socket_global_shutdown();
}
}
| > > > > | 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
i++;
}
/* printf("Got line: [%s]\n", &transport.pBuf[iStart]); */
return &transport.pBuf[iStart];
}
void transport_global_shutdown(void){
if( g.urlIsSsh && g.sshIn ){
pclose(g.sshIn);
g.sshIn = 0;
}
if( g.urlIsHttps ){
#ifdef FOSSIL_ENABLE_SSL
ssl_global_shutdown();
#endif
}else{
socket_global_shutdown();
}
}
|
Changes to src/main.c.
| ︙ | ︙ | |||
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | Th_Interp *interp; /* The TH1 interpreter */ FILE *httpIn; /* Accept HTTP input from here */ FILE *httpOut; /* Send HTTP output here */ int xlinkClusterOnly; /* Set when cloning. Only process clusters */ int fTimeFormat; /* 1 for UTC. 2 for localtime. 0 not yet selected */ int *aCommitFile; /* Array of files to be committed */ int markPrivate; /* All new artifacts are private if true */ int urlIsFile; /* True if a "file:" url */ int urlIsHttps; /* True if a "https:" url */ char *urlName; /* Hostname for http: or filename for file: */ char *urlHostname; /* The HOST: parameter on http headers */ char *urlProtocol; /* "http" or "https" */ int urlPort; /* TCP port number for http: or https: */ int urlDfltPort; /* The default port for the given protocol */ char *urlPath; /* Pathname for http: */ char *urlUser; /* User id for http: */ char *urlPasswd; /* Password for http: */ char *urlCanonical; /* Canonical representation of the URL */ char *urlProxyAuth; /* Proxy-Authorizer: string */ int dontKeepUrl; /* Do not persist the URL */ | > > > > > | 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
Th_Interp *interp; /* The TH1 interpreter */
FILE *httpIn; /* Accept HTTP input from here */
FILE *httpOut; /* Send HTTP output here */
int xlinkClusterOnly; /* Set when cloning. Only process clusters */
int fTimeFormat; /* 1 for UTC. 2 for localtime. 0 not yet selected */
int *aCommitFile; /* Array of files to be committed */
int markPrivate; /* All new artifacts are private if true */
char *zAccessToken; /* X-Fossil-Access-Token HTTP header field */
FILE *sshIn; /* Result of popen("ssh") */
int urlIsFile; /* True if a "file:" url */
int urlIsHttps; /* True if a "https:" url */
int urlIsSsh; /* True if an "ssh:" url */
char *urlName; /* Hostname for http: or filename for file: */
char *urlSshHost; /* Hostname for ssh: tunnels */
char *urlHostname; /* The HOST: parameter on http headers */
char *urlProtocol; /* "http" or "https" */
int urlPort; /* TCP port number for http: or https: */
int urlDfltPort; /* The default port for the given protocol */
int urlSshPort; /* TCP port for SSH */
char *urlPath; /* Pathname for http: */
char *urlUser; /* User id for http: */
char *urlPasswd; /* Password for http: */
char *urlCanonical; /* Canonical representation of the URL */
char *urlProxyAuth; /* Proxy-Authorizer: string */
int dontKeepUrl; /* Do not persist the URL */
|
| ︙ | ︙ |
Changes to src/url.c.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
**
*******************************************************************************
**
** This file contains code for parsing URLs that appear on the command-line
*/
#include "config.h"
#include "url.h"
/*
** Parse the given URL. Populate variables in the global "g" structure.
**
** g.urlIsFile True if FILE:
** g.urlIsHttps True if HTTPS:
** g.urlProtocol "http" or "https" or "file"
** g.urlName Hostname for HTTP: or HTTPS:. Filename for FILE:
** g.urlPort TCP port number for HTTP or HTTPS.
** g.urlDfltPort Default TCP port number (80 or 443).
** g.urlPath Path name for HTTP or HTTPS.
** g.urlUser Userid.
** g.urlPasswd Password.
** g.urlHostname HOST:PORT or just HOST if port is the default.
** g.urlCanonical The URL in canonical form, omitting the password
**
** HTTP url format is:
**
** http://userid:password@host:port/path?query#fragment
**
*/
void url_parse(const char *zUrl){
int i, j, c;
char *zFile = 0;
if( strncmp(zUrl, "http://", 7)==0 || strncmp(zUrl, "https://", 8)==0 ){
int iStart;
char *zLogin;
| > > > > > > > > > > > > > > > > > | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
**
*******************************************************************************
**
** This file contains code for parsing URLs that appear on the command-line
*/
#include "config.h"
#include "url.h"
/*
** Convert a string to lower-case.
*/
static void url_tolower(char *z){
while( *z ){
*z = tolower(*z);
z++;
}
}
/*
** Parse the given URL. Populate variables in the global "g" structure.
**
** g.urlIsFile True if FILE:
** g.urlIsHttps True if HTTPS:
** g.urlIsSsh True if SSH:
** g.urlProtocol "http" or "https" or "file"
** g.urlName Hostname for HTTP: or HTTPS:. Filename for FILE:
** g.urlSshHost Hostname for SSH: tunnel
** g.urlPort TCP port number for HTTP or HTTPS.
** g.urlDfltPort Default TCP port number (80 or 443).
** g.urlSshPort TCP port for SSH: tunnel
** g.urlPath Path name for HTTP or HTTPS.
** g.urlUser Userid.
** g.urlPasswd Password.
** g.urlHostname HOST:PORT or just HOST if port is the default.
** g.urlCanonical The URL in canonical form, omitting the password
**
** HTTP url format is:
**
** http://userid:password@host:port/path?query#fragment
**
** SSH url format is:
**
** ssh://userid@host:port/fullpath
**
*/
void url_parse(const char *zUrl){
int i, j, c;
char *zFile = 0;
if( strncmp(zUrl, "http://", 7)==0 || strncmp(zUrl, "https://", 8)==0 ){
int iStart;
char *zLogin;
|
| ︙ | ︙ | |||
68 69 70 71 72 73 74 |
dehttpize(g.urlPasswd);
}
for(j=i+1; (c=zUrl[j])!=0 && c!='/' && c!=':'; j++){}
g.urlName = mprintf("%.*s", j-i-1, &zUrl[i+1]);
i = j;
zLogin = mprintf("%t@", g.urlUser);
}else{
| < | | 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
dehttpize(g.urlPasswd);
}
for(j=i+1; (c=zUrl[j])!=0 && c!='/' && c!=':'; j++){}
g.urlName = mprintf("%.*s", j-i-1, &zUrl[i+1]);
i = j;
zLogin = mprintf("%t@", g.urlUser);
}else{
g.urlName = mprintf("%.*s", i-iStart, &zUrl[iStart]);
zLogin = mprintf("");
}
url_tolower(g.urlName);
if( c==':' ){
g.urlPort = 0;
i++;
while( (c = zUrl[i])!=0 && isdigit(c) ){
g.urlPort = g.urlPort*10 + c - '0';
i++;
}
|
| ︙ | ︙ | |||
99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
);
}else{
g.urlCanonical = mprintf(
"%s://%s%T:%d%T",
g.urlProtocol, zLogin, g.urlName, g.urlPort, g.urlPath
);
}
free(zLogin);
}else if( strncmp(zUrl, "file:", 5)==0 ){
g.urlIsFile = 1;
if( zUrl[5]=='/' && zUrl[6]=='/' ){
i = 7;
}else{
i = 5;
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
);
}else{
g.urlCanonical = mprintf(
"%s://%s%T:%d%T",
g.urlProtocol, zLogin, g.urlName, g.urlPort, g.urlPath
);
}
free(zLogin);
}else if( strncmp(zUrl, "ssh://", 6)==0 ){
char *zLogin;
int r;
g.urlIsFile = 0;
g.urlIsSsh = 1;
g.urlProtocol = "ssh";
sqlite3_randomness(sizeof(r), &r);
g.urlPort = 18800 + (r & 0x7fffff)%2000;
g.urlDfltPort = 80;
g.urlName = "127.0.0.1";
g.urlHostname = g.urlName;
for(i=6; (c=zUrl[i])!=0 && c!='/' && c!='@'; i++){}
if( c=='@' ){
for(j=6; j<i && zUrl[j]!=':'; j++){}
g.urlUser = mprintf("%.*s", j-6, &zUrl[6]);
dehttpize(g.urlUser);
if( j<i ){
g.urlPasswd = mprintf("%.*s", i-j-1, &zUrl[j+1]);
dehttpize(g.urlPasswd);
}
for(j=i+1; (c=zUrl[j])!=0 && c!='/'; j++){}
g.urlSshHost = mprintf("%.*s", j-i-1, &zUrl[i+1]);
i = j;
zLogin = mprintf("%t@", g.urlUser);
}else{
g.urlSshHost = mprintf("%.*s", i-6, &zUrl[6]);
zLogin = mprintf("");
}
url_tolower(g.urlSshHost);
g.urlPath = mprintf(&zUrl[i+1]);
dehttpize(g.urlPath);
g.urlCanonical = mprintf(
"ssh://%s%T/%T",
zLogin, g.urlSshHost, g.urlPath
);
free(zLogin);
}else if( strncmp(zUrl, "file:", 5)==0 ){
g.urlIsFile = 1;
if( zUrl[5]=='/' && zUrl[6]=='/' ){
i = 7;
}else{
i = 5;
|
| ︙ | ︙ | |||
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
if( g.argc!=3 && g.argc!=4 ){
usage("URL");
}
url_parse(g.argv[2]);
for(i=0; i<2; i++){
printf("g.urlIsFile = %d\n", g.urlIsFile);
printf("g.urlIsHttps = %d\n", g.urlIsHttps);
printf("g.urlProtocol = %s\n", g.urlProtocol);
printf("g.urlName = %s\n", g.urlName);
printf("g.urlPort = %d\n", g.urlPort);
printf("g.urlDfltPort = %d\n", g.urlDfltPort);
printf("g.urlHostname = %s\n", g.urlHostname);
printf("g.urlPath = %s\n", g.urlPath);
printf("g.urlUser = %s\n", g.urlUser);
printf("g.urlPasswd = %s\n", g.urlPasswd);
printf("g.urlCanonical = %s\n", g.urlCanonical);
if( i==0 ){
printf("********\n");
url_enable_proxy("Using proxy: ");
}
}
}
| > > > | 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
if( g.argc!=3 && g.argc!=4 ){
usage("URL");
}
url_parse(g.argv[2]);
for(i=0; i<2; i++){
printf("g.urlIsFile = %d\n", g.urlIsFile);
printf("g.urlIsHttps = %d\n", g.urlIsHttps);
printf("g.urlIsSsh = %d\n", g.urlIsSsh);
printf("g.urlProtocol = %s\n", g.urlProtocol);
printf("g.urlName = %s\n", g.urlName);
printf("g.urlSshHost = %s\n", g.urlSshHost);
printf("g.urlPort = %d\n", g.urlPort);
printf("g.urlDfltPort = %d\n", g.urlDfltPort);
printf("g.urlHostname = %s\n", g.urlHostname);
printf("g.urlPath = %s\n", g.urlPath);
printf("g.urlUser = %s\n", g.urlUser);
printf("g.urlPasswd = %s\n", g.urlPasswd);
printf("g.urlCanonical = %s\n", g.urlCanonical);
if( g.urlIsFile || g.urlIsSsh ) break;
if( i==0 ){
printf("********\n");
url_enable_proxy("Using proxy: ");
}
}
}
|
| ︙ | ︙ |
Changes to src/xfer.c.
| ︙ | ︙ | |||
984 985 986 987 988 989 990 991 992 993 994 995 996 997 |
nCardSent++;
}
if( pushFlag ){
blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
nCardSent++;
}
manifest_crosslink_begin();
fossil_print(zLabelFormat, "", "Bytes", "Cards", "Artifacts", "Deltas");
while( go ){
int newPhantom = 0;
char *zRandomness;
/* Send make the most recently received cookie. Let the server
| > | 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 |
nCardSent++;
}
if( pushFlag ){
blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
nCardSent++;
}
manifest_crosslink_begin();
transport_global_startup();
fossil_print(zLabelFormat, "", "Bytes", "Cards", "Artifacts", "Deltas");
while( go ){
int newPhantom = 0;
char *zRandomness;
/* Send make the most recently received cookie. Let the server
|
| ︙ | ︙ |