| | @@ -27,11 +27,11 @@ |
| 27 | 27 | typedef struct Xfer Xfer; |
| 28 | 28 | struct Xfer { |
| 29 | 29 | Blob *pIn; /* Input text from the other side */ |
| 30 | 30 | Blob *pOut; /* Compose our reply here */ |
| 31 | 31 | Blob line; /* The current line of input */ |
| 32 | | - Blob aToken[5]; /* Tokenized version of line */ |
| 32 | + Blob aToken[6]; /* Tokenized version of line */ |
| 33 | 33 | Blob err; /* Error message text */ |
| 34 | 34 | int nToken; /* Number of tokens in line */ |
| 35 | 35 | int nIGotSent; /* Number of "igot" cards sent */ |
| 36 | 36 | int nGimmeSent; /* Number of gimme cards sent */ |
| 37 | 37 | int nFileSent; /* Number of files sent */ |
| | @@ -132,20 +132,20 @@ |
| 132 | 132 | pXfer->nDeltaRcvd++; |
| 133 | 133 | }else{ |
| 134 | 134 | srcid = 0; |
| 135 | 135 | pXfer->nFileRcvd++; |
| 136 | 136 | } |
| 137 | | - rid = content_put(&content, blob_str(&pXfer->aToken[1]), srcid); |
| 137 | + rid = content_put(&content, blob_str(&pXfer->aToken[1]), srcid, 0); |
| 138 | 138 | remote_has(rid); |
| 139 | 139 | blob_reset(&content); |
| 140 | 140 | return; |
| 141 | 141 | } |
| 142 | 142 | if( pXfer->nToken==4 ){ |
| 143 | 143 | Blob src, next; |
| 144 | 144 | srcid = rid_from_uuid(&pXfer->aToken[2], 1); |
| 145 | 145 | if( content_get(srcid, &src)==0 ){ |
| 146 | | - rid = content_put(&content, blob_str(&pXfer->aToken[1]), srcid); |
| 146 | + rid = content_put(&content, blob_str(&pXfer->aToken[1]), srcid, 0); |
| 147 | 147 | pXfer->nDanglingFile++; |
| 148 | 148 | db_multi_exec("DELETE FROM phantom WHERE rid=%d", rid); |
| 149 | 149 | content_make_public(rid); |
| 150 | 150 | return; |
| 151 | 151 | } |
| | @@ -159,20 +159,79 @@ |
| 159 | 159 | } |
| 160 | 160 | sha1sum_blob(&content, &hash); |
| 161 | 161 | if( !blob_eq_str(&pXfer->aToken[1], blob_str(&hash), -1) ){ |
| 162 | 162 | blob_appendf(&pXfer->err, "content does not match sha1 hash"); |
| 163 | 163 | } |
| 164 | | - rid = content_put(&content, blob_str(&hash), 0); |
| 164 | + rid = content_put(&content, blob_str(&hash), 0, 0); |
| 165 | 165 | blob_reset(&hash); |
| 166 | 166 | if( rid==0 ){ |
| 167 | 167 | blob_appendf(&pXfer->err, "%s", g.zErrMsg); |
| 168 | 168 | }else{ |
| 169 | 169 | content_make_public(rid); |
| 170 | 170 | manifest_crosslink(rid, &content); |
| 171 | 171 | } |
| 172 | 172 | remote_has(rid); |
| 173 | 173 | } |
| 174 | + |
| 175 | +/* |
| 176 | +** The aToken[0..nToken-1] blob array is a parse of a "cfile" line |
| 177 | +** message. This routine finishes parsing that message and does |
| 178 | +** a record insert of the file. The difference between "file" and |
| 179 | +** "cfile" is that with "cfile" the content is already compressed. |
| 180 | +** |
| 181 | +** The file line is in one of the following two forms: |
| 182 | +** |
| 183 | +** cfile UUID USIZE CSIZE \n CONTENT |
| 184 | +** cfile UUID DELTASRC USIZE CSIZE \n CONTENT |
| 185 | +** |
| 186 | +** The content is CSIZE bytes immediately following the newline. |
| 187 | +** If DELTASRC exists, then the CONTENT is a delta against the |
| 188 | +** content of DELTASRC. |
| 189 | +** |
| 190 | +** The original size of the UUID artifact is USIZE. |
| 191 | +** |
| 192 | +** If any error occurs, write a message into pErr which has already |
| 193 | +** be initialized to an empty string. |
| 194 | +** |
| 195 | +** Any artifact successfully received by this routine is considered to |
| 196 | +** be public and is therefore removed from the "private" table. |
| 197 | +*/ |
| 198 | +static void xfer_accept_compressed_file(Xfer *pXfer){ |
| 199 | + int szC; /* CSIZE */ |
| 200 | + int szU; /* USIZE */ |
| 201 | + int rid; |
| 202 | + int srcid = 0; |
| 203 | + Blob content; |
| 204 | + |
| 205 | + if( pXfer->nToken<4 |
| 206 | + || pXfer->nToken>5 |
| 207 | + || !blob_is_uuid(&pXfer->aToken[1]) |
| 208 | + || !blob_is_int(&pXfer->aToken[pXfer->nToken-2], &szU) |
| 209 | + || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &szC) |
| 210 | + || szC<0 || szU<0 |
| 211 | + || (pXfer->nToken==5 && !blob_is_uuid(&pXfer->aToken[2])) |
| 212 | + ){ |
| 213 | + blob_appendf(&pXfer->err, "malformed cfile line"); |
| 214 | + return; |
| 215 | + } |
| 216 | + blob_zero(&content); |
| 217 | + blob_extract(pXfer->pIn, szC, &content); |
| 218 | + if( uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){ |
| 219 | + /* Ignore files that have been shunned */ |
| 220 | + return; |
| 221 | + } |
| 222 | + if( pXfer->nToken==5 ){ |
| 223 | + srcid = rid_from_uuid(&pXfer->aToken[2], 1); |
| 224 | + pXfer->nDeltaRcvd++; |
| 225 | + }else{ |
| 226 | + srcid = 0; |
| 227 | + pXfer->nFileRcvd++; |
| 228 | + } |
| 229 | + rid = content_put(&content, blob_str(&pXfer->aToken[1]), srcid, szC); |
| 230 | + remote_has(rid); |
| 231 | + blob_reset(&content); |
| 232 | +} |
| 174 | 233 | |
| 175 | 234 | /* |
| 176 | 235 | ** Try to send a file as a delta against its parent. |
| 177 | 236 | ** If successful, return the number of bytes in the delta. |
| 178 | 237 | ** If we cannot generate an appropriate delta, then send |
| | @@ -333,10 +392,55 @@ |
| 333 | 392 | } |
| 334 | 393 | } |
| 335 | 394 | remote_has(rid); |
| 336 | 395 | blob_reset(&uuid); |
| 337 | 396 | } |
| 397 | + |
| 398 | +/* |
| 399 | +** Send the file identified by rid as a compressed artifact. Basically, |
| 400 | +** send the content exactly as it appears in the BLOB table using |
| 401 | +** a "cfile" card. |
| 402 | +*/ |
| 403 | +static void send_compressed_file(Xfer *pXfer, int rid){ |
| 404 | + const char *zContent; |
| 405 | + const char *zUuid; |
| 406 | + char *zDelta; |
| 407 | + int szU; |
| 408 | + int szC; |
| 409 | + int rc; |
| 410 | + Stmt s; |
| 411 | + |
| 412 | + db_prepare(&s, |
| 413 | + "SELECT uuid, size, content FROM blob" |
| 414 | + " WHERE rid=%d" |
| 415 | + " AND size>=0" |
| 416 | + " AND uuid NOT IN shun" |
| 417 | + " AND rid NOT IN private", |
| 418 | + rid |
| 419 | + ); |
| 420 | + rc = db_step(&s); |
| 421 | + if( rc==SQLITE_ROW ){ |
| 422 | + zUuid = db_column_text(&s, 0); |
| 423 | + szU = db_column_int(&s, 1); |
| 424 | + szC = db_column_bytes(&s, 2); |
| 425 | + zContent = db_column_raw(&s, 2); |
| 426 | + zDelta = db_text(0, "SELECT uuid FROM blob WHERE rid=" |
| 427 | + " (SELECT srcid FROM delta WHERE rid=%d)", rid); |
| 428 | + blob_appendf(pXfer->pOut, "cfile %s ", zUuid); |
| 429 | + if( zDelta ){ |
| 430 | + blob_appendf(pXfer->pOut, "%s ", zDelta); |
| 431 | + fossil_free(zDelta); |
| 432 | + pXfer->nDeltaSent++; |
| 433 | + }else{ |
| 434 | + pXfer->nFileSent++; |
| 435 | + } |
| 436 | + blob_appendf(pXfer->pOut, "%d %d\n", szU, szC); |
| 437 | + blob_append(pXfer->pOut, zContent, szC); |
| 438 | + blob_append(pXfer->pOut, "\n", 1); |
| 439 | + } |
| 440 | + db_finalize(&s); |
| 441 | +} |
| 338 | 442 | |
| 339 | 443 | /* |
| 340 | 444 | ** Send a gimme message for every phantom. |
| 341 | 445 | ** |
| 342 | 446 | ** It should not be possible to have a private phantom. But just to be |
| | @@ -511,11 +615,11 @@ |
| 511 | 615 | nRow++; |
| 512 | 616 | if( nRow>=800 && nUncl>nRow+100 ){ |
| 513 | 617 | md5sum_blob(&cluster, &cksum); |
| 514 | 618 | blob_appendf(&cluster, "Z %b\n", &cksum); |
| 515 | 619 | blob_reset(&cksum); |
| 516 | | - content_put(&cluster, 0, 0); |
| 620 | + content_put(&cluster, 0, 0, 0); |
| 517 | 621 | blob_reset(&cluster); |
| 518 | 622 | nUncl -= nRow; |
| 519 | 623 | nRow = 0; |
| 520 | 624 | } |
| 521 | 625 | } |
| | @@ -523,11 +627,11 @@ |
| 523 | 627 | db_multi_exec("DELETE FROM unclustered"); |
| 524 | 628 | if( nRow>0 ){ |
| 525 | 629 | md5sum_blob(&cluster, &cksum); |
| 526 | 630 | blob_appendf(&cluster, "Z %b\n", &cksum); |
| 527 | 631 | blob_reset(&cksum); |
| 528 | | - content_put(&cluster, 0, 0); |
| 632 | + content_put(&cluster, 0, 0, 0); |
| 529 | 633 | blob_reset(&cluster); |
| 530 | 634 | } |
| 531 | 635 | } |
| 532 | 636 | } |
| 533 | 637 | |
| | @@ -666,10 +770,31 @@ |
| 666 | 770 | @ error %T(blob_str(&xfer.err)) |
| 667 | 771 | nErr++; |
| 668 | 772 | break; |
| 669 | 773 | } |
| 670 | 774 | }else |
| 775 | + |
| 776 | + /* cfile UUID USIZE CSIZE \n CONTENT |
| 777 | + ** cfile UUID DELTASRC USIZE CSIZE \n CONTENT |
| 778 | + ** |
| 779 | + ** Accept a file from the client. |
| 780 | + */ |
| 781 | + if( blob_eq(&xfer.aToken[0], "cfile") ){ |
| 782 | + if( !isPush ){ |
| 783 | + cgi_reset_content(); |
| 784 | + @ error not\sauthorized\sto\swrite |
| 785 | + nErr++; |
| 786 | + break; |
| 787 | + } |
| 788 | + xfer_accept_compressed_file(&xfer); |
| 789 | + if( blob_size(&xfer.err) ){ |
| 790 | + cgi_reset_content(); |
| 791 | + @ error %T(blob_str(&xfer.err)) |
| 792 | + nErr++; |
| 793 | + break; |
| 794 | + } |
| 795 | + }else |
| 671 | 796 | |
| 672 | 797 | /* gimme UUID |
| 673 | 798 | ** |
| 674 | 799 | ** Client is requesting a file. Send it. |
| 675 | 800 | */ |
| | @@ -763,14 +888,21 @@ |
| 763 | 888 | if( xfer.nToken==3 |
| 764 | 889 | && blob_is_int(&xfer.aToken[1], &iVers) |
| 765 | 890 | && iVers>=2 |
| 766 | 891 | ){ |
| 767 | 892 | int seqno, max; |
| 893 | + if( iVers>=3 ){ |
| 894 | + cgi_set_content_type("application/x-fossil-uncompressed"); |
| 895 | + } |
| 768 | 896 | blob_is_int(&xfer.aToken[2], &seqno); |
| 769 | 897 | max = db_int(0, "SELECT max(rid) FROM blob"); |
| 770 | 898 | while( xfer.mxSend>blob_size(xfer.pOut) && seqno<=max ){ |
| 771 | | - send_file(&xfer, seqno, 0, 1); |
| 899 | + if( iVers>=3 ){ |
| 900 | + send_compressed_file(&xfer, seqno); |
| 901 | + }else{ |
| 902 | + send_file(&xfer, seqno, 0, 1); |
| 903 | + } |
| 772 | 904 | seqno++; |
| 773 | 905 | } |
| 774 | 906 | if( seqno>=max ) seqno = 0; |
| 775 | 907 | @ clone_seqno %d(seqno) |
| 776 | 908 | }else{ |
| | @@ -1032,11 +1164,11 @@ |
| 1032 | 1164 | |
| 1033 | 1165 | /* |
| 1034 | 1166 | ** Always begin with a clone, pull, or push message |
| 1035 | 1167 | */ |
| 1036 | 1168 | if( cloneFlag ){ |
| 1037 | | - blob_appendf(&send, "clone 2 %d\n", cloneSeqno); |
| 1169 | + blob_appendf(&send, "clone 3 %d\n", cloneSeqno); |
| 1038 | 1170 | pushFlag = 0; |
| 1039 | 1171 | pullFlag = 0; |
| 1040 | 1172 | nCardSent++; |
| 1041 | 1173 | /* TBD: Request all transferable configuration values */ |
| 1042 | 1174 | content_enable_dephantomize(0); |
| | @@ -1191,10 +1323,19 @@ |
| 1191 | 1323 | ** Receive a file transmitted from the server. |
| 1192 | 1324 | */ |
| 1193 | 1325 | if( blob_eq(&xfer.aToken[0],"file") ){ |
| 1194 | 1326 | xfer_accept_file(&xfer, cloneFlag); |
| 1195 | 1327 | }else |
| 1328 | + |
| 1329 | + /* cfile UUID USIZE CSIZE \n CONTENT |
| 1330 | + ** cfile UUID DELTASRC USIZE CSIZE \n CONTENT |
| 1331 | + ** |
| 1332 | + ** Receive a compressed file transmitted from the server. |
| 1333 | + */ |
| 1334 | + if( blob_eq(&xfer.aToken[0],"cfile") ){ |
| 1335 | + xfer_accept_compressed_file(&xfer); |
| 1336 | + }else |
| 1196 | 1337 | |
| 1197 | 1338 | /* gimme UUID |
| 1198 | 1339 | ** |
| 1199 | 1340 | ** Server is requesting a file. If the file is a manifest, assume |
| 1200 | 1341 | ** that the server will also want to know all of the content files |
| | @@ -1249,11 +1390,11 @@ |
| 1249 | 1390 | } |
| 1250 | 1391 | if( zPCode==0 ){ |
| 1251 | 1392 | zPCode = mprintf("%b", &xfer.aToken[2]); |
| 1252 | 1393 | db_set("project-code", zPCode, 0); |
| 1253 | 1394 | } |
| 1254 | | - blob_appendf(&send, "clone 2 %d\n", cloneSeqno); |
| 1395 | + blob_appendf(&send, "clone 3 %d\n", cloneSeqno); |
| 1255 | 1396 | nCardSent++; |
| 1256 | 1397 | }else |
| 1257 | 1398 | |
| 1258 | 1399 | /* config NAME SIZE \n CONTENT |
| 1259 | 1400 | ** |
| | @@ -1363,20 +1504,20 @@ |
| 1363 | 1504 | break; |
| 1364 | 1505 | } |
| 1365 | 1506 | }else |
| 1366 | 1507 | |
| 1367 | 1508 | /* Unknown message */ |
| 1368 | | - { |
| 1509 | + if( xfer.nToken>0 ){ |
| 1369 | 1510 | if( blob_str(&xfer.aToken[0])[0]=='<' ){ |
| 1370 | 1511 | fossil_warning( |
| 1371 | 1512 | "server replies with HTML instead of fossil sync protocol:\n%b", |
| 1372 | 1513 | &recv |
| 1373 | 1514 | ); |
| 1374 | 1515 | nErr++; |
| 1375 | 1516 | break; |
| 1376 | 1517 | } |
| 1377 | | - blob_appendf(&xfer.err, "unknown command: %b", &xfer.aToken[0]); |
| 1518 | + blob_appendf(&xfer.err, "unknown command: [%b]", &xfer.aToken[0]); |
| 1378 | 1519 | } |
| 1379 | 1520 | |
| 1380 | 1521 | if( blob_size(&xfer.err) ){ |
| 1381 | 1522 | fossil_warning("%b", &xfer.err); |
| 1382 | 1523 | nErr++; |
| 1383 | 1524 | |