Changes On Branch private-sync
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch private-sync Excluding Merge-Ins

This is equivalent to a diff from babe3fb52a to bbf257dc9e

2011-02-27
21:08
Merge the --private sync enhancement into the trunk. check-in: 8b8cc4f1b7 user: drh tags: trunk
21:03
Fix issues with file-to-file sync. Allow --localauth to enable --private syncing. Closed-Leaf check-in: bbf257dc9e user: drh tags: private-sync
17:48
Bug fix: Pull public artifacts when --private is not used. check-in: e3e368c329 user: drh tags: private-sync
2011-02-26
21:49
First cut at code to enable syncing private branches. Code compiles but is otherwise untested. The "x" privilege is required on the server in order to sync privately. check-in: 4a17f85182 user: drh tags: private-sync
16:57
Adding a new skin option with gradients, shadows, and rounded corners. check-in: babe3fb52a user: drh tags: trunk
15:32
Extend the file format for manifests to include the Q-card for recording cherry-picks. Parse and ignore these cards for now. check-in: 7fcbbb1da0 user: drh tags: trunk

Changes to src/clone.c.

33
34
35
36
37
38
39
40

41
42
43
44
45
46

47

48
49
50
51
52
53
54
..
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
**
** By default, your current login name is used to create the default
** admin user. This can be overridden using the -A|--admin-user
** parameter.
**
** Options:
**
**    --admin-user|-A USERNAME

