Fossil SCM

fossil-scm / src / xfer.c
Source Blame History 3088 lines
dbda8d6… drh 1 /*
c19f34c… drh 2 ** Copyright (c) 2007 D. Richard Hipp
dbda8d6… drh 3 **
dbda8d6… drh 4 ** This program is free software; you can redistribute it and/or
c06edd2… drh 5 ** modify it under the terms of the Simplified BSD License (also
c06edd2… drh 6 ** known as the "2-Clause License" or "FreeBSD License".)
27d743e… drh 7 **
dbda8d6… drh 8 ** This program is distributed in the hope that it will be useful,
c06edd2… drh 9 ** but without any warranty; without even the implied warranty of
c06edd2… drh 10 ** merchantability or fitness for a particular purpose.
dbda8d6… drh 11 **
dbda8d6… drh 12 ** Author contact information:
dbda8d6… drh 13 ** [email protected]
dbda8d6… drh 14 ** http://www.hwaci.com/drh/
dbda8d6… drh 15 **
dbda8d6… drh 16 *******************************************************************************
dbda8d6… drh 17 **
dbda8d6… drh 18 ** This file contains code to implement the file transfer protocol.
dbda8d6… drh 19 */
dbda8d6… drh 20 #include "config.h"
dbda8d6… drh 21 #include "xfer.h"
dbda8d6… drh 22
769c90a… drh 23 #include <time.h>
769c90a… drh 24
769c90a… drh 25 /*
fe56e5a… jan.nijtmans 26 ** Maximum number of HTTP redirects that any http_exchange() call will
fe56e5a… jan.nijtmans 27 ** follow before throwing a fatal error. Most browsers use a limit of 20.
fe56e5a… jan.nijtmans 28 */
fe56e5a… jan.nijtmans 29 #define MAX_REDIRECTS 20
fe56e5a… jan.nijtmans 30
fe56e5a… jan.nijtmans 31 /*
573a464… drh 32 ** This structure holds information about the current state of either
573a464… drh 33 ** a client or a server that is participating in xfer.
573a464… drh 34 */
573a464… drh 35 typedef struct Xfer Xfer;
573a464… drh 36 struct Xfer {
573a464… drh 37 Blob *pIn; /* Input text from the other side */
573a464… drh 38 Blob *pOut; /* Compose our reply here */
573a464… drh 39 Blob line; /* The current line of input */
3543ed6… drh 40 Blob aToken[6]; /* Tokenized version of line */
573a464… drh 41 Blob err; /* Error message text */
573a464… drh 42 int nToken; /* Number of tokens in line */
243e02b… drh 43 int nIGotSent; /* Number of "igot" cards sent */
71b6ada… drh 44 int nPrivIGot; /* Number of private "igot" cards */
243e02b… drh 45 int nGimmeSent; /* Number of gimme cards sent */
edbb332… drh 46 int nFileSent; /* Number of files sent */
edbb332… drh 47 int nDeltaSent; /* Number of deltas sent */
edbb332… drh 48 int nFileRcvd; /* Number of files received */
edbb332… drh 49 int nDeltaRcvd; /* Number of deltas received */
573a464… drh 50 int nDanglingFile; /* Number of dangling deltas received */
bd7daa1… stephan 51 int mxSend; /* Stop sending "file" when pOut reaches this size */
05ba15e… drh 52 int resync; /* Send igot cards for all holdings */
8b8cc4f… drh 53 u8 syncPrivate; /* True to enable syncing private content */
8b8cc4f… drh 54 u8 nextIsPrivate; /* If true, next "file" received is a private */
1f7b409… drh 55 u32 remoteVersion; /* Version of fossil running on the other side */
1f7b409… drh 56 u32 remoteDate; /* Date for specific client software edition */
e2bdc10… danield 57 u32 remoteTime; /* Time of date corresponding on remoteDate */
769c90a… drh 58 time_t maxTime; /* Time when this transfer should be finished */
573a464… drh 59 };
573a464… drh 60
573a464… drh 61
573a464… drh 62 /*
fd9b7bd… drh 63 ** The input blob contains an artifact. Convert it into a record ID.
573a464… drh 64 ** Create a phantom record if no prior record exists and
573a464… drh 65 ** phantomize is true.
573a464… drh 66 **
573a464… drh 67 ** Compare to uuid_to_rid(). This routine takes a blob argument
573a464… drh 68 ** and does less error checking.
573a464… drh 69 */
8b8cc4f… drh 70 static int rid_from_uuid(Blob *pUuid, int phantomize, int isPrivate){
043d63d… drh 71 static Stmt q;
043d63d… drh 72 int rid;
043d63d… drh 73
043d63d… drh 74 db_static_prepare(&q, "SELECT rid FROM blob WHERE uuid=:uuid");
043d63d… drh 75 db_bind_str(&q, ":uuid", pUuid);
043d63d… drh 76 if( db_step(&q)==SQLITE_ROW ){
043d63d… drh 77 rid = db_column_int(&q, 0);
043d63d… drh 78 }else{
043d63d… drh 79 rid = 0;
043d63d… drh 80 }
043d63d… drh 81 db_reset(&q);
573a464… drh 82 if( rid==0 && phantomize ){
8b8cc4f… drh 83 rid = content_new(blob_str(pUuid), isPrivate);
573a464… drh 84 }
573a464… drh 85 return rid;
573a464… drh 86 }
573a464… drh 87
15652ff… aku 88 /*
15652ff… aku 89 ** Remember that the other side of the connection already has a copy
15652ff… aku 90 ** of the file rid.
15652ff… aku 91 */
15652ff… aku 92 static void remote_has(int rid){
fda9b15… drh 93 if( rid ){
fda9b15… drh 94 static Stmt q;
fda9b15… drh 95 db_static_prepare(&q, "INSERT OR IGNORE INTO onremote VALUES(:r)");
fda9b15… drh 96 db_bind_int(&q, ":r", rid);
fda9b15… drh 97 db_step(&q);
fda9b15… drh 98 db_reset(&q);
fda9b15… drh 99 }
405457c… drh 100 }
405457c… drh 101
405457c… drh 102 /*
405457c… drh 103 ** Remember that the other side of the connection lacks a copy of
405457c… drh 104 ** the artifact with the given hash.
405457c… drh 105 */
405457c… drh 106 static void remote_unk(Blob *pHash){
405457c… drh 107 static Stmt q;
405457c… drh 108 db_static_prepare(&q, "INSERT OR IGNORE INTO unk VALUES(:h)");
405457c… drh 109 db_bind_text(&q, ":h", blob_str(pHash));
405457c… drh 110 db_step(&q);
405457c… drh 111 db_reset(&q);
fd9b7bd… drh 112 }
fd9b7bd… drh 113
fd9b7bd… drh 114 /*
45f3516… jan.nijtmans 115 ** The aToken[0..nToken-1] blob array is a parse of a "file" line
dbda8d6… drh 116 ** message. This routine finishes parsing that message and does
dbda8d6… drh 117 ** a record insert of the file.
dbda8d6… drh 118 **
dbda8d6… drh 119 ** The file line is in one of the following two forms:
dbda8d6… drh 120 **
fd9b7bd… drh 121 ** file HASH SIZE \n CONTENT
fd9b7bd… drh 122 ** file HASH DELTASRC SIZE \n CONTENT
dbda8d6… drh 123 **
dbda8d6… drh 124 ** The content is SIZE bytes immediately following the newline.
dbda8d6… drh 125 ** If DELTASRC exists, then the CONTENT is a delta against the
dbda8d6… drh 126 ** content of DELTASRC.
dbda8d6… drh 127 **
dbda8d6… drh 128 ** If any error occurs, write a message into pErr which has already
dbda8d6… drh 129 ** be initialized to an empty string.
02a584f… drh 130 **
02a584f… drh 131 ** Any artifact successfully received by this routine is considered to
02a584f… drh 132 ** be public and is therefore removed from the "private" table.
dbda8d6… drh 133 */
fc32fae… mistachkin 134 static void xfer_accept_file(
fc32fae… mistachkin 135 Xfer *pXfer,
fc32fae… mistachkin 136 int cloneFlag,
fc32fae… mistachkin 137 char **pzUuidList,
fc32fae… mistachkin 138 int *pnUuidList
fc32fae… mistachkin 139 ){
dbda8d6… drh 140 int n;
dbda8d6… drh 141 int rid;
d0305b3… aku 142 int srcid = 0;
fd9b7bd… drh 143 Blob content;
8b8cc4f… drh 144 int isPriv;
fd9b7bd… drh 145 Blob *pUuid;
45f3516… jan.nijtmans 146
8b8cc4f… drh 147 isPriv = pXfer->nextIsPrivate;
8b8cc4f… drh 148 pXfer->nextIsPrivate = 0;
45f3516… jan.nijtmans 149 if( pXfer->nToken<3
573a464… drh 150 || pXfer->nToken>4
fd9b7bd… drh 151 || !blob_is_hname(&pXfer->aToken[1])
573a464… drh 152 || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &n)
b0ad3f9… drh 153 || n<0
fd9b7bd… drh 154 || (pXfer->nToken==4 && !blob_is_hname(&pXfer->aToken[2]))
573a464… drh 155 ){
573a464… drh 156 blob_appendf(&pXfer->err, "malformed file line");
573a464… drh 157 return;
573a464… drh 158 }
573a464… drh 159 blob_zero(&content);
573a464… drh 160 blob_extract(pXfer->pIn, n, &content);
fd9b7bd… drh 161 pUuid = &pXfer->aToken[1];
fd9b7bd… drh 162 if( !cloneFlag && uuid_is_shunned(blob_str(pUuid)) ){
d0305b3… aku 163 /* Ignore files that have been shunned */
1e39808… mistachkin 164 blob_reset(&content);
8b8cc4f… drh 165 return;
8b8cc4f… drh 166 }
b344d3c… drh 167 if( isPriv && !g.perm.Private ){
8b8cc4f… drh 168 /* Do not accept private files if not authorized */
1e39808… mistachkin 169 blob_reset(&content);
fda9b15… drh 170 return;
fda9b15… drh 171 }
fda9b15… drh 172 if( cloneFlag ){
fda9b15… drh 173 if( pXfer->nToken==4 ){
8b8cc4f… drh 174 srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
fda9b15… drh 175 pXfer->nDeltaRcvd++;
fda9b15… drh 176 }else{
fda9b15… drh 177 srcid = 0;
fda9b15… drh 178 pXfer->nFileRcvd++;
fda9b15… drh 179 }
fd9b7bd… drh 180 rid = content_put_ex(&content, blob_str(pUuid), srcid,
8b8cc4f… drh 181 0, isPriv);
fd9b7bd… drh 182 Th_AppendToList(pzUuidList, pnUuidList, blob_str(pUuid),
fd9b7bd… drh 183 blob_size(pUuid));
fda9b15… drh 184 remote_has(rid);
fda9b15… drh 185 blob_reset(&content);
d0305b3… aku 186 return;
d0305b3… aku 187 }
573a464… drh 188 if( pXfer->nToken==4 ){
b8f134b… drh 189 Blob src, next;
8b8cc4f… drh 190 srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
573a464… drh 191 if( content_get(srcid, &src)==0 ){
fd9b7bd… drh 192 rid = content_put_ex(&content, blob_str(pUuid), srcid,
8b8cc4f… drh 193 0, isPriv);
fd9b7bd… drh 194 Th_AppendToList(pzUuidList, pnUuidList, blob_str(pUuid),
fd9b7bd… drh 195 blob_size(pUuid));
573a464… drh 196 pXfer->nDanglingFile++;
7646ee1… drh 197 db_multi_exec("DELETE FROM phantom WHERE rid=%d", rid);
8b8cc4f… drh 198 if( !isPriv ) content_make_public(rid);
1e39808… mistachkin 199 blob_reset(&src);
1e39808… mistachkin 200 blob_reset(&content);
573a464… drh 201 return;
573a464… drh 202 }
edbb332… drh 203 pXfer->nDeltaRcvd++;
b8f134b… drh 204 blob_delta_apply(&src, &content, &next);
573a464… drh 205 blob_reset(&src);
b8f134b… drh 206 blob_reset(&content);
b8f134b… drh 207 content = next;
573a464… drh 208 }else{
edbb332… drh 209 pXfer->nFileRcvd++;
edbb332… drh 210 }
fd9b7bd… drh 211 if( hname_verify_hash(&content, blob_buffer(pUuid), blob_size(pUuid))==0 ){
fd9b7bd… drh 212 blob_appendf(&pXfer->err, "wrong hash on received artifact: %b", pUuid);
9954612… stephan 213 blob_reset(&content);
9954612… stephan 214 return;
fd9b7bd… drh 215 }
fd9b7bd… drh 216 rid = content_put_ex(&content, blob_str(pUuid), 0, 0, isPriv);
fd9b7bd… drh 217 Th_AppendToList(pzUuidList, pnUuidList, blob_str(pUuid), blob_size(pUuid));
573a464… drh 218 if( rid==0 ){
573a464… drh 219 blob_appendf(&pXfer->err, "%s", g.zErrMsg);
d22519e… drh 220 blob_reset(&content);
3543ed6… drh 221 }else{
8b8cc4f… drh 222 if( !isPriv ) content_make_public(rid);
e9bccd1… mistachkin 223 manifest_crosslink(rid, &content, MC_NO_ERRORS);
3543ed6… drh 224 }
d22519e… drh 225 assert( blob_is_reset(&content) );
3543ed6… drh 226 remote_has(rid);
3543ed6… drh 227 }
3543ed6… drh 228
3543ed6… drh 229 /*
45f3516… jan.nijtmans 230 ** The aToken[0..nToken-1] blob array is a parse of a "cfile" line
3543ed6… drh 231 ** message. This routine finishes parsing that message and does
3543ed6… drh 232 ** a record insert of the file. The difference between "file" and
3543ed6… drh 233 ** "cfile" is that with "cfile" the content is already compressed.
3543ed6… drh 234 **
3543ed6… drh 235 ** The file line is in one of the following two forms:
3543ed6… drh 236 **
fd9b7bd… drh 237 ** cfile HASH USIZE CSIZE \n CONTENT
fd9b7bd… drh 238 ** cfile HASH DELTASRC USIZE CSIZE \n CONTENT
3543ed6… drh 239 **
3543ed6… drh 240 ** The content is CSIZE bytes immediately following the newline.
3543ed6… drh 241 ** If DELTASRC exists, then the CONTENT is a delta against the
3543ed6… drh 242 ** content of DELTASRC.
3543ed6… drh 243 **
fd9b7bd… drh 244 ** The original size of the HASH artifact is USIZE.
3543ed6… drh 245 **
3543ed6… drh 246 ** If any error occurs, write a message into pErr which has already
3543ed6… drh 247 ** be initialized to an empty string.
3543ed6… drh 248 **
3543ed6… drh 249 ** Any artifact successfully received by this routine is considered to
3543ed6… drh 250 ** be public and is therefore removed from the "private" table.
3543ed6… drh 251 */
fc32fae… mistachkin 252 static void xfer_accept_compressed_file(
fc32fae… mistachkin 253 Xfer *pXfer,
fc32fae… mistachkin 254 char **pzUuidList,
fc32fae… mistachkin 255 int *pnUuidList
fc32fae… mistachkin 256 ){
3543ed6… drh 257 int szC; /* CSIZE */
3543ed6… drh 258 int szU; /* USIZE */
3543ed6… drh 259 int rid;
3543ed6… drh 260 int srcid = 0;
3543ed6… drh 261 Blob content;
8b8cc4f… drh 262 int isPriv;
45f3516… jan.nijtmans 263
8b8cc4f… drh 264 isPriv = pXfer->nextIsPrivate;
8b8cc4f… drh 265 pXfer->nextIsPrivate = 0;
45f3516… jan.nijtmans 266 if( pXfer->nToken<4
3543ed6… drh 267 || pXfer->nToken>5
fd9b7bd… drh 268 || !blob_is_hname(&pXfer->aToken[1])
3543ed6… drh 269 || !blob_is_int(&pXfer->aToken[pXfer->nToken-2], &szU)
3543ed6… drh 270 || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &szC)
3543ed6… drh 271 || szC<0 || szU<0
fd9b7bd… drh 272 || (pXfer->nToken==5 && !blob_is_hname(&pXfer->aToken[2]))
3543ed6… drh 273 ){
3543ed6… drh 274 blob_appendf(&pXfer->err, "malformed cfile line");
8b8cc4f… drh 275 return;
8b8cc4f… drh 276 }
b344d3c… drh 277 if( isPriv && !g.perm.Private ){
8b8cc4f… drh 278 /* Do not accept private files if not authorized */
3543ed6… drh 279 return;
3543ed6… drh 280 }
3543ed6… drh 281 blob_zero(&content);
3543ed6… drh 282 blob_extract(pXfer->pIn, szC, &content);
3543ed6… drh 283 if( uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
3543ed6… drh 284 /* Ignore files that have been shunned */
1e39808… mistachkin 285 blob_reset(&content);
3543ed6… drh 286 return;
3543ed6… drh 287 }
3543ed6… drh 288 if( pXfer->nToken==5 ){
8b8cc4f… drh 289 srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
3543ed6… drh 290 pXfer->nDeltaRcvd++;
15652ff… aku 291 }else{
3543ed6… drh 292 srcid = 0;
3543ed6… drh 293 pXfer->nFileRcvd++;
15652ff… aku 294 }
8b8cc4f… drh 295 rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid,
8b8cc4f… drh 296 szC, isPriv);
fc32fae… mistachkin 297 Th_AppendToList(pzUuidList, pnUuidList, blob_str(&pXfer->aToken[1]),
fc32fae… mistachkin 298 blob_size(&pXfer->aToken[1]));
15652ff… aku 299 remote_has(rid);
fc32fae… mistachkin 300 blob_reset(&content);
fc32fae… mistachkin 301 }
fc32fae… mistachkin 302
fc32fae… mistachkin 303 /*
27d743e… drh 304 ** The aToken[0..nToken-1] blob array is a parse of a "uvfile" line
27d743e… drh 305 ** message. This routine finishes parsing that message and adds the
27d743e… drh 306 ** unversioned file to the "unversioned" table.
27d743e… drh 307 **
27d743e… drh 308 ** The file line is in one of the following two forms:
27d743e… drh 309 **
27d743e… drh 310 ** uvfile NAME MTIME HASH SIZE FLAGS
27d743e… drh 311 ** uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT
27d743e… drh 312 **
d32155d… jan.nijtmans 313 ** If the 0x0001 bit of FLAGS is set, that means the file has been
27d743e… drh 314 ** deleted, SIZE is zero, the HASH is "-", and the "\n CONTENT" is omitted.
27d743e… drh 315 **
27d743e… drh 316 ** SIZE is the number of bytes of CONTENT. The CONTENT is uncompressed.
fd9b7bd… drh 317 ** HASH is the artifact hash of CONTENT.
27d743e… drh 318 **
27d743e… drh 319 ** If the 0x0004 bit of FLAGS is set, that means the CONTENT is omitted.
27d743e… drh 320 ** The sender might have omitted the content because it is too big to
27d743e… drh 321 ** transmit, or because it is unchanged and this record exists purely
27d743e… drh 322 ** to update the MTIME.
27d743e… drh 323 */
27d743e… drh 324 static void xfer_accept_unversioned_file(Xfer *pXfer, int isWriter){
27d743e… drh 325 sqlite3_int64 mtime; /* The MTIME */
27d743e… drh 326 Blob *pHash; /* The HASH value */
27d743e… drh 327 int sz; /* The SIZE */
27d743e… drh 328 int flags; /* The FLAGS */
27d743e… drh 329 Blob content; /* The CONTENT */
27d743e… drh 330 Blob x; /* Compressed content */
27d743e… drh 331 Stmt q; /* SQL statements for comparison and insert */
27d743e… drh 332 int isDelete; /* HASH is "-" indicating this is a delete */
27d743e… drh 333 int nullContent; /* True of CONTENT is NULL */
27d743e… drh 334 int iStatus; /* Result from unversioned_status() */
27d743e… drh 335
27d743e… drh 336 pHash = &pXfer->aToken[3];
27d743e… drh 337 if( pXfer->nToken==5
27d743e… drh 338 || !blob_is_filename(&pXfer->aToken[1])
27d743e… drh 339 || !blob_is_int64(&pXfer->aToken[2], &mtime)
fd9b7bd… drh 340 || (!blob_eq(pHash,"-") && !blob_is_hname(pHash))
27d743e… drh 341 || !blob_is_int(&pXfer->aToken[4], &sz)
27d743e… drh 342 || !blob_is_int(&pXfer->aToken[5], &flags)
f9f90d9… stephan 343 || (mtime<0 || sz<0 || flags<0)
27d743e… drh 344 ){
27d743e… drh 345 blob_appendf(&pXfer->err, "malformed uvfile line");
27d743e… drh 346 return;
27d743e… drh 347 }
27d743e… drh 348 blob_init(&content, 0, 0);
27d743e… drh 349 blob_init(&x, 0, 0);
27d743e… drh 350 if( sz>0 && (flags & 0x0005)==0 ){
27d743e… drh 351 blob_extract(pXfer->pIn, sz, &content);
27d743e… drh 352 nullContent = 0;
fd9b7bd… drh 353 if( hname_verify_hash(&content, blob_buffer(pHash), blob_size(pHash))==0 ){
27d743e… drh 354 blob_appendf(&pXfer->err, "in uvfile line, HASH does not match CONTENT");
27d743e… drh 355 goto end_accept_unversioned_file;
27d743e… drh 356 }
27d743e… drh 357 }else{
27d743e… drh 358 nullContent = 1;
27d743e… drh 359 }
27d743e… drh 360
27d743e… drh 361 /* The isWriter flag must be true in order to land the new file */
f8baa04… danield 362 if( !isWriter ){
275da70… danield 363 blob_appendf(&pXfer->err,"Write permissions for unversioned files missing");
f8baa04… danield 364 goto end_accept_unversioned_file;
f8baa04… danield 365 }
7deeb51… drh 366
7deeb51… drh 367 /* Make sure we have a valid g.rcvid marker */
1d6b678… drh 368 content_rcvid_init(0);
27d743e… drh 369
27d743e… drh 370 /* Check to see if current content really should be overwritten. Ideally,
27d743e… drh 371 ** a uvfile card should never have been sent unless the overwrite should
d32155d… jan.nijtmans 372 ** occur. But do not trust the sender. Double-check.
27d743e… drh 373 */
27d743e… drh 374 iStatus = unversioned_status(blob_str(&pXfer->aToken[1]), mtime,
27d743e… drh 375 blob_str(pHash));
27d743e… drh 376 if( iStatus>=3 ) goto end_accept_unversioned_file;
d32155d… jan.nijtmans 377
27d743e… drh 378 /* Store the content */
27d743e… drh 379 isDelete = blob_eq(pHash, "-");
27d743e… drh 380 if( isDelete ){
27d743e… drh 381 db_prepare(&q,
27d743e… drh 382 "UPDATE unversioned"
27d743e… drh 383 " SET rcvid=:rcvid, mtime=:mtime, hash=NULL,"
27d743e… drh 384 " sz=0, encoding=0, content=NULL"
27d743e… drh 385 " WHERE name=:name"
27d743e… drh 386 );
27d743e… drh 387 db_bind_int(&q, ":rcvid", g.rcvid);
669cd40… drh 388 }else if( iStatus==2 ){
27d743e… drh 389 db_prepare(&q, "UPDATE unversioned SET mtime=:mtime WHERE name=:name");
27d743e… drh 390 }else{
27d743e… drh 391 db_prepare(&q,
27d743e… drh 392 "REPLACE INTO unversioned(name,rcvid,mtime,hash,sz,encoding,content)"
27d743e… drh 393 " VALUES(:name,:rcvid,:mtime,:hash,:sz,:encoding,:content)"
27d743e… drh 394 );
27d743e… drh 395 db_bind_int(&q, ":rcvid", g.rcvid);
27d743e… drh 396 db_bind_text(&q, ":hash", blob_str(pHash));
27d743e… drh 397 db_bind_int(&q, ":sz", blob_size(&content));
27d743e… drh 398 if( !nullContent ){
27d743e… drh 399 blob_compress(&content, &x);
27d743e… drh 400 if( blob_size(&x) < 0.8*blob_size(&content) ){
27d743e… drh 401 db_bind_blob(&q, ":content", &x);
27d743e… drh 402 db_bind_int(&q, ":encoding", 1);
27d743e… drh 403 }else{
27d743e… drh 404 db_bind_blob(&q, ":content", &content);
27d743e… drh 405 db_bind_int(&q, ":encoding", 0);
27d743e… drh 406 }
27d743e… drh 407 }else{
27d743e… drh 408 db_bind_int(&q, ":encoding", 0);
27d743e… drh 409 }
27d743e… drh 410 }
27d743e… drh 411 db_bind_text(&q, ":name", blob_str(&pXfer->aToken[1]));
27d743e… drh 412 db_bind_int64(&q, ":mtime", mtime);
27d743e… drh 413 db_step(&q);
27d743e… drh 414 db_finalize(&q);
27d743e… drh 415 db_unset("uv-hash", 0);
27d743e… drh 416
27d743e… drh 417 end_accept_unversioned_file:
27d743e… drh 418 blob_reset(&x);
27d743e… drh 419 blob_reset(&content);
27d743e… drh 420 }
27d743e… drh 421
27d743e… drh 422 /*
d0305b3… aku 423 ** Try to send a file as a delta against its parent.
d0305b3… aku 424 ** If successful, return the number of bytes in the delta.
d0305b3… aku 425 ** If we cannot generate an appropriate delta, then send
d0305b3… aku 426 ** nothing and return zero.
02a584f… drh 427 **
02a584f… drh 428 ** Never send a delta against a private artifact.
573a464… drh 429 */
d0305b3… aku 430 static int send_delta_parent(
573a464… drh 431 Xfer *pXfer, /* The transfer context */
573a464… drh 432 int rid, /* record id of the file to send */
8b8cc4f… drh 433 int isPrivate, /* True if rid is a private artifact */
573a464… drh 434 Blob *pContent, /* The content of the file to send */
fd9b7bd… drh 435 Blob *pUuid /* The HASH of the file to send */
573a464… drh 436 ){
6032dd5… jan.nijtmans 437 static const char *const azQuery[] = {
d0305b3… aku 438 "SELECT pid FROM plink x"
573a464… drh 439 " WHERE cid=%d"
e5b8eb1… drh 440 " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)",
45f3516… jan.nijtmans 441
e5b8eb1… drh 442 "SELECT pid, min(mtime) FROM mlink, event ON mlink.mid=event.objid"
573a464… drh 443 " WHERE fid=%d"
d0305b3… aku 444 " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
573a464… drh 445 };
573a464… drh 446 int i;
573a464… drh 447 Blob src, delta;
573a464… drh 448 int size = 0;
d0305b3… aku 449 int srcId = 0;
573a464… drh 450
573a464… drh 451 for(i=0; srcId==0 && i<count(azQuery); i++){
49b0ff1… drh 452 srcId = db_int(0, azQuery[i] /*works-like:"%d"*/, rid);
573a464… drh 453 }
8b8cc4f… drh 454 if( srcId>0
8b8cc4f… drh 455 && (pXfer->syncPrivate || !content_is_private(srcId))
8b8cc4f… drh 456 && content_get(srcId, &src)
8b8cc4f… drh 457 ){
573a464… drh 458 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", srcId);
573a464… drh 459 blob_delta_create(&src, pContent, &delta);
573a464… drh 460 size = blob_size(&delta);
53db40e… drh 461 if( size>=(int)blob_size(pContent)-50 ){
a48474b… drh 462 size = 0;
a48474b… drh 463 }else if( uuid_is_shunned(zUuid) ){
573a464… drh 464 size = 0;
573a464… drh 465 }else{
e5b8eb1… drh 466 if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
573a464… drh 467 blob_appendf(pXfer->pOut, "file %b %s %d\n", pUuid, zUuid, size);
573a464… drh 468 blob_append(pXfer->pOut, blob_buffer(&delta), size);
573a464… drh 469 }
573a464… drh 470 blob_reset(&delta);
573a464… drh 471 free(zUuid);
573a464… drh 472 blob_reset(&src);
573a464… drh 473 }
573a464… drh 474 return size;
573a464… drh 475 }
573a464… drh 476
573a464… drh 477 /*
45f3516… jan.nijtmans 478 ** Try to send a file as a native delta.
d0305b3… aku 479 ** If successful, return the number of bytes in the delta.
d0305b3… aku 480 ** If we cannot generate an appropriate delta, then send
d0305b3… aku 481 ** nothing and return zero.
02a584f… drh 482 **
02a584f… drh 483 ** Never send a delta against a private artifact.
d0305b3… aku 484 */
d0305b3… aku 485 static int send_delta_native(
d0305b3… aku 486 Xfer *pXfer, /* The transfer context */
d0305b3… aku 487 int rid, /* record id of the file to send */
8b8cc4f… drh 488 int isPrivate, /* True if rid is a private artifact */
fd9b7bd… drh 489 Blob *pUuid /* The HASH of the file to send */
d0305b3… aku 490 ){
d0305b3… aku 491 Blob src, delta;
d0305b3… aku 492 int size = 0;
d0305b3… aku 493 int srcId;
d0305b3… aku 494
d0305b3… aku 495 srcId = db_int(0, "SELECT srcid FROM delta WHERE rid=%d", rid);
8b8cc4f… drh 496 if( srcId>0
8b8cc4f… drh 497 && (pXfer->syncPrivate || !content_is_private(srcId))
8b8cc4f… drh 498 ){
a48474b… drh 499 blob_zero(&src);
a48474b… drh 500 db_blob(&src, "SELECT uuid FROM blob WHERE rid=%d", srcId);
a48474b… drh 501 if( uuid_is_shunned(blob_str(&src)) ){
a48474b… drh 502 blob_reset(&src);
a48474b… drh 503 return 0;
a48474b… drh 504 }
d0305b3… aku 505 blob_zero(&delta);
d0305b3… aku 506 db_blob(&delta, "SELECT content FROM blob WHERE rid=%d", rid);
d0305b3… aku 507 blob_uncompress(&delta, &delta);
8b8cc4f… drh 508 if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
d0305b3… aku 509 blob_appendf(pXfer->pOut, "file %b %b %d\n",
d0305b3… aku 510 pUuid, &src, blob_size(&delta));
d0305b3… aku 511 blob_append(pXfer->pOut, blob_buffer(&delta), blob_size(&delta));
d0305b3… aku 512 size = blob_size(&delta);
d0305b3… aku 513 blob_reset(&delta);
d0305b3… aku 514 blob_reset(&src);
d0305b3… aku 515 }else{
d0305b3… aku 516 size = 0;
d0305b3… aku 517 }
d0305b3… aku 518 return size;
d0305b3… aku 519 }
d0305b3… aku 520
d0305b3… aku 521 /*
b098c93… jan.nijtmans 522 ** Push an error message to alert the older client that the repository
a137387… drh 523 ** has SHA3 content and cannot be synced or cloned.
a137387… drh 524 */
a137387… drh 525 static void xfer_cannot_send_sha3_error(Xfer *pXfer){
b098c93… jan.nijtmans 526 blob_appendf(pXfer->pOut,
a137387… drh 527 "error Fossil\\sversion\\s2.0\\sor\\slater\\srequired.\n"
a137387… drh 528 );
a137387… drh 529 }
a137387… drh 530
a137387… drh 531
a137387… drh 532 /*
dbda8d6… drh 533 ** Send the file identified by rid.
edbb332… drh 534 **
fd9b7bd… drh 535 ** The pUuid can be NULL in which case the correct hash is computed
edbb332… drh 536 ** from the rid.
edbb332… drh 537 **
d0305b3… aku 538 ** Try to send the file as a native delta if nativeDelta is true, or
d0305b3… aku 539 ** as a parent delta if nativeDelta is false.
02a584f… drh 540 **
02a584f… drh 541 ** It should never be the case that rid is a private artifact. But
02a584f… drh 542 ** as a precaution, this routine does check on rid and if it is private
02a584f… drh 543 ** this routine becomes a no-op.
dbda8d6… drh 544 */
d0305b3… aku 545 static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int nativeDelta){
dbda8d6… drh 546 Blob content, uuid;
573a464… drh 547 int size = 0;
8b8cc4f… drh 548 int isPriv = content_is_private(rid);
eea381f… drh 549
81445af… drh 550 if( isPriv && pXfer->syncPrivate==0 ){
81445af… drh 551 if( pXfer->remoteDate>=20200413 && pUuid && blob_size(pUuid)>0 ){
81445af… drh 552 /* If the artifact is private and we are not doing a private sync,
81445af… drh 553 ** at least tell the other side that the artifact exists and is
81445af… drh 554 ** known to be private. But only do this for newer clients since
81445af… drh 555 ** older ones will throw an error if they get a private igot card
81445af… drh 556 ** and private syncing is disallowed */
81445af… drh 557 blob_appendf(pXfer->pOut, "igot %b 1\n", pUuid);
81445af… drh 558 pXfer->nIGotSent++;
81445af… drh 559 pXfer->nPrivIGot++;
81445af… drh 560 }
81445af… drh 561 return;
81445af… drh 562 }
50150ad… drh 563 if( db_exists("SELECT 1 FROM onremote WHERE rid=%d", rid) ){
573a464… drh 564 return;
573a464… drh 565 }
dbda8d6… drh 566 blob_zero(&uuid);
a48474b… drh 567 db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d AND size>=0", rid);
a48474b… drh 568 if( blob_size(&uuid)==0 ){
a137387… drh 569 return;
a137387… drh 570 }
1f7b409… drh 571 if( blob_size(&uuid)>HNAME_LEN_SHA1 && pXfer->remoteVersion<20000 ){
a137387… drh 572 xfer_cannot_send_sha3_error(pXfer);
a48474b… drh 573 return;
a48474b… drh 574 }
a48474b… drh 575 if( pUuid ){
a48474b… drh 576 if( blob_compare(pUuid, &uuid)!=0 ){
a48474b… drh 577 blob_reset(&uuid);
edbb332… drh 578 return;
edbb332… drh 579 }
a48474b… drh 580 }else{
edbb332… drh 581 pUuid = &uuid;
edbb332… drh 582 }
a48474b… drh 583 if( uuid_is_shunned(blob_str(pUuid)) ){
a48474b… drh 584 blob_reset(&uuid);
a48474b… drh 585 return;
a48474b… drh 586 }
45f3516… jan.nijtmans 587 if( (pXfer->maxTime != -1 && time(NULL) >= pXfer->maxTime) ||
53db40e… drh 588 pXfer->mxSend<=(int)blob_size(pXfer->pOut) ){
8b8cc4f… drh 589 const char *zFormat = isPriv ? "igot %b 1\n" : "igot %b\n";
49b0ff1… drh 590 blob_appendf(pXfer->pOut, zFormat /*works-like:"%b"*/, pUuid);
edbb332… drh 591 pXfer->nIGotSent++;
edbb332… drh 592 blob_reset(&uuid);
edbb332… drh 593 return;
edbb332… drh 594 }
d0305b3… aku 595 if( nativeDelta ){
8b8cc4f… drh 596 size = send_delta_native(pXfer, rid, isPriv, pUuid);
d0305b3… aku 597 if( size ){
d0305b3… aku 598 pXfer->nDeltaSent++;
d0305b3… aku 599 }
edbb332… drh 600 }
edbb332… drh 601 if( size==0 ){
d0305b3… aku 602 content_get(rid, &content);
d0305b3… aku 603
d0305b3… aku 604 if( !nativeDelta && blob_size(&content)>100 ){
8b8cc4f… drh 605 size = send_delta_parent(pXfer, rid, isPriv, &content, pUuid);
d0305b3… aku 606 }
d0305b3… aku 607 if( size==0 ){
d0305b3… aku 608 int size = blob_size(&content);
8b8cc4f… drh 609 if( isPriv ) blob_append(pXfer->pOut, "private\n", -1);
d0305b3… aku 610 blob_appendf(pXfer->pOut, "file %b %d\n", pUuid, size);
d0305b3… aku 611 blob_append(pXfer->pOut, blob_buffer(&content), size);
d0305b3… aku 612 pXfer->nFileSent++;
d0305b3… aku 613 }else{
d0305b3… aku 614 pXfer->nDeltaSent++;
d0305b3… aku 615 }
1e39808… mistachkin 616 blob_reset(&content);
15652ff… aku 617 }
15652ff… aku 618 remote_has(rid);
edbb332… drh 619 blob_reset(&uuid);
8b8cc4f… drh 620 #if 0
8b8cc4f… drh 621 if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){
49b0ff1… drh 622 blob_append(pXfer->pOut, "\n", 1);
8b8cc4f… drh 623 }
8b8cc4f… drh 624 #endif
3543ed6… drh 625 }
3543ed6… drh 626
3543ed6… drh 627 /*
3543ed6… drh 628 ** Send the file identified by rid as a compressed artifact. Basically,
45f3516… jan.nijtmans 629 ** send the content exactly as it appears in the BLOB table using
3543ed6… drh 630 ** a "cfile" card.
3543ed6… drh 631 */
3543ed6… drh 632 static void send_compressed_file(Xfer *pXfer, int rid){
3543ed6… drh 633 const char *zContent;
3543ed6… drh 634 const char *zUuid;
1f41b04… drh 635 const char *zDelta;
3543ed6… drh 636 int szU;
3543ed6… drh 637 int szC;
3543ed6… drh 638 int rc;
8b8cc4f… drh 639 int isPrivate;
8d155c5… drh 640 int srcIsPrivate;
1f41b04… drh 641 static Stmt q1;
8d155c5… drh 642 Blob fullContent;
1f41b04… drh 643
8b8cc4f… drh 644 isPrivate = content_is_private(rid);
8b8cc4f… drh 645 if( isPrivate && pXfer->syncPrivate==0 ) return;
1f41b04… drh 646 db_static_prepare(&q1,
8d155c5… drh 647 "SELECT uuid, size, content, delta.srcid IN private,"
8d155c5… drh 648 " (SELECT uuid FROM blob WHERE rid=delta.srcid)"
8d155c5… drh 649 " FROM blob LEFT JOIN delta ON (blob.rid=delta.rid)"
8d155c5… drh 650 " WHERE blob.rid=:rid"
8d155c5… drh 651 " AND blob.size>=0"
1654456… drh 652 " AND NOT EXISTS(SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)"
3543ed6… drh 653 );
1f41b04… drh 654 db_bind_int(&q1, ":rid", rid);
1f41b04… drh 655 rc = db_step(&q1);
3543ed6… drh 656 if( rc==SQLITE_ROW ){
1f41b04… drh 657 zUuid = db_column_text(&q1, 0);
1f41b04… drh 658 szU = db_column_int(&q1, 1);
1f41b04… drh 659 szC = db_column_bytes(&q1, 2);
1f41b04… drh 660 zContent = db_column_raw(&q1, 2);
8d155c5… drh 661 srcIsPrivate = db_column_int(&q1, 3);
8d155c5… drh 662 zDelta = db_column_text(&q1, 4);
8b8cc4f… drh 663 if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
1f7b409… drh 664 if( pXfer->remoteVersion<20000 && db_column_bytes(&q1,0)!=HNAME_LEN_SHA1 ){
a137387… drh 665 xfer_cannot_send_sha3_error(pXfer);
a137387… drh 666 db_reset(&q1);
a137387… drh 667 return;
a137387… drh 668 }
3543ed6… drh 669 blob_appendf(pXfer->pOut, "cfile %s ", zUuid);
8d155c5… drh 670 if( !isPrivate && srcIsPrivate ){
8d155c5… drh 671 content_get(rid, &fullContent);
8d155c5… drh 672 szU = blob_size(&fullContent);
8d155c5… drh 673 blob_compress(&fullContent, &fullContent);
8d155c5… drh 674 szC = blob_size(&fullContent);
8d155c5… drh 675 zContent = blob_buffer(&fullContent);
8d155c5… drh 676 zDelta = 0;
8d155c5… drh 677 }
8d155c5… drh 678 if( zDelta ){
3543ed6… drh 679 blob_appendf(pXfer->pOut, "%s ", zDelta);
3543ed6… drh 680 pXfer->nDeltaSent++;
3543ed6… drh 681 }else{
3543ed6… drh 682 pXfer->nFileSent++;
3543ed6… drh 683 }
3543ed6… drh 684 blob_appendf(pXfer->pOut, "%d %d\n", szU, szC);
3543ed6… drh 685 blob_append(pXfer->pOut, zContent, szC);
8b8cc4f… drh 686 if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){
49b0ff1… drh 687 blob_append(pXfer->pOut, "\n", 1);
8d155c5… drh 688 }
8d155c5… drh 689 if( !isPrivate && srcIsPrivate ){
8d155c5… drh 690 blob_reset(&fullContent);
8b8cc4f… drh 691 }
1f41b04… drh 692 }
1f41b04… drh 693 db_reset(&q1);
49b0ff1… drh 694 }
49b0ff1… drh 695
49b0ff1… drh 696 /*
27d743e… drh 697 ** Send the unversioned file identified by zName by generating the
27d743e… drh 698 ** appropriate "uvfile" card.
27d743e… drh 699 **
27d743e… drh 700 ** uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT
27d743e… drh 701 **
27d743e… drh 702 ** If the noContent flag is set, omit the CONTENT and set the 0x0004
27d743e… drh 703 ** flag in FLAGS.
27d743e… drh 704 */
27d743e… drh 705 static void send_unversioned_file(
27d743e… drh 706 Xfer *pXfer, /* Transfer context */
27d743e… drh 707 const char *zName, /* Name of unversioned file to be sent */
27d743e… drh 708 int noContent /* True to omit the content */
27d743e… drh 709 ){
27d743e… drh 710 Stmt q1;
27d743e… drh 711
53db40e… drh 712 if( (int)blob_size(pXfer->pOut)>=pXfer->mxSend ) noContent = 1;
27d743e… drh 713 if( noContent ){
27d743e… drh 714 db_prepare(&q1,
27d743e… drh 715 "SELECT mtime, hash, encoding, sz FROM unversioned WHERE name=%Q",
27d743e… drh 716 zName
27d743e… drh 717 );
27d743e… drh 718 }else{
27d743e… drh 719 db_prepare(&q1,
27d743e… drh 720 "SELECT mtime, hash, encoding, sz, content FROM unversioned"
27d743e… drh 721 " WHERE name=%Q",
27d743e… drh 722 zName
27d743e… drh 723 );
27d743e… drh 724 }
27d743e… drh 725 if( db_step(&q1)==SQLITE_ROW ){
27d743e… drh 726 sqlite3_int64 mtime = db_column_int64(&q1, 0);
27d743e… drh 727 const char *zHash = db_column_text(&q1, 1);
1f7b409… drh 728 if( pXfer->remoteVersion<20000 && db_column_bytes(&q1,1)>HNAME_LEN_SHA1 ){
a137387… drh 729 xfer_cannot_send_sha3_error(pXfer);
a137387… drh 730 db_reset(&q1);
a137387… drh 731 return;
a137387… drh 732 }
53db40e… drh 733 if( (int)blob_size(pXfer->pOut)>=pXfer->mxSend ){
27d743e… drh 734 /* If we have already reached the send size limit, send a (short)
27d743e… drh 735 ** uvigot card rather than a uvfile card. This only happens on the
27d743e… drh 736 ** server side. The uvigot card will provoke the client to resend
27d743e… drh 737 ** another uvgimme on the next cycle. */
27d743e… drh 738 blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
27d743e… drh 739 zName, mtime, zHash, db_column_int(&q1,3));
27d743e… drh 740 }else{
27d743e… drh 741 blob_appendf(pXfer->pOut, "uvfile %s %lld", zName, mtime);
27d743e… drh 742 if( zHash==0 ){
27d743e… drh 743 blob_append(pXfer->pOut, " - 0 1\n", -1);
27d743e… drh 744 }else if( noContent ){
27d743e… drh 745 blob_appendf(pXfer->pOut, " %s %d 4\n", zHash, db_column_int(&q1,3));
27d743e… drh 746 }else{
27d743e… drh 747 Blob content;
27d743e… drh 748 blob_init(&content, 0, 0);
27d743e… drh 749 db_column_blob(&q1, 4, &content);
27d743e… drh 750 if( db_column_int(&q1, 2) ){
27d743e… drh 751 blob_uncompress(&content, &content);
27d743e… drh 752 }
27d743e… drh 753 blob_appendf(pXfer->pOut, " %s %d 0\n", zHash, blob_size(&content));
27d743e… drh 754 blob_append(pXfer->pOut, blob_buffer(&content), blob_size(&content));
27d743e… drh 755 blob_reset(&content);
27d743e… drh 756 }
27d743e… drh 757 }
27d743e… drh 758 }
27d743e… drh 759 db_finalize(&q1);
27d743e… drh 760 }
27d743e… drh 761
27d743e… drh 762 /*
bbcb632… aku 763 ** Send a gimme message for every phantom.
02a584f… drh 764 **
8b8cc4f… drh 765 ** Except: do not request shunned artifacts. And do not request
8b8cc4f… drh 766 ** private artifacts if we are not doing a private transfer.
15652ff… aku 767 */
dbdc160… aku 768 static void request_phantoms(Xfer *pXfer, int maxReq){
15652ff… aku 769 Stmt q;
45f3516… jan.nijtmans 770 db_prepare(&q,
421c9ee… drh 771 "SELECT uuid FROM phantom CROSS JOIN blob USING(rid) /*scan*/"
405457c… drh 772 " WHERE NOT EXISTS(SELECT 1 FROM unk WHERE unk.uuid=blob.uuid)"
405457c… drh 773 " AND NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid) %s",
8b8cc4f… drh 774 (pXfer->syncPrivate ? "" :
8b8cc4f… drh 775 " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)")
d0305b3… aku 776 );
dbdc160… aku 777 while( db_step(&q)==SQLITE_ROW && maxReq-- > 0 ){
edbb332… drh 778 const char *zUuid = db_column_text(&q, 0);
edbb332… drh 779 blob_appendf(pXfer->pOut, "gimme %s\n", zUuid);
edbb332… drh 780 pXfer->nGimmeSent++;
edbb332… drh 781 }
edbb332… drh 782 db_finalize(&q);
edbb332… drh 783 }
edbb332… drh 784
f76192b… aku 785 /*
fd9b7bd… drh 786 ** Compute an hash on the tail of pMsg. Verify that it matches the
390b414… drh 787 ** the hash given in pHash. Return non-zero for an error and 0 on success.
fd9b7bd… drh 788 **
8a76861… jan.nijtmans 789 ** The type of hash computed (SHA1, SHA3-256) is determined by
fd9b7bd… drh 790 ** the length of the input hash in pHash.
f76192b… aku 791 */
f76192b… aku 792 static int check_tail_hash(Blob *pHash, Blob *pMsg){
f76192b… aku 793 Blob tail;
f76192b… aku 794 int rc;
f76192b… aku 795 blob_tail(pMsg, &tail);
fd9b7bd… drh 796 rc = hname_verify_hash(&tail, blob_buffer(pHash), blob_size(pHash));
f76192b… aku 797 blob_reset(&tail);
fd9b7bd… drh 798 return rc==HNAME_ERROR;
f76192b… aku 799 }
dbda8d6… drh 800
dbda8d6… drh 801 /*
dbda8d6… drh 802 ** Check the signature on an application/x-fossil payload received by
dbda8d6… drh 803 ** the HTTP server. The signature is a line of the following form:
dbda8d6… drh 804 **
dbda8d6… drh 805 ** login LOGIN NONCE SIGNATURE
dbda8d6… drh 806 **
45f3516… jan.nijtmans 807 ** The NONCE is the SHA1 hash of the remainder of the input.
45f3516… jan.nijtmans 808 ** SIGNATURE is the SHA1 checksum of the NONCE concatenated
41ba6ea… drh 809 ** with the sha1_shared_secret() encoding of the users password.
41ba6ea… drh 810 **
41ba6ea… drh 811 ** SIGNATURE = sha1_sum( NONCE + sha1_shared_secret(PASSWORD) );
dbda8d6… drh 812 **
db0c512… drh 813 ** The parameters to this routine are ephemeral blobs holding the
dbda8d6… drh 814 ** LOGIN, NONCE and SIGNATURE.
dbda8d6… drh 815 **
dbda8d6… drh 816 ** This routine attempts to locate the user and verify the signature.
dbda8d6… drh 817 ** If everything checks out, the USER.CAP column for the USER table
dbda8d6… drh 818 ** is consulted to set privileges in the global g variable.
dbda8d6… drh 819 **
dbda8d6… drh 820 ** If anything fails to check out, no changes are made to privileges.
dbda8d6… drh 821 **
45f3516… jan.nijtmans 822 ** Signature generation on the client side is handled by the
dbda8d6… drh 823 ** http_exchange() routine.
390b414… drh 824 **
390b414… drh 825 ** Return non-zero for a login failure and zero for success.
dbda8d6… drh 826 */
6d0083a… drh 827 static int check_login(Blob *pLogin, Blob *pNonce, Blob *pSig){
dbda8d6… drh 828 Stmt q;
6021279… drh 829 int rc = -1;
7100bab… drh 830 char *zLogin = blob_terminate(pLogin);
7100bab… drh 831 defossilize(zLogin);
dbda8d6… drh 832
27d743e… drh 833 if( fossil_strcmp(zLogin, "nobody")==0
1862890… stephan 834 || fossil_strcmp(zLogin, "anonymous")==0
27d743e… drh 835 ){
1942d58… drh 836 return 0; /* Anybody is allowed to sync as "nobody" or "anonymous" */
c1506ad… drh 837 }
5d536c5… drh 838 if( fossil_strcmp(P("REMOTE_USER"), zLogin)==0
5d536c5… drh 839 && db_get_boolean("remote_user_ok",0) ){
c1506ad… drh 840 return 0; /* Accept Basic Authorization */
1942d58… drh 841 }
6021279… drh 842 db_prepare(&q,
6021279… drh 843 "SELECT pw, cap, uid FROM user"
7100bab… drh 844 " WHERE login=%Q"
bbb8ae7… drh 845 " AND login NOT IN ('anonymous','nobody','developer','reader')"
6021279… drh 846 " AND length(pw)>0",
7100bab… drh 847 zLogin
6021279… drh 848 );
dbda8d6… drh 849 if( db_step(&q)==SQLITE_ROW ){
596f3c1… drh 850 int szPw;
dbda8d6… drh 851 Blob pw, combined, hash;
dbda8d6… drh 852 blob_zero(&pw);
dbda8d6… drh 853 db_ephemeral_blob(&q, 0, &pw);
596f3c1… drh 854 szPw = blob_size(&pw);
dbda8d6… drh 855 blob_zero(&combined);
dbda8d6… drh 856 blob_copy(&combined, pNonce);
596f3c1… drh 857 blob_append(&combined, blob_buffer(&pw), szPw);
dbda8d6… drh 858 sha1sum_blob(&combined, &hash);
596f3c1… drh 859 assert( blob_size(&hash)==40 );
d4a341b… dmitry 860 rc = blob_constant_time_cmp(&hash, pSig);
dbda8d6… drh 861 blob_reset(&hash);
dbda8d6… drh 862 blob_reset(&combined);
596f3c1… drh 863 if( rc!=0 && szPw!=40 ){
596f3c1… drh 864 /* If this server stores cleartext passwords and the password did not
596f3c1… drh 865 ** match, then perhaps the client is sending SHA1 passwords. Try
596f3c1… drh 866 ** again with the SHA1 password.
596f3c1… drh 867 */
596f3c1… drh 868 const char *zPw = db_column_text(&q, 0);
a257fde… drh 869 char *zSecret = sha1_shared_secret(zPw, blob_str(pLogin), 0);
596f3c1… drh 870 blob_zero(&combined);
596f3c1… drh 871 blob_copy(&combined, pNonce);
596f3c1… drh 872 blob_append(&combined, zSecret, -1);
1862890… stephan 873 fossil_free(zSecret);
596f3c1… drh 874 sha1sum_blob(&combined, &hash);
d4a341b… dmitry 875 rc = blob_constant_time_cmp(&hash, pSig);
596f3c1… drh 876 blob_reset(&hash);
596f3c1… drh 877 blob_reset(&combined);
596f3c1… drh 878 }
916b6e4… drh 879 if( rc==0 ){
dbda8d6… drh 880 const char *zCap;
dbda8d6… drh 881 zCap = db_column_text(&q, 1);
3bd2de4… drh 882 login_set_capabilities(zCap, 0);
dbda8d6… drh 883 g.userUid = db_column_int(&q, 2);
dbda8d6… drh 884 g.zLogin = mprintf("%b", pLogin);
dbda8d6… drh 885 g.zNonce = mprintf("%b", pNonce);
dbda8d6… drh 886 }
dbda8d6… drh 887 }
31e94c0… drh 888 db_finalize(&q);
390b414… drh 889 return rc;
bbcb632… aku 890 }
bbcb632… aku 891
bbcb632… aku 892 /*
bbcb632… aku 893 ** Send the content of all files in the unsent table.
bbcb632… aku 894 **
bbcb632… aku 895 ** This is really just an optimization. If you clear the
bbcb632… aku 896 ** unsent table, all the right files will still get transferred.
bbcb632… aku 897 ** It just might require an extra round trip or two.
bbcb632… aku 898 */
bbcb632… aku 899 static void send_unsent(Xfer *pXfer){
bbcb632… aku 900 Stmt q;
02a584f… drh 901 db_prepare(&q, "SELECT rid FROM unsent EXCEPT SELECT rid FROM private");
bbcb632… aku 902 while( db_step(&q)==SQLITE_ROW ){
bbcb632… aku 903 int rid = db_column_int(&q, 0);
bbcb632… aku 904 send_file(pXfer, rid, 0, 0);
bbcb632… aku 905 }
bbcb632… aku 906 db_finalize(&q);
bbcb632… aku 907 db_multi_exec("DELETE FROM unsent");
bbcb632… aku 908 }
bbcb632… aku 909
bbcb632… aku 910 /*
bbcb632… aku 911 ** Check to see if the number of unclustered entries is greater than
bbcb632… aku 912 ** 100 and if it is, form a new cluster. Unclustered phantoms do not
bbcb632… aku 913 ** count toward the 100 total. And phantoms are never added to a new
bbcb632… aku 914 ** cluster.
bbcb632… aku 915 */
fda9b15… drh 916 void create_cluster(void){
bbcb632… aku 917 Blob cluster, cksum;
64a9c81… drh 918 Blob deleteWhere;
bbcb632… aku 919 Stmt q;
bbcb632… aku 920 int nUncl;
fda9b15… drh 921 int nRow = 0;
64a9c81… drh 922 int rid;
02a584f… drh 923
5da9a8c… drh 924 #if 0
02a584f… drh 925 /* We should not ever get any private artifacts in the unclustered table.
02a584f… drh 926 ** But if we do (because of a bug) now is a good time to delete them. */
02a584f… drh 927 db_multi_exec(
02a584f… drh 928 "DELETE FROM unclustered WHERE rid IN (SELECT rid FROM private)"
02a584f… drh 929 );
5da9a8c… drh 930 #endif
02a584f… drh 931
71c40d3… drh 932 nUncl = db_int(0, "SELECT count(*) FROM unclustered /*scan*/"
bbcb632… aku 933 " WHERE NOT EXISTS(SELECT 1 FROM phantom"
bbcb632… aku 934 " WHERE rid=unclustered.rid)");
fda9b15… drh 935 if( nUncl>=100 ){
fda9b15… drh 936 blob_zero(&cluster);
64a9c81… drh 937 blob_zero(&deleteWhere);
fda9b15… drh 938 db_prepare(&q, "SELECT uuid FROM unclustered, blob"
fda9b15… drh 939 " WHERE NOT EXISTS(SELECT 1 FROM phantom"
fda9b15… drh 940 " WHERE rid=unclustered.rid)"
fda9b15… drh 941 " AND unclustered.rid=blob.rid"
fda9b15… drh 942 " AND NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
fda9b15… drh 943 " ORDER BY 1");
fda9b15… drh 944 while( db_step(&q)==SQLITE_ROW ){
fda9b15… drh 945 blob_appendf(&cluster, "M %s\n", db_column_text(&q, 0));
fda9b15… drh 946 nRow++;
fda9b15… drh 947 if( nRow>=800 && nUncl>nRow+100 ){
fda9b15… drh 948 md5sum_blob(&cluster, &cksum);
fda9b15… drh 949 blob_appendf(&cluster, "Z %b\n", &cksum);
fda9b15… drh 950 blob_reset(&cksum);
1b45161… drh 951 rid = content_put(&cluster);
2460659… drh 952 manifest_crosslink(rid, &cluster, MC_NONE);
fda9b15… drh 953 blob_reset(&cluster);
fda9b15… drh 954 nUncl -= nRow;
fda9b15… drh 955 nRow = 0;
49b0ff1… drh 956 blob_append_sql(&deleteWhere, ",%d", rid);
fda9b15… drh 957 }
fda9b15… drh 958 }
fda9b15… drh 959 db_finalize(&q);
64a9c81… drh 960 db_multi_exec(
3fa7965… drh 961 "DELETE FROM unclustered WHERE rid NOT IN (0 %s)"
3fa7965… drh 962 " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=unclustered.rid)",
49b0ff1… drh 963 blob_sql_text(&deleteWhere)
64a9c81… drh 964 );
64a9c81… drh 965 blob_reset(&deleteWhere);
fda9b15… drh 966 if( nRow>0 ){
fda9b15… drh 967 md5sum_blob(&cluster, &cksum);
fda9b15… drh 968 blob_appendf(&cluster, "Z %b\n", &cksum);
fda9b15… drh 969 blob_reset(&cksum);
2460659… drh 970 rid = content_put(&cluster);
2460659… drh 971 manifest_crosslink(rid, &cluster, MC_NONE);
fda9b15… drh 972 blob_reset(&cluster);
fda9b15… drh 973 }
fda9b15… drh 974 }
fda9b15… drh 975 }
fda9b15… drh 976
fda9b15… drh 977 /*
8b8cc4f… drh 978 ** Send igot messages for every private artifact
8b8cc4f… drh 979 */
8b8cc4f… drh 980 static int send_private(Xfer *pXfer){
8b8cc4f… drh 981 int cnt = 0;
8b8cc4f… drh 982 Stmt q;
8b8cc4f… drh 983 if( pXfer->syncPrivate ){
8b8cc4f… drh 984 db_prepare(&q, "SELECT uuid FROM private JOIN blob USING(rid)");
8b8cc4f… drh 985 while( db_step(&q)==SQLITE_ROW ){
8b8cc4f… drh 986 blob_appendf(pXfer->pOut, "igot %s 1\n", db_column_text(&q,0));
8b8cc4f… drh 987 cnt++;
8b8cc4f… drh 988 }
8b8cc4f… drh 989 db_finalize(&q);
8b8cc4f… drh 990 }
8b8cc4f… drh 991 return cnt;
8b8cc4f… drh 992 }
8b8cc4f… drh 993
8b8cc4f… drh 994 /*
bbcb632… aku 995 ** Send an igot message for every entry in unclustered table.
243e02b… drh 996 ** Return the number of cards sent.
1f7b409… drh 997 **
1f7b409… drh 998 ** Except:
1f7b409… drh 999 ** * Do not send igot cards for shunned artifacts
1f7b409… drh 1000 ** * Do not send igot cards for phantoms
1f7b409… drh 1001 ** * Do not send igot cards for private artifacts
1f7b409… drh 1002 ** * Do not send igot cards for any artifact that is in the
1f7b409… drh 1003 ** ONREMOTE table, if that table exists.
1f7b409… drh 1004 **
1f7b409… drh 1005 ** If the pXfer->resync flag is set, that means we are doing a "--verily"
1f7b409… drh 1006 ** sync and all artifacts that don't meet the restrictions above should
1f7b409… drh 1007 ** be sent.
bbcb632… aku 1008 */
bbcb632… aku 1009 static int send_unclustered(Xfer *pXfer){
bbcb632… aku 1010 Stmt q;
bbcb632… aku 1011 int cnt = 0;
1f7b409… drh 1012 const char *zExtra;
1f7b409… drh 1013 if( db_table_exists("temp","onremote") ){
1f7b409… drh 1014 zExtra = " AND NOT EXISTS(SELECT 1 FROM onremote WHERE rid=blob.rid)";
1f7b409… drh 1015 }else{
1f7b409… drh 1016 zExtra = "";
1f7b409… drh 1017 }
05ba15e… drh 1018 if( pXfer->resync ){
45f3516… jan.nijtmans 1019 db_prepare(&q,
05ba15e… drh 1020 "SELECT uuid, rid FROM blob"
05ba15e… drh 1021 " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
05ba15e… drh 1022 " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
1f7b409… drh 1023 " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)%s"
05ba15e… drh 1024 " AND blob.rid<=%d"
05ba15e… drh 1025 " ORDER BY blob.rid DESC",
1f7b409… drh 1026 zExtra /*safe-for-%s*/, pXfer->resync
05ba15e… drh 1027 );
05ba15e… drh 1028 }else{
45f3516… jan.nijtmans 1029 db_prepare(&q,
85dd55d… drh 1030 "SELECT uuid FROM unclustered JOIN blob USING(rid) /*scan*/"
05ba15e… drh 1031 " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
05ba15e… drh 1032 " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
1f7b409… drh 1033 " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)%s",
1f7b409… drh 1034 zExtra /*safe-for-%s*/
05ba15e… drh 1035 );
05ba15e… drh 1036 }
05ba15e… drh 1037 while( db_step(&q)==SQLITE_ROW ){
05ba15e… drh 1038 blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
05ba15e… drh 1039 cnt++;
53db40e… drh 1040 if( pXfer->resync && pXfer->mxSend<(int)blob_size(pXfer->pOut) ){
05ba15e… drh 1041 pXfer->resync = db_column_int(&q, 1)-1;
05ba15e… drh 1042 }
05ba15e… drh 1043 }
05ba15e… drh 1044 db_finalize(&q);
05ba15e… drh 1045 if( cnt==0 ) pXfer->resync = 0;
53db40e… drh 1046 return cnt;
53db40e… drh 1047 }
53db40e… drh 1048
53db40e… drh 1049 /*
637fcef… drh 1050 ** Send an igot message for every cluster artifact that is not a phantom,
637fcef… drh 1051 ** is not shunned, is not private, and that is not in the UNCLUSTERED table.
637fcef… drh 1052 ** Return the number of cards sent.
637fcef… drh 1053 */
637fcef… drh 1054 static int send_all_clusters(Xfer *pXfer){
637fcef… drh 1055 Stmt q;
637fcef… drh 1056 int cnt = 0;
637fcef… drh 1057 const char *zExtra;
637fcef… drh 1058 if( db_table_exists("temp","onremote") ){
637fcef… drh 1059 zExtra = " AND NOT EXISTS(SELECT 1 FROM onremote WHERE rid=blob.rid)";
637fcef… drh 1060 }else{
637fcef… drh 1061 zExtra = "";
637fcef… drh 1062 }
637fcef… drh 1063 db_prepare(&q,
637fcef… drh 1064 "SELECT uuid"
637fcef… drh 1065 " FROM tagxref JOIN blob ON tagxref.rid=blob.rid AND tagxref.tagid=%d"
637fcef… drh 1066 " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
637fcef… drh 1067 " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
637fcef… drh 1068 " AND NOT EXISTS(SELECT 1 FROM unclustered WHERE rid=blob.rid)"
637fcef… drh 1069 " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)%s",
637fcef… drh 1070 TAG_CLUSTER, zExtra /*safe-for-%s*/
637fcef… drh 1071 );
637fcef… drh 1072 while( db_step(&q)==SQLITE_ROW ){
637fcef… drh 1073 if( cnt==0 ) blob_appendf(pXfer->pOut, "# sending-clusters\n");
637fcef… drh 1074 blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
637fcef… drh 1075 cnt++;
637fcef… drh 1076 }
637fcef… drh 1077 db_finalize(&q);
637fcef… drh 1078 if( cnt ) blob_appendf(pXfer->pOut, "# end-of-clusters\n");
637fcef… drh 1079 return cnt;
637fcef… drh 1080 }
637fcef… drh 1081
637fcef… drh 1082 /*
243e02b… drh 1083 ** Send an igot message for every artifact.
243e02b… drh 1084 */
243e02b… drh 1085 static void send_all(Xfer *pXfer){
243e02b… drh 1086 Stmt q;
45f3516… jan.nijtmans 1087 db_prepare(&q,
a48474b… drh 1088 "SELECT uuid FROM blob "
a48474b… drh 1089 " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
02a584f… drh 1090 " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
7c2577b… drh 1091 " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
a48474b… drh 1092 );
243e02b… drh 1093 while( db_step(&q)==SQLITE_ROW ){
243e02b… drh 1094 blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
243e02b… drh 1095 }
243e02b… drh 1096 db_finalize(&q);
6b0b57a… drh 1097 }
6b0b57a… drh 1098
6b0b57a… drh 1099 /*
27d743e… drh 1100 ** pXfer is a "pragma uv-hash HASH" card.
1654456… drh 1101 **
27d743e… drh 1102 ** If HASH is different from the unversioned content hash on this server,
27d743e… drh 1103 ** then send a bunch of uvigot cards, one for each entry unversioned file
27d743e… drh 1104 ** on this server.
27d743e… drh 1105 */
27d743e… drh 1106 static void send_unversioned_catalog(Xfer *pXfer){
d015a26… drh 1107 Stmt uvq;
27d743e… drh 1108 unversioned_schema();
d015a26… drh 1109 db_prepare(&uvq,
d015a26… drh 1110 "SELECT name, mtime, hash, sz FROM unversioned"
d015a26… drh 1111 );
d015a26… drh 1112 while( db_step(&uvq)==SQLITE_ROW ){
d015a26… drh 1113 const char *zName = db_column_text(&uvq,0);
d015a26… drh 1114 sqlite3_int64 mtime = db_column_int64(&uvq,1);
d015a26… drh 1115 const char *zHash = db_column_text(&uvq,2);
d015a26… drh 1116 int sz = db_column_int(&uvq,3);
d015a26… drh 1117 if( zHash==0 ){ sz = 0; zHash = "-"; }
d015a26… drh 1118 blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
d015a26… drh 1119 zName, mtime, zHash, sz);
d015a26… drh 1120 }
d015a26… drh 1121 db_finalize(&uvq);
d015a26… drh 1122 }
d015a26… drh 1123
d015a26… drh 1124 /*
2765f04… drh 1125 ** Return a string that contains supplemental information about a
2765f04… drh 1126 ** "not authorized" error. The string might be empty if no additional
2765f04… drh 1127 ** information is available.
2765f04… drh 1128 */
2765f04… drh 1129 static char *whyNotAuth(void){
00638d9… drh 1130 if( g.useLocalauth && db_get_boolean("localauth",0)!=0 ){
2765f04… drh 1131 return "\\sbecause\\sthe\\s'localauth'\\ssetting\\sis\\senabled";
2765f04… drh 1132 }
2765f04… drh 1133 return "";
2765f04… drh 1134 }
2765f04… drh 1135
2765f04… drh 1136 /*
8b8cc4f… drh 1137 ** Called when there is an attempt to transfer private content to and
8b8cc4f… drh 1138 ** from a server without authorization.
8b8cc4f… drh 1139 */
8b8cc4f… drh 1140 static void server_private_xfer_not_authorized(void){
2765f04… drh 1141 @ error not\sauthorized\sto\ssync\sprivate\scontent%s(whyNotAuth())
1311841… jan.nijtmans 1142 }
1311841… jan.nijtmans 1143
1311841… jan.nijtmans 1144 /*
1311841… jan.nijtmans 1145 ** Return the common TH1 code to evaluate prior to evaluating any other
1311841… jan.nijtmans 1146 ** TH1 transfer notification scripts.
1311841… jan.nijtmans 1147 */
1311841… jan.nijtmans 1148 const char *xfer_common_code(void){
1311841… jan.nijtmans 1149 return db_get("xfer-common-script", 0);
1311841… jan.nijtmans 1150 }
1311841… jan.nijtmans 1151
1311841… jan.nijtmans 1152 /*
1311841… jan.nijtmans 1153 ** Return the TH1 code to evaluate when a push is processed.
1311841… jan.nijtmans 1154 */
1311841… jan.nijtmans 1155 const char *xfer_push_code(void){
1311841… jan.nijtmans 1156 return db_get("xfer-push-script", 0);
1311841… jan.nijtmans 1157 }
1311841… jan.nijtmans 1158
1311841… jan.nijtmans 1159 /*
1311841… jan.nijtmans 1160 ** Return the TH1 code to evaluate when a commit is processed.
1311841… jan.nijtmans 1161 */
1311841… jan.nijtmans 1162 const char *xfer_commit_code(void){
1311841… jan.nijtmans 1163 return db_get("xfer-commit-script", 0);
1311841… jan.nijtmans 1164 }
1311841… jan.nijtmans 1165
1311841… jan.nijtmans 1166 /*
1311841… jan.nijtmans 1167 ** Return the TH1 code to evaluate when a ticket change is processed.
1311841… jan.nijtmans 1168 */
1311841… jan.nijtmans 1169 const char *xfer_ticket_code(void){
1311841… jan.nijtmans 1170 return db_get("xfer-ticket-script", 0);
295b396… stephan 1171 }
295b396… stephan 1172
295b396… stephan 1173 /*
295b396… stephan 1174 ** Reset the CGI content, roll back any pending db transaction, and
f9f90d9… stephan 1175 ** emit an "error" xfer message. The message text gets fossil-encoded
f9f90d9… stephan 1176 ** by this function. This is only intended for use with
f9f90d9… stephan 1177 ** fail-fast/fatal errors, not ones which can be skipped over.
295b396… stephan 1178 */
f9f90d9… stephan 1179 static void xfer_fatal_error(const char *zMsg){
295b396… stephan 1180 cgi_reset_content();
f9f90d9… stephan 1181 if( db_transaction_nesting_depth()>0 ){
295b396… stephan 1182 db_rollback_transaction();
295b396… stephan 1183 }
f9f90d9… stephan 1184 @ error %F(zMsg)
fc32fae… mistachkin 1185 }
fc32fae… mistachkin 1186
fc32fae… mistachkin 1187 /*
42316a1… mistachkin 1188 ** Run the specified TH1 script, if any, and returns 1 on error.
42316a1… mistachkin 1189 */
fc32fae… mistachkin 1190 int xfer_run_script(
fc32fae… mistachkin 1191 const char *zScript,
fc32fae… mistachkin 1192 const char *zUuidOrList,
fc32fae… mistachkin 1193 int bIsList
fc32fae… mistachkin 1194 ){
fc32fae… mistachkin 1195 int rc = TH_OK;
fc32fae… mistachkin 1196 if( !zScript ) return rc;
42316a1… mistachkin 1197 Th_FossilInit(TH_INIT_DEFAULT);
fc32fae… mistachkin 1198 Th_Store(bIsList ? "uuids" : "uuid", zUuidOrList ? zUuidOrList : "");
42316a1… mistachkin 1199 rc = Th_Eval(g.interp, 0, zScript, -1);
42316a1… mistachkin 1200 if( rc!=TH_OK ){
42316a1… mistachkin 1201 fossil_error(1, "%s", Th_GetResult(g.interp, 0));
42316a1… mistachkin 1202 }
42316a1… mistachkin 1203 return rc;
1311841… jan.nijtmans 1204 }
1311841… jan.nijtmans 1205
1311841… jan.nijtmans 1206 /*
1311841… jan.nijtmans 1207 ** Runs the pre-transfer TH1 script, if any, and returns its return code.
1311841… jan.nijtmans 1208 ** This script may be run multiple times. If the script performs actions
1311841… jan.nijtmans 1209 ** that cannot be redone, it should use an internal [if] guard similar to
1311841… jan.nijtmans 1210 ** the following:
1311841… jan.nijtmans 1211 **
1311841… jan.nijtmans 1212 ** if {![info exists common_done]} {
1311841… jan.nijtmans 1213 ** # ... code here
1311841… jan.nijtmans 1214 ** set common_done 1
1311841… jan.nijtmans 1215 ** }
1311841… jan.nijtmans 1216 */
1311841… jan.nijtmans 1217 int xfer_run_common_script(void){
fc32fae… mistachkin 1218 return xfer_run_script(xfer_common_code(), 0, 0);
fc32fae… mistachkin 1219 }
fc32fae… mistachkin 1220
fc32fae… mistachkin 1221 /*
84358b7… drh 1222 ** This routine makes a "syncwith:URL" entry in the CONFIG table to
e2bdc10… danield 1223 ** indicate that a sync is occurring with zUrl.
ebcce3c… drh 1224 **
ebcce3c… drh 1225 ** Add a "syncfrom:URL" entry instead of "syncwith:URL" if bSyncFrom is true.
84358b7… drh 1226 */
ebcce3c… drh 1227 static void xfer_syncwith(const char *zUrl, int bSyncFrom){
84358b7… drh 1228 UrlData x;
84358b7… drh 1229 memset(&x, 0, sizeof(x));
84358b7… drh 1230 url_parse_local(zUrl, URL_OMIT_USER, &x);
84358b7… drh 1231 if( x.protocol && strncmp(x.protocol,"http",4)==0
84358b7… drh 1232 && x.name && sqlite3_strlike("%localhost%", x.name, 0)!=0
84358b7… drh 1233 ){
84358b7… drh 1234 db_unprotect(PROTECT_CONFIG);
84358b7… drh 1235 db_multi_exec("REPLACE INTO config(name,value,mtime)"
ebcce3c… drh 1236 "VALUES('sync%q:%q','{}',now())",
ebcce3c… drh 1237 bSyncFrom ? "from" : "with", x.canonical);
84358b7… drh 1238 db_protect_pop();
84358b7… drh 1239 }
84358b7… drh 1240 url_unparse(&x);
84358b7… drh 1241 }
84358b7… drh 1242
84358b7… drh 1243 /*
dbda8d6… drh 1244 ** If this variable is set, disable login checks. Used for debugging
dbda8d6… drh 1245 ** only.
dbda8d6… drh 1246 */
dbda8d6… drh 1247 static int disableLogin = 0;
1862890… stephan 1248
1862890… stephan 1249 /*
1862890… stephan 1250 ** Must be passed the version info from pragmas
1862890… stephan 1251 ** client-version/server-version cards. If the version info is "new
1862890… stephan 1252 ** enough" then the loginCardMode is ORd into the X-Fossil-Xfer-Login
1862890… stephan 1253 ** card flag, else this is a no-op.
1862890… stephan 1254 */
1862890… stephan 1255 static void xfer_xflc_check(int iRemoteVersion, int iDate, int iTime,
1862890… stephan 1256 int fLoginCardMode){
1862890… stephan 1257 if( iRemoteVersion>=22700
1862890… stephan 1258 && (iDate > 20250727
1862890… stephan 1259 || (iDate == 20250727 && iTime >= 110500)) ){
1862890… stephan 1260 g.syncInfo.fLoginCardMode |= fLoginCardMode;
1862890… stephan 1261 }
1862890… stephan 1262 }
650305e… drh 1263
650305e… drh 1264 /*
16ec6e5… drh 1265 ** The CGI/HTTP preprocessor always redirects requests with a content-type
16ec6e5… drh 1266 ** of application/x-fossil or application/x-fossil-debug to this page,
16ec6e5… drh 1267 ** regardless of what path was specified in the HTTP header. This allows
16ec6e5… drh 1268 ** clone clients to specify a URL that omits default pathnames, such
e365324… bharder 1269 ** as "http://fossil-scm.org/" instead of "http://fossil-scm.org/index.cgi".
16ec6e5… drh 1270 **
650305e… drh 1271 ** WEBPAGE: xfer raw-content loadavg-exempt
dbda8d6… drh 1272 **
dbda8d6… drh 1273 ** This is the transfer handler on the server side. The transfer
dbda8d6… drh 1274 ** message has been uncompressed and placed in the g.cgiIn blob.
dbda8d6… drh 1275 ** Process this message and form an appropriate reply.
dbda8d6… drh 1276 */
dbda8d6… drh 1277 void page_xfer(void){
edbb332… drh 1278 int isPull = 0;
edbb332… drh 1279 int isPush = 0;
edbb332… drh 1280 int nErr = 0;
edbb332… drh 1281 Xfer xfer;
d0305b3… aku 1282 int deltaFlag = 0;
243e02b… drh 1283 int isClone = 0;
243e02b… drh 1284 int nGimme = 0;
6b0b57a… drh 1285 int size;
8b8cc4f… drh 1286 char *zNow;
42316a1… mistachkin 1287 int rc;
fc32fae… mistachkin 1288 const char *zScript = 0;
fc32fae… mistachkin 1289 char *zUuidList = 0;
fc32fae… mistachkin 1290 int nUuidList = 0;
fc32fae… mistachkin 1291 char **pzUuidList = 0;
fc32fae… mistachkin 1292 int *pnUuidList = 0;
b9fd594… drh 1293 int uvCatalogSent = 0;
12d2f70… drh 1294 int bSendLinks = 0;
1862890… stephan 1295 int nLogin = 0;
8b8cc4f… drh 1296
32ad9a1… drh 1297 if( fossil_strcmp(PD("REQUEST_METHOD","POST"),"POST") ){
8b8cc4f… drh 1298 fossil_redirect_home();
8b8cc4f… drh 1299 }
8b8cc4f… drh 1300 g.zLogin = "anonymous";
8b8cc4f… drh 1301 login_set_anon_nobody_capabilities();
c1506ad… drh 1302 login_check_credentials();
e81cac3… drh 1303 if( g.perm.Clone ) g.perm.Read = 1;
57f1e87… drh 1304 cgi_check_for_malice();
8b8cc4f… drh 1305 memset(&xfer, 0, sizeof(xfer));
8b8cc4f… drh 1306 blobarray_zero(xfer.aToken, count(xfer.aToken));
8b8cc4f… drh 1307 cgi_set_content_type(g.zContentType);
2d16208… drh 1308 cgi_reset_content();
0ca3644… drh 1309 if( db_schema_is_outofdate() ){
dc87a90… bharder 1310 @ error database\sschema\sis\sout-of-date\son\sthe\sserver.
0ca3644… drh 1311 return;
0ca3644… drh 1312 }
8b8cc4f… drh 1313 blob_zero(&xfer.err);
8b8cc4f… drh 1314 xfer.pIn = &g.cgiIn;
8b8cc4f… drh 1315 xfer.pOut = cgi_output_blob();
8b8cc4f… drh 1316 xfer.mxSend = db_get_int("max-download", 5000000);
769c90a… drh 1317 xfer.maxTime = db_get_int("max-download-time", 30);
769c90a… drh 1318 if( xfer.maxTime<1 ) xfer.maxTime = 1;
769c90a… drh 1319 xfer.maxTime += time(NULL);
8b8cc4f… drh 1320 g.xferPanic = 1;
8b8cc4f… drh 1321
d9543f4… drh 1322 db_begin_write();
8b8cc4f… drh 1323 db_multi_exec(
8b8cc4f… drh 1324 "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
405457c… drh 1325 "CREATE TEMP TABLE unk(uuid TEXT PRIMARY KEY) WITHOUT ROWID;"
8b8cc4f… drh 1326 );
8b8cc4f… drh 1327 manifest_crosslink_begin();
42316a1… mistachkin 1328 rc = xfer_run_common_script();
42316a1… mistachkin 1329 if( rc==TH_ERROR ){
0b61e3c… drh 1330 cgi_reset_content();
1311841… jan.nijtmans 1331 @ error common\sscript\sfailed:\s%F(g.zErrMsg)
0b61e3c… drh 1332 nErr++;
42316a1… mistachkin 1333 }
fc32fae… mistachkin 1334 zScript = xfer_push_code();
fc32fae… mistachkin 1335 if( zScript ){ /* NOTE: Are TH1 transfer hooks enabled? */
fc32fae… mistachkin 1336 pzUuidList = &zUuidList;
fc32fae… mistachkin 1337 pnUuidList = &nUuidList;
1862890… stephan 1338 }
1862890… stephan 1339 if( g.syncInfo.zLoginCard ){
1862890… stephan 1340 /* Login card received via HTTP Cookie header */
1862890… stephan 1341 blob_zero(&xfer.line);
1862890… stephan 1342 blob_append(&xfer.line, g.syncInfo.zLoginCard, -1);
1862890… stephan 1343 xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken,
1862890… stephan 1344 count(xfer.aToken));
1862890… stephan 1345 fossil_free( g.syncInfo.zLoginCard );
1862890… stephan 1346 g.syncInfo.zLoginCard = 0;
1862890… stephan 1347 if( xfer.nToken==4
1862890… stephan 1348 && blob_eq(&xfer.aToken[0], "login") ){
1862890… stephan 1349 goto handle_login_card;
1862890… stephan 1350 }
2765f04… drh 1351 }
8b8cc4f… drh 1352 while( blob_line(xfer.pIn, &xfer.line) ){
8b8cc4f… drh 1353 if( blob_buffer(&xfer.line)[0]=='#' ) continue;
8b8cc4f… drh 1354 if( blob_size(&xfer.line)==0 ) continue;
8b8cc4f… drh 1355 xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
8b8cc4f… drh 1356
fd9b7bd… drh 1357 /* file HASH SIZE \n CONTENT
fd9b7bd… drh 1358 ** file HASH DELTASRC SIZE \n CONTENT
fc32fae… mistachkin 1359 **
1f7b409… drh 1360 ** Server accepts a file from the client.
fc32fae… mistachkin 1361 */
fc32fae… mistachkin 1362 if( blob_eq(&xfer.aToken[0], "file") ){
fc32fae… mistachkin 1363 if( !isPush ){
fc32fae… mistachkin 1364 cgi_reset_content();
2765f04… drh 1365 @ error not\sauthorized\sto\swrite%s(whyNotAuth())
fc32fae… mistachkin 1366 nErr++;
fc32fae… mistachkin 1367 break;
fc32fae… mistachkin 1368 }
fc32fae… mistachkin 1369 xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList);
fc32fae… mistachkin 1370 if( blob_size(&xfer.err) ){
fc32fae… mistachkin 1371 cgi_reset_content();
fc32fae… mistachkin 1372 @ error %T(blob_str(&xfer.err))
fc32fae… mistachkin 1373 nErr++;
fc32fae… mistachkin 1374 break;
fc32fae… mistachkin 1375 }
fc32fae… mistachkin 1376 }else
fc32fae… mistachkin 1377
fd9b7bd… drh 1378 /* cfile HASH USIZE CSIZE \n CONTENT
fd9b7bd… drh 1379 ** cfile HASH DELTASRC USIZE CSIZE \n CONTENT
fc32fae… mistachkin 1380 **
1f7b409… drh 1381 ** Server accepts a compressed file from the client.
fc32fae… mistachkin 1382 */
fc32fae… mistachkin 1383 if( blob_eq(&xfer.aToken[0], "cfile") ){
fc32fae… mistachkin 1384 if( !isPush ){
fc32fae… mistachkin 1385 cgi_reset_content();
2765f04… drh 1386 @ error not\sauthorized\sto\swrite%s(whyNotAuth())
fc32fae… mistachkin 1387 nErr++;
fc32fae… mistachkin 1388 break;
fc32fae… mistachkin 1389 }
fc32fae… mistachkin 1390 xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList);
fc32fae… mistachkin 1391 if( blob_size(&xfer.err) ){
fc32fae… mistachkin 1392 cgi_reset_content();
fc32fae… mistachkin 1393 @ error %T(blob_str(&xfer.err))
fc32fae… mistachkin 1394 nErr++;
fc32fae… mistachkin 1395 break;
fc32fae… mistachkin 1396 }
fc32fae… mistachkin 1397 }else
fc32fae… mistachkin 1398
27d743e… drh 1399 /* uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT
27d743e… drh 1400 **
1f7b409… drh 1401 ** Server accepts an unversioned file from the client.
27d743e… drh 1402 */
27d743e… drh 1403 if( blob_eq(&xfer.aToken[0], "uvfile") ){
27d743e… drh 1404 xfer_accept_unversioned_file(&xfer, g.perm.WrUnver);
27d743e… drh 1405 if( blob_size(&xfer.err) ){
ac15e6d… drh 1406 cgi_reset_content();
27d743e… drh 1407 @ error %T(blob_str(&xfer.err))
f8baa04… danield 1408 fossil_print("%%%%%%%% xfer.err: '%s'\n", blob_str(&xfer.err));
27d743e… drh 1409 nErr++;
27d743e… drh 1410 break;
27d743e… drh 1411 }
27d743e… drh 1412 }else
27d743e… drh 1413
fd9b7bd… drh 1414 /* gimme HASH
fc32fae… mistachkin 1415 **
1f7b409… drh 1416 ** Client is requesting a file from the server. Send it.
fc32fae… mistachkin 1417 */
fc32fae… mistachkin 1418 if( blob_eq(&xfer.aToken[0], "gimme")
fc32fae… mistachkin 1419 && xfer.nToken==2
fd9b7bd… drh 1420 && blob_is_hname(&xfer.aToken[1])
fc32fae… mistachkin 1421 ){
fc32fae… mistachkin 1422 nGimme++;
405457c… drh 1423 remote_unk(&xfer.aToken[1]);
8b8cc4f… drh 1424 if( isPull ){
8b8cc4f… drh 1425 int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
d0305b3… aku 1426 if( rid ){
d0305b3… aku 1427 send_file(&xfer, rid, &xfer.aToken[1], deltaFlag);
d0305b3… aku 1428 }
d0305b3… aku 1429 }
d0305b3… aku 1430 }else
d0305b3… aku 1431
27d743e… drh 1432 /* uvgimme NAME
27d743e… drh 1433 **
1f7b409… drh 1434 ** Client is requesting an unversioned file from the server. Send it.
27d743e… drh 1435 */
27d743e… drh 1436 if( blob_eq(&xfer.aToken[0], "uvgimme")
27d743e… drh 1437 && xfer.nToken==2
27d743e… drh 1438 && blob_is_filename(&xfer.aToken[1])
27d743e… drh 1439 ){
54efccf… stephan 1440 if( g.perm.Read ){
54efccf… stephan 1441 send_unversioned_file(&xfer, blob_str(&xfer.aToken[1]), 0);
54efccf… stephan 1442 }
27d743e… drh 1443 }else
27d743e… drh 1444
fd9b7bd… drh 1445 /* igot HASH ?ISPRIVATE?
d0305b3… aku 1446 **
8b8cc4f… drh 1447 ** Client announces that it has a particular file. If the ISPRIVATE
1f7b409… drh 1448 ** argument exists and is "1", then the file is a private file.
d0305b3… aku 1449 */
8b8cc4f… drh 1450 if( xfer.nToken>=2
d0305b3… aku 1451 && blob_eq(&xfer.aToken[0], "igot")
fd9b7bd… drh 1452 && blob_is_hname(&xfer.aToken[1])
d0305b3… aku 1453 ){
d0305b3… aku 1454 if( isPush ){
1f7b409… drh 1455 int rid = 0;
ec93507… drh 1456 int isPriv = 0;
8b8cc4f… drh 1457 if( xfer.nToken==2 || blob_eq(&xfer.aToken[2],"1")==0 ){
1f7b409… drh 1458 /* Client says the artifact is public */
1f7b409… drh 1459 rid = rid_from_uuid(&xfer.aToken[1], 1, 0);
b344d3c… drh 1460 }else if( g.perm.Private ){
1f7b409… drh 1461 /* Client says the artifact is private and the client has
1f7b409… drh 1462 ** permission to push private content. Create a new phantom
1f7b409… drh 1463 ** artifact that is marked private. */
1f7b409… drh 1464 rid = rid_from_uuid(&xfer.aToken[1], 1, 1);
ec93507… drh 1465 isPriv = 1;
8b8cc4f… drh 1466 }else{
1f7b409… drh 1467 /* Client says the artifact is private and the client is unable
1f7b409… drh 1468 ** or unwilling to send us the artifact. If we already hold the
1f7b409… drh 1469 ** artifact here on the server as a phantom, make sure that
1f7b409… drh 1470 ** phantom is marked as private so that we don't keep asking about
1f7b409… drh 1471 ** it in subsequent sync requests. */
1f7b409… drh 1472 rid = rid_from_uuid(&xfer.aToken[1], 0, 1);
ec93507… drh 1473 isPriv = 1;
ec93507… drh 1474 }
ec93507… drh 1475 if( rid ){
ec93507… drh 1476 remote_has(rid);
ec93507… drh 1477 if( isPriv ){
50a7f89… drh 1478 content_make_private(rid);
ec93507… drh 1479 }else{
ec93507… drh 1480 content_make_public(rid);
1f7b409… drh 1481 }
8b8cc4f… drh 1482 }
d0305b3… aku 1483 }
d0305b3… aku 1484 }else
45f3516… jan.nijtmans 1485
45f3516… jan.nijtmans 1486
d0305b3… aku 1487 /* pull SERVERCODE PROJECTCODE
d0305b3… aku 1488 ** push SERVERCODE PROJECTCODE
d0305b3… aku 1489 **
d0305b3… aku 1490 ** The client wants either send or receive. The server should
fd9b7bd… drh 1491 ** verify that the project code matches. The server code is ignored.
d0305b3… aku 1492 */
d0305b3… aku 1493 if( xfer.nToken==3
d0305b3… aku 1494 && (blob_eq(&xfer.aToken[0], "pull") || blob_eq(&xfer.aToken[0], "push"))
fd9b7bd… drh 1495 && blob_is_hname(&xfer.aToken[2])
d0305b3… aku 1496 ){
d0305b3… aku 1497 const char *zPCode;
d0305b3… aku 1498 zPCode = db_get("project-code", 0);
d0305b3… aku 1499 if( zPCode==0 ){
99fcc43… drh 1500 fossil_fatal("missing project code");
573a464… drh 1501 }
573a464… drh 1502 if( !blob_eq_str(&xfer.aToken[2], zPCode, -1) ){
dbda8d6… drh 1503 cgi_reset_content();
dbda8d6… drh 1504 @ error wrong\sproject
dbda8d6… drh 1505 nErr++;
dbda8d6… drh 1506 break;
dbda8d6… drh 1507 }
e621b6d… drh 1508 login_check_credentials();
573a464… drh 1509 if( blob_eq(&xfer.aToken[0], "pull") ){
b344d3c… drh 1510 if( !g.perm.Read ){
dbda8d6… drh 1511 cgi_reset_content();
2765f04… drh 1512 @ error not\sauthorized\sto\sread%s(whyNotAuth())
dbda8d6… drh 1513 nErr++;
dbda8d6… drh 1514 break;
dbda8d6… drh 1515 }
dbda8d6… drh 1516 isPull = 1;
dbda8d6… drh 1517 }else{
b344d3c… drh 1518 if( !g.perm.Write ){
d0305b3… aku 1519 if( !isPull ){
d0305b3… aku 1520 cgi_reset_content();
2765f04… drh 1521 @ error not\sauthorized\sto\swrite%s(whyNotAuth())
d0305b3… aku 1522 nErr++;
d0305b3… aku 1523 }else{
2765f04… drh 1524 @ message pull\sonly\s-\snot\sauthorized\sto\spush%s(whyNotAuth())
d0305b3… aku 1525 }
d0305b3… aku 1526 }else{
d0305b3… aku 1527 isPush = 1;
bbcb632… aku 1528 }
dbda8d6… drh 1529 }
dbda8d6… drh 1530 }else
dbda8d6… drh 1531
fda9b15… drh 1532 /* clone ?PROTOCOL-VERSION? ?SEQUENCE-NUMBER?
dbda8d6… drh 1533 **
dbda8d6… drh 1534 ** The client knows nothing. Tell all.
dbda8d6… drh 1535 */
573a464… drh 1536 if( blob_eq(&xfer.aToken[0], "clone") ){
fda9b15… drh 1537 int iVers;
e621b6d… drh 1538 login_check_credentials();
b344d3c… drh 1539 if( !g.perm.Clone ){
dbda8d6… drh 1540 cgi_reset_content();
596f3c1… drh 1541 @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
2765f04… drh 1542 @ error not\sauthorized\sto\sclone%s(whyNotAuth())
dbda8d6… drh 1543 nErr++;
dbda8d6… drh 1544 break;
b9fd594… drh 1545 }
b9fd594… drh 1546 if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){
b9fd594… drh 1547 @ pragma uv-pull-only
b9fd594… drh 1548 send_unversioned_catalog(&xfer);
b9fd594… drh 1549 uvCatalogSent = 1;
fda9b15… drh 1550 }
fda9b15… drh 1551 if( xfer.nToken==3
fda9b15… drh 1552 && blob_is_int(&xfer.aToken[1], &iVers)
fda9b15… drh 1553 && iVers>=2
fda9b15… drh 1554 ){
fda9b15… drh 1555 int seqno, max;
3543ed6… drh 1556 if( iVers>=3 ){
3543ed6… drh 1557 cgi_set_content_type("application/x-fossil-uncompressed");
3543ed6… drh 1558 }
fda9b15… drh 1559 blob_is_int(&xfer.aToken[2], &seqno);
295b396… stephan 1560 if( seqno<=0 ){
f9f90d9… stephan 1561 xfer_fatal_error("invalid clone sequence number");
bb86049… stephan 1562 db_rollback_transaction();
295b396… stephan 1563 return;
295b396… stephan 1564 }
fda9b15… drh 1565 max = db_int(0, "SELECT max(rid) FROM blob");
53db40e… drh 1566 while( xfer.mxSend>(int)blob_size(xfer.pOut) && seqno<=max){
769c90a… drh 1567 if( time(NULL) >= xfer.maxTime ) break;
3543ed6… drh 1568 if( iVers>=3 ){
3543ed6… drh 1569 send_compressed_file(&xfer, seqno);
3543ed6… drh 1570 }else{
3543ed6… drh 1571 send_file(&xfer, seqno, 0, 1);
3543ed6… drh 1572 }
fda9b15… drh 1573 seqno++;
fda9b15… drh 1574 }
0db6f98… drh 1575 if( seqno>max ) seqno = 0;
fda9b15… drh 1576 @ clone_seqno %d(seqno)
fda9b15… drh 1577 }else{
fda9b15… drh 1578 isClone = 1;
fda9b15… drh 1579 isPull = 1;
fda9b15… drh 1580 deltaFlag = 1;
fda9b15… drh 1581 }
bbcb632… aku 1582 @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
916b6e4… drh 1583 }else
dbda8d6… drh 1584
dbda8d6… drh 1585 /* login USER NONCE SIGNATURE
dbda8d6… drh 1586 **
1f7b409… drh 1587 ** The client has sent login credentials to the server.
1f7b409… drh 1588 ** Validate the login. This has to happen before anything else.
1862890… stephan 1589 **
1862890… stephan 1590 ** For many years, Fossil would accept multiple login cards with
1862890… stephan 1591 ** cumulative permissions. But that feature was never used. Hence
1862890… stephan 1592 ** it is now prohibited. Any login card after the first generates
1862890… stephan 1593 ** a fatal error.
dbda8d6… drh 1594 */
573a464… drh 1595 if( blob_eq(&xfer.aToken[0], "login")
edbb332… drh 1596 && xfer.nToken==4
573a464… drh 1597 ){
1862890… stephan 1598 handle_login_card:
1862890… stephan 1599 nLogin++;
dbda8d6… drh 1600 if( disableLogin ){
b344d3c… drh 1601 g.perm.Read = g.perm.Write = g.perm.Private = g.perm.Admin = 1;
1862890… stephan 1602 }else if( nLogin > 1 ){
1862890… stephan 1603 cgi_reset_content();
1862890… stephan 1604 @ error multiple\slogin\cards
1862890… stephan 1605 nErr++;
1862890… stephan 1606 break;
390b414… drh 1607 }else{
390b414… drh 1608 if( check_tail_hash(&xfer.aToken[2], xfer.pIn)
390b414… drh 1609 || check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3])
390b414… drh 1610 ){
390b414… drh 1611 cgi_reset_content();
390b414… drh 1612 @ error login\sfailed
390b414… drh 1613 nErr++;
390b414… drh 1614 break;
45f3516… jan.nijtmans 1615 }
a241c81… drh 1616 }
a241c81… drh 1617 }else
45f3516… jan.nijtmans 1618
a241c81… drh 1619 /* reqconfig NAME
a241c81… drh 1620 **
1f7b409… drh 1621 ** Client is requesting a configuration value from the server
a241c81… drh 1622 */
a241c81… drh 1623 if( blob_eq(&xfer.aToken[0], "reqconfig")
a241c81… drh 1624 && xfer.nToken==2
a241c81… drh 1625 ){
b344d3c… drh 1626 if( g.perm.Read ){
ba8af87… drh 1627 char *zName = blob_str(&xfer.aToken[1]);
1654456… drh 1628 if( zName[0]=='/' ){
1654456… drh 1629 /* New style configuration transfer */
1654456… drh 1630 int groupMask = configure_name_to_mask(&zName[1], 0);
7f0c7fc… drh 1631 if( !g.perm.Admin ) groupMask &= ~(CONFIGSET_USER|CONFIGSET_SCRIBER);
b344d3c… drh 1632 if( !g.perm.RdAddr ) groupMask &= ~CONFIGSET_ADDR;
1654456… drh 1633 configure_send_group(xfer.pOut, groupMask, 0);
6b0b57a… drh 1634 }
6b0b57a… drh 1635 }
6b0b57a… drh 1636 }else
45f3516… jan.nijtmans 1637
6b0b57a… drh 1638 /* config NAME SIZE \n CONTENT
6b0b57a… drh 1639 **
1f7b409… drh 1640 ** Client has sent a configuration value to the server.
1f7b409… drh 1641 ** This is only permitted for high-privilege users.
6b0b57a… drh 1642 */
6b0b57a… drh 1643 if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3
6b0b57a… drh 1644 && blob_is_int(&xfer.aToken[2], &size) ){
6b0b57a… drh 1645 const char *zName = blob_str(&xfer.aToken[1]);
6b0b57a… drh 1646 Blob content;
f9f90d9… stephan 1647 if( size<0 ){
f9f90d9… stephan 1648 xfer_fatal_error("invalid config record");
bb86049… stephan 1649 db_rollback_transaction();
f9f90d9… stephan 1650 return;
f9f90d9… stephan 1651 }
6b0b57a… drh 1652 blob_zero(&content);
6b0b57a… drh 1653 blob_extract(xfer.pIn, size, &content);
b344d3c… drh 1654 if( !g.perm.Admin ){
6b0b57a… drh 1655 cgi_reset_content();
2765f04… drh 1656 @ error not\sauthorized\sto\spush\sconfiguration%s(whyNotAuth())
6b0b57a… drh 1657 nErr++;
6b0b57a… drh 1658 break;
6b0b57a… drh 1659 }
b44d4a8… drh 1660 configure_receive(zName, &content, CONFIGSET_ALL);
6b0b57a… drh 1661 blob_reset(&content);
6b0b57a… drh 1662 blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
bbcb632… aku 1663 }else
6b0b57a… drh 1664
6b0b57a… drh 1665
bbcb632… aku 1666 /* cookie TEXT
bbcb632… aku 1667 **
99a319b… wyoung 1668 ** A cookie contains an arbitrary-length argument that is server-defined.
bbcb632… aku 1669 ** The argument must be encoded so as not to contain any whitespace.
bbcb632… aku 1670 ** The server can optionally send a cookie to the client. The client
bbcb632… aku 1671 ** might then return the same cookie back to the server on its next
bbcb632… aku 1672 ** communication. The cookie might record information that helps
bbcb632… aku 1673 ** the server optimize a push or pull.
bbcb632… aku 1674 **
bbcb632… aku 1675 ** The client is not required to return a cookie. So the server
bbcb632… aku 1676 ** must not depend on the cookie. The cookie should be an optimization
bbcb632… aku 1677 ** only. The client might also send a cookie that came from a different
bbcb632… aku 1678 ** server. So the server must be prepared to distinguish its own cookie
bbcb632… aku 1679 ** from cookies originating from other servers. The client might send
bbcb632… aku 1680 ** back several different cookies to the server. The server should be
bbcb632… aku 1681 ** prepared to sift through the cookies and pick the one that it wants.
bbcb632… aku 1682 */
bbcb632… aku 1683 if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
bbcb632… aku 1684 /* Process the cookie */
bbcb632… aku 1685 }else
bbcb632… aku 1686
8b8cc4f… drh 1687
8b8cc4f… drh 1688 /* private
8b8cc4f… drh 1689 **
1f7b409… drh 1690 ** The client card indicates that the next "file" or "cfile" will contain
8b8cc4f… drh 1691 ** private content.
8b8cc4f… drh 1692 */
8b8cc4f… drh 1693 if( blob_eq(&xfer.aToken[0], "private") ){
b344d3c… drh 1694 if( !g.perm.Private ){
8b8cc4f… drh 1695 server_private_xfer_not_authorized();
8b8cc4f… drh 1696 }else{
8b8cc4f… drh 1697 xfer.nextIsPrivate = 1;
8b8cc4f… drh 1698 }
8b8cc4f… drh 1699 }else
8b8cc4f… drh 1700
8b8cc4f… drh 1701 /* pragma NAME VALUE...
8b8cc4f… drh 1702 **
a8b6fdc… andybradford 1703 ** The client issues pragmas to try to influence the behavior of the
8b8cc4f… drh 1704 ** server. These are requests only. Unknown pragmas are silently
8b8cc4f… drh 1705 ** ignored.
8b8cc4f… drh 1706 */
8b8cc4f… drh 1707 if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
27d743e… drh 1708
8b8cc4f… drh 1709 /* pragma send-private
1f7b409… drh 1710 **
1f7b409… drh 1711 ** The client is requesting private artifacts.
8b8cc4f… drh 1712 **
8b8cc4f… drh 1713 ** If the user has the "x" privilege (which must be set explicitly -
8b8cc4f… drh 1714 ** it is not automatic with "a" or "s") then this pragma causes
8b8cc4f… drh 1715 ** private information to be pulled in addition to public records.
8b8cc4f… drh 1716 */
8b8cc4f… drh 1717 if( blob_eq(&xfer.aToken[1], "send-private") ){
8b8cc4f… drh 1718 login_check_credentials();
b344d3c… drh 1719 if( !g.perm.Private ){
8b8cc4f… drh 1720 server_private_xfer_not_authorized();
8b8cc4f… drh 1721 }else{
8b8cc4f… drh 1722 xfer.syncPrivate = 1;
8b8cc4f… drh 1723 }
12d2f70… drh 1724 }else
27d743e… drh 1725
05ba15e… drh 1726 /* pragma send-catalog
05ba15e… drh 1727 **
1f7b409… drh 1728 ** The client wants to see igot cards for all known artifacts.
1f7b409… drh 1729 ** This is used as part of "sync --verily" to help ensure that
1f7b409… drh 1730 ** no artifacts have been missed on prior syncs.
05ba15e… drh 1731 */
05ba15e… drh 1732 if( blob_eq(&xfer.aToken[1], "send-catalog") ){
05ba15e… drh 1733 xfer.resync = 0x7fffffff;
12d2f70… drh 1734 }else
fd9b7bd… drh 1735
1f7b409… drh 1736 /* pragma client-version VERSION ?DATE? ?TIME?
fd9b7bd… drh 1737 **
1f7b409… drh 1738 ** The client announces to the server what version of Fossil it
1f7b409… drh 1739 ** is running. The DATE and TIME are a pure numeric ISO8601 time
1f7b409… drh 1740 ** for the specific check-in of the client.
fd9b7bd… drh 1741 */
fd9b7bd… drh 1742 if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "client-version") ){
1862890… stephan 1743 xfer.remoteVersion = g.syncInfo.remoteVersion =
1862890… stephan 1744 atoi(blob_str(&xfer.aToken[2]));
1f7b409… drh 1745 if( xfer.nToken>=5 ){
1f7b409… drh 1746 xfer.remoteDate = atoi(blob_str(&xfer.aToken[3]));
1f7b409… drh 1747 xfer.remoteTime = atoi(blob_str(&xfer.aToken[4]));
1f7b409… drh 1748 @ pragma server-version %d(RELEASE_VERSION_NUMBER) \
1f7b409… drh 1749 @ %d(MANIFEST_NUMERIC_DATE) %d(MANIFEST_NUMERIC_TIME)
1f7b409… drh 1750 }
1862890… stephan 1751 xfer_xflc_check( xfer.remoteVersion, xfer.remoteDate,
1862890… stephan 1752 xfer.remoteTime, 0x04 );
12d2f70… drh 1753 }else
27d743e… drh 1754
27d743e… drh 1755 /* pragma uv-hash HASH
27d743e… drh 1756 **
27d743e… drh 1757 ** The client wants to make sure that unversioned files are all synced.
27d743e… drh 1758 ** If the HASH does not match, send a complete catalog of
27d743e… drh 1759 ** "uvigot" cards.
27d743e… drh 1760 */
27d743e… drh 1761 if( blob_eq(&xfer.aToken[1], "uv-hash")
fd9b7bd… drh 1762 && blob_is_hname(&xfer.aToken[2])
27d743e… drh 1763 ){
d015a26… drh 1764 if( !uvCatalogSent
d015a26… drh 1765 && g.perm.Read
d015a26… drh 1766 && !blob_eq_str(&xfer.aToken[2], unversioned_content_hash(0),-1)
d015a26… drh 1767 ){
d015a26… drh 1768 if( g.perm.WrUnver ){
b9fd594… drh 1769 @ pragma uv-push-ok
b9fd594… drh 1770 }else if( g.perm.Read ){
b9fd594… drh 1771 @ pragma uv-pull-only
b9fd594… drh 1772 }
d015a26… drh 1773 send_unversioned_catalog(&xfer);
27d743e… drh 1774 }
b9fd594… drh 1775 uvCatalogSent = 1;
12d2f70… drh 1776 }else
03cc332… drh 1777
03cc332… drh 1778 /* pragma ci-lock CHECKIN-HASH CLIENT-ID
03cc332… drh 1779 **
03cc332… drh 1780 ** The client wants to make non-branch commit against the check-in
03cc332… drh 1781 ** identified by CHECKIN-HASH. The server will remember this and
1f7b409… drh 1782 ** subsequent ci-lock requests from different clients will generate
03cc332… drh 1783 ** a ci-lock-fail pragma in the reply.
03cc332… drh 1784 */
03cc332… drh 1785 if( blob_eq(&xfer.aToken[1], "ci-lock")
03cc332… drh 1786 && xfer.nToken==4
03cc332… drh 1787 && blob_is_hname(&xfer.aToken[2])
03cc332… drh 1788 ){
03cc332… drh 1789 Stmt q;
03cc332… drh 1790 sqlite3_int64 iNow = time(0);
18d5880… drh 1791 sqlite3_int64 maxAge = db_get_int("lock-timeout",60);
03cc332… drh 1792 int seenFault = 0;
03cc332… drh 1793 db_prepare(&q,
50d433e… drh 1794 "SELECT value->>'login',"
03cc332… drh 1795 " mtime,"
50d433e… drh 1796 " value->>'clientid',"
03cc332… drh 1797 " (SELECT rid FROM blob WHERE uuid=substr(name,9)),"
03cc332… drh 1798 " name"
50d433e… drh 1799 " FROM config"
50d433e… drh 1800 " WHERE name GLOB 'ci-lock-*'"
50d433e… drh 1801 " AND json_valid(value)"
03cc332… drh 1802 );
03cc332… drh 1803 while( db_step(&q)==SQLITE_ROW ){
03cc332… drh 1804 int x = db_column_int(&q,3);
03cc332… drh 1805 const char *zName = db_column_text(&q,4);
6d3daf7… drh 1806 if( db_column_int64(&q,1)<=iNow-maxAge || !is_a_leaf(x) ){
3a5caa8… drh 1807 /* check-in locks expire after maxAge seconds, or when the
3a5caa8… drh 1808 ** check-in is no longer a leaf */
f741baa… drh 1809 db_unprotect(PROTECT_CONFIG);
03cc332… drh 1810 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
f741baa… drh 1811 db_protect_pop();
03cc332… drh 1812 continue;
03cc332… drh 1813 }
03cc332… drh 1814 if( fossil_strcmp(zName+8, blob_str(&xfer.aToken[2]))==0 ){
03cc332… drh 1815 const char *zClientId = db_column_text(&q, 2);
03cc332… drh 1816 const char *zLogin = db_column_text(&q,0);
03cc332… drh 1817 sqlite3_int64 mtime = db_column_int64(&q, 1);
03cc332… drh 1818 if( fossil_strcmp(zClientId, blob_str(&xfer.aToken[3]))!=0 ){
8d0de9d… stephan 1819 if( 0==zLogin || 0==zLogin[0] ) zLogin = "nobody";
03cc332… drh 1820 @ pragma ci-lock-fail %F(zLogin) %lld(mtime)
03cc332… drh 1821 }
03cc332… drh 1822 seenFault = 1;
03cc332… drh 1823 }
03cc332… drh 1824 }
03cc332… drh 1825 db_finalize(&q);
03cc332… drh 1826 if( !seenFault ){
f741baa… drh 1827 db_unprotect(PROTECT_CONFIG);
03cc332… drh 1828 db_multi_exec(
03cc332… drh 1829 "REPLACE INTO config(name,value,mtime)"
03cc332… drh 1830 "VALUES('ci-lock-%q',json_object('login',%Q,'clientid',%Q),now())",
03cc332… drh 1831 blob_str(&xfer.aToken[2]), g.zLogin,
03cc332… drh 1832 blob_str(&xfer.aToken[3])
03cc332… drh 1833 );
f741baa… drh 1834 db_protect_pop();
9116c6f… drh 1835 }
a439b11… drh 1836 if( db_get_boolean("forbid-delta-manifests",1) ){
9116c6f… drh 1837 @ pragma avoid-delta-manifests
03cc332… drh 1838 }
12d2f70… drh 1839 }else
3a5caa8… drh 1840
3a5caa8… drh 1841 /* pragma ci-unlock CLIENT-ID
3a5caa8… drh 1842 **
3a5caa8… drh 1843 ** Remove any locks previously held by CLIENT-ID. Clients send this
3a5caa8… drh 1844 ** pragma with their own ID whenever they know that they no longer
3a5caa8… drh 1845 ** have any commits pending.
3a5caa8… drh 1846 */
3a5caa8… drh 1847 if( blob_eq(&xfer.aToken[1], "ci-unlock")
3a5caa8… drh 1848 && xfer.nToken==3
3a5caa8… drh 1849 && blob_is_hname(&xfer.aToken[2])
3a5caa8… drh 1850 ){
f741baa… drh 1851 db_unprotect(PROTECT_CONFIG);
3a5caa8… drh 1852 db_multi_exec(
3a5caa8… drh 1853 "DELETE FROM config"
3a5caa8… drh 1854 " WHERE name GLOB 'ci-lock-*'"
50d433e… drh 1855 " AND (NOT json_valid(value) OR value->>'clientid'==%Q)",
3a5caa8… drh 1856 blob_str(&xfer.aToken[2])
3a5caa8… drh 1857 );
f741baa… drh 1858 db_protect_pop();
12d2f70… drh 1859 }else
84358b7… drh 1860
84358b7… drh 1861 /* pragma client-url URL
84358b7… drh 1862 **
84358b7… drh 1863 ** This pragma is an informational notification to the server that
84358b7… drh 1864 ** their relationship could, in theory, be inverted by having the
84358b7… drh 1865 ** server call the client at URL.
84358b7… drh 1866 */
84358b7… drh 1867 if( blob_eq(&xfer.aToken[1], "client-url")
84358b7… drh 1868 && xfer.nToken==3
84358b7… drh 1869 && g.perm.Write
84358b7… drh 1870 ){
ebcce3c… drh 1871 xfer_syncwith(blob_str(&xfer.aToken[2]), 1);
12d2f70… drh 1872 }else
12d2f70… drh 1873
12d2f70… drh 1874 /* pragma req-links
12d2f70… drh 1875 **
12d2f70… drh 1876 ** The client sends this message to the server to ask the server
12d2f70… drh 1877 ** to tell it about alternative repositories in the reply.
12d2f70… drh 1878 */
12d2f70… drh 1879 if( blob_eq(&xfer.aToken[1], "req-links") ){
12d2f70… drh 1880 bSendLinks = 1;
637fcef… drh 1881 }else
637fcef… drh 1882
637fcef… drh 1883 /* pragma req-clusters
637fcef… drh 1884 **
637fcef… drh 1885 ** This pragma requests that the server send igot cards for every
637fcef… drh 1886 ** cluster artifact that it knows about.
637fcef… drh 1887 */
637fcef… drh 1888 if( blob_eq(&xfer.aToken[1], "req-clusters") ){
637fcef… drh 1889 send_all_clusters(&xfer);
5bb4f12… drh 1890 }
5bb4f12… drh 1891
8b8cc4f… drh 1892 }else
8b8cc4f… drh 1893
dbda8d6… drh 1894 /* Unknown message
dbda8d6… drh 1895 */
dbda8d6… drh 1896 {
dbda8d6… drh 1897 cgi_reset_content();
573a464… drh 1898 @ error bad\scommand:\s%F(blob_str(&xfer.line))
573a464… drh 1899 }
573a464… drh 1900 blobarray_reset(xfer.aToken, xfer.nToken);
dbb5e2d… drh 1901 blob_reset(&xfer.line);
573a464… drh 1902 }
573a464… drh 1903 if( isPush ){
42316a1… mistachkin 1904 if( rc==TH_OK ){
fc32fae… mistachkin 1905 rc = xfer_run_script(zScript, zUuidList, 1);
42316a1… mistachkin 1906 if( rc==TH_ERROR ){
1311841… jan.nijtmans 1907 cgi_reset_content();
1311841… jan.nijtmans 1908 @ error push\sscript\sfailed:\s%F(g.zErrMsg)
1311841… jan.nijtmans 1909 nErr++;
1311841… jan.nijtmans 1910 }
0b61e3c… drh 1911 }
dbdc160… aku 1912 request_phantoms(&xfer, 500);
fc32fae… mistachkin 1913 }
fc32fae… mistachkin 1914 if( zUuidList ){
fc32fae… mistachkin 1915 Th_Free(g.interp, zUuidList);
243e02b… drh 1916 }
243e02b… drh 1917 if( isClone && nGimme==0 ){
243e02b… drh 1918 /* The initial "clone" message from client to server contains no
243e02b… drh 1919 ** "gimme" cards. On that initial message, send the client an "igot"
7c0f4ec… jan.nijtmans 1920 ** card for every artifact currently in the repository. This will
243e02b… drh 1921 ** cause the client to create phantoms for all artifacts, which will
243e02b… drh 1922 ** in turn make sure that the entire repository is sent efficiently
243e02b… drh 1923 ** and expeditiously.
243e02b… drh 1924 */
243e02b… drh 1925 send_all(&xfer);
8b8cc4f… drh 1926 if( xfer.syncPrivate ) send_private(&xfer);
243e02b… drh 1927 }else if( isPull ){
bbcb632… aku 1928 create_cluster();
bbcb632… aku 1929 send_unclustered(&xfer);
8b8cc4f… drh 1930 if( xfer.syncPrivate ) send_private(&xfer);
6b0b57a… drh 1931 }
9d8db79… drh 1932 hook_expecting_more_artifacts(xfer.nGimmeSent?60:0);
405457c… drh 1933 db_multi_exec("DROP TABLE onremote; DROP TABLE unk;");
42316a1… mistachkin 1934 manifest_crosslink_end(MC_PERMIT_HOOKS);
9d8db79… drh 1935
12d2f70… drh 1936 /* Send URLs for alternative repositories for the same project,
12d2f70… drh 1937 ** if requested by the client. */
12d2f70… drh 1938 if( bSendLinks && g.zBaseURL ){
12d2f70… drh 1939 Stmt q;
12d2f70… drh 1940 db_prepare(&q,
12d2f70… drh 1941 "WITH remote(mtime, url, arg) AS (\n"
12d2f70… drh 1942 " SELECT mtime, substr(name,10), '{}' FROM config\n"
12d2f70… drh 1943 " WHERE name GLOB 'syncwith:http*'\n"
12d2f70… drh 1944 " UNION ALL\n"
12d2f70… drh 1945 " SELECT mtime, substr(name,10), '{}' FROM config\n"
12d2f70… drh 1946 " WHERE name GLOB 'syncfrom:http*'\n"
12d2f70… drh 1947 " UNION ALL\n"
12d2f70… drh 1948 " SELECT mtime, substr(name,9), '{\"type\":\"git\"}' FROM config\n"
12d2f70… drh 1949 " WHERE name GLOB 'gitpush:*'\n"
12d2f70… drh 1950 ")\n"
12d2f70… drh 1951 "SELECT url, json_insert(arg,'$.src',%Q), max(mtime)\n"
12d2f70… drh 1952 " FROM remote WHERE mtime>unixepoch('now','-1 month')\n"
12d2f70… drh 1953 " GROUP BY url;",
12d2f70… drh 1954 g.zBaseURL
12d2f70… drh 1955 );
12d2f70… drh 1956 while( db_step(&q)==SQLITE_ROW ){
12d2f70… drh 1957 UrlData x;
12d2f70… drh 1958 const char *zUrl = db_column_text(&q, 0);
12d2f70… drh 1959 const char *zArg = db_column_text(&q, 1);
12d2f70… drh 1960 i64 iMtime = db_column_int64(&q, 2);
12d2f70… drh 1961 memset(&x, 0, sizeof(x));
12d2f70… drh 1962 url_parse_local(zUrl, URL_OMIT_USER, &x);
12d2f70… drh 1963 if( x.name!=0 && sqlite3_strlike("%localhost%", x.name, 0)!=0 ){
12d2f70… drh 1964 @ pragma link %F(x.canonical) %F(zArg) %lld(iMtime)
12d2f70… drh 1965 }
275da70… danield 1966 url_unparse(&x);
12d2f70… drh 1967 }
12d2f70… drh 1968 db_finalize(&q);
12d2f70… drh 1969 }
12d2f70… drh 1970
fda9b15… drh 1971 /* Send the server timestamp last, in case prior processing happened
fda9b15… drh 1972 ** to use up a significant fraction of our time window.
fda9b15… drh 1973 */
fda9b15… drh 1974 zNow = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S', 'now')");
e486a0a… drh 1975 @ # timestamp %s(zNow) errors %d(nErr)
295b396… stephan 1976 fossil_free(zNow);
fda9b15… drh 1977
d9543f4… drh 1978 db_commit_transaction();
0047370… joel 1979 configure_rebuild();
dbda8d6… drh 1980 }
dbda8d6… drh 1981
dbda8d6… drh 1982 /*
dbda8d6… drh 1983 ** COMMAND: test-xfer
dbda8d6… drh 1984 **
601948b… drh 1985 ** Usage: %fossil test-xfer ?OPTIONS? XFERFILE
601948b… drh 1986 **
601948b… drh 1987 ** Pass the sync-protocol input file XFERFILE into the server-side sync
601948b… drh 1988 ** protocol handler. Generate a reply on standard output.
601948b… drh 1989 **
601948b… drh 1990 ** This command was original created to help debug the server side of
275da70… danield 1991 ** sync messages. The XFERFILE is the uncompressed content of an
601948b… drh 1992 ** "xfer" HTTP request from client to server. This command interprets
601948b… drh 1993 ** that message and generates the content of an HTTP reply (without any
601948b… drh 1994 ** encoding and without the HTTP reply headers) and writes that reply
601948b… drh 1995 ** on standard output.
601948b… drh 1996 **
601948b… drh 1997 ** One possible usages scenario is to capture some XFERFILE examples
601948b… drh 1998 ** using a command like:
601948b… drh 1999 **
601948b… drh 2000 ** fossil push http://bogus/ --httptrace
601948b… drh 2001 **
601948b… drh 2002 ** The complete HTTP requests are stored in files named "http-request-N.txt".
601948b… drh 2003 ** Find one of those requests, remove the HTTP header, and make other edits
601948b… drh 2004 ** as necessary to generate an appropriate XFERFILE test case. Then run:
601948b… drh 2005 **
601948b… drh 2006 ** fossil test-xfer xferfile.txt
601948b… drh 2007 **
601948b… drh 2008 ** Options:
601948b… drh 2009 ** --host HOSTNAME Supply a server hostname used to populate
601948b… drh 2010 ** g.zBaseURL and similar.
dbda8d6… drh 2011 */
dbda8d6… drh 2012 void cmd_test_xfer(void){
601948b… drh 2013 const char *zHost;
1654456… drh 2014 db_find_and_open_repository(0,0);
601948b… drh 2015 zHost = find_option("host",0,1);
601948b… drh 2016 verify_all_options();
dbda8d6… drh 2017 if( g.argc!=2 && g.argc!=3 ){
dbda8d6… drh 2018 usage("?MESSAGEFILE?");
dbda8d6… drh 2019 }
601948b… drh 2020 if( zHost==0 ) zHost = "localhost:8080";
601948b… drh 2021 g.zBaseURL = mprintf("http://%s", zHost);
601948b… drh 2022 g.zHttpsURL = mprintf("https://%s", zHost);
601948b… drh 2023 g.zTop = mprintf("");
dbda8d6… drh 2024 blob_zero(&g.cgiIn);
1772357… drh 2025 blob_read_from_file(&g.cgiIn, g.argc==2 ? "-" : g.argv[2], ExtFILE);
dbda8d6… drh 2026 disableLogin = 1;
dbda8d6… drh 2027 page_xfer();
601948b… drh 2028 fossil_print("%s", cgi_extract_content());
dbda8d6… drh 2029 }
dbda8d6… drh 2030
243e02b… drh 2031 /*
243e02b… drh 2032 ** Format strings for progress reporting.
243e02b… drh 2033 */
64c58fd… drh 2034 static const char zLabelFormat[] = "%-10s %10s %10s %10s %10s\n";
64c58fd… drh 2035 static const char zValueFormat[] = "\r%-10s %10d %10d %10d %10d\n";
1570857… drh 2036 static const char zBriefFormat[] =
1570857… drh 2037 "Round-trips: %d Artifacts sent: %d received: %d\r";
1570857… drh 2038
1570857… drh 2039 #if INTERFACE
1570857… drh 2040 /*
1570857… drh 2041 ** Flag options for controlling client_sync()
1570857… drh 2042 */
12d2f70… drh 2043 #define SYNC_PUSH 0x00001 /* push content client to server */
12d2f70… drh 2044 #define SYNC_PULL 0x00002 /* pull content server to client */
12d2f70… drh 2045 #define SYNC_CLONE 0x00004 /* clone the repository */
12d2f70… drh 2046 #define SYNC_PRIVATE 0x00008 /* Also transfer private content */
12d2f70… drh 2047 #define SYNC_VERBOSE 0x00010 /* Extra diagnostics */
12d2f70… drh 2048 #define SYNC_RESYNC 0x00020 /* --verily */
12d2f70… drh 2049 #define SYNC_FROMPARENT 0x00040 /* Pull from the parent project */
12d2f70… drh 2050 #define SYNC_UNVERSIONED 0x00100 /* Sync unversioned content */
12d2f70… drh 2051 #define SYNC_UV_REVERT 0x00200 /* Copy server unversioned to client */
12d2f70… drh 2052 #define SYNC_UV_TRACE 0x00400 /* Describe UV activities */
12d2f70… drh 2053 #define SYNC_UV_DRYRUN 0x00800 /* Do not actually exchange files */
12d2f70… drh 2054 #define SYNC_IFABLE 0x01000 /* Inability to sync is not fatal */
12d2f70… drh 2055 #define SYNC_CKIN_LOCK 0x02000 /* Lock the current check-in */
12d2f70… drh 2056 #define SYNC_NOHTTPCOMPRESS 0x04000 /* Do not compression HTTP messages */
12d2f70… drh 2057 #define SYNC_ALLURL 0x08000 /* The --all flag - sync to all URLs */
12d2f70… drh 2058 #define SYNC_SHARE_LINKS 0x10000 /* Request alternate repo links */
8089622… drh 2059 #define SYNC_XVERBOSE 0x20000 /* Extra verbose. Network traffic */
8fa8fe8… drh 2060 #define SYNC_PING 0x40000 /* Verify server is alive */
8fa8fe8… drh 2061 #define SYNC_QUIET 0x80000 /* No output */
1570857… drh 2062 #endif
243e02b… drh 2063
1570857… drh 2064 /*
051cf59… drh 2065 ** Floating-point absolute value
051cf59… drh 2066 */
051cf59… drh 2067 static double fossil_fabs(double x){
051cf59… drh 2068 return x>0.0 ? x : -x;
051cf59… drh 2069 }
051cf59… drh 2070
051cf59… drh 2071 /*
5fdad9b… drh 2072 ** Sync to the host identified in g.url.name and g.url.path. This
dbda8d6… drh 2073 ** routine is called by the client.
dbda8d6… drh 2074 **
dbda8d6… drh 2075 ** Records are pushed to the server if pushFlag is true. Records
dbda8d6… drh 2076 ** are pulled if pullFlag is true. A full sync occurs if both are
dbda8d6… drh 2077 ** true.
dbda8d6… drh 2078 */
0cc4875… drh 2079 int client_sync(
21e8aed… drh 2080 unsigned syncFlags, /* Mask of SYNC_* flags */
21e8aed… drh 2081 unsigned configRcvMask, /* Receive these configuration items */
21e8aed… drh 2082 unsigned configSendMask, /* Send these configuration items */
0cd5589… drh 2083 const char *zAltPCode, /* Alternative project code (usually NULL) */
0cd5589… drh 2084 int *pnRcvd /* Set to # received artifacts, if not NULL */
6b0b57a… drh 2085 ){
737e76a… drh 2086 int go = 1; /* Loop until zero */
0690aa1… drh 2087 int nCardSent = 0; /* Number of cards sent */
0690aa1… drh 2088 int nCardRcvd = 0; /* Number of cards received */
737e76a… drh 2089 int nCycle = 0; /* Number of round trips to the server */
27d743e… drh 2090 int size; /* Size of a config value or uvfile */
6b0b57a… drh 2091 int origConfigRcvMask; /* Original value of configRcvMask */
637fcef… drh 2092 int nFileRecv = 0; /* Number of files received */
dbdc160… aku 2093 int mxPhantomReq = 200; /* Max number of phantoms to request per comm */
dbdc160… aku 2094 const char *zCookie; /* Server cookie */
9abbd35… danield 2095 i64 nUncSent, nUncRcvd; /* Bytes sent and received (before compression) */
a9e05e2… drh 2096 i64 nSent, nRcvd; /* Bytes sent and received (after compression) */
fda9b15… drh 2097 int cloneSeqno = 1; /* Sequence number for clones */
737e76a… drh 2098 Blob send; /* Text we are sending to the server */
737e76a… drh 2099 Blob recv; /* Reply we got back from the server */
737e76a… drh 2100 Xfer xfer; /* Transfer data */
fda9b15… drh 2101 int pctDone; /* Percentage done with a message */
fda9b15… drh 2102 int lastPctDone = -1; /* Last displayed pctDone */
fda9b15… drh 2103 double rArrivalTime; /* Time at which a message arrived */
737e76a… drh 2104 const char *zSCode = db_get("server-code", "x");
737e76a… drh 2105 const char *zPCode = db_get("project-code", 0);
0cc4875… drh 2106 int nErr = 0; /* Number of errors */
1570857… drh 2107 int nRoundtrip= 0; /* Number of HTTP requests */
1570857… drh 2108 int nArtifactSent = 0; /* Total artifacts sent */
1570857… drh 2109 int nArtifactRcvd = 0; /* Total artifacts received */
ea5afad… drh 2110 int nPriorArtifact = 0; /* Artifacts received on prior round-trips */
1570857… drh 2111 const char *zOpType = 0;/* Push, Pull, Sync, Clone */
051cf59… drh 2112 double rSkew = 0.0; /* Maximum time skew */
27d743e… drh 2113 int uvHashSent = 0; /* The "pragma uv-hash" message has been sent */
27d743e… drh 2114 int uvDoPush = 0; /* Generate uvfile messages to send to server */
22005df… drh 2115 int uvPullOnly = 0; /* 1: pull-only. 2: pull-only warning issued */
27d743e… drh 2116 int nUvGimmeSent = 0; /* Number of uvgimme cards sent on this cycle */
27d743e… drh 2117 int nUvFileRcvd = 0; /* Number of uvfile cards received on this cycle */
637fcef… drh 2118 int nGimmeRcvd = 0; /* Number of gimme cards recevied on the prev cycle */
27d743e… drh 2119 sqlite3_int64 mtime; /* Modification time on a UV file */
3350be2… drh 2120 int autopushFailed = 0; /* Autopush following commit failed if true */
03cc332… drh 2121 const char *zCkinLock; /* Name of check-in to lock. NULL for none */
3a5caa8… drh 2122 const char *zClientId; /* A unique identifier for this check-out */
1ad56ce… drh 2123 unsigned int mHttpFlags;/* Flags for the http_exchange() subsystem */
f8800f3… stephan 2124 const int bOutIsTty = fossil_isatty(fossil_fileno(stdout));
1570857… drh 2125
0cd5589… drh 2126 if( pnRcvd ) *pnRcvd = 0;
1570857… drh 2127 if( db_get_boolean("dont-push", 0) ) syncFlags &= ~SYNC_PUSH;
8fa8fe8… drh 2128 if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE|SYNC_UNVERSIONED|SYNC_PING))
8fa8fe8… drh 2129 ==0
b330ce6… drh 2130 && configRcvMask==0
b330ce6… drh 2131 && configSendMask==0
b330ce6… drh 2132 ){
b330ce6… drh 2133 return 0; /* Nothing to do */
b330ce6… drh 2134 }
b330ce6… drh 2135
b330ce6… drh 2136 /* Compute an appropriate project code. zPCode is the project code
b330ce6… drh 2137 ** for the local repository. zAltPCode will usually be NULL, but might
b330ce6… drh 2138 ** also be an alternative project code to expect on the server. When
b330ce6… drh 2139 ** zAltPCode is not NULL, that means we are doing a cross-project import -
b330ce6… drh 2140 ** in other words, reading content from one project into a different
b330ce6… drh 2141 ** project.
b330ce6… drh 2142 */
dddc58d… drh 2143 if( syncFlags & SYNC_FROMPARENT ){
b330ce6… drh 2144 const char *zPX;
dddc58d… drh 2145 configRcvMask = 0;
dddc58d… drh 2146 configSendMask = 0;
dddc58d… drh 2147 syncFlags &= ~(SYNC_PUSH);
b330ce6… drh 2148 zPX = db_get("parent-project-code", 0);
b330ce6… drh 2149 if( zPX==0 || db_get("parent-project-name",0)==0 ){
dddc58d… drh 2150 fossil_fatal("there is no parent project: set the 'parent-project-code'"
fa9d33b… drh 2151 " and 'parent-project-name' config parameters in order"
dddc58d… drh 2152 " to pull from a parent project");
dddc58d… drh 2153 }
b330ce6… drh 2154 if( zPX ){
b330ce6… drh 2155 zAltPCode = zPX;
b330ce6… drh 2156 }
b330ce6… drh 2157 }
b330ce6… drh 2158 if( zAltPCode!=0 && zPCode!=0 && sqlite3_stricmp(zPCode, zAltPCode)==0 ){
b330ce6… drh 2159 zAltPCode = 0;
dddc58d… drh 2160 }
dbdc160… aku 2161
79be902… drh 2162 transport_stats(0, 0, 1);
737e76a… drh 2163 socket_global_init();
573a464… drh 2164 memset(&xfer, 0, sizeof(xfer));
573a464… drh 2165 xfer.pIn = &recv;
573a464… drh 2166 xfer.pOut = &send;
edbb332… drh 2167 xfer.mxSend = db_get_int("max-upload", 250000);
769c90a… drh 2168 xfer.maxTime = -1;
1f7b409… drh 2169 xfer.remoteVersion = RELEASE_VERSION_NUMBER;
1570857… drh 2170 if( syncFlags & SYNC_PRIVATE ){
b344d3c… drh 2171 g.perm.Private = 1;
8b8cc4f… drh 2172 xfer.syncPrivate = 1;
8b8cc4f… drh 2173 }
327823e… drh 2174
573a464… drh 2175 blobarray_zero(xfer.aToken, count(xfer.aToken));
dbda8d6… drh 2176 blob_zero(&send);
dbda8d6… drh 2177 blob_zero(&recv);
573a464… drh 2178 blob_zero(&xfer.err);
edbb332… drh 2179 blob_zero(&xfer.line);
596f3c1… drh 2180 origConfigRcvMask = 0;
9abbd35… danield 2181 nUncSent = nUncRcvd = 0;
8b8cc4f… drh 2182
8b8cc4f… drh 2183 /* Send the send-private pragma if we are trying to sync private data */
1570857… drh 2184 if( syncFlags & SYNC_PRIVATE ){
1570857… drh 2185 blob_append(&send, "pragma send-private\n", -1);
1570857… drh 2186 }
03cc332… drh 2187
03cc332… drh 2188 /* Figure out which check-in to lock */
03cc332… drh 2189 if( syncFlags & SYNC_CKIN_LOCK ){
03cc332… drh 2190 int vid = db_lget_int("checkout",0);
03cc332… drh 2191 zCkinLock = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
03cc332… drh 2192 }else{
03cc332… drh 2193 zCkinLock = 0;
03cc332… drh 2194 }
0dba0f4… drh 2195 zClientId = g.localOpen ? db_lget("client-id", 0) : 0;
122ab3f… drh 2196
27d743e… drh 2197 /* When syncing unversioned files, create a TEMP table in which to store
122ab3f… drh 2198 ** the names of files that need to be sent from client to server.
122ab3f… drh 2199 **
122ab3f… drh 2200 ** The initial assumption is that all unversioned files need to be sent
122ab3f… drh 2201 ** to the other side. But "uvigot" cards received back from the remote
122ab3f… drh 2202 ** side will normally cause many of these entries to be removed since they
122ab3f… drh 2203 ** do not really need to be sent.
27d743e… drh 2204 */
b9fd594… drh 2205 if( (syncFlags & (SYNC_UNVERSIONED|SYNC_CLONE))!=0 ){
a5d8548… drh 2206 unversioned_schema();
27d743e… drh 2207 db_multi_exec(
95014ae… drh 2208 "CREATE TEMP TABLE IF NOT EXISTS uv_tosend("
122ab3f… drh 2209 " name TEXT PRIMARY KEY," /* Name of file to send client->server */
122ab3f… drh 2210 " mtimeOnly BOOLEAN" /* True to only send mtime, not content */
27d743e… drh 2211 ") WITHOUT ROWID;"
1b1887c… wyoung 2212 "REPLACE INTO uv_tosend(name,mtimeOnly)"
27d743e… drh 2213 " SELECT name, 0 FROM unversioned WHERE hash IS NOT NULL;"
27d743e… drh 2214 );
27d743e… drh 2215 }
27d743e… drh 2216
50150ad… drh 2217 /*
1f7b409… drh 2218 ** The request from the client always begin with a clone, pull,
1f7b409… drh 2219 ** or push message.
50150ad… drh 2220 */
1f7b409… drh 2221 blob_appendf(&send, "pragma client-version %d %d %d\n",
1f7b409… drh 2222 RELEASE_VERSION_NUMBER, MANIFEST_NUMERIC_DATE,
1f7b409… drh 2223 MANIFEST_NUMERIC_TIME);
1570857… drh 2224 if( syncFlags & SYNC_CLONE ){
3543ed6… drh 2225 blob_appendf(&send, "clone 3 %d\n", cloneSeqno);
1570857… drh 2226 syncFlags &= ~(SYNC_PUSH|SYNC_PULL);
0690aa1… drh 2227 nCardSent++;
a241c81… drh 2228 /* TBD: Request all transferable configuration values */
fda9b15… drh 2229 content_enable_dephantomize(0);
1570857… drh 2230 zOpType = "Clone";
1570857… drh 2231 }else if( syncFlags & SYNC_PULL ){
21e8aed… drh 2232 blob_appendf(&send, "pull %s %s\n", zSCode,
21e8aed… drh 2233 zAltPCode ? zAltPCode : zPCode);
0690aa1… drh 2234 nCardSent++;
97040d6… jan.nijtmans 2235 zOpType = (syncFlags & SYNC_PUSH)?"Sync":"Pull";
05ba15e… drh 2236 if( (syncFlags & SYNC_RESYNC)!=0 && nCycle<2 ){
05ba15e… drh 2237 blob_appendf(&send, "pragma send-catalog\n");
05ba15e… drh 2238 nCardSent++;
05ba15e… drh 2239 }
a241c81… drh 2240 }
1570857… drh 2241 if( syncFlags & SYNC_PUSH ){
50150ad… drh 2242 blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
0690aa1… drh 2243 nCardSent++;
1570857… drh 2244 if( (syncFlags & SYNC_PULL)==0 ) zOpType = "Push";
05ba15e… drh 2245 if( (syncFlags & SYNC_RESYNC)!=0 ) xfer.resync = 0x7fffffff;
1570857… drh 2246 }
1570857… drh 2247 if( syncFlags & SYNC_VERBOSE ){
49b0ff1… drh 2248 fossil_print(zLabelFormat /*works-like:"%s%s%s%s%d"*/,
49b0ff1… drh 2249 "", "Bytes", "Cards", "Artifacts", "Deltas");
49b0ff1… drh 2250 }
49b0ff1… drh 2251
ebcce3c… drh 2252 /* Send the client-url pragma on the first cycle if the client has
ebcce3c… drh 2253 ** a known public url.
ebcce3c… drh 2254 */
ebcce3c… drh 2255 if( zAltPCode==0 ){
ebcce3c… drh 2256 const char *zSelfUrl = public_url();
ebcce3c… drh 2257 if( zSelfUrl ){
ebcce3c… drh 2258 blob_appendf(&send, "pragma client-url %s\n", zSelfUrl);
ebcce3c… drh 2259 }
ebcce3c… drh 2260 }
ebcce3c… drh 2261
12d2f70… drh 2262 /* Request URLs of alternative repositories
ebcce3c… drh 2263 */
12d2f70… drh 2264 if( zAltPCode==0 && (syncFlags & SYNC_SHARE_LINKS)!=0 ){
12d2f70… drh 2265 blob_appendf(&send, "pragma req-links\n");
ebcce3c… drh 2266 }
ebcce3c… drh 2267
dbda8d6… drh 2268 while( go ){
50150ad… drh 2269 int newPhantom = 0;
3cb547b… drh 2270 char *zRandomness;
e1962ef… drh 2271 db_begin_transaction();
16da1b6… andybradford 2272 db_record_repository_filename(0);
16da1b6… andybradford 2273 db_multi_exec(
16da1b6… andybradford 2274 "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
405457c… drh 2275 "CREATE TEMP TABLE unk(uuid TEXT PRIMARY KEY) WITHOUT ROWID;"
16da1b6… andybradford 2276 );
16da1b6… andybradford 2277 manifest_crosslink_begin();
16da1b6… andybradford 2278
fd9b7bd… drh 2279
1f7b409… drh 2280 /* Client sends the most recently received cookie back to the server.
1f7b409… drh 2281 ** Let the server figure out if this is a cookie that it cares about.
bbcb632… aku 2282 */
bbcb632… aku 2283 zCookie = db_get("cookie", 0);
bbcb632… aku 2284 if( zCookie ){
bbcb632… aku 2285 blob_appendf(&send, "cookie %s\n", zCookie);
bbcb632… aku 2286 }
45f3516… jan.nijtmans 2287
1f7b409… drh 2288 /* Client sends gimme cards for phantoms
bbcb632… aku 2289 */
1570857… drh 2290 if( (syncFlags & SYNC_PULL)!=0
1570857… drh 2291 || ((syncFlags & SYNC_CLONE)!=0 && cloneSeqno==1)
1570857… drh 2292 ){
dbdc160… aku 2293 request_phantoms(&xfer, mxPhantomReq);
637fcef… drh 2294 if( xfer.nGimmeSent>0 && nCycle==2 && (syncFlags & SYNC_PULL)!=0 ){
637fcef… drh 2295 blob_appendf(&send, "pragma req-clusters\n");
637fcef… drh 2296 }
bbcb632… aku 2297 }
1570857… drh 2298 if( syncFlags & SYNC_PUSH ){
bbcb632… aku 2299 send_unsent(&xfer);
0690aa1… drh 2300 nCardSent += send_unclustered(&xfer);
1570857… drh 2301 if( syncFlags & SYNC_PRIVATE ) send_private(&xfer);
637fcef… drh 2302 if( nGimmeRcvd>0 && nCycle==2 ){
637fcef… drh 2303 send_all_clusters(&xfer);
637fcef… drh 2304 }
28e5628… drh 2305 }
28e5628… drh 2306
1f7b409… drh 2307 /* Client sends configuration parameter requests. On a clone, delay sending
45f3516… jan.nijtmans 2308 ** this until the second cycle since the login card might fail on
596f3c1… drh 2309 ** the first cycle.
596f3c1… drh 2310 */
1570857… drh 2311 if( configRcvMask && ((syncFlags & SYNC_CLONE)==0 || nCycle>0) ){
28e5628… drh 2312 const char *zName;
1570857… drh 2313 if( zOpType==0 ) zOpType = "Pull";
6b0b57a… drh 2314 zName = configure_first_name(configRcvMask);
28e5628… drh 2315 while( zName ){
28e5628… drh 2316 blob_appendf(&send, "reqconfig %s\n", zName);
6b0b57a… drh 2317 zName = configure_next_name(configRcvMask);
0690aa1… drh 2318 nCardSent++;
6b0b57a… drh 2319 }
596f3c1… drh 2320 origConfigRcvMask = configRcvMask;
6b0b57a… drh 2321 configRcvMask = 0;
6b0b57a… drh 2322 }
6b0b57a… drh 2323
1f7b409… drh 2324 /* Client sends a request to sync unversioned files.
1f7b409… drh 2325 ** On a clone, delay sending this until the second cycle since
1f7b409… drh 2326 ** the login card might fail on the first cycle.
27d743e… drh 2327 */
22005df… drh 2328 if( (syncFlags & SYNC_UNVERSIONED)!=0
27d743e… drh 2329 && ((syncFlags & SYNC_CLONE)==0 || nCycle>0)
27d743e… drh 2330 && !uvHashSent
27d743e… drh 2331 ){
27d743e… drh 2332 blob_appendf(&send, "pragma uv-hash %s\n", unversioned_content_hash(0));
27d743e… drh 2333 nCardSent++;
27d743e… drh 2334 uvHashSent = 1;
27d743e… drh 2335 }
27d743e… drh 2336
1f7b409… drh 2337 /* On a "fossil config push", the client send configuration parameters
1f7b409… drh 2338 ** being pushed up to the server */
6b0b57a… drh 2339 if( configSendMask ){
1570857… drh 2340 if( zOpType==0 ) zOpType = "Push";
2ed7c11… drh 2341 nCardSent += configure_send_group(xfer.pOut, configSendMask, 0);
6b0b57a… drh 2342 configSendMask = 0;
6b0b57a… drh 2343 }
6b0b57a… drh 2344
27d743e… drh 2345 /* Send unversioned files present here on the client but missing or
27d743e… drh 2346 ** obsolete on the server.
c18f231… drh 2347 **
c18f231… drh 2348 ** Or, if the SYNC_UV_REVERT flag is set, delete the local unversioned
c18f231… drh 2349 ** files that do not exist on the server.
122ab3f… drh 2350 **
122ab3f… drh 2351 ** This happens on the second exchange, since we do not know what files
122ab3f… drh 2352 ** need to be sent until after the uvigot cards from the first exchange
122ab3f… drh 2353 ** have been processed.
27d743e… drh 2354 */
27d743e… drh 2355 if( uvDoPush ){
27d743e… drh 2356 assert( (syncFlags & SYNC_UNVERSIONED)!=0 );
b5641c5… drh 2357 if( syncFlags & SYNC_UV_DRYRUN ){
b5641c5… drh 2358 uvDoPush = 0;
b5641c5… drh 2359 }else if( syncFlags & SYNC_UV_REVERT ){
c18f231… drh 2360 db_multi_exec(
c18f231… drh 2361 "DELETE FROM unversioned"
c18f231… drh 2362 " WHERE name IN (SELECT name FROM uv_tosend);"
c18f231… drh 2363 "DELETE FROM uv_tosend;"
c18f231… drh 2364 );
c18f231… drh 2365 uvDoPush = 0;
c18f231… drh 2366 }else{
c18f231… drh 2367 Stmt uvq;
c18f231… drh 2368 int rc = SQLITE_OK;
c18f231… drh 2369 db_prepare(&uvq, "SELECT name, mtimeOnly FROM uv_tosend");
c18f231… drh 2370 while( (rc = db_step(&uvq))==SQLITE_ROW ){
c18f231… drh 2371 const char *zName = db_column_text(&uvq, 0);
c18f231… drh 2372 send_unversioned_file(&xfer, zName, db_column_int(&uvq,1));
c18f231… drh 2373 nCardSent++;
c18f231… drh 2374 nArtifactSent++;
c18f231… drh 2375 db_multi_exec("DELETE FROM uv_tosend WHERE name=%Q", zName);
c18f231… drh 2376 if( syncFlags & SYNC_VERBOSE ){
c18f231… drh 2377 fossil_print("\rUnversioned-file sent: %s\n", zName);
c18f231… drh 2378 }
53db40e… drh 2379 if( (int)blob_size(xfer.pOut)>xfer.mxSend ) break;
c18f231… drh 2380 }
c18f231… drh 2381 db_finalize(&uvq);
c18f231… drh 2382 if( rc==SQLITE_DONE ) uvDoPush = 0;
c18f231… drh 2383 }
c18f231… drh 2384 }
c18f231… drh 2385
03cc332… drh 2386 /* Lock the current check-out */
03cc332… drh 2387 if( zCkinLock ){
03cc332… drh 2388 if( zClientId==0 ){
03cc332… drh 2389 zClientId = db_text(0, "SELECT lower(hex(randomblob(20)))");
03cc332… drh 2390 db_lset("client-id", zClientId);
03cc332… drh 2391 }
03cc332… drh 2392 blob_appendf(&send, "pragma ci-lock %s %s\n", zCkinLock, zClientId);
03cc332… drh 2393 zCkinLock = 0;
3a5caa8… drh 2394 }else if( zClientId ){
3a5caa8… drh 2395 blob_appendf(&send, "pragma ci-unlock %s\n", zClientId);
03cc332… drh 2396 }
1f7b409… drh 2397 /* Append randomness to the end of the uplink message. This makes all
33d3bf3… km 2398 ** messages unique so that the login-card nonce will always
596f3c1… drh 2399 ** be unique.
596f3c1… drh 2400 */
3cb547b… drh 2401 zRandomness = db_text(0, "SELECT hex(randomblob(20))");
3cb547b… drh 2402 blob_appendf(&send, "# %s\n", zRandomness);
1862890… stephan 2403 fossil_free(zRandomness);
3cb547b… drh 2404
8089622… drh 2405 if( (syncFlags & SYNC_VERBOSE)!=0
8089622… drh 2406 && (syncFlags & SYNC_XVERBOSE)==0
8089622… drh 2407 ){
dbb5e2d… drh 2408 fossil_print("waiting for server...");
dbb5e2d… drh 2409 }
dbb5e2d… drh 2410 fflush(stdout);
dbda8d6… drh 2411 /* Exchange messages with the server */
1ad56ce… drh 2412 if( (syncFlags & SYNC_CLONE)!=0 && nCycle==0 ){
1ad56ce… drh 2413 /* Do not send a login card on the first round-trip of a clone */
1ad56ce… drh 2414 mHttpFlags = 0;
1ad56ce… drh 2415 }else{
1ad56ce… drh 2416 mHttpFlags = HTTP_USE_LOGIN;
1ad56ce… drh 2417 }
2b8ac4d… drh 2418 if( syncFlags & SYNC_NOHTTPCOMPRESS ){
2b8ac4d… drh 2419 mHttpFlags |= HTTP_NOCOMPRESS;
8089622… drh 2420 }
8089622… drh 2421 if( syncFlags & SYNC_XVERBOSE ){
8089622… drh 2422 mHttpFlags |= HTTP_VERBOSE;
1862890… stephan 2423 }
8fa8fe8… drh 2424 if( syncFlags & SYNC_QUIET ){
8fa8fe8… drh 2425 mHttpFlags |= HTTP_QUIET;
8fa8fe8… drh 2426 }
89e5c03… drh 2427
89e5c03… drh 2428 /* Do the round-trip to the server */
1ad56ce… drh 2429 if( http_exchange(&send, &recv, mHttpFlags, MAX_REDIRECTS, 0) ){
dbb5e2d… drh 2430 nErr++;
16da1b6… andybradford 2431 go = 2;
dbb5e2d… drh 2432 break;
dbb5e2d… drh 2433 }
dbb5e2d… drh 2434
89e5c03… drh 2435 /* Remember the URL of the sync target in the config file on the
89e5c03… drh 2436 ** first successful round-trip */
89e5c03… drh 2437 if( nCycle==0 && db_is_writeable("repository") ){
ebcce3c… drh 2438 xfer_syncwith(g.url.canonical, 0);
89e5c03… drh 2439 }
89e5c03… drh 2440
dbb5e2d… drh 2441 /* Output current stats */
dca483a… drh 2442 nRoundtrip++;
04b9b01… drh 2443 nArtifactSent += xfer.nFileSent + xfer.nDeltaSent;
1570857… drh 2444 if( syncFlags & SYNC_VERBOSE ){
49b0ff1… drh 2445 fossil_print(zValueFormat /*works-like:"%s%d%d%d%d"*/, "Sent:",
1570857… drh 2446 blob_size(&send), nCardSent+xfer.nGimmeSent+xfer.nIGotSent,
1570857… drh 2447 xfer.nFileSent, xfer.nDeltaSent);
8fa8fe8… drh 2448 }else if( syncFlags & SYNC_QUIET ){
8fa8fe8… drh 2449 /* No-op */
1570857… drh 2450 }else{
f8800f3… stephan 2451 if( bOutIsTty!=0 ){
f8800f3… stephan 2452 fossil_print(zBriefFormat /*works-like:"%d%d%d"*/,
f8800f3… stephan 2453 nRoundtrip, nArtifactSent, nArtifactRcvd);
f8800f3… stephan 2454 }
1570857… drh 2455 }
0690aa1… drh 2456 nCardSent = 0;
0690aa1… drh 2457 nCardRcvd = 0;
edbb332… drh 2458 xfer.nFileSent = 0;
edbb332… drh 2459 xfer.nDeltaSent = 0;
edbb332… drh 2460 xfer.nGimmeSent = 0;
7c2577b… drh 2461 xfer.nIGotSent = 0;
71b6ada… drh 2462 xfer.nPrivIGot = 0;
dbb5e2d… drh 2463
fda9b15… drh 2464 lastPctDone = -1;
9abbd35… danield 2465 nUncSent += blob_size(&send);
dbda8d6… drh 2466 blob_reset(&send);
1f7b409… drh 2467 blob_appendf(&send, "pragma client-version %d %d %d\n",
1f7b409… drh 2468 RELEASE_VERSION_NUMBER, MANIFEST_NUMERIC_DATE,
1f7b409… drh 2469 MANIFEST_NUMERIC_TIME);
fda9b15… drh 2470 rArrivalTime = db_double(0.0, "SELECT julianday('now')");
8b8cc4f… drh 2471
8b8cc4f… drh 2472 /* Send the send-private pragma if we are trying to sync private data */
1570857… drh 2473 if( syncFlags & SYNC_PRIVATE ){
1570857… drh 2474 blob_append(&send, "pragma send-private\n", -1);
1570857… drh 2475 }
50150ad… drh 2476
50150ad… drh 2477 /* Begin constructing the next message (which might never be
243e02b… drh 2478 ** sent) by beginning with the pull or push cards
50150ad… drh 2479 */
1570857… drh 2480 if( syncFlags & SYNC_PULL ){
21e8aed… drh 2481 blob_appendf(&send, "pull %s %s\n", zSCode,
21e8aed… drh 2482 zAltPCode ? zAltPCode : zPCode);
0690aa1… drh 2483 nCardSent++;
50150ad… drh 2484 }
1570857… drh 2485 if( syncFlags & SYNC_PUSH ){
50150ad… drh 2486 blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
0690aa1… drh 2487 nCardSent++;
50150ad… drh 2488 }
390b414… drh 2489 go = 0;
27d743e… drh 2490 nUvGimmeSent = 0;
27d743e… drh 2491 nUvFileRcvd = 0;
637fcef… drh 2492 nGimmeRcvd = 0;
ea5afad… drh 2493 nPriorArtifact = nArtifactRcvd;
dbda8d6… drh 2494
dbda8d6… drh 2495 /* Process the reply that came back from the server */
573a464… drh 2496 while( blob_line(&recv, &xfer.line) ){
edbb332… drh 2497 if( blob_buffer(&xfer.line)[0]=='#' ){
71c40d3… drh 2498 const char *zLine = blob_buffer(&xfer.line);
71c40d3… drh 2499 if( memcmp(zLine, "# timestamp ", 12)==0 ){
71c40d3… drh 2500 char zTime[20];
71c40d3… drh 2501 double rDiff;
71c40d3… drh 2502 sqlite3_snprintf(sizeof(zTime), zTime, "%.19s", &zLine[12]);
fda9b15… drh 2503 rDiff = db_double(9e99, "SELECT julianday('%q') - %.17g",
fda9b15… drh 2504 zTime, rArrivalTime);
139c7e5… drh 2505 if( rDiff>9e98 || rDiff<-9e98 ) rDiff = 0.0;
27d743e… drh 2506 if( rDiff*24.0*3600.0 >= -(blob_size(&recv)/5000.0 + 20) ){
27d743e… drh 2507 rDiff = 0.0;
27d743e… drh 2508 }
051cf59… drh 2509 if( fossil_fabs(rDiff)>fossil_fabs(rSkew) ) rSkew = rDiff;
085b6a1… drh 2510 }
085b6a1… drh 2511 nCardRcvd++;
edbb332… drh 2512 continue;
edbb332… drh 2513 }
573a464… drh 2514 xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
0690aa1… drh 2515 nCardRcvd++;
1570857… drh 2516 if( (syncFlags & SYNC_VERBOSE)!=0 && recv.nUsed>0 ){
fda9b15… drh 2517 pctDone = (recv.iCursor*100)/recv.nUsed;
fda9b15… drh 2518 if( pctDone!=lastPctDone ){
d8ec765… drh 2519 fossil_print("\rprocessed: %d%% ", pctDone);
fda9b15… drh 2520 lastPctDone = pctDone;
fda9b15… drh 2521 fflush(stdout);
fda9b15… drh 2522 }
dccd27c… jeremy_c 2523 }
0690aa1… drh 2524
fd9b7bd… drh 2525 /* file HASH SIZE \n CONTENT
fd9b7bd… drh 2526 ** file HASH DELTASRC SIZE \n CONTENT
0690aa1… drh 2527 **
1f7b409… drh 2528 ** Client receives a file transmitted from the server.
573a464… drh 2529 */
573a464… drh 2530 if( blob_eq(&xfer.aToken[0],"file") ){
fc32fae… mistachkin 2531 xfer_accept_file(&xfer, (syncFlags & SYNC_CLONE)!=0, 0, 0);
1570857… drh 2532 nArtifactRcvd++;
3543ed6… drh 2533 }else
3543ed6… drh 2534
fd9b7bd… drh 2535 /* cfile HASH USIZE CSIZE \n CONTENT
fd9b7bd… drh 2536 ** cfile HASH DELTASRC USIZE CSIZE \n CONTENT
3543ed6… drh 2537 **
1f7b409… drh 2538 ** Client receives a compressed file transmitted from the server.
3543ed6… drh 2539 */
3543ed6… drh 2540 if( blob_eq(&xfer.aToken[0],"cfile") ){
fc32fae… mistachkin 2541 xfer_accept_compressed_file(&xfer, 0, 0);
fc32fae… mistachkin 2542 nArtifactRcvd++;
fc32fae… mistachkin 2543 }else
fc32fae… mistachkin 2544
27d743e… drh 2545 /* uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT
27d743e… drh 2546 **
1f7b409… drh 2547 ** Client accepts an unversioned file from the server.
27d743e… drh 2548 */
27d743e… drh 2549 if( blob_eq(&xfer.aToken[0], "uvfile") ){
27d743e… drh 2550 xfer_accept_unversioned_file(&xfer, 1);
27d743e… drh 2551 nArtifactRcvd++;
27d743e… drh 2552 nUvFileRcvd++;
c18f231… drh 2553 if( syncFlags & SYNC_VERBOSE ){
c18f231… drh 2554 fossil_print("\rUnversioned-file received: %s\n",
c18f231… drh 2555 blob_str(&xfer.aToken[1]));
c18f231… drh 2556 }
27d743e… drh 2557 }else
27d743e… drh 2558
fd9b7bd… drh 2559 /* gimme HASH
573a464… drh 2560 **
1f7b409… drh 2561 ** Client receives an artifact request from the server.
1f7b409… drh 2562 ** If the file is a manifest, assume that the server will also want
1f7b409… drh 2563 ** to know all of the content artifacts associated with the manifest
1f7b409… drh 2564 ** and send those too.
573a464… drh 2565 */
573a464… drh 2566 if( blob_eq(&xfer.aToken[0], "gimme")
573a464… drh 2567 && xfer.nToken==2
fd9b7bd… drh 2568 && blob_is_hname(&xfer.aToken[1])
573a464… drh 2569 ){
405457c… drh 2570 remote_unk(&xfer.aToken[1]);
1570857… drh 2571 if( syncFlags & SYNC_PUSH ){
8b8cc4f… drh 2572 int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
637fcef… drh 2573 if( rid ){
637fcef… drh 2574 send_file(&xfer, rid, &xfer.aToken[1], 0);
637fcef… drh 2575 nGimmeRcvd++;
637fcef… drh 2576 }
573a464… drh 2577 }
573a464… drh 2578 }else
45f3516… jan.nijtmans 2579
fd9b7bd… drh 2580 /* igot HASH ?PRIVATEFLAG?
15652ff… aku 2581 **
15652ff… aku 2582 ** Server announces that it has a particular file. If this is
15652ff… aku 2583 ** not a file that we have and we are pulling, then create a
15652ff… aku 2584 ** phantom to cause this file to be requested on the next cycle.
15652ff… aku 2585 ** Always remember that the server has this file so that we do
15652ff… aku 2586 ** not transmit it by accident.
8b8cc4f… drh 2587 **
45f3516… jan.nijtmans 2588 ** If the PRIVATE argument exists and is 1, then the file is
8b8cc4f… drh 2589 ** private. Pretend it does not exists if we are not pulling
8b8cc4f… drh 2590 ** private files.
573a464… drh 2591 */
8b8cc4f… drh 2592 if( xfer.nToken>=2
573a464… drh 2593 && blob_eq(&xfer.aToken[0], "igot")
fd9b7bd… drh 2594 && blob_is_hname(&xfer.aToken[1])
a48474b… drh 2595 ){
8b8cc4f… drh 2596 int rid;
8b8cc4f… drh 2597 int isPriv = xfer.nToken>=3 && blob_eq(&xfer.aToken[2],"1");
8b8cc4f… drh 2598 rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
02a584f… drh 2599 if( rid>0 ){
50a7f89… drh 2600 if( isPriv ){
50a7f89… drh 2601 content_make_private(rid);
50a7f89… drh 2602 }else{
50a7f89… drh 2603 content_make_public(rid);
50a7f89… drh 2604 }
b517a98… drh 2605 }else if( isPriv && !g.perm.Private ){
8b8cc4f… drh 2606 /* ignore private files */
1570857… drh 2607 }else if( (syncFlags & (SYNC_PULL|SYNC_CLONE))!=0 ){
8b8cc4f… drh 2608 rid = content_new(blob_str(&xfer.aToken[1]), isPriv);
a48474b… drh 2609 if( rid ) newPhantom = 1;
a48474b… drh 2610 }
a48474b… drh 2611 remote_has(rid);
a48474b… drh 2612 }else
45f3516… jan.nijtmans 2613
27d743e… drh 2614 /* uvigot NAME MTIME HASH SIZE
27d743e… drh 2615 **
27d743e… drh 2616 ** Server announces that it has a particular unversioned file. The
27d743e… drh 2617 ** server will only send this card if the client had previously sent
27d743e… drh 2618 ** a "pragma uv-hash" card with a hash that does not match.
27d743e… drh 2619 **
27d743e… drh 2620 ** If the identified file needs to be transferred, then setup for the
27d743e… drh 2621 ** transfer. Generate a "uvgimme" card in the reply if the server
27d743e… drh 2622 ** version is newer than the client. Generate a "uvfile" card if
27d743e… drh 2623 ** the client version is newer than the server. If HASH is "-"
27d743e… drh 2624 ** (indicating that the file has been deleted) and MTIME is newer,
27d743e… drh 2625 ** then do the deletion.
27d743e… drh 2626 */
27d743e… drh 2627 if( xfer.nToken==5
27d743e… drh 2628 && blob_eq(&xfer.aToken[0], "uvigot")
27d743e… drh 2629 && blob_is_filename(&xfer.aToken[1])
27d743e… drh 2630 && blob_is_int64(&xfer.aToken[2], &mtime)
27d743e… drh 2631 && blob_is_int(&xfer.aToken[4], &size)
fd9b7bd… drh 2632 && (blob_eq(&xfer.aToken[3],"-") || blob_is_hname(&xfer.aToken[3]))
27d743e… drh 2633 ){
27d743e… drh 2634 const char *zName = blob_str(&xfer.aToken[1]);
27d743e… drh 2635 const char *zHash = blob_str(&xfer.aToken[3]);
27d743e… drh 2636 int iStatus;
f9f90d9… stephan 2637 if( mtime<0 || size<0 ){
f9f90d9… stephan 2638 xfer_fatal_error("invalid uvigot");
f9f90d9… stephan 2639 return ++nErr;
f9f90d9… stephan 2640 }
27d743e… drh 2641 iStatus = unversioned_status(zName, mtime, zHash);
122ab3f… drh 2642 if( (syncFlags & SYNC_UV_REVERT)!=0 ){
122ab3f… drh 2643 if( iStatus==4 ) iStatus = 2;
122ab3f… drh 2644 if( iStatus==5 ) iStatus = 1;
b5641c5… drh 2645 }
b5641c5… drh 2646 if( syncFlags & (SYNC_UV_TRACE|SYNC_UV_DRYRUN) ){
b5641c5… drh 2647 const char *zMsg = 0;
b5641c5… drh 2648 switch( iStatus ){
b5641c5… drh 2649 case 0:
fe3d30f… jan.nijtmans 2650 case 1: zMsg = "UV-PULL"; break;
b5641c5… drh 2651 case 2: zMsg = "UV-PULL-MTIME-ONLY"; break;
b5641c5… drh 2652 case 4: zMsg = "UV-PUSH-MTIME-ONLY"; break;
b5641c5… drh 2653 case 5: zMsg = "UV-PUSH"; break;
b5641c5… drh 2654 }
b5641c5… drh 2655 if( zMsg ) fossil_print("\r%s: %s\n", zMsg, zName);
b5641c5… drh 2656 if( syncFlags & SYNC_UV_DRYRUN ){
b5641c5… drh 2657 iStatus = 99; /* Prevent any changes or reply messages */
b5641c5… drh 2658 }
122ab3f… drh 2659 }
27d743e… drh 2660 if( iStatus<=1 ){
27d743e… drh 2661 if( zHash[0]!='-' ){
27d743e… drh 2662 blob_appendf(xfer.pOut, "uvgimme %s\n", zName);
27d743e… drh 2663 nCardSent++;
27d743e… drh 2664 nUvGimmeSent++;
122ab3f… drh 2665 db_multi_exec("DELETE FROM unversioned WHERE name=%Q", zName);
27d743e… drh 2666 }else if( iStatus==1 ){
27d743e… drh 2667 db_multi_exec(
27d743e… drh 2668 "UPDATE unversioned"
27d743e… drh 2669 " SET mtime=%lld, hash=NULL, sz=0, encoding=0, content=NULL"
27d743e… drh 2670 " WHERE name=%Q", mtime, zName
27d743e… drh 2671 );
27d743e… drh 2672 db_unset("uv-hash", 0);
27d743e… drh 2673 }
27d743e… drh 2674 }else if( iStatus==2 ){
27d743e… drh 2675 db_multi_exec(
27d743e… drh 2676 "UPDATE unversioned SET mtime=%lld WHERE name=%Q", mtime, zName
27d743e… drh 2677 );
27d743e… drh 2678 db_unset("uv-hash", 0);
27d743e… drh 2679 }
22005df… drh 2680 if( iStatus>=4 && uvPullOnly==1 ){
22005df… drh 2681 fossil_warning(
22005df… drh 2682 "Warning: uv-pull-only \n"
22005df… drh 2683 " Unable to push unversioned content because you lack\n"
22005df… drh 2684 " sufficient permission on the server\n"
22005df… drh 2685 );
22005df… drh 2686 uvPullOnly = 2;
275da70… danield 2687 }
22005df… drh 2688 if( iStatus<=3 || uvPullOnly ){
27d743e… drh 2689 db_multi_exec("DELETE FROM uv_tosend WHERE name=%Q", zName);
27d743e… drh 2690 }else if( iStatus==4 ){
27d743e… drh 2691 db_multi_exec("UPDATE uv_tosend SET mtimeOnly=1 WHERE name=%Q",zName);
27d743e… drh 2692 }else if( iStatus==5 ){
27d743e… drh 2693 db_multi_exec("REPLACE INTO uv_tosend(name,mtimeOnly) VALUES(%Q,0)",
27d743e… drh 2694 zName);
27d743e… drh 2695 }
27d743e… drh 2696 }else
45f3516… jan.nijtmans 2697
15652ff… aku 2698 /* push SERVERCODE PRODUCTCODE
15652ff… aku 2699 **
15652ff… aku 2700 ** Should only happen in response to a clone. This message tells
1f7b409… drh 2701 ** the client what product code to use for the new database.
573a464… drh 2702 */
573a464… drh 2703 if( blob_eq(&xfer.aToken[0],"push")
573a464… drh 2704 && xfer.nToken==3
1570857… drh 2705 && (syncFlags & SYNC_CLONE)!=0
fd9b7bd… drh 2706 && blob_is_hname(&xfer.aToken[2])
573a464… drh 2707 ){
573a464… drh 2708 if( zPCode==0 ){
573a464… drh 2709 zPCode = mprintf("%b", &xfer.aToken[2]);
d0305b3… aku 2710 db_set("project-code", zPCode, 0);
d0305b3… aku 2711 }
addf670… drh 2712 if( cloneSeqno>0 ) blob_appendf(&send, "clone 3 %d\n", cloneSeqno);
0690aa1… drh 2713 nCardSent++;
a241c81… drh 2714 }else
45f3516… jan.nijtmans 2715
a241c81… drh 2716 /* config NAME SIZE \n CONTENT
a241c81… drh 2717 **
1f7b409… drh 2718 ** Client receive a configuration value from the server.
b44d4a8… drh 2719 **
b44d4a8… drh 2720 ** The received configuration setting is silently ignored if it was
b44d4a8… drh 2721 ** not requested by a prior "reqconfig" sent from client to server.
a241c81… drh 2722 */
28e5628… drh 2723 if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3
28e5628… drh 2724 && blob_is_int(&xfer.aToken[2], &size) ){
28e5628… drh 2725 const char *zName = blob_str(&xfer.aToken[1]);
28e5628… drh 2726 Blob content;
f9f90d9… stephan 2727 if( size<0 ){
f9f90d9… stephan 2728 xfer_fatal_error("invalid config record");
f9f90d9… stephan 2729 return ++nErr;
f9f90d9… stephan 2730 }
28e5628… drh 2731 blob_zero(&content);
28e5628… drh 2732 blob_extract(xfer.pIn, size, &content);
b344d3c… drh 2733 g.perm.Admin = g.perm.RdAddr = 1;
b44d4a8… drh 2734 configure_receive(zName, &content, origConfigRcvMask);
1570857… drh 2735 nCardRcvd++;
1570857… drh 2736 nArtifactRcvd++;
28e5628… drh 2737 blob_reset(&content);
28e5628… drh 2738 blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
d0305b3… aku 2739 }else
a241c81… drh 2740
45f3516… jan.nijtmans 2741
bbcb632… aku 2742 /* cookie TEXT
bbcb632… aku 2743 **
1f7b409… drh 2744 ** The client reserves a cookie from the server. The client
bbcb632… aku 2745 ** should remember this cookie and send it back to the server
bbcb632… aku 2746 ** in its next query.
bbcb632… aku 2747 **
bbcb632… aku 2748 ** Each cookie received overwrites the prior cookie from the
bbcb632… aku 2749 ** same server.
bbcb632… aku 2750 */
bbcb632… aku 2751 if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
d0305b3… aku 2752 db_set("cookie", blob_str(&xfer.aToken[1]), 0);
d0305b3… aku 2753 }else
d0305b3… aku 2754
8b8cc4f… drh 2755
8b8cc4f… drh 2756 /* private
8b8cc4f… drh 2757 **
1f7b409… drh 2758 ** The server tells the client that the next "file" or "cfile" will
1f7b409… drh 2759 ** contain private content.
8b8cc4f… drh 2760 */
8b8cc4f… drh 2761 if( blob_eq(&xfer.aToken[0], "private") ){
8b8cc4f… drh 2762 xfer.nextIsPrivate = 1;
8b8cc4f… drh 2763 }else
8b8cc4f… drh 2764
8b8cc4f… drh 2765
fda9b15… drh 2766 /* clone_seqno N
fda9b15… drh 2767 **
fda9b15… drh 2768 ** When doing a clone, the server tries to send all of its artifacts
fda9b15… drh 2769 ** in sequence. This card indicates the sequence number of the next
fda9b15… drh 2770 ** blob that needs to be sent. If N<=0 that indicates that all blobs
fda9b15… drh 2771 ** have been sent.
fda9b15… drh 2772 */
fda9b15… drh 2773 if( blob_eq(&xfer.aToken[0], "clone_seqno") && xfer.nToken==2 ){
fda9b15… drh 2774 blob_is_int(&xfer.aToken[1], &cloneSeqno);
f9f90d9… stephan 2775 if( cloneSeqno<0 ){
f9f90d9… stephan 2776 xfer_fatal_error("invalid clone_seqno");
f9f90d9… stephan 2777 return ++nErr;
f9f90d9… stephan 2778 }
fda9b15… drh 2779 }else
fda9b15… drh 2780
d0305b3… aku 2781 /* message MESSAGE
d0305b3… aku 2782 **
1f7b409… drh 2783 ** A message is received from the server. Print it.
1f7b409… drh 2784 ** Similar to "error" but does not stop processing.
45f3516… jan.nijtmans 2785 */
d0305b3… aku 2786 if( blob_eq(&xfer.aToken[0],"message") && xfer.nToken==2 ){
d0305b3… aku 2787 char *zMsg = blob_terminate(&xfer.aToken[1]);
d0305b3… aku 2788 defossilize(zMsg);
27d743e… drh 2789 if( (syncFlags & SYNC_PUSH) && zMsg
27d743e… drh 2790 && sqlite3_strglob("pull only *", zMsg)==0 ){
1570857… drh 2791 syncFlags &= ~SYNC_PUSH;
e5b8eb1… drh 2792 zMsg = 0;
e5b8eb1… drh 2793 }
2981ac5… drh 2794 if( zMsg && zMsg[0] ){
2981ac5… drh 2795 fossil_force_newline();
2981ac5… drh 2796 fossil_print("Server says: %s\n", zMsg);
2981ac5… drh 2797 }
eb82de6… drh 2798 }else
eb82de6… drh 2799
eb82de6… drh 2800 /* pragma NAME VALUE...
eb82de6… drh 2801 **
eb82de6… drh 2802 ** The server can send pragmas to try to convey meta-information to
45f3516… jan.nijtmans 2803 ** the client. These are informational only. Unknown pragmas are
eb82de6… drh 2804 ** silently ignored.
eb82de6… drh 2805 */
eb82de6… drh 2806 if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
1f7b409… drh 2807 /* pragma server-version VERSION ?DATE? ?TIME?
1f7b409… drh 2808 **
a8b6fdc… andybradford 2809 ** The server announces to the server what version of Fossil it
1f7b409… drh 2810 ** is running. The DATE and TIME are a pure numeric ISO8601 time
1f7b409… drh 2811 ** for the specific check-in of the client.
1f7b409… drh 2812 */
ec93507… drh 2813 if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "server-version") ){
1862890… stephan 2814 xfer.remoteVersion = g.syncInfo.remoteVersion =
1862890… stephan 2815 atoi(blob_str(&xfer.aToken[2]));
1f7b409… drh 2816 if( xfer.nToken>=5 ){
1f7b409… drh 2817 xfer.remoteDate = atoi(blob_str(&xfer.aToken[3]));
1f7b409… drh 2818 xfer.remoteTime = atoi(blob_str(&xfer.aToken[4]));
1f7b409… drh 2819 }
1862890… stephan 2820 xfer_xflc_check( xfer.remoteVersion, xfer.remoteDate,
1862890… stephan 2821 xfer.remoteTime, 0x08 );
1f7b409… drh 2822 }
1f7b409… drh 2823
1f7b409… drh 2824 /* pragma uv-pull-only
22005df… drh 2825 ** pragma uv-push-ok
1f7b409… drh 2826 **
a8b6fdc… andybradford 2827 ** If the server is unwilling to accept new unversioned content (because
27d743e… drh 2828 ** this client lacks the necessary permissions) then it sends a
27d743e… drh 2829 ** "uv-pull-only" pragma so that the client will know not to waste
27d743e… drh 2830 ** bandwidth trying to upload unversioned content. If the server
27d743e… drh 2831 ** does accept new unversioned content, it sends "uv-push-ok".
27d743e… drh 2832 */
12d2f70… drh 2833 else if( syncFlags & SYNC_UNVERSIONED ){
d015a26… drh 2834 if( blob_eq(&xfer.aToken[1], "uv-pull-only") ){
22005df… drh 2835 uvPullOnly = 1;
d015a26… drh 2836 if( syncFlags & SYNC_UV_REVERT ) uvDoPush = 1;
d015a26… drh 2837 }else if( blob_eq(&xfer.aToken[1], "uv-push-ok") ){
d015a26… drh 2838 uvDoPush = 1;
d015a26… drh 2839 }
03cc332… drh 2840 }
03cc332… drh 2841
03cc332… drh 2842 /* pragma ci-lock-fail USER-HOLDING-LOCK LOCK-TIME
03cc332… drh 2843 **
03cc332… drh 2844 ** The server generates this message when a "pragma ci-lock"
03cc332… drh 2845 ** is attempted on a check-in for which there is an existing
03cc332… drh 2846 ** lock. USER-HOLDING-LOCK is the name of the user who originated
03cc332… drh 2847 ** the lock, and LOCK-TIME is the timestamp (seconds since 1970)
03cc332… drh 2848 ** when the lock was taken.
03cc332… drh 2849 */
03cc332… drh 2850 else if( blob_eq(&xfer.aToken[1], "ci-lock-fail") && xfer.nToken==4 ){
03cc332… drh 2851 char *zUser = blob_terminate(&xfer.aToken[2]);
3a5caa8… drh 2852 sqlite3_int64 mtime, iNow;
03cc332… drh 2853 defossilize(zUser);
3a5caa8… drh 2854 iNow = time(NULL);
3a5caa8… drh 2855 if( blob_is_int64(&xfer.aToken[3], &mtime) && iNow>mtime ){
f9f90d9… stephan 2856 if( mtime<0 ){
f9f90d9… stephan 2857 xfer_fatal_error("invalid ci-lock-fail time");
f9f90d9… stephan 2858 return ++nErr;
f9f90d9… stephan 2859 }
3a5caa8… drh 2860 iNow = time(NULL);
66f06fe… drh 2861 fossil_print("\nParent check-in locked by %s %s ago\n",
3a5caa8… drh 2862 zUser, human_readable_age((iNow+1-mtime)/86400.0));
3a5caa8… drh 2863 }else{
3a5caa8… drh 2864 fossil_print("\nParent check-in locked by %s\n", zUser);
3a5caa8… drh 2865 }
3a5caa8… drh 2866 g.ckinLockFail = fossil_strdup(zUser);
3a5caa8… drh 2867 }
9116c6f… drh 2868
9116c6f… drh 2869 /* pragma avoid-delta-manifests
9116c6f… drh 2870 **
9116c6f… drh 2871 ** Discourage the use of delta manifests. The remote side sends
9116c6f… drh 2872 ** this pragma when its forbid-delta-manifests setting is true.
9116c6f… drh 2873 */
9116c6f… drh 2874 else if( blob_eq(&xfer.aToken[1], "avoid-delta-manifests") ){
9116c6f… drh 2875 g.bAvoidDeltaManifests = 1;
9116c6f… drh 2876 }
12d2f70… drh 2877
12d2f70… drh 2878 /* pragma link URL ARG MTIME
12d2f70… drh 2879 **
12d2f70… drh 2880 ** The server has sent the URL for a link to another repository.
12d2f70… drh 2881 ** Record this as a link:URL entry in the config table.
12d2f70… drh 2882 */
12d2f70… drh 2883 else if( blob_eq(&xfer.aToken[1], "link")
12d2f70… drh 2884 && xfer.nToken==5
12d2f70… drh 2885 && (syncFlags & SYNC_SHARE_LINKS)!=0
12d2f70… drh 2886 ){
12d2f70… drh 2887 UrlData x;
12d2f70… drh 2888 char *zUrl = blob_str(&xfer.aToken[2]);
12d2f70… drh 2889 char *zArg = blob_str(&xfer.aToken[3]);
12d2f70… drh 2890 i64 iTime = strtoll(blob_str(&xfer.aToken[4]),0,0);
12d2f70… drh 2891 memset(&x, 0, sizeof(x));
12d2f70… drh 2892 defossilize(zUrl);
12d2f70… drh 2893 defossilize(zArg);
12d2f70… drh 2894 url_parse_local(zUrl, URL_OMIT_USER, &x);
12d2f70… drh 2895 if( x.protocol
12d2f70… drh 2896 && strncmp(x.protocol,"http",4)==0
12d2f70… drh 2897 && iTime>0
12d2f70… drh 2898 ){
12d2f70… drh 2899 db_unprotect(PROTECT_CONFIG);
12d2f70… drh 2900 db_multi_exec(
12d2f70… drh 2901 "INSERT INTO config(name,value,mtime)\n"
12d2f70… drh 2902 " VALUES('link:%q',%Q,%lld)\n"
12d2f70… drh 2903 " ON CONFLICT DO UPDATE\n"
12d2f70… drh 2904 " SET value=excluded.value, mtime=excluded.mtime\n"
12d2f70… drh 2905 " WHERE mtime<excluded.mtime;",
12d2f70… drh 2906 zUrl, zArg, iTime
12d2f70… drh 2907 );
12d2f70… drh 2908 db_protect_pop();
12d2f70… drh 2909 }
12d2f70… drh 2910 url_unparse(&x);
12d2f70… drh 2911 }
12d2f70… drh 2912
dbda8d6… drh 2913 }else
dbda8d6… drh 2914
dbda8d6… drh 2915 /* error MESSAGE
dbda8d6… drh 2916 **
1f7b409… drh 2917 ** The server is reporting an error. The client will abandon
1f7b409… drh 2918 ** the sync session.
596f3c1… drh 2919 **
596f3c1… drh 2920 ** Except, when cloning we will sometimes get an error on the
596f3c1… drh 2921 ** first message exchange because the project-code is unknown
596f3c1… drh 2922 ** and so the login card on the request was invalid. The project-code
45f3516… jan.nijtmans 2923 ** is returned in the reply before the error card, so second and
596f3c1… drh 2924 ** subsequent messages should be OK. Nevertheless, we need to ignore
596f3c1… drh 2925 ** the error card on the first message of a clone.
3350be2… drh 2926 **
3350be2… drh 2927 ** Also ignore "not authorized to write" errors if this is an
3350be2… drh 2928 ** autopush following a commit.
45f3516… jan.nijtmans 2929 */
573a464… drh 2930 if( blob_eq(&xfer.aToken[0],"error") && xfer.nToken==2 ){
3350be2… drh 2931 char *zMsg = blob_terminate(&xfer.aToken[1]);
3350be2… drh 2932 defossilize(zMsg);
92406fa… drh 2933 if( (syncFlags & SYNC_IFABLE)!=0
3350be2… drh 2934 && sqlite3_strlike("%not authorized to write%",zMsg,0)==0 ){
3350be2… drh 2935 autopushFailed = 1;
3350be2… drh 2936 nErr++;
3350be2… drh 2937 }else if( (syncFlags & SYNC_CLONE)==0 || nCycle>0 ){
2981ac5… drh 2938 fossil_force_newline();
6d6740d… drh 2939 fossil_print("Error: %s\n", zMsg);
e4ca60c… drh 2940 blob_appendf(&xfer.err, "server says: %s\n", zMsg);
e4ca60c… drh 2941 nErr++;
0cc4875… drh 2942 break;
596f3c1… drh 2943 }
dbda8d6… drh 2944 }else
dbda8d6… drh 2945
dbda8d6… drh 2946 /* Unknown message */
3543ed6… drh 2947 if( xfer.nToken>0 ){
80457ec… drh 2948 if( blob_str(&xfer.aToken[0])[0]=='<' ){
0cc4875… drh 2949 fossil_warning(
80457ec… drh 2950 "server replies with HTML instead of fossil sync protocol:\n%b",
80457ec… drh 2951 &recv
80457ec… drh 2952 );
0cc4875… drh 2953 nErr++;
0cc4875… drh 2954 break;
80457ec… drh 2955 }
1862890… stephan 2956 blob_appendf(&xfer.err, "unknown command: [%b]\n", &xfer.line);
dbda8d6… drh 2957 }
dbda8d6… drh 2958
573a464… drh 2959 if( blob_size(&xfer.err) ){
2981ac5… drh 2960 fossil_force_newline();
0cc4875… drh 2961 fossil_warning("%b", &xfer.err);
0cc4875… drh 2962 nErr++;
0cc4875… drh 2963 break;
eea381f… drh 2964 }
573a464… drh 2965 blobarray_reset(xfer.aToken, xfer.nToken);
edbb332… drh 2966 blob_reset(&xfer.line);
edbb332… drh 2967 }
6b0b57a… drh 2968 origConfigRcvMask = 0;
1570857… drh 2969 if( nCardRcvd>0 && (syncFlags & SYNC_VERBOSE) ){
49b0ff1… drh 2970 fossil_print(zValueFormat /*works-like:"%s%d%d%d%d"*/, "Received:",
96722b6… drh 2971 blob_size(&recv), nCardRcvd,
96722b6… drh 2972 xfer.nFileRcvd, xfer.nDeltaRcvd + xfer.nDanglingFile);
8e6c55a… drh 2973 }else if( syncFlags & SYNC_QUIET ){
8fa8fe8… drh 2974 /* No-op */
1570857… drh 2975 }else{
f8800f3… stephan 2976 if( bOutIsTty!=0 ){
f8800f3… stephan 2977 fossil_print(zBriefFormat /*works-like:"%d%d%d"*/,
f8800f3… stephan 2978 nRoundtrip, nArtifactSent, nArtifactRcvd);
f8800f3… stephan 2979 }
0690aa1… drh 2980 }
9abbd35… danield 2981 nUncRcvd += blob_size(&recv);
dbda8d6… drh 2982 blob_reset(&recv);
bbcb632… aku 2983 nCycle++;
dbdc160… aku 2984
ea5afad… drh 2985 /* Set go to 1 if we need to continue the sync/push/pull/clone for
ea5afad… drh 2986 ** another round. Set go to 0 if it is time to quit. */
dbdc160… aku 2987 nFileRecv = xfer.nFileRcvd + xfer.nDeltaRcvd + xfer.nDanglingFile;
dbdc160… aku 2988 if( (nFileRecv>0 || newPhantom) && db_exists("SELECT 1 FROM phantom") ){
dbdc160… aku 2989 go = 1;
dbdc160… aku 2990 mxPhantomReq = nFileRecv*2;
dbdc160… aku 2991 if( mxPhantomReq<200 ) mxPhantomReq = 200;
ea5afad… drh 2992 }else if( xfer.nFileSent+xfer.nDeltaSent>0 || uvDoPush ){
ea5afad… drh 2993 /* Go another round if files are queued to send */
ea5afad… drh 2994 go = 1;
ea5afad… drh 2995 }else if( xfer.nPrivIGot>0 && nCycle==1 ){
ea5afad… drh 2996 go = 1;
52648d0… wyoung 2997 }else if( nUvGimmeSent>0 && (nUvFileRcvd>0 || nCycle<3) ){
52648d0… wyoung 2998 /* Continue looping as long as new uvfile cards are being received
52648d0… wyoung 2999 ** and uvgimme cards are being sent. */
52648d0… wyoung 3000 go = 1;
ea5afad… drh 3001 }else if( (syncFlags & SYNC_CLONE)!=0 ){
ea5afad… drh 3002 if( nCycle==1 ){
ea5afad… drh 3003 go = 1; /* go at least two rounds on a clone */
6293b28… wyoung 3004 }else if( nFileRecv>0 ){
6293b28… wyoung 3005 go = 1;
ea5afad… drh 3006 }else if( cloneSeqno>0 && nArtifactRcvd>nPriorArtifact ){
ea5afad… drh 3007 /* Continue the clone until we see the clone_seqno 0" card or
ea5afad… drh 3008 ** until we stop receiving artifacts */
ea5afad… drh 3009 go = 1;
ea5afad… drh 3010 }
bbcb632… aku 3011 }
ea5afad… drh 3012
0690aa1… drh 3013 nCardRcvd = 0;
edbb332… drh 3014 xfer.nFileRcvd = 0;
edbb332… drh 3015 xfer.nDeltaRcvd = 0;
edbb332… drh 3016 xfer.nDanglingFile = 0;
405457c… drh 3017 db_multi_exec("DROP TABLE onremote; DROP TABLE unk;");
16da1b6… andybradford 3018 if( go ){
16da1b6… andybradford 3019 manifest_crosslink_end(MC_PERMIT_HOOKS);
16da1b6… andybradford 3020 }else{
16da1b6… andybradford 3021 manifest_crosslink_end(MC_PERMIT_HOOKS);
16da1b6… andybradford 3022 content_enable_dephantomize(1);
16da1b6… andybradford 3023 }
16da1b6… andybradford 3024 db_end_transaction(0);
1862890… stephan 3025 }; /* while(go) */
16da1b6… andybradford 3026 transport_stats(&nSent, &nRcvd, 1);
0cd5589… drh 3027 if( pnRcvd ) *pnRcvd = nArtifactRcvd;
051cf59… drh 3028 if( (rSkew*24.0*3600.0) > 10.0 ){
051cf59… drh 3029 fossil_warning("*** time skew *** server is fast by %s",
051cf59… drh 3030 db_timespan_name(rSkew));
051cf59… drh 3031 g.clockSkewSeen = 1;
051cf59… drh 3032 }else if( rSkew*24.0*3600.0 < -10.0 ){
051cf59… drh 3033 fossil_warning("*** time skew *** server is slow by %s",
051cf59… drh 3034 db_timespan_name(-rSkew));
051cf59… drh 3035 g.clockSkewSeen = 1;
051cf59… drh 3036 }
8fa8fe8… drh 3037 if( bOutIsTty==0 && (syncFlags & SYNC_QUIET)==0 ){
f8800f3… stephan 3038 fossil_print(zBriefFormat /*works-like:"%d%d%d"*/,
f8800f3… stephan 3039 nRoundtrip, nArtifactSent, nArtifactRcvd);
f8800f3… stephan 3040 fossil_force_newline();
f8800f3… stephan 3041 }
f1ef221… drh 3042 fossil_force_newline();
f485a16… drh 3043 if( g.zHttpCmd==0 ){
8fa8fe8… drh 3044 if( syncFlags & SYNC_QUIET ){
8fa8fe8… drh 3045 /* no-op */
8fa8fe8… drh 3046 }else if( syncFlags & SYNC_VERBOSE ){
cc34f3c… danield 3047 fossil_print(
cc34f3c… danield 3048 "%s done, wire bytes sent: %lld received: %lld remote: %s%s\n",
cc34f3c… danield 3049 zOpType, nSent, nRcvd,
cc34f3c… danield 3050 (g.url.name && g.url.name[0]!='\0') ? g.url.name : "",
cc34f3c… danield 3051 (g.zIpAddr && g.zIpAddr[0]!='\0'
cc34f3c… danield 3052 && fossil_strcmp(g.zIpAddr, g.url.name))
cc34f3c… danield 3053 ? mprintf(" (%s)", g.zIpAddr) : "");
cc34f3c… danield 3054 }else{
cc34f3c… danield 3055 fossil_print(
cc34f3c… danield 3056 "%s done, wire bytes sent: %lld received: %lld remote: %s\n",
cc34f3c… danield 3057 zOpType, nSent, nRcvd, g.zIpAddr);
cc34f3c… danield 3058 }
f485a16… drh 3059 }
9abbd35… danield 3060 if( syncFlags & SYNC_VERBOSE ){
9abbd35… danield 3061 fossil_print(
2b8ac4d… drh 3062 "Uncompressed payload sent: %lld received: %lld\n", nUncSent, nUncRcvd);
9abbd35… danield 3063 }
0cd5589… drh 3064 blob_reset(&send);
0cd5589… drh 3065 blob_reset(&recv);
5fdad9b… drh 3066 transport_close(&g.url);
5fdad9b… drh 3067 transport_global_shutdown(&g.url);
16da1b6… andybradford 3068 if( nErr && go==2 ){
405457c… drh 3069 db_multi_exec("DROP TABLE onremote; DROP TABLE unk;");
16da1b6… andybradford 3070 manifest_crosslink_end(MC_PERMIT_HOOKS);
16da1b6… andybradford 3071 content_enable_dephantomize(1);
16da1b6… andybradford 3072 db_end_transaction(0);
3350be2… drh 3073 }
3350be2… drh 3074 if( nErr && autopushFailed ){
3350be2… drh 3075 fossil_warning(
3350be2… drh 3076 "Warning: The check-in was successful and is saved locally but you\n"
3350be2… drh 3077 " are not authorized to push the changes back to the server\n"
3350be2… drh 3078 " at %s",
3350be2… drh 3079 g.url.canonical
3350be2… drh 3080 );
3350be2… drh 3081 nErr--;
440ed5d… andybradford 3082 }
440ed5d… andybradford 3083 if( (syncFlags & SYNC_CLONE)==0 && g.rcvid && fossil_any_has_fork(g.rcvid) ){
3a49779… jan.nijtmans 3084 fossil_warning("***** WARNING: a fork has occurred *****\n"
3a49779… jan.nijtmans 3085 "use \"fossil leaves -multiple\" for more details.");
16da1b6… andybradford 3086 }
0cc4875… drh 3087 return nErr;
dbda8d6… drh 3088 }

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button