**
*/
void clone_cmd(void){
  char *zPassword;
  const char *zDefaultUser;   /* Optional name of the default user */
  int nErr = 0;



  url_proxy_options();
  if( g.argc < 4 ){
    usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
  }
  db_open_config(0);
  if( file_size(g.argv[3])>0 ){
    fossil_panic("file already exists: %s", g.argv[3]);
................................................................................
    db_set("last-sync-url", g.argv[2], 0);
    db_multi_exec(
      "REPLACE INTO config(name,value)"
      " VALUES('server-code', lower(hex(randomblob(20))));"
    );
    url_enable_proxy(0);
    g.xlinkClusterOnly = 1;
    nErr = client_sync(0,0,1,CONFIGSET_ALL,0);
    g.xlinkClusterOnly = 0;
    verify_cancel();
    db_end_transaction(0);
    db_close(1);
    if( nErr ){
      unlink(g.argv[3]);
      fossil_fatal("server returned an error - clone aborted");






|
>






>

>







 







|







33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
..
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
**
** By default, your current login name is used to create the default
** admin user. This can be overridden using the -A|--admin-user
** parameter.
**
** Options:
**
**    --admin-user|-A USERNAME    Make USERNAME the administrator
**    --private                   Also clone private branches 
**
*/
void clone_cmd(void){
  char *zPassword;
  const char *zDefaultUser;   /* Optional name of the default user */
  int nErr = 0;
  int bPrivate;               /* Also clone private branches */

  bPrivate = find_option("private",0,0)!=0;
  url_proxy_options();
  if( g.argc < 4 ){
    usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
  }
  db_open_config(0);
  if( file_size(g.argv[3])>0 ){
    fossil_panic("file already exists: %s", g.argv[3]);
................................................................................
    db_set("last-sync-url", g.argv[2], 0);
    db_multi_exec(
      "REPLACE INTO config(name,value)"
      " VALUES('server-code', lower(hex(randomblob(20))));"
    );
    url_enable_proxy(0);
    g.xlinkClusterOnly = 1;
    nErr = client_sync(0,0,1,bPrivate,CONFIGSET_ALL,0);
    g.xlinkClusterOnly = 0;
    verify_cancel();
    db_end_transaction(0);
    db_close(1);
    if( nErr ){
      unlink(g.argv[3]);
      fossil_fatal("server returned an error - clone aborted");

Changes to src/configure.c.

462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
      zPw = unobscure(db_get("last-sync-pw", 0));
    }
    url_parse(zServer);
    if( g.urlPasswd==0 && zPw ) g.urlPasswd = mprintf("%s", zPw);
    user_select();
    url_enable_proxy("via proxy: ");
    if( strncmp(zMethod, "push", n)==0 ){
      client_sync(0,0,0,0,mask);
    }else{
      client_sync(0,0,0,mask,0);
    }
  }else
  if( strncmp(zMethod, "reset", n)==0 ){
    int mask, i;
    char *zBackup;
    if( g.argc!=4 ) usage("reset AREA");
    mask = find_area(g.argv[3]);






|

|







462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
      zPw = unobscure(db_get("last-sync-pw", 0));
    }
    url_parse(zServer);
    if( g.urlPasswd==0 && zPw ) g.urlPasswd = mprintf("%s", zPw);
    user_select();
    url_enable_proxy("via proxy: ");
    if( strncmp(zMethod, "push", n)==0 ){
      client_sync(0,0,0,0,0,mask);
    }else{
      client_sync(0,0,0,0,mask,0);
    }
  }else
  if( strncmp(zMethod, "reset", n)==0 ){
    int mask, i;
    char *zBackup;
    if( g.argc!=4 ) usage("reset AREA");
    mask = find_area(g.argv[3]);

Changes to src/http_transport.c.

300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
*/
void transport_flip(void){
  if( g.urlIsSsh ){
    fprintf(sshOut, "\n\n");
  }else if( g.urlIsFile ){
    char *zCmd;
    fclose(transport.pFile);
    zCmd = mprintf("\"%s\" http \"%s\" \"%s\" \"%s\" 127.0.0.1",
       fossil_nameofexe(), g.urlName, transport.zOutFile, transport.zInFile
    );
    fossil_system(zCmd);
    free(zCmd);
    transport.pFile = fopen(transport.zInFile, "rb");
  }
}






|







300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
*/
void transport_flip(void){
  if( g.urlIsSsh ){
    fprintf(sshOut, "\n\n");
  }else if( g.urlIsFile ){
    char *zCmd;
    fclose(transport.pFile);
    zCmd = mprintf("\"%s\" http \"%s\" \"%s\" \"%s\" 127.0.0.1 --localauth",
       fossil_nameofexe(), g.urlName, transport.zOutFile, transport.zInFile
    );
    fossil_system(zCmd);
    free(zCmd);
    transport.pFile = fopen(transport.zInFile, "rb");
  }
}

Changes to src/login.c.

372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
...
533
534
535
536
537
538
539

540
541
542
543
544
545
546
  if( strcmp(zRemoteAddr, "127.0.0.1")==0
   && g.useLocalauth
   && db_get_int("localauth",0)==0
   && P("HTTPS")==0
  ){
    uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
    g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
    zCap = "s";
    g.noPswd = 1;
    sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "localhost");
  }

  /* Check the login cookie to see if it matches a known valid user.
  */
  if( uid==0 && (zCookie = P(login_cookie_name()))!=0 ){
................................................................................
      case 'r':   g.okRdTkt = 1;                                break;
      case 'n':   g.okNewTkt = 1;                               break;
      case 'w':   g.okWrTkt = g.okRdTkt = g.okNewTkt = 
                  g.okApndTkt = 1;                              break;
      case 'c':   g.okApndTkt = 1;                              break;
      case 't':   g.okTktFmt = 1;                               break;
      case 'b':   g.okAttach = 1;                               break;


      /* The "u" privileges is a little different.  It recursively 
      ** inherits all privileges of the user named "reader" */
      case 'u': {
        if( zUser==0 ){
          zUser = db_text("", "SELECT cap FROM user WHERE login='reader'");
          login_set_capabilities(zUser);






|







 







>







372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
...
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
  if( strcmp(zRemoteAddr, "127.0.0.1")==0
   && g.useLocalauth
   && db_get_int("localauth",0)==0
   && P("HTTPS")==0
  ){
    uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
    g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
    zCap = "sx";
    g.noPswd = 1;
    sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "localhost");
  }

  /* Check the login cookie to see if it matches a known valid user.
  */
  if( uid==0 && (zCookie = P(login_cookie_name()))!=0 ){
................................................................................
      case 'r':   g.okRdTkt = 1;                                break;
      case 'n':   g.okNewTkt = 1;                               break;
      case 'w':   g.okWrTkt = g.okRdTkt = g.okNewTkt = 
                  g.okApndTkt = 1;                              break;
      case 'c':   g.okApndTkt = 1;                              break;
      case 't':   g.okTktFmt = 1;                               break;
      case 'b':   g.okAttach = 1;                               break;
      case 'x':   g.okPrivate = 1;                              break;

      /* The "u" privileges is a little different.  It recursively 
      ** inherits all privileges of the user named "reader" */
      case 'u': {
        if( zUser==0 ){
          zUser = db_text("", "SELECT cap FROM user WHERE login='reader'");
          login_set_capabilities(zUser);

Changes to src/main.c.

132
133
134
135
136
137
138

139
140
141
142
143
144
145
  int okNewTkt;           /* n: create new tickets */
  int okApndTkt;          /* c: append to tickets via the web */
  int okWrTkt;            /* w: make changes to tickets via web */
  int okAttach;           /* b: add attachments */
  int okTktFmt;           /* t: create new ticket report formats */
  int okRdAddr;           /* e: read email addresses or other private data */
  int okZip;              /* z: download zipped artifact via /zip URL */


  /* For defense against Cross-site Request Forgery attacks */
  char zCsrfToken[12];    /* Value of the anti-CSRF token */
  int okCsrf;             /* Anti-CSRF token is present and valid */

  FILE *fDebug;           /* Write debug information here, if the file exists */
  int thTrace;            /* True to enable TH1 debugging output */






>







132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
  int okNewTkt;           /* n: create new tickets */
  int okApndTkt;          /* c: append to tickets via the web */
  int okWrTkt;            /* w: make changes to tickets via web */
  int okAttach;           /* b: add attachments */
  int okTktFmt;           /* t: create new ticket report formats */
  int okRdAddr;           /* e: read email addresses or other private data */
  int okZip;              /* z: download zipped artifact via /zip URL */
  int okPrivate;          /* x: can send and receive private content */

  /* For defense against Cross-site Request Forgery attacks */
  char zCsrfToken[12];    /* Value of the anti-CSRF token */
  int okCsrf;             /* Anti-CSRF token is present and valid */

  FILE *fDebug;           /* Write debug information here, if the file exists */
  int thTrace;            /* True to enable TH1 debugging output */

Changes to src/rebuild.c.

538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553

554
555
556
557
558
559
560
561
562
563

564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581


582







583
584
585
586
587
588
589
590
591
592
593

594
595
596
597
598
599
600
    }
    db_finalize(&q);
  }
}

/*
** COMMAND: scrub
** %fossil scrub [--verily] [--force] [REPOSITORY]
**
** The command removes sensitive information (such as passwords) from a
** repository so that the respository can be sent to an untrusted reader.
**
** By default, only passwords are removed.  However, if the --verily option
** is added, then private branches, concealed email addresses, IP
** addresses of correspondents, and similar privacy-sensitive fields
** are also purged.

**
** This command permanently deletes the scrubbed information.  The effects
** of this command are irreversible.  Use with caution.
**
** The user is prompted to confirm the scrub unless the --force option
** is used.
*/
void scrub_cmd(void){
  int bVerily = find_option("verily",0,0)!=0;
  int bForce = find_option("force", "f", 0)!=0;

  int bNeedRebuild = 0;
  if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
  if( g.argc==2 ){
    db_must_be_within_tree();
  }else{
    db_open_repository(g.argv[2]);
  }
  if( !bForce ){
    Blob ans;
    blob_zero(&ans);
    prompt_user("Scrubbing the repository will permanently remove user\n"
                "passwords and other information. Changes cannot be undone.\n"
                "Continue (y/N)? ", &ans);
    if( blob_str(&ans)[0]!='y' ){
      fossil_exit(1);
    }
  }
  db_begin_transaction();


  db_multi_exec(







    "UPDATE user SET pw='';"
    "DELETE FROM config WHERE name GLOB 'last-sync-*';"
  );
  if( bVerily ){
    bNeedRebuild = db_exists("SELECT 1 FROM private");
    db_multi_exec(
      "DELETE FROM concealed;"
      "UPDATE rcvfrom SET ipaddr='unknown';"
      "UPDATE user SET photo=NULL, info='';"
      "INSERT INTO shun SELECT uuid FROM blob WHERE rid IN private;"
    );

  }
  if( !bNeedRebuild ){
    db_end_transaction(0);
    db_multi_exec("VACUUM;");
  }else{
    rebuild_db(0, 1, 0);
    db_end_transaction(0);






|







|
>










>










|
<
|





>
>
|
>
>
>
>
>
>
>
|
|
|
|
<
|
|
|
|
<
|
>







538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576

577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596

597
598
599
600

601
602
603
604
605
606
607
608
609
    }
    db_finalize(&q);
  }
}

/*
** COMMAND: scrub
** %fossil scrub [--verily] [--force] [--private] [REPOSITORY]
**
** The command removes sensitive information (such as passwords) from a
** repository so that the respository can be sent to an untrusted reader.
**
** By default, only passwords are removed.  However, if the --verily option
** is added, then private branches, concealed email addresses, IP
** addresses of correspondents, and similar privacy-sensitive fields
** are also purged.  If the --private option is used, then only private
** branches are removed and all other information is left intact.
**
** This command permanently deletes the scrubbed information.  The effects
** of this command are irreversible.  Use with caution.
**
** The user is prompted to confirm the scrub unless the --force option
** is used.
*/
void scrub_cmd(void){
  int bVerily = find_option("verily",0,0)!=0;
  int bForce = find_option("force", "f", 0)!=0;
  int privateOnly = find_option("private",0,0)!=0;
  int bNeedRebuild = 0;
  if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
  if( g.argc==2 ){
    db_must_be_within_tree();
  }else{
    db_open_repository(g.argv[2]);
  }
  if( !bForce ){
    Blob ans;
    blob_zero(&ans);
    prompt_user("Scrubbing the repository will permanently information.\n"

                "Changes cannot be undone.  Continue (y/N)? ", &ans);
    if( blob_str(&ans)[0]!='y' ){
      fossil_exit(1);
    }
  }
  db_begin_transaction();
  if( privateOnly || bVerily ){
    bNeedRebuild = db_exists("SELECT 1 FROM private");
    db_multi_exec(
      "DELETE FROM blob WHERE rid IN private;"
      "DELETE FROM delta WHERE rid IN private;"
      "DELETE FROM private;"
    );
  }
  if( !privateOnly ){
    db_multi_exec(
      "UPDATE user SET pw='';"
      "DELETE FROM config WHERE name GLOB 'last-sync-*';"
    );
    if( bVerily ){

      db_multi_exec(
        "DELETE FROM concealed;"
        "UPDATE rcvfrom SET ipaddr='unknown';"
        "UPDATE user SET photo=NULL, info='';"

      );
    }
  }
  if( !bNeedRebuild ){
    db_end_transaction(0);
    db_multi_exec("VACUUM;");
  }else{
    rebuild_db(0, 1, 0);
    db_end_transaction(0);

Changes to src/setup.c.

120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
...
186
187
188
189
190
191
192


193
194
195
196
197
198
199
...
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
...
298
299
300
301
302
303
304

305
306
307
308
309
310
311
...
320
321
322
323
324
325
326

327
328
329
330
331
332
333
...
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
...
385
386
387
388
389
390
391

392
393
394
395
396
397
398
...
482
483
484
485
486
487
488

489
490
491
492
493
494
495
  @   <th class="usetupListUser" style="text-align: right;padding-right: 20px;">User&nbsp;ID</th>
  @   <th class="usetupListCap" style="text-align: center;padding-right: 15px;">Capabilities</th>
  @   <th class="usetupListCon"  style="text-align: left;">Contact&nbsp;Info</th>
  @ </tr>
  db_prepare(&s, "SELECT uid, login, cap, info FROM user ORDER BY login");
  while( db_step(&s)==SQLITE_ROW ){
    const char *zCap = db_column_text(&s, 2);
    if( strstr(zCap, "s") ) zCap = "s";
    @ <tr>
    @ <td class="usetupListUser" style="text-align: right;padding-right: 20px;white-space:nowrap;">
    if( g.okAdmin && (zCap[0]!='s' || g.okSetup) ){
      @ <a href="setup_uedit?id=%d(db_column_int(&s,0))">
    }
    @ %h(db_column_text(&s,1))
    if( g.okAdmin ){
................................................................................
     @   <td><i>Reader:</i> Inherit privileges of
     @   user <tt>reader</tt></td></tr>
     @ <tr><td valign="top"><b>v</b></td>
     @   <td><i>Developer:</i> Inherit privileges of
     @   user <tt>developer</tt></td></tr>
     @ <tr><td valign="top"><b>w</b></td>
     @   <td><i>Write-Tkt:</i> Edit tickets</td></tr>


     @ <tr><td valign="top"><b>z</b></td>
     @   <td><i>Zip download:</i> Download a baseline via the
     @   <tt>/zip</tt> URL even without 
     @    check<span class="capability">o</span>ut
     @    and <span class="capability">h</span>istory permissions</td></tr>
  @ </table>
  @ </li>
................................................................................
/*
** WEBPAGE: /setup_uedit
*/
void user_edit(void){
  const char *zId, *zLogin, *zInfo, *zCap, *zPw;
  char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap;
  char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae;
  char *oat, *oau, *oav, *oab, *oaz;
  const char *inherit[128];
  int doWrite;
  int uid;
  int higherUser = 0;  /* True if user being edited is SETUP and the */
                       /* user doing the editing is ADMIN.  Disallow editing */

  /* Must have ADMIN privleges to access this page
................................................................................
    int af = P("af")!=0;
    int am = P("am")!=0;
    int ah = P("ah")!=0;
    int ag = P("ag")!=0;
    int at = P("at")!=0;
    int au = P("au")!=0;
    int av = P("av")!=0;

    int az = P("az")!=0;
    if( aa ){ zCap[i++] = 'a'; }
    if( ab ){ zCap[i++] = 'b'; }
    if( ac ){ zCap[i++] = 'c'; }
    if( ad ){ zCap[i++] = 'd'; }
    if( ae ){ zCap[i++] = 'e'; }
    if( af ){ zCap[i++] = 'f'; }
................................................................................
    if( ap ){ zCap[i++] = 'p'; }
    if( ar ){ zCap[i++] = 'r'; }
    if( as ){ zCap[i++] = 's'; }
    if( at ){ zCap[i++] = 't'; }
    if( au ){ zCap[i++] = 'u'; }
    if( av ){ zCap[i++] = 'v'; }
    if( aw ){ zCap[i++] = 'w'; }

    if( az ){ zCap[i++] = 'z'; }

    zCap[i] = 0;
    zPw = P("pw");
    zLogin = P("login");
    if( isValidPwString(zPw) ){
      zPw = sha1_shared_secret(zPw, zLogin);
................................................................................
  /* Load the existing information about the user, if any
  */
  zLogin = "";
  zInfo = "";
  zCap = "";
  zPw = "";
  oaa = oab = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam =
        oan = oao = oap = oar = oas = oat = oau = oav = oaw = oaz = "";
  if( uid ){
    zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
    zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
    zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
    zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid);
    if( strchr(zCap, 'a') ) oaa = " checked=\"checked\"";
    if( strchr(zCap, 'b') ) oab = " checked=\"checked\"";
................................................................................
    if( strchr(zCap, 'p') ) oap = " checked=\"checked\"";
    if( strchr(zCap, 'r') ) oar = " checked=\"checked\"";
    if( strchr(zCap, 's') ) oas = " checked=\"checked\"";
    if( strchr(zCap, 't') ) oat = " checked=\"checked\"";
    if( strchr(zCap, 'u') ) oau = " checked=\"checked\"";
    if( strchr(zCap, 'v') ) oav = " checked=\"checked\"";
    if( strchr(zCap, 'w') ) oaw = " checked=\"checked\"";

    if( strchr(zCap, 'z') ) oaz = " checked=\"checked\"";
  }

  /* figure out inherited permissions */
  memset(inherit, 0, sizeof(inherit));
  if( strcmp(zLogin, "developer") ){
    char *z1, *z2;
................................................................................
  @    <input type="checkbox" name="ak"%s(oak) />%s(B('k'))Write Wiki<br />
  @    <input type="checkbox" name="ab"%s(oab) />%s(B('b'))Attachments<br />
  @    <input type="checkbox" name="ar"%s(oar) />%s(B('r'))Read Ticket<br />
  @    <input type="checkbox" name="an"%s(oan) />%s(B('n'))New Ticket<br />
  @    <input type="checkbox" name="ac"%s(oac) />%s(B('c'))Append Ticket<br />
  @    <input type="checkbox" name="aw"%s(oaw) />%s(B('w'))Write Ticket<br />
  @    <input type="checkbox" name="at"%s(oat) />%s(B('t'))Ticket Report<br />

  @    <input type="checkbox" name="az"%s(oaz) />%s(B('z'))Download Zip
  @   </td>
  @ </tr>
  @ <tr>
  @   <td align="right">Password:</td>
  if( zPw[0] ){
    /* Obscure the password for all users */






<







 







>
>







 







|







 







>







 







>







 







|







 







>







 







>







120
121
122
123
124
125
126

127
128
129
130
131
132
133
...
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
...
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
...
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
...
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
...
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
...
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
...
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
  @   <th class="usetupListUser" style="text-align: right;padding-right: 20px;">User&nbsp;ID</th>
  @   <th class="usetupListCap" style="text-align: center;padding-right: 15px;">Capabilities</th>
  @   <th class="usetupListCon"  style="text-align: left;">Contact&nbsp;Info</th>
  @ </tr>
  db_prepare(&s, "SELECT uid, login, cap, info FROM user ORDER BY login");
  while( db_step(&s)==SQLITE_ROW ){
    const char *zCap = db_column_text(&s, 2);

    @ <tr>
    @ <td class="usetupListUser" style="text-align: right;padding-right: 20px;white-space:nowrap;">
    if( g.okAdmin && (zCap[0]!='s' || g.okSetup) ){
      @ <a href="setup_uedit?id=%d(db_column_int(&s,0))">
    }
    @ %h(db_column_text(&s,1))
    if( g.okAdmin ){
................................................................................
     @   <td><i>Reader:</i> Inherit privileges of
     @   user <tt>reader</tt></td></tr>
     @ <tr><td valign="top"><b>v</b></td>
     @   <td><i>Developer:</i> Inherit privileges of
     @   user <tt>developer</tt></td></tr>
     @ <tr><td valign="top"><b>w</b></td>
     @   <td><i>Write-Tkt:</i> Edit tickets</td></tr>
     @ <tr><td valign="top"><b>x</b></td>
     @   <td><i>Private:</i> Push and/or pull private branches</td></tr>
     @ <tr><td valign="top"><b>z</b></td>
     @   <td><i>Zip download:</i> Download a baseline via the
     @   <tt>/zip</tt> URL even without 
     @    check<span class="capability">o</span>ut
     @    and <span class="capability">h</span>istory permissions</td></tr>
  @ </table>
  @ </li>
................................................................................
/*
** WEBPAGE: /setup_uedit
*/
void user_edit(void){
  const char *zId, *zLogin, *zInfo, *zCap, *zPw;
  char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap;
  char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae;
  char *oat, *oau, *oav, *oab, *oax, *oaz;
  const char *inherit[128];
  int doWrite;
  int uid;
  int higherUser = 0;  /* True if user being edited is SETUP and the */
                       /* user doing the editing is ADMIN.  Disallow editing */

  /* Must have ADMIN privleges to access this page
................................................................................
    int af = P("af")!=0;
    int am = P("am")!=0;
    int ah = P("ah")!=0;
    int ag = P("ag")!=0;
    int at = P("at")!=0;
    int au = P("au")!=0;
    int av = P("av")!=0;
    int ax = P("ax")!=0;
    int az = P("az")!=0;
    if( aa ){ zCap[i++] = 'a'; }
    if( ab ){ zCap[i++] = 'b'; }
    if( ac ){ zCap[i++] = 'c'; }
    if( ad ){ zCap[i++] = 'd'; }
    if( ae ){ zCap[i++] = 'e'; }
    if( af ){ zCap[i++] = 'f'; }
................................................................................
    if( ap ){ zCap[i++] = 'p'; }
    if( ar ){ zCap[i++] = 'r'; }
    if( as ){ zCap[i++] = 's'; }
    if( at ){ zCap[i++] = 't'; }
    if( au ){ zCap[i++] = 'u'; }
    if( av ){ zCap[i++] = 'v'; }
    if( aw ){ zCap[i++] = 'w'; }
    if( ax ){ zCap[i++] = 'x'; }
    if( az ){ zCap[i++] = 'z'; }

    zCap[i] = 0;
    zPw = P("pw");
    zLogin = P("login");
    if( isValidPwString(zPw) ){
      zPw = sha1_shared_secret(zPw, zLogin);
................................................................................
  /* Load the existing information about the user, if any
  */
  zLogin = "";
  zInfo = "";
  zCap = "";
  zPw = "";
  oaa = oab = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam =
        oan = oao = oap = oar = oas = oat = oau = oav = oaw = oax = oaz = "";
  if( uid ){
    zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
    zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
    zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
    zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid);
    if( strchr(zCap, 'a') ) oaa = " checked=\"checked\"";
    if( strchr(zCap, 'b') ) oab = " checked=\"checked\"";
................................................................................
    if( strchr(zCap, 'p') ) oap = " checked=\"checked\"";
    if( strchr(zCap, 'r') ) oar = " checked=\"checked\"";
    if( strchr(zCap, 's') ) oas = " checked=\"checked\"";
    if( strchr(zCap, 't') ) oat = " checked=\"checked\"";
    if( strchr(zCap, 'u') ) oau = " checked=\"checked\"";
    if( strchr(zCap, 'v') ) oav = " checked=\"checked\"";
    if( strchr(zCap, 'w') ) oaw = " checked=\"checked\"";
    if( strchr(zCap, 'x') ) oax = " checked=\"checked\"";
    if( strchr(zCap, 'z') ) oaz = " checked=\"checked\"";
  }

  /* figure out inherited permissions */
  memset(inherit, 0, sizeof(inherit));
  if( strcmp(zLogin, "developer") ){
    char *z1, *z2;
................................................................................
  @    <input type="checkbox" name="ak"%s(oak) />%s(B('k'))Write Wiki<br />
  @    <input type="checkbox" name="ab"%s(oab) />%s(B('b'))Attachments<br />
  @    <input type="checkbox" name="ar"%s(oar) />%s(B('r'))Read Ticket<br />
  @    <input type="checkbox" name="an"%s(oan) />%s(B('n'))New Ticket<br />
  @    <input type="checkbox" name="ac"%s(oac) />%s(B('c'))Append Ticket<br />
  @    <input type="checkbox" name="aw"%s(oaw) />%s(B('w'))Write Ticket<br />
  @    <input type="checkbox" name="at"%s(oat) />%s(B('t'))Ticket Report<br />
  @    <input type="checkbox" name="ax"%s(oax) />%s(B('x'))Private<br />
  @    <input type="checkbox" name="az"%s(oaz) />%s(B('z'))Download Zip
  @   </td>
  @ </tr>
  @ <tr>
  @   <td align="right">Password:</td>
  if( zPw[0] ){
    /* Obscure the password for all users */

Changes to src/sync.c.

76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99

100
101
102
103
104
105
106
...
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
...
143
144
145
146
147
148
149



150
151
152
153
154


155
156
157
158
159
160
161
162
...
167
168
169
170
171
172
173



174
175
176
177


178
179
180
181
182
183
184
185
186
...
197
198
199
200
201
202
203



204
205
206
207
208


209
210
211
212
213
214
215
216
    ** autosync, or something?
    */
    configSync = CONFIGSET_SHUN;
  }
#endif
  printf("Autosync:  %s\n", g.urlCanonical);
  url_enable_proxy("via proxy: ");
  rc = client_sync((flags & AUTOSYNC_PUSH)!=0, 1, 0, configSync, 0);
  if( rc ) fossil_warning("Autosync failed");
  return rc;
}

/*
** This routine processes the command-line argument for push, pull,
** and sync.  If a command-line argument is given, that is the URL
** of a server to sync against.  If no argument is given, use the
** most recently synced URL.  Remember the current URL for next time.
*/
static int process_sync_args(void){
  const char *zUrl = 0;
  const char *zPw = 0;
  int configSync = 0;
  int urlOptional = find_option("autourl",0,0)!=0;
  g.dontKeepUrl = find_option("once",0,0)!=0;

  url_proxy_options();
  db_find_and_open_repository(0, 0);
  db_open_config(0);
  if( g.argc==2 ){
    zUrl = db_get("last-sync-url", 0);
    zPw = unobscure(db_get("last-sync-pw", 0));
    if( db_get_boolean("auto-sync",1) ) configSync = CONFIGSET_SHUN;
................................................................................
    if( g.urlPasswd ) db_set("last-sync-pw", obscure(g.urlPasswd), 0);
  }
  user_select();
  if( g.argc==2 ){
    printf("Server:    %s\n", g.urlCanonical);
  }
  url_enable_proxy("via proxy: ");
  return configSync;
}

/*
** COMMAND: pull
**
** Usage: %fossil pull ?URL? ?options?
**
................................................................................
** If the URL is not specified, then the URL from the most recent
** clone, push, pull, remote-url, or sync command is used.
**
** The URL specified normally becomes the new "remote-url" used for
** subsequent push, pull, and sync operations.  However, the "--once"
** command-line option makes the URL a one-time-use URL that is not
** saved.



**
** See also: clone, push, sync, remote-url
*/
void pull_cmd(void){
  int syncFlags = process_sync_args();


  client_sync(0,1,0,syncFlags,0);
}

/*
** COMMAND: push
**
** Usage: %fossil push ?URL? ?options?
**
................................................................................
** If the URL is not specified, then the URL from the most recent
** clone, push, pull, remote-url, or sync command is used.
**
** The URL specified normally becomes the new "remote-url" used for
** subsequent push, pull, and sync operations.  However, the "--once"
** command-line option makes the URL a one-time-use URL that is not
** saved.



**
** See also: clone, pull, sync, remote-url
*/
void push_cmd(void){


  process_sync_args();
  client_sync(1,0,0,0,0);
}


/*
** COMMAND: sync
**
** Usage: %fossil sync ?URL? ?options?
................................................................................
** If the URL is not specified, then the URL from the most recent successful
** clone, push, pull, remote-url, or sync command is used.
**
** The URL specified normally becomes the new "remote-url" used for
** subsequent push, pull, and sync operations.  However, the "--once"
** command-line option makes the URL a one-time-use URL that is not
** saved.



**
** See also:  clone, push, pull, remote-url
*/
void sync_cmd(void){
  int syncFlags = process_sync_args();


  client_sync(1,1,0,syncFlags,0);
}

/*
** COMMAND: remote-url
**
** Usage: %fossil remote-url ?URL|off?
**






|










|





>







 







|







 







>
>
>




|
>
>
|







 







>
>
>




>
>
|
|







 







>
>
>




|
>
>
|







76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
...
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
...
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
...
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
...
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
    ** autosync, or something?
    */
    configSync = CONFIGSET_SHUN;
  }
#endif
  printf("Autosync:  %s\n", g.urlCanonical);
  url_enable_proxy("via proxy: ");
  rc = client_sync((flags & AUTOSYNC_PUSH)!=0, 1, 0, 0, configSync, 0);
  if( rc ) fossil_warning("Autosync failed");
  return rc;
}

/*
** This routine processes the command-line argument for push, pull,
** and sync.  If a command-line argument is given, that is the URL
** of a server to sync against.  If no argument is given, use the
** most recently synced URL.  Remember the current URL for next time.
*/
static void process_sync_args(int *pConfigSync, int *pPrivate){
  const char *zUrl = 0;
  const char *zPw = 0;
  int configSync = 0;
  int urlOptional = find_option("autourl",0,0)!=0;
  g.dontKeepUrl = find_option("once",0,0)!=0;
  *pPrivate = find_option("private",0,0)!=0;
  url_proxy_options();
  db_find_and_open_repository(0, 0);
  db_open_config(0);
  if( g.argc==2 ){
    zUrl = db_get("last-sync-url", 0);
    zPw = unobscure(db_get("last-sync-pw", 0));
    if( db_get_boolean("auto-sync",1) ) configSync = CONFIGSET_SHUN;
................................................................................
    if( g.urlPasswd ) db_set("last-sync-pw", obscure(g.urlPasswd), 0);
  }
  user_select();
  if( g.argc==2 ){
    printf("Server:    %s\n", g.urlCanonical);
  }
  url_enable_proxy("via proxy: ");
  *pConfigSync = configSync;
}

/*
** COMMAND: pull
**
** Usage: %fossil pull ?URL? ?options?
**
................................................................................
** If the URL is not specified, then the URL from the most recent
** clone, push, pull, remote-url, or sync command is used.
**
** The URL specified normally becomes the new "remote-url" used for
** subsequent push, pull, and sync operations.  However, the "--once"
** command-line option makes the URL a one-time-use URL that is not
** saved.
**
** Use the --private option to pull private branches from the
** remote repository.
**
** See also: clone, push, sync, remote-url
*/
void pull_cmd(void){
  int syncFlags;
  int bPrivate;
  process_sync_args(&syncFlags, &bPrivate);
  client_sync(0,1,0,bPrivate,syncFlags,0);
}

/*
** COMMAND: push
**
** Usage: %fossil push ?URL? ?options?
**
................................................................................
** If the URL is not specified, then the URL from the most recent
** clone, push, pull, remote-url, or sync command is used.
**
** The URL specified normally becomes the new "remote-url" used for
** subsequent push, pull, and sync operations.  However, the "--once"
** command-line option makes the URL a one-time-use URL that is not
** saved.
**
** Use the --private option to push private branches to the
** remote repository.
**
** See also: clone, pull, sync, remote-url
*/
void push_cmd(void){
  int syncFlags;
  int bPrivate;
  process_sync_args(&syncFlags, &bPrivate);
  client_sync(1,0,0,bPrivate,0,0);
}


/*
** COMMAND: sync
**
** Usage: %fossil sync ?URL? ?options?
................................................................................
** If the URL is not specified, then the URL from the most recent successful
** clone, push, pull, remote-url, or sync command is used.
**
** The URL specified normally becomes the new "remote-url" used for
** subsequent push, pull, and sync operations.  However, the "--once"
** command-line option makes the URL a one-time-use URL that is not
** saved.
**
** Use the --private option to sync private branches with the
** remote repository.
**
** See also:  clone, push, pull, remote-url
*/
void sync_cmd(void){
  int syncFlags;
  int bPrivate;
  process_sync_args(&syncFlags, &bPrivate);
  client_sync(1,1,0,bPrivate,syncFlags,0);
}

/*
** COMMAND: remote-url
**
** Usage: %fossil remote-url ?URL|off?
**

Changes to src/tag.c.

202
203
204
205
206
207
208






209
210
211
212
213
214
215
    case TAG_COMMENT: {
      zCol = "ecomment";
      break;
    }
    case TAG_USER: {
      zCol = "euser";
      break;






    }
  }
  if( zCol ){
    db_multi_exec("UPDATE event SET %s=%Q WHERE objid=%d", zCol, zValue, rid);
    if( tagid==TAG_COMMENT ){
      char *zCopy = mprintf("%s", zValue);
      wiki_extract_links(zCopy, rid, 0, mtime, 1, WIKI_INLINE);






>
>
>
>
>
>







202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
    case TAG_COMMENT: {
      zCol = "ecomment";
      break;
    }
    case TAG_USER: {
      zCol = "euser";
      break;
    }
    case TAG_PRIVATE: {
      db_multi_exec(
        "INSERT OR IGNORE INTO private(rid) VALUES(%d);",
        rid
      );
    }
  }
  if( zCol ){
    db_multi_exec("UPDATE event SET %s=%Q WHERE objid=%d", zCol, zValue, rid);
    if( tagid==TAG_COMMENT ){
      char *zCopy = mprintf("%s", zValue);
      wiki_extract_links(zCopy, rid, 0, mtime, 1, WIKI_INLINE);

Changes to src/xfer.c.

36
37
38
39
40
41
42


43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
...
104
105
106
107
108
109
110

111


112
113
114
115
116
117
118
...
121
122
123
124
125
126
127




128
129
130
131
132
133
134
135
136
137

138
139
140
141
142
143
144
145
146

147
148
149
150
151
152
153
154
155
156
...
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
...
199
200
201
202
203
204
205

206


207
208
209
210
211
212
213
214
215
216




217
218
219
220
221
222
223
224
225
226
227
228
229
230
231

232
233
234
235
236
237
238
...
240
241
242
243
244
245
246

247
248
249
250
251
252
253
...
264
265
266
267
268
269
270


271

272
273
274
275
276
277
278
279

280
281
282
283
284
285
286
287
288
289
...
295
296
297
298
299
300
301

302
303
304
305
306
307
308

309

310
311
312
313
314
315
316
317
318

319
320
321
322
323
324
325
...
340
341
342
343
344
345
346

347
348

349
350
351
352
353
354
355
...
363
364
365
366
367
368
369

370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388

389
390
391
392
393
394
395
396
397





398
399
400
401
402
403
404
...
405
406
407
408
409
410
411

412
413


414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432

433
434
435
436
437
438
439
440
441

442

443
444
445
446
447
448
449
450
451
452
453
454
455
456
457

458
459
460
461
462
463
464
465
...
639
640
641
642
643
644
645

















646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
...
702
703
704
705
706
707
708









709
710
711
712
713
714
715
...
755
756
757
758
759
760
761

762
763
764
765
766
767
768
...
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825

826
827
828
829
830
831

832





833
834
835
836
837
838
839
...
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
....
1027
1028
1029
1030
1031
1032
1033






































1034
1035
1036
1037
1038
1039
1040
....
1047
1048
1049
1050
1051
1052
1053

1054
1055
1056

1057
1058
1059
1060
1061
1062
1063
....
1118
1119
1120
1121
1122
1123
1124

1125
1126
1127
1128
1129
1130
1131
....
1153
1154
1155
1156
1157
1158
1159




1160
1161
1162
1163
1164
1165
1166
....
1167
1168
1169
1170
1171
1172
1173




1174
1175
1176
1177
1178
1179
1180
....
1210
1211
1212
1213
1214
1215
1216

1217
1218
1219
1220
1221
1222
1223
....
1273
1274
1275
1276
1277
1278
1279



1280
1281
1282
1283
1284
1285
1286
....
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365




1366
1367
1368
1369
1370


1371
1372
1373


1374
1375
1376
1377
1378
1379
1380
1381
1382
....
1452
1453
1454
1455
1456
1457
1458











1459
1460
1461
1462
1463
1464
1465
  int nGimmeSent;     /* Number of gimme cards sent */
  int nFileSent;      /* Number of files sent */
  int nDeltaSent;     /* Number of deltas sent */
  int nFileRcvd;      /* Number of files received */
  int nDeltaRcvd;     /* Number of deltas received */
  int nDanglingFile;  /* Number of dangling deltas received */
  int mxSend;         /* Stop sending "file" with pOut reaches this size */


};


/*
** The input blob contains a UUID.  Convert it into a record ID.
** Create a phantom record if no prior record exists and
** phantomize is true.
**
** Compare to uuid_to_rid().  This routine takes a blob argument
** and does less error checking.
*/
static int rid_from_uuid(Blob *pUuid, int phantomize){
  static Stmt q;
  int rid;

  db_static_prepare(&q, "SELECT rid FROM blob WHERE uuid=:uuid");
  db_bind_str(&q, ":uuid", pUuid);
  if( db_step(&q)==SQLITE_ROW ){
    rid = db_column_int(&q, 0);
  }else{
    rid = 0;
  }
  db_reset(&q);
  if( rid==0 && phantomize ){
    rid = content_new(blob_str(pUuid), 0);
  }
  return rid;
}

/*
** Remember that the other side of the connection already has a copy
** of the file rid.
................................................................................
** be public and is therefore removed from the "private" table.
*/
static void xfer_accept_file(Xfer *pXfer, int cloneFlag){
  int n;
  int rid;
  int srcid = 0;
  Blob content, hash;

  


  if( pXfer->nToken<3 
   || pXfer->nToken>4
   || !blob_is_uuid(&pXfer->aToken[1])
   || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &n)
   || n<0
   || (pXfer->nToken==4 && !blob_is_uuid(&pXfer->aToken[2]))
  ){
................................................................................
  }
  blob_zero(&content);
  blob_zero(&hash);
  blob_extract(pXfer->pIn, n, &content);
  if( !cloneFlag && uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
    /* Ignore files that have been shunned */
    return;




  }
  if( cloneFlag ){
    if( pXfer->nToken==4 ){
      srcid = rid_from_uuid(&pXfer->aToken[2], 1);
      pXfer->nDeltaRcvd++;
    }else{
      srcid = 0;
      pXfer->nFileRcvd++;
    }
    rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid, 0, 0);

    remote_has(rid);
    blob_reset(&content);
    return;
  }
  if( pXfer->nToken==4 ){
    Blob src, next;
    srcid = rid_from_uuid(&pXfer->aToken[2], 1);
    if( content_get(srcid, &src)==0 ){
      rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid, 0, 0);

      pXfer->nDanglingFile++;
      db_multi_exec("DELETE FROM phantom WHERE rid=%d", rid);
      content_make_public(rid);
      return;
    }
    pXfer->nDeltaRcvd++;
    blob_delta_apply(&src, &content, &next);
    blob_reset(&src);
    blob_reset(&content);
    content = next;
................................................................................
  }else{
    pXfer->nFileRcvd++;
  }
  sha1sum_blob(&content, &hash);
  if( !blob_eq_str(&pXfer->aToken[1], blob_str(&hash), -1) ){
    blob_appendf(&pXfer->err, "content does not match sha1 hash");
  }
  rid = content_put_ex(&content, blob_str(&hash), 0, 0, 0);
  blob_reset(&hash);
  if( rid==0 ){
    blob_appendf(&pXfer->err, "%s", g.zErrMsg);
    blob_reset(&content);
  }else{
    content_make_public(rid);
    manifest_crosslink(rid, &content);
  }
  assert( blob_is_reset(&content) );
  remote_has(rid);
}

/*
................................................................................
*/
static void xfer_accept_compressed_file(Xfer *pXfer){
  int szC;   /* CSIZE */
  int szU;   /* USIZE */
  int rid;
  int srcid = 0;
  Blob content;

  


  if( pXfer->nToken<4 
   || pXfer->nToken>5
   || !blob_is_uuid(&pXfer->aToken[1])
   || !blob_is_int(&pXfer->aToken[pXfer->nToken-2], &szU)
   || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &szC)
   || szC<0 || szU<0
   || (pXfer->nToken==5 && !blob_is_uuid(&pXfer->aToken[2]))
  ){
    blob_appendf(&pXfer->err, "malformed cfile line");
    return;




  }
  blob_zero(&content);
  blob_extract(pXfer->pIn, szC, &content);
  if( uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
    /* Ignore files that have been shunned */
    return;
  }
  if( pXfer->nToken==5 ){
    srcid = rid_from_uuid(&pXfer->aToken[2], 1);
    pXfer->nDeltaRcvd++;
  }else{
    srcid = 0;
    pXfer->nFileRcvd++;
  }
  rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid, szC, 0);

  remote_has(rid);
  blob_reset(&content);
}

/*
** Try to send a file as a delta against its parent.
** If successful, return the number of bytes in the delta.
................................................................................
** nothing and return zero.
**
** Never send a delta against a private artifact.
*/
static int send_delta_parent(
  Xfer *pXfer,            /* The transfer context */
  int rid,                /* record id of the file to send */

  Blob *pContent,         /* The content of the file to send */
  Blob *pUuid             /* The UUID of the file to send */
){
  static const char *azQuery[] = {
    "SELECT pid FROM plink x"
    " WHERE cid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
................................................................................
  Blob src, delta;
  int size = 0;
  int srcId = 0;

  for(i=0; srcId==0 && i<count(azQuery); i++){
    srcId = db_int(0, azQuery[i], rid);
  }


  if( srcId>0 && !content_is_private(srcId) && content_get(srcId, &src) ){

    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", srcId);
    blob_delta_create(&src, pContent, &delta);
    size = blob_size(&delta);
    if( size>=blob_size(pContent)-50 ){
      size = 0;
    }else if( uuid_is_shunned(zUuid) ){
      size = 0;
    }else{

      blob_appendf(pXfer->pOut, "file %b %s %d\n", pUuid, zUuid, size);
      blob_append(pXfer->pOut, blob_buffer(&delta), size);
      /* blob_appendf(pXfer->pOut, "\n", 1); */
    }
    blob_reset(&delta);
    free(zUuid);
    blob_reset(&src);
  }
  return size;
}
................................................................................
** nothing and return zero.
**
** Never send a delta against a private artifact.
*/
static int send_delta_native(
  Xfer *pXfer,            /* The transfer context */
  int rid,                /* record id of the file to send */

  Blob *pUuid             /* The UUID of the file to send */
){
  Blob src, delta;
  int size = 0;
  int srcId;

  srcId = db_int(0, "SELECT srcid FROM delta WHERE rid=%d", rid);

  if( srcId>0 && !content_is_private(srcId) ){

    blob_zero(&src);
    db_blob(&src, "SELECT uuid FROM blob WHERE rid=%d", srcId);
    if( uuid_is_shunned(blob_str(&src)) ){
      blob_reset(&src);
      return 0;
    }
    blob_zero(&delta);
    db_blob(&delta, "SELECT content FROM blob WHERE rid=%d", rid);
    blob_uncompress(&delta, &delta);

    blob_appendf(pXfer->pOut, "file %b %b %d\n",
                pUuid, &src, blob_size(&delta));
    blob_append(pXfer->pOut, blob_buffer(&delta), blob_size(&delta));
    size = blob_size(&delta);
    blob_reset(&delta);
    blob_reset(&src);
  }else{
................................................................................
** It should never be the case that rid is a private artifact.  But
** as a precaution, this routine does check on rid and if it is private
** this routine becomes a no-op.
*/
static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int nativeDelta){
  Blob content, uuid;
  int size = 0;


  if( content_is_private(rid) ) return;

  if( db_exists("SELECT 1 FROM onremote WHERE rid=%d", rid) ){
     return;
  }
  blob_zero(&uuid);
  db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d AND size>=0", rid);
  if( blob_size(&uuid)==0 ){
    return;
................................................................................
    pUuid = &uuid;
  }
  if( uuid_is_shunned(blob_str(pUuid)) ){
    blob_reset(&uuid);
    return;
  }
  if( pXfer->mxSend<=blob_size(pXfer->pOut) ){

    blob_appendf(pXfer->pOut, "igot %b\n", pUuid);
    pXfer->nIGotSent++;
    blob_reset(&uuid);
    return;
  }
  if( nativeDelta ){
    size = send_delta_native(pXfer, rid, pUuid);
    if( size ){
      pXfer->nDeltaSent++;
    }
  }
  if( size==0 ){
    content_get(rid, &content);

    if( !nativeDelta && blob_size(&content)>100 ){
      size = send_delta_parent(pXfer, rid, &content, pUuid);
    }
    if( size==0 ){
      int size = blob_size(&content);

      blob_appendf(pXfer->pOut, "file %b %d\n", pUuid, size);
      blob_append(pXfer->pOut, blob_buffer(&content), size);
      pXfer->nFileSent++;
    }else{
      pXfer->nDeltaSent++;
    }
  }
  remote_has(rid);
  blob_reset(&uuid);





}

/*
** Send the file identified by rid as a compressed artifact.  Basically,
** send the content exactly as it appears in the BLOB table using 
** a "cfile" card.
*/
................................................................................
static void send_compressed_file(Xfer *pXfer, int rid){
  const char *zContent;
  const char *zUuid;
  const char *zDelta;
  int szU;
  int szC;
  int rc;

  static Stmt q1;



  db_static_prepare(&q1,
    "SELECT uuid, size, content,"
         "  (SELECT uuid FROM delta, blob"
         "    WHERE delta.rid=:rid AND delta.srcid=blob.rid)"
    " FROM blob"
    " WHERE rid=:rid"
    "   AND size>=0"
    "   AND uuid NOT IN shun"
    "   AND rid NOT IN private",
    rid
  );
  db_bind_int(&q1, ":rid", rid);
  rc = db_step(&q1);
  if( rc==SQLITE_ROW ){
    zUuid = db_column_text(&q1, 0);
    szU = db_column_int(&q1, 1);
    szC = db_column_bytes(&q1, 2);
    zContent = db_column_raw(&q1, 2);
    zDelta = db_column_text(&q1, 3);

    blob_appendf(pXfer->pOut, "cfile %s ", zUuid);
    if( zDelta ){
      blob_appendf(pXfer->pOut, "%s ", zDelta);
      pXfer->nDeltaSent++;
    }else{
      pXfer->nFileSent++;
    }
    blob_appendf(pXfer->pOut, "%d %d\n", szU, szC);
    blob_append(pXfer->pOut, zContent, szC);

    blob_append(pXfer->pOut, "\n", 1);

  }
  db_reset(&q1);
}

/*
** Send a gimme message for every phantom.
**
** It should not be possible to have a private phantom.  But just to be
** sure, take care not to send any "gimme" messagse on private artifacts.
*/
static void request_phantoms(Xfer *pXfer, int maxReq){
  Stmt q;
  db_prepare(&q, 
    "SELECT uuid FROM phantom JOIN blob USING(rid)"
    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"

    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
  );
  while( db_step(&q)==SQLITE_ROW && maxReq-- > 0 ){
    const char *zUuid = db_column_text(&q, 0);
    blob_appendf(pXfer->pOut, "gimme %s\n", zUuid);
    pXfer->nGimmeSent++;
  }
  db_finalize(&q);
................................................................................
      blob_appendf(&cluster, "Z %b\n", &cksum);
      blob_reset(&cksum);
      content_put(&cluster);
      blob_reset(&cluster);
    }
  }
}


















/*
** Send an igot message for every entry in unclustered table.
** Return the number of cards sent.
*/
static int send_unclustered(Xfer *pXfer){
  Stmt q;
  int cnt = 0;
  db_prepare(&q, 
    "SELECT uuid FROM unclustered JOIN blob USING(rid)"
    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
  );
  while( db_step(&q)==SQLITE_ROW ){
    blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
    cnt++;
  }
  db_finalize(&q);
  return cnt;
................................................................................
    configure_render_special_name(zName, &content);
    blob_appendf(pXfer->pOut, "config %s %d\n%s\n", zName,
                 blob_size(&content), blob_str(&content));
    blob_reset(&content);
  }
}











/*
** If this variable is set, disable login checks.  Used for debugging
** only.
*/
static int disableLogin = 0;

................................................................................
  db_begin_transaction();
  db_multi_exec(
     "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
  );
  manifest_crosslink_begin();
  while( blob_line(xfer.pIn, &xfer.line) ){
    if( blob_buffer(&xfer.line)[0]=='#' ) continue;

    xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));

    /*   file UUID SIZE \n CONTENT
    **   file UUID DELTASRC SIZE \n CONTENT
    **
    ** Accept a file from the client.
    */
................................................................................
    */
    if( blob_eq(&xfer.aToken[0], "gimme")
     && xfer.nToken==2
     && blob_is_uuid(&xfer.aToken[1])
    ){
      nGimme++;
      if( isPull ){
        int rid = rid_from_uuid(&xfer.aToken[1], 0);
        if( rid ){
          send_file(&xfer, rid, &xfer.aToken[1], deltaFlag);
        }
      }
    }else

    /*   igot UUID
    **
    ** Client announces that it has a particular file.

    */
    if( xfer.nToken==2
     && blob_eq(&xfer.aToken[0], "igot")
     && blob_is_uuid(&xfer.aToken[1])
    ){
      if( isPush ){

        rid_from_uuid(&xfer.aToken[1], 1);





      }
    }else
  
    
    /*    pull  SERVERCODE  PROJECTCODE
    **    push  SERVERCODE  PROJECTCODE
    **
................................................................................
    ** Check for a valid login.  This has to happen before anything else.
    ** The client can send multiple logins.  Permissions are cumulative.
    */
    if( blob_eq(&xfer.aToken[0], "login")
     && xfer.nToken==4
    ){
      if( disableLogin ){
        g.okRead = g.okWrite = 1;
      }else{
        if( check_tail_hash(&xfer.aToken[2], xfer.pIn)
         || check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3])
        ){
          cgi_reset_content();
          @ error login\sfailed
          nErr++;
................................................................................
    ** back several different cookies to the server.  The server should be
    ** prepared to sift through the cookies and pick the one that it wants.
    */
    if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
      /* Process the cookie */
    }else







































    /* Unknown message
    */
    {
      cgi_reset_content();
      @ error bad\scommand:\s%F(blob_str(&xfer.line))
    }
    blobarray_reset(xfer.aToken, xfer.nToken);
................................................................................
    ** "gimme" cards. On that initial message, send the client an "igot"
    ** card for every artifact currently in the respository.  This will
    ** cause the client to create phantoms for all artifacts, which will
    ** in turn make sure that the entire repository is sent efficiently
    ** and expeditiously.
    */
    send_all(&xfer);

  }else if( isPull ){
    create_cluster();
    send_unclustered(&xfer);

  }
  if( recvConfig ){
    configure_finalize_receive();
  }
  manifest_crosslink_end();

  /* Send the server timestamp last, in case prior processing happened
................................................................................
** are pulled if pullFlag is true.  A full sync occurs if both are
** true.
*/
int client_sync(
  int pushFlag,           /* True to do a push (or a sync) */
  int pullFlag,           /* True to do a pull (or a sync) */
  int cloneFlag,          /* True if this is a clone */

  int configRcvMask,      /* Receive these configuration items */
  int configSendMask      /* Send these configuration items */
){
  int go = 1;             /* Loop until zero */
  int nCardSent = 0;      /* Number of cards sent */
  int nCardRcvd = 0;      /* Number of cards received */
  int nCycle = 0;         /* Number of round trips to the server */
................................................................................

  transport_stats(0, 0, 1);
  socket_global_init();
  memset(&xfer, 0, sizeof(xfer));
  xfer.pIn = &recv;
  xfer.pOut = &send;
  xfer.mxSend = db_get_int("max-upload", 250000);





  assert( pushFlag | pullFlag | cloneFlag | configRcvMask | configSendMask );
  db_begin_transaction();
  db_record_repository_filename(0);
  db_multi_exec(
    "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
  );
................................................................................
  blobarray_zero(xfer.aToken, count(xfer.aToken));
  blob_zero(&send);
  blob_zero(&recv);
  blob_zero(&xfer.err);
  blob_zero(&xfer.line);
  origConfigRcvMask = 0;





  /*
  ** Always begin with a clone, pull, or push message
  */
  if( cloneFlag ){
    blob_appendf(&send, "clone 3 %d\n", cloneSeqno);
    pushFlag = 0;
    pullFlag = 0;
................................................................................
    */
    if( pullFlag || (cloneFlag && cloneSeqno==1) ){
      request_phantoms(&xfer, mxPhantomReq);
    }
    if( pushFlag ){
      send_unsent(&xfer);
      nCardSent += send_unclustered(&xfer);

    }

    /* Send configuration parameter requests.  On a clone, delay sending
    ** this until the second cycle since the login card might fail on 
    ** the first cycle.
    */
    if( configRcvMask && (cloneFlag==0 || nCycle>0) ){
................................................................................
    if( http_exchange(&send, &recv, cloneFlag==0 || nCycle>0) ){
      nErr++;
      break;
    }
    lastPctDone = -1;
    blob_reset(&send);
    rArrivalTime = db_double(0.0, "SELECT julianday('now')");




    /* Begin constructing the next message (which might never be
    ** sent) by beginning with the pull or push cards
    */
    if( pullFlag ){
      blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
      nCardSent++;
................................................................................
      ** associated with the manifest and send those too.
      */
      if( blob_eq(&xfer.aToken[0], "gimme")
       && xfer.nToken==2
       && blob_is_uuid(&xfer.aToken[1])
      ){
        if( pushFlag ){
          int rid = rid_from_uuid(&xfer.aToken[1], 0);
          if( rid ) send_file(&xfer, rid, &xfer.aToken[1], 0);
        }
      }else
  
      /*   igot UUID
      **
      ** Server announces that it has a particular file.  If this is
      ** not a file that we have and we are pulling, then create a
      ** phantom to cause this file to be requested on the next cycle.
      ** Always remember that the server has this file so that we do
      ** not transmit it by accident.




      */
      if( xfer.nToken==2
       && blob_eq(&xfer.aToken[0], "igot")
       && blob_is_uuid(&xfer.aToken[1])
      ){


        int rid = rid_from_uuid(&xfer.aToken[1], 0);
        if( rid>0 ){
          content_make_public(rid);


        }else if( pullFlag || cloneFlag ){
          rid = content_new(blob_str(&xfer.aToken[1]), 0);
          if( rid ) newPhantom = 1;
        }
        remote_has(rid);
      }else
    
      
      /*   push  SERVERCODE  PRODUCTCODE
................................................................................
      **
      ** Each cookie received overwrites the prior cookie from the
      ** same server.
      */
      if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
        db_set("cookie", blob_str(&xfer.aToken[1]), 0);
      }else












      /*    clone_seqno N
      **
      ** When doing a clone, the server tries to send all of its artifacts
      ** in sequence.  This card indicates the sequence number of the next
      ** blob that needs to be sent.  If N<=0 that indicates that all blobs
      ** have been sent.






>
>











|












|







 







>

>
>







 







>
>
>
>



|





|
>






|

|
>


|







 







|





|







 







>

>
>










>
>
>
>








|





|
>







 







>







 







>
>
|
>








>


<







 







>







>
|
>









>







 







>

<
>







 







>
|





|








|



>









>
>
>
>
>







 







>


>
>








<
<









>

|







>
|
>







|
|





|
>
|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>











|
|







 







>
>
>
>
>
>
>
>
>







 







>







 







|






|

|
>

|




>
|
>
>
>
>
>







 







|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>



>







 







>







 







>
>
>
>







 







>
>
>
>







 







>







 







>
>
>







 







|




|






>
>
>
>

|



>
>
|

|
>
>

|







 







>
>
>
>
>
>
>
>
>
>
>







36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
...
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
...
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
...
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
...
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
...
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
...
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305

306
307
308
309
310
311
312
...
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
...
367
368
369
370
371
372
373
374
375

376
377
378
379
380
381
382
383
...
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
...
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459


460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
...
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
...
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
...
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
...
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
....
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
....
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
....
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
....
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
....
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
....
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
....
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
....
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
....
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
....
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
  int nGimmeSent;     /* Number of gimme cards sent */
  int nFileSent;      /* Number of files sent */
  int nDeltaSent;     /* Number of deltas sent */
  int nFileRcvd;      /* Number of files received */
  int nDeltaRcvd;     /* Number of deltas received */
  int nDanglingFile;  /* Number of dangling deltas received */
  int mxSend;         /* Stop sending "file" with pOut reaches this size */
  u8 syncPrivate;     /* True to enable syncing private content */
  u8 nextIsPrivate;   /* If true, next "file" received is a private */
};


/*
** The input blob contains a UUID.  Convert it into a record ID.
** Create a phantom record if no prior record exists and
** phantomize is true.
**
** Compare to uuid_to_rid().  This routine takes a blob argument
** and does less error checking.
*/
static int rid_from_uuid(Blob *pUuid, int phantomize, int isPrivate){
  static Stmt q;
  int rid;

  db_static_prepare(&q, "SELECT rid FROM blob WHERE uuid=:uuid");
  db_bind_str(&q, ":uuid", pUuid);
  if( db_step(&q)==SQLITE_ROW ){
    rid = db_column_int(&q, 0);
  }else{
    rid = 0;
  }
  db_reset(&q);
  if( rid==0 && phantomize ){
    rid = content_new(blob_str(pUuid), isPrivate);
  }
  return rid;
}

/*
** Remember that the other side of the connection already has a copy
** of the file rid.
................................................................................
** be public and is therefore removed from the "private" table.
*/
static void xfer_accept_file(Xfer *pXfer, int cloneFlag){
  int n;
  int rid;
  int srcid = 0;
  Blob content, hash;
  int isPriv;
  
  isPriv = pXfer->nextIsPrivate;
  pXfer->nextIsPrivate = 0;
  if( pXfer->nToken<3 
   || pXfer->nToken>4
   || !blob_is_uuid(&pXfer->aToken[1])
   || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &n)
   || n<0
   || (pXfer->nToken==4 && !blob_is_uuid(&pXfer->aToken[2]))
  ){
................................................................................
  }
  blob_zero(&content);
  blob_zero(&hash);
  blob_extract(pXfer->pIn, n, &content);
  if( !cloneFlag && uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
    /* Ignore files that have been shunned */
    return;
  }
  if( isPriv && !g.okPrivate ){
    /* Do not accept private files if not authorized */
    return;
  }
  if( cloneFlag ){
    if( pXfer->nToken==4 ){
      srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
      pXfer->nDeltaRcvd++;
    }else{
      srcid = 0;
      pXfer->nFileRcvd++;
    }
    rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid,
                         0, isPriv);
    remote_has(rid);
    blob_reset(&content);
    return;
  }
  if( pXfer->nToken==4 ){
    Blob src, next;
    srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
    if( content_get(srcid, &src)==0 ){
      rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid,
                           0, isPriv);
      pXfer->nDanglingFile++;
      db_multi_exec("DELETE FROM phantom WHERE rid=%d", rid);
      if( !isPriv ) content_make_public(rid);
      return;
    }
    pXfer->nDeltaRcvd++;
    blob_delta_apply(&src, &content, &next);
    blob_reset(&src);
    blob_reset(&content);
    content = next;
................................................................................
  }else{
    pXfer->nFileRcvd++;
  }
  sha1sum_blob(&content, &hash);
  if( !blob_eq_str(&pXfer->aToken[1], blob_str(&hash), -1) ){
    blob_appendf(&pXfer->err, "content does not match sha1 hash");
  }
  rid = content_put_ex(&content, blob_str(&hash), 0, 0, isPriv);
  blob_reset(&hash);
  if( rid==0 ){
    blob_appendf(&pXfer->err, "%s", g.zErrMsg);
    blob_reset(&content);
  }else{
    if( !isPriv ) content_make_public(rid);
    manifest_crosslink(rid, &content);
  }
  assert( blob_is_reset(&content) );
  remote_has(rid);
}

/*
................................................................................
*/
static void xfer_accept_compressed_file(Xfer *pXfer){
  int szC;   /* CSIZE */
  int szU;   /* USIZE */
  int rid;
  int srcid = 0;
  Blob content;
  int isPriv;
  
  isPriv = pXfer->nextIsPrivate;
  pXfer->nextIsPrivate = 0;
  if( pXfer->nToken<4 
   || pXfer->nToken>5
   || !blob_is_uuid(&pXfer->aToken[1])
   || !blob_is_int(&pXfer->aToken[pXfer->nToken-2], &szU)
   || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &szC)
   || szC<0 || szU<0
   || (pXfer->nToken==5 && !blob_is_uuid(&pXfer->aToken[2]))
  ){
    blob_appendf(&pXfer->err, "malformed cfile line");
    return;
  }
  if( isPriv && !g.okPrivate ){
    /* Do not accept private files if not authorized */
    return;
  }
  blob_zero(&content);
  blob_extract(pXfer->pIn, szC, &content);
  if( uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
    /* Ignore files that have been shunned */
    return;
  }
  if( pXfer->nToken==5 ){
    srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
    pXfer->nDeltaRcvd++;
  }else{
    srcid = 0;
    pXfer->nFileRcvd++;
  }
  rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid,
                       szC, isPriv);
  remote_has(rid);
  blob_reset(&content);
}

/*
** Try to send a file as a delta against its parent.
** If successful, return the number of bytes in the delta.
................................................................................
** nothing and return zero.
**
** Never send a delta against a private artifact.
*/
static int send_delta_parent(
  Xfer *pXfer,            /* The transfer context */
  int rid,                /* record id of the file to send */
  int isPrivate,          /* True if rid is a private artifact */
  Blob *pContent,         /* The content of the file to send */
  Blob *pUuid             /* The UUID of the file to send */
){
  static const char *azQuery[] = {
    "SELECT pid FROM plink x"
    " WHERE cid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
................................................................................
  Blob src, delta;
  int size = 0;
  int srcId = 0;

  for(i=0; srcId==0 && i<count(azQuery); i++){
    srcId = db_int(0, azQuery[i], rid);
  }
  if( srcId>0
   && (pXfer->syncPrivate || !content_is_private(srcId))
   && content_get(srcId, &src)
  ){
    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", srcId);
    blob_delta_create(&src, pContent, &delta);
    size = blob_size(&delta);
    if( size>=blob_size(pContent)-50 ){
      size = 0;
    }else if( uuid_is_shunned(zUuid) ){
      size = 0;
    }else{
      if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
      blob_appendf(pXfer->pOut, "file %b %s %d\n", pUuid, zUuid, size);
      blob_append(pXfer->pOut, blob_buffer(&delta), size);

    }
    blob_reset(&delta);
    free(zUuid);
    blob_reset(&src);
  }
  return size;
}
................................................................................
** nothing and return zero.
**
** Never send a delta against a private artifact.
*/
static int send_delta_native(
  Xfer *pXfer,            /* The transfer context */
  int rid,                /* record id of the file to send */
  int isPrivate,          /* True if rid is a private artifact */
  Blob *pUuid             /* The UUID of the file to send */
){
  Blob src, delta;
  int size = 0;
  int srcId;

  srcId = db_int(0, "SELECT srcid FROM delta WHERE rid=%d", rid);
  if( srcId>0
   && (pXfer->syncPrivate || !content_is_private(srcId))
  ){
    blob_zero(&src);
    db_blob(&src, "SELECT uuid FROM blob WHERE rid=%d", srcId);
    if( uuid_is_shunned(blob_str(&src)) ){
      blob_reset(&src);
      return 0;
    }
    blob_zero(&delta);
    db_blob(&delta, "SELECT content FROM blob WHERE rid=%d", rid);
    blob_uncompress(&delta, &delta);
    if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
    blob_appendf(pXfer->pOut, "file %b %b %d\n",
                pUuid, &src, blob_size(&delta));
    blob_append(pXfer->pOut, blob_buffer(&delta), blob_size(&delta));
    size = blob_size(&delta);
    blob_reset(&delta);
    blob_reset(&src);
  }else{
................................................................................
** It should never be the case that rid is a private artifact.  But
** as a precaution, this routine does check on rid and if it is private
** this routine becomes a no-op.
*/
static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int nativeDelta){
  Blob content, uuid;
  int size = 0;
  int isPriv = content_is_private(rid);


  if( pXfer->syncPrivate==0 && isPriv ) return;
  if( db_exists("SELECT 1 FROM onremote WHERE rid=%d", rid) ){
     return;
  }
  blob_zero(&uuid);
  db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d AND size>=0", rid);
  if( blob_size(&uuid)==0 ){
    return;
................................................................................
    pUuid = &uuid;
  }
  if( uuid_is_shunned(blob_str(pUuid)) ){
    blob_reset(&uuid);
    return;
  }
  if( pXfer->mxSend<=blob_size(pXfer->pOut) ){
    const char *zFormat = isPriv ? "igot %b 1\n" : "igot %b\n";
    blob_appendf(pXfer->pOut, zFormat, pUuid);
    pXfer->nIGotSent++;
    blob_reset(&uuid);
    return;
  }
  if( nativeDelta ){
    size = send_delta_native(pXfer, rid, isPriv, pUuid);
    if( size ){
      pXfer->nDeltaSent++;
    }
  }
  if( size==0 ){
    content_get(rid, &content);

    if( !nativeDelta && blob_size(&content)>100 ){
      size = send_delta_parent(pXfer, rid, isPriv, &content, pUuid);
    }
    if( size==0 ){
      int size = blob_size(&content);
      if( isPriv ) blob_append(pXfer->pOut, "private\n", -1);
      blob_appendf(pXfer->pOut, "file %b %d\n", pUuid, size);
      blob_append(pXfer->pOut, blob_buffer(&content), size);
      pXfer->nFileSent++;
    }else{
      pXfer->nDeltaSent++;
    }
  }
  remote_has(rid);
  blob_reset(&uuid);
#if 0
  if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){
    blob_appendf(pXfer->pOut, "\n", 1);
  }
#endif
}

/*
** Send the file identified by rid as a compressed artifact.  Basically,
** send the content exactly as it appears in the BLOB table using 
** a "cfile" card.
*/
................................................................................
static void send_compressed_file(Xfer *pXfer, int rid){
  const char *zContent;
  const char *zUuid;
  const char *zDelta;
  int szU;
  int szC;
  int rc;
  int isPrivate;
  static Stmt q1;

  isPrivate = content_is_private(rid);
  if( isPrivate && pXfer->syncPrivate==0 ) return;
  db_static_prepare(&q1,
    "SELECT uuid, size, content,"
         "  (SELECT uuid FROM delta, blob"
         "    WHERE delta.rid=:rid AND delta.srcid=blob.rid)"
    " FROM blob"
    " WHERE rid=:rid"
    "   AND size>=0"
    "   AND uuid NOT IN shun"


  );
  db_bind_int(&q1, ":rid", rid);
  rc = db_step(&q1);
  if( rc==SQLITE_ROW ){
    zUuid = db_column_text(&q1, 0);
    szU = db_column_int(&q1, 1);
    szC = db_column_bytes(&q1, 2);
    zContent = db_column_raw(&q1, 2);
    zDelta = db_column_text(&q1, 3);
    if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
    blob_appendf(pXfer->pOut, "cfile %s ", zUuid);
     if( zDelta ){
      blob_appendf(pXfer->pOut, "%s ", zDelta);
      pXfer->nDeltaSent++;
    }else{
      pXfer->nFileSent++;
    }
    blob_appendf(pXfer->pOut, "%d %d\n", szU, szC);
    blob_append(pXfer->pOut, zContent, szC);
    if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){
      blob_appendf(pXfer->pOut, "\n", 1);
    }
  }
  db_reset(&q1);
}

/*
** Send a gimme message for every phantom.
**
** Except: do not request shunned artifacts.  And do not request
** private artifacts if we are not doing a private transfer.
*/
static void request_phantoms(Xfer *pXfer, int maxReq){
  Stmt q;
  db_prepare(&q, 
    "SELECT uuid FROM phantom JOIN blob USING(rid)"
    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid) %s",
    (pXfer->syncPrivate ? "" :
         "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)")
  );
  while( db_step(&q)==SQLITE_ROW && maxReq-- > 0 ){
    const char *zUuid = db_column_text(&q, 0);
    blob_appendf(pXfer->pOut, "gimme %s\n", zUuid);
    pXfer->nGimmeSent++;
  }
  db_finalize(&q);
................................................................................
      blob_appendf(&cluster, "Z %b\n", &cksum);
      blob_reset(&cksum);
      content_put(&cluster);
      blob_reset(&cluster);
    }
  }
}

/*
** Send igot messages for every private artifact
*/
static int send_private(Xfer *pXfer){
  int cnt = 0;
  Stmt q;
  if( pXfer->syncPrivate ){
    db_prepare(&q, "SELECT uuid FROM private JOIN blob USING(rid)");
    while( db_step(&q)==SQLITE_ROW ){
      blob_appendf(pXfer->pOut, "igot %s 1\n", db_column_text(&q,0));
      cnt++;
    }
    db_finalize(&q);
  }
  return cnt;
}

/*
** Send an igot message for every entry in unclustered table.
** Return the number of cards sent.
*/
static int send_unclustered(Xfer *pXfer){
  Stmt q;
  int cnt = 0;
  db_prepare(&q, 
    "SELECT uuid FROM unclustered JOIN blob USING(rid)"
    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
  );
  while( db_step(&q)==SQLITE_ROW ){
    blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
    cnt++;
  }
  db_finalize(&q);
  return cnt;
................................................................................
    configure_render_special_name(zName, &content);
    blob_appendf(pXfer->pOut, "config %s %d\n%s\n", zName,
                 blob_size(&content), blob_str(&content));
    blob_reset(&content);
  }
}


/*
** Called when there is an attempt to transfer private content to and
** from a server without authorization.
*/
static void server_private_xfer_not_authorized(void){
  @ error not\sauthorized\sto\ssync\sprivate\scontent
}


/*
** If this variable is set, disable login checks.  Used for debugging
** only.
*/
static int disableLogin = 0;

................................................................................
  db_begin_transaction();
  db_multi_exec(
     "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
  );
  manifest_crosslink_begin();
  while( blob_line(xfer.pIn, &xfer.line) ){
    if( blob_buffer(&xfer.line)[0]=='#' ) continue;
    if( blob_size(&xfer.line)==0 ) continue;
    xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));

    /*   file UUID SIZE \n CONTENT
    **   file UUID DELTASRC SIZE \n CONTENT
    **
    ** Accept a file from the client.
    */
................................................................................
    */
    if( blob_eq(&xfer.aToken[0], "gimme")
     && xfer.nToken==2
     && blob_is_uuid(&xfer.aToken[1])
    ){
      nGimme++;
      if( isPull ){
        int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
        if( rid ){
          send_file(&xfer, rid, &xfer.aToken[1], deltaFlag);
        }
      }
    }else

    /*   igot UUID ?ISPRIVATE?
    **
    ** Client announces that it has a particular file.  If the ISPRIVATE
    ** argument exists and is non-zero, then the file is a private file.
    */
    if( xfer.nToken>=2
     && blob_eq(&xfer.aToken[0], "igot")
     && blob_is_uuid(&xfer.aToken[1])
    ){
      if( isPush ){
        if( xfer.nToken==2 || blob_eq(&xfer.aToken[2],"1")==0 ){
          rid_from_uuid(&xfer.aToken[1], 1, 0);
        }else if( g.okPrivate ){
          rid_from_uuid(&xfer.aToken[1], 1, 1);
        }else{
          server_private_xfer_not_authorized();
        }
      }
    }else
  
    
    /*    pull  SERVERCODE  PROJECTCODE
    **    push  SERVERCODE  PROJECTCODE
    **
................................................................................
    ** Check for a valid login.  This has to happen before anything else.
    ** The client can send multiple logins.  Permissions are cumulative.
    */
    if( blob_eq(&xfer.aToken[0], "login")
     && xfer.nToken==4
    ){
      if( disableLogin ){
        g.okRead = g.okWrite = g.okPrivate = 1;
      }else{
        if( check_tail_hash(&xfer.aToken[2], xfer.pIn)
         || check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3])
        ){
          cgi_reset_content();
          @ error login\sfailed
          nErr++;
................................................................................
    ** back several different cookies to the server.  The server should be
    ** prepared to sift through the cookies and pick the one that it wants.
    */
    if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
      /* Process the cookie */
    }else


    /*    private
    **
    ** This card indicates that the next "file" or "cfile" will contain
    ** private content.
    */
    if( blob_eq(&xfer.aToken[0], "private") ){
      if( !g.okPrivate ){
        server_private_xfer_not_authorized();
      }else{
        xfer.nextIsPrivate = 1;
      }
    }else


    /*    pragma NAME VALUE...
    **
    ** The client issue pragmas to try to influence the behavior of the
    ** server.  These are requests only.  Unknown pragmas are silently
    ** ignored.
    */
    if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
      /*   pragma send-private
      **
      ** If the user has the "x" privilege (which must be set explicitly -
      ** it is not automatic with "a" or "s") then this pragma causes
      ** private information to be pulled in addition to public records.
      */
      if( blob_eq(&xfer.aToken[1], "send-private") ){
        login_check_credentials();
        if( !g.okPrivate ){
          server_private_xfer_not_authorized();
        }else{
          xfer.syncPrivate = 1;
        }
      }
    }else

    /* Unknown message
    */
    {
      cgi_reset_content();
      @ error bad\scommand:\s%F(blob_str(&xfer.line))
    }
    blobarray_reset(xfer.aToken, xfer.nToken);
................................................................................
    ** "gimme" cards. On that initial message, send the client an "igot"
    ** card for every artifact currently in the respository.  This will
    ** cause the client to create phantoms for all artifacts, which will
    ** in turn make sure that the entire repository is sent efficiently
    ** and expeditiously.
    */
    send_all(&xfer);
    if( xfer.syncPrivate ) send_private(&xfer);
  }else if( isPull ){
    create_cluster();
    send_unclustered(&xfer);
    if( xfer.syncPrivate ) send_private(&xfer);
  }
  if( recvConfig ){
    configure_finalize_receive();
  }
  manifest_crosslink_end();

  /* Send the server timestamp last, in case prior processing happened
................................................................................
** are pulled if pullFlag is true.  A full sync occurs if both are
** true.
*/
int client_sync(
  int pushFlag,           /* True to do a push (or a sync) */
  int pullFlag,           /* True to do a pull (or a sync) */
  int cloneFlag,          /* True if this is a clone */
  int privateFlag,        /* True to exchange private branches */
  int configRcvMask,      /* Receive these configuration items */
  int configSendMask      /* Send these configuration items */
){
  int go = 1;             /* Loop until zero */
  int nCardSent = 0;      /* Number of cards sent */
  int nCardRcvd = 0;      /* Number of cards received */
  int nCycle = 0;         /* Number of round trips to the server */
................................................................................

  transport_stats(0, 0, 1);
  socket_global_init();
  memset(&xfer, 0, sizeof(xfer));
  xfer.pIn = &recv;
  xfer.pOut = &send;
  xfer.mxSend = db_get_int("max-upload", 250000);
  if( privateFlag ){
    g.okPrivate = 1;
    xfer.syncPrivate = 1;
  }

  assert( pushFlag | pullFlag | cloneFlag | configRcvMask | configSendMask );
  db_begin_transaction();
  db_record_repository_filename(0);
  db_multi_exec(
    "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
  );
................................................................................
  blobarray_zero(xfer.aToken, count(xfer.aToken));
  blob_zero(&send);
  blob_zero(&recv);
  blob_zero(&xfer.err);
  blob_zero(&xfer.line);
  origConfigRcvMask = 0;


  /* Send the send-private pragma if we are trying to sync private data */
  if( privateFlag ) blob_append(&send, "pragma send-private\n", -1);

  /*
  ** Always begin with a clone, pull, or push message
  */
  if( cloneFlag ){
    blob_appendf(&send, "clone 3 %d\n", cloneSeqno);
    pushFlag = 0;
    pullFlag = 0;
................................................................................
    */
    if( pullFlag || (cloneFlag && cloneSeqno==1) ){
      request_phantoms(&xfer, mxPhantomReq);
    }
    if( pushFlag ){
      send_unsent(&xfer);
      nCardSent += send_unclustered(&xfer);
      if( privateFlag ) send_private(&xfer);
    }

    /* Send configuration parameter requests.  On a clone, delay sending
    ** this until the second cycle since the login card might fail on 
    ** the first cycle.
    */
    if( configRcvMask && (cloneFlag==0 || nCycle>0) ){
................................................................................
    if( http_exchange(&send, &recv, cloneFlag==0 || nCycle>0) ){
      nErr++;
      break;
    }
    lastPctDone = -1;
    blob_reset(&send);
    rArrivalTime = db_double(0.0, "SELECT julianday('now')");

    /* Send the send-private pragma if we are trying to sync private data */
    if( privateFlag ) blob_append(&send, "pragma send-private\n", -1);

    /* Begin constructing the next message (which might never be
    ** sent) by beginning with the pull or push cards
    */
    if( pullFlag ){
      blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
      nCardSent++;
................................................................................
      ** associated with the manifest and send those too.
      */
      if( blob_eq(&xfer.aToken[0], "gimme")
       && xfer.nToken==2
       && blob_is_uuid(&xfer.aToken[1])
      ){
        if( pushFlag ){
          int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
          if( rid ) send_file(&xfer, rid, &xfer.aToken[1], 0);
        }
      }else
  
      /*   igot UUID  ?PRIVATEFLAG?
      **
      ** Server announces that it has a particular file.  If this is
      ** not a file that we have and we are pulling, then create a
      ** phantom to cause this file to be requested on the next cycle.
      ** Always remember that the server has this file so that we do
      ** not transmit it by accident.
      **
      ** If the PRIVATE argument exists and is 1, then the file is 
      ** private.  Pretend it does not exists if we are not pulling
      ** private files.
      */
      if( xfer.nToken>=2
       && blob_eq(&xfer.aToken[0], "igot")
       && blob_is_uuid(&xfer.aToken[1])
      ){
        int rid;
        int isPriv = xfer.nToken>=3 && blob_eq(&xfer.aToken[2],"1");
        rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
        if( rid>0 ){
          if( !isPriv ) content_make_public(rid);
        }else if( isPriv && !g.okPrivate ){
          /* ignore private files */
        }else if( pullFlag || cloneFlag ){
          rid = content_new(blob_str(&xfer.aToken[1]), isPriv);
          if( rid ) newPhantom = 1;
        }
        remote_has(rid);
      }else
    
      
      /*   push  SERVERCODE  PRODUCTCODE
................................................................................
      **
      ** Each cookie received overwrites the prior cookie from the
      ** same server.
      */
      if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
        db_set("cookie", blob_str(&xfer.aToken[1]), 0);
      }else


      /*    private
      **
      ** This card indicates that the next "file" or "cfile" will contain
      ** private content.
      */
      if( blob_eq(&xfer.aToken[0], "private") ){
        xfer.nextIsPrivate = 1;
      }else


      /*    clone_seqno N
      **
      ** When doing a clone, the server tries to send all of its artifacts
      ** in sequence.  This card indicates the sequence number of the next
      ** blob that needs to be sent.  If N<=0 that indicates that all blobs
      ** have been sent.