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     33   **
    34     34   ** By default, your current login name is used to create the default
    35     35   ** admin user. This can be overridden using the -A|--admin-user
    36     36   ** parameter.
    37     37   **
    38     38   ** Options:
    39     39   **
    40         -**    --admin-user|-A USERNAME
           40  +**    --admin-user|-A USERNAME    Make USERNAME the administrator
           41  +**    --private                   Also clone private branches 
    41     42   **
    42     43   */
    43     44   void clone_cmd(void){
    44     45     char *zPassword;
    45     46     const char *zDefaultUser;   /* Optional name of the default user */
    46     47     int nErr = 0;
           48  +  int bPrivate;               /* Also clone private branches */
    47     49   
           50  +  bPrivate = find_option("private",0,0)!=0;
    48     51     url_proxy_options();
    49     52     if( g.argc < 4 ){
    50     53       usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
    51     54     }
    52     55     db_open_config(0);
    53     56     if( file_size(g.argv[3])>0 ){
    54     57       fossil_panic("file already exists: %s", g.argv[3]);
................................................................................
    92     95       db_set("last-sync-url", g.argv[2], 0);
    93     96       db_multi_exec(
    94     97         "REPLACE INTO config(name,value)"
    95     98         " VALUES('server-code', lower(hex(randomblob(20))));"
    96     99       );
    97    100       url_enable_proxy(0);
    98    101       g.xlinkClusterOnly = 1;
    99         -    nErr = client_sync(0,0,1,CONFIGSET_ALL,0);
          102  +    nErr = client_sync(0,0,1,bPrivate,CONFIGSET_ALL,0);
   100    103       g.xlinkClusterOnly = 0;
   101    104       verify_cancel();
   102    105       db_end_transaction(0);
   103    106       db_close(1);
   104    107       if( nErr ){
   105    108         unlink(g.argv[3]);
   106    109         fossil_fatal("server returned an error - clone aborted");

Changes to src/configure.c.

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

Changes to src/http_transport.c.

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

Changes to src/login.c.

   372    372     if( strcmp(zRemoteAddr, "127.0.0.1")==0
   373    373      && g.useLocalauth
   374    374      && db_get_int("localauth",0)==0
   375    375      && P("HTTPS")==0
   376    376     ){
   377    377       uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
   378    378       g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
   379         -    zCap = "s";
          379  +    zCap = "sx";
   380    380       g.noPswd = 1;
   381    381       sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "localhost");
   382    382     }
   383    383   
   384    384     /* Check the login cookie to see if it matches a known valid user.
   385    385     */
   386    386     if( uid==0 && (zCookie = P(login_cookie_name()))!=0 ){
................................................................................
   533    533         case 'r':   g.okRdTkt = 1;                                break;
   534    534         case 'n':   g.okNewTkt = 1;                               break;
   535    535         case 'w':   g.okWrTkt = g.okRdTkt = g.okNewTkt = 
   536    536                     g.okApndTkt = 1;                              break;
   537    537         case 'c':   g.okApndTkt = 1;                              break;
   538    538         case 't':   g.okTktFmt = 1;                               break;
   539    539         case 'b':   g.okAttach = 1;                               break;
          540  +      case 'x':   g.okPrivate = 1;                              break;
   540    541   
   541    542         /* The "u" privileges is a little different.  It recursively 
   542    543         ** inherits all privileges of the user named "reader" */
   543    544         case 'u': {
   544    545           if( zUser==0 ){
   545    546             zUser = db_text("", "SELECT cap FROM user WHERE login='reader'");
   546    547             login_set_capabilities(zUser);

Changes to src/main.c.

   132    132     int okNewTkt;           /* n: create new tickets */
   133    133     int okApndTkt;          /* c: append to tickets via the web */
   134    134     int okWrTkt;            /* w: make changes to tickets via web */
   135    135     int okAttach;           /* b: add attachments */
   136    136     int okTktFmt;           /* t: create new ticket report formats */
   137    137     int okRdAddr;           /* e: read email addresses or other private data */
   138    138     int okZip;              /* z: download zipped artifact via /zip URL */
          139  +  int okPrivate;          /* x: can send and receive private content */
   139    140   
   140    141     /* For defense against Cross-site Request Forgery attacks */
   141    142     char zCsrfToken[12];    /* Value of the anti-CSRF token */
   142    143     int okCsrf;             /* Anti-CSRF token is present and valid */
   143    144   
   144    145     FILE *fDebug;           /* Write debug information here, if the file exists */
   145    146     int thTrace;            /* True to enable TH1 debugging output */

Changes to src/rebuild.c.

   538    538       }
   539    539       db_finalize(&q);
   540    540     }
   541    541   }
   542    542   
   543    543   /*
   544    544   ** COMMAND: scrub
   545         -** %fossil scrub [--verily] [--force] [REPOSITORY]
          545  +** %fossil scrub [--verily] [--force] [--private] [REPOSITORY]
   546    546   **
   547    547   ** The command removes sensitive information (such as passwords) from a
   548    548   ** repository so that the respository can be sent to an untrusted reader.
   549    549   **
   550    550   ** By default, only passwords are removed.  However, if the --verily option
   551    551   ** is added, then private branches, concealed email addresses, IP
   552    552   ** addresses of correspondents, and similar privacy-sensitive fields
   553         -** are also purged.
          553  +** are also purged.  If the --private option is used, then only private
          554  +** branches are removed and all other information is left intact.
   554    555   **
   555    556   ** This command permanently deletes the scrubbed information.  The effects
   556    557   ** of this command are irreversible.  Use with caution.
   557    558   **
   558    559   ** The user is prompted to confirm the scrub unless the --force option
   559    560   ** is used.
   560    561   */
   561    562   void scrub_cmd(void){
   562    563     int bVerily = find_option("verily",0,0)!=0;
   563    564     int bForce = find_option("force", "f", 0)!=0;
          565  +  int privateOnly = find_option("private",0,0)!=0;
   564    566     int bNeedRebuild = 0;
   565    567     if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
   566    568     if( g.argc==2 ){
   567    569       db_must_be_within_tree();
   568    570     }else{
   569    571       db_open_repository(g.argv[2]);
   570    572     }
   571    573     if( !bForce ){
   572    574       Blob ans;
   573    575       blob_zero(&ans);
   574         -    prompt_user("Scrubbing the repository will permanently remove user\n"
   575         -                "passwords and other information. Changes cannot be undone.\n"
   576         -                "Continue (y/N)? ", &ans);
          576  +    prompt_user("Scrubbing the repository will permanently information.\n"
          577  +                "Changes cannot be undone.  Continue (y/N)? ", &ans);
   577    578       if( blob_str(&ans)[0]!='y' ){
   578    579         fossil_exit(1);
   579    580       }
   580    581     }
   581    582     db_begin_transaction();
   582         -  db_multi_exec(
   583         -    "UPDATE user SET pw='';"
   584         -    "DELETE FROM config WHERE name GLOB 'last-sync-*';"
   585         -  );
   586         -  if( bVerily ){
          583  +  if( privateOnly || bVerily ){
   587    584       bNeedRebuild = db_exists("SELECT 1 FROM private");
   588    585       db_multi_exec(
   589         -      "DELETE FROM concealed;"
   590         -      "UPDATE rcvfrom SET ipaddr='unknown';"
   591         -      "UPDATE user SET photo=NULL, info='';"
   592         -      "INSERT INTO shun SELECT uuid FROM blob WHERE rid IN private;"
          586  +      "DELETE FROM blob WHERE rid IN private;"
          587  +      "DELETE FROM delta WHERE rid IN private;"
          588  +      "DELETE FROM private;"
          589  +    );
          590  +  }
          591  +  if( !privateOnly ){
          592  +    db_multi_exec(
          593  +      "UPDATE user SET pw='';"
          594  +      "DELETE FROM config WHERE name GLOB 'last-sync-*';"
   593    595       );
          596  +    if( bVerily ){
          597  +      db_multi_exec(
          598  +        "DELETE FROM concealed;"
          599  +        "UPDATE rcvfrom SET ipaddr='unknown';"
          600  +        "UPDATE user SET photo=NULL, info='';"
          601  +      );
          602  +    }
   594    603     }
   595    604     if( !bNeedRebuild ){
   596    605       db_end_transaction(0);
   597    606       db_multi_exec("VACUUM;");
   598    607     }else{
   599    608       rebuild_db(0, 1, 0);
   600    609       db_end_transaction(0);

Changes to src/setup.c.

   120    120     @   <th class="usetupListUser" style="text-align: right;padding-right: 20px;">User&nbsp;ID</th>
   121    121     @   <th class="usetupListCap" style="text-align: center;padding-right: 15px;">Capabilities</th>
   122    122     @   <th class="usetupListCon"  style="text-align: left;">Contact&nbsp;Info</th>
   123    123     @ </tr>
   124    124     db_prepare(&s, "SELECT uid, login, cap, info FROM user ORDER BY login");
   125    125     while( db_step(&s)==SQLITE_ROW ){
   126    126       const char *zCap = db_column_text(&s, 2);
   127         -    if( strstr(zCap, "s") ) zCap = "s";
   128    127       @ <tr>
   129    128       @ <td class="usetupListUser" style="text-align: right;padding-right: 20px;white-space:nowrap;">
   130    129       if( g.okAdmin && (zCap[0]!='s' || g.okSetup) ){
   131    130         @ <a href="setup_uedit?id=%d(db_column_int(&s,0))">
   132    131       }
   133    132       @ %h(db_column_text(&s,1))
   134    133       if( g.okAdmin ){
................................................................................
   186    185        @   <td><i>Reader:</i> Inherit privileges of
   187    186        @   user <tt>reader</tt></td></tr>
   188    187        @ <tr><td valign="top"><b>v</b></td>
   189    188        @   <td><i>Developer:</i> Inherit privileges of
   190    189        @   user <tt>developer</tt></td></tr>
   191    190        @ <tr><td valign="top"><b>w</b></td>
   192    191        @   <td><i>Write-Tkt:</i> Edit tickets</td></tr>
          192  +     @ <tr><td valign="top"><b>x</b></td>
          193  +     @   <td><i>Private:</i> Push and/or pull private branches</td></tr>
   193    194        @ <tr><td valign="top"><b>z</b></td>
   194    195        @   <td><i>Zip download:</i> Download a baseline via the
   195    196        @   <tt>/zip</tt> URL even without 
   196    197        @    check<span class="capability">o</span>ut
   197    198        @    and <span class="capability">h</span>istory permissions</td></tr>
   198    199     @ </table>
   199    200     @ </li>
................................................................................
   241    242   /*
   242    243   ** WEBPAGE: /setup_uedit
   243    244   */
   244    245   void user_edit(void){
   245    246     const char *zId, *zLogin, *zInfo, *zCap, *zPw;
   246    247     char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap;
   247    248     char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae;
   248         -  char *oat, *oau, *oav, *oab, *oaz;
          249  +  char *oat, *oau, *oav, *oab, *oax, *oaz;
   249    250     const char *inherit[128];
   250    251     int doWrite;
   251    252     int uid;
   252    253     int higherUser = 0;  /* True if user being edited is SETUP and the */
   253    254                          /* user doing the editing is ADMIN.  Disallow editing */
   254    255   
   255    256     /* Must have ADMIN privleges to access this page
................................................................................
   298    299       int af = P("af")!=0;
   299    300       int am = P("am")!=0;
   300    301       int ah = P("ah")!=0;
   301    302       int ag = P("ag")!=0;
   302    303       int at = P("at")!=0;
   303    304       int au = P("au")!=0;
   304    305       int av = P("av")!=0;
          306  +    int ax = P("ax")!=0;
   305    307       int az = P("az")!=0;
   306    308       if( aa ){ zCap[i++] = 'a'; }
   307    309       if( ab ){ zCap[i++] = 'b'; }
   308    310       if( ac ){ zCap[i++] = 'c'; }
   309    311       if( ad ){ zCap[i++] = 'd'; }
   310    312       if( ae ){ zCap[i++] = 'e'; }
   311    313       if( af ){ zCap[i++] = 'f'; }
................................................................................
   320    322       if( ap ){ zCap[i++] = 'p'; }
   321    323       if( ar ){ zCap[i++] = 'r'; }
   322    324       if( as ){ zCap[i++] = 's'; }
   323    325       if( at ){ zCap[i++] = 't'; }
   324    326       if( au ){ zCap[i++] = 'u'; }
   325    327       if( av ){ zCap[i++] = 'v'; }
   326    328       if( aw ){ zCap[i++] = 'w'; }
          329  +    if( ax ){ zCap[i++] = 'x'; }
   327    330       if( az ){ zCap[i++] = 'z'; }
   328    331   
   329    332       zCap[i] = 0;
   330    333       zPw = P("pw");
   331    334       zLogin = P("login");
   332    335       if( isValidPwString(zPw) ){
   333    336         zPw = sha1_shared_secret(zPw, zLogin);
................................................................................
   358    361     /* Load the existing information about the user, if any
   359    362     */
   360    363     zLogin = "";
   361    364     zInfo = "";
   362    365     zCap = "";
   363    366     zPw = "";
   364    367     oaa = oab = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam =
   365         -        oan = oao = oap = oar = oas = oat = oau = oav = oaw = oaz = "";
          368  +        oan = oao = oap = oar = oas = oat = oau = oav = oaw = oax = oaz = "";
   366    369     if( uid ){
   367    370       zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
   368    371       zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
   369    372       zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
   370    373       zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid);
   371    374       if( strchr(zCap, 'a') ) oaa = " checked=\"checked\"";
   372    375       if( strchr(zCap, 'b') ) oab = " checked=\"checked\"";
................................................................................
   385    388       if( strchr(zCap, 'p') ) oap = " checked=\"checked\"";
   386    389       if( strchr(zCap, 'r') ) oar = " checked=\"checked\"";
   387    390       if( strchr(zCap, 's') ) oas = " checked=\"checked\"";
   388    391       if( strchr(zCap, 't') ) oat = " checked=\"checked\"";
   389    392       if( strchr(zCap, 'u') ) oau = " checked=\"checked\"";
   390    393       if( strchr(zCap, 'v') ) oav = " checked=\"checked\"";
   391    394       if( strchr(zCap, 'w') ) oaw = " checked=\"checked\"";
          395  +    if( strchr(zCap, 'x') ) oax = " checked=\"checked\"";
   392    396       if( strchr(zCap, 'z') ) oaz = " checked=\"checked\"";
   393    397     }
   394    398   
   395    399     /* figure out inherited permissions */
   396    400     memset(inherit, 0, sizeof(inherit));
   397    401     if( strcmp(zLogin, "developer") ){
   398    402       char *z1, *z2;
................................................................................
   482    486     @    <input type="checkbox" name="ak"%s(oak) />%s(B('k'))Write Wiki<br />
   483    487     @    <input type="checkbox" name="ab"%s(oab) />%s(B('b'))Attachments<br />
   484    488     @    <input type="checkbox" name="ar"%s(oar) />%s(B('r'))Read Ticket<br />
   485    489     @    <input type="checkbox" name="an"%s(oan) />%s(B('n'))New Ticket<br />
   486    490     @    <input type="checkbox" name="ac"%s(oac) />%s(B('c'))Append Ticket<br />
   487    491     @    <input type="checkbox" name="aw"%s(oaw) />%s(B('w'))Write Ticket<br />
   488    492     @    <input type="checkbox" name="at"%s(oat) />%s(B('t'))Ticket Report<br />
          493  +  @    <input type="checkbox" name="ax"%s(oax) />%s(B('x'))Private<br />
   489    494     @    <input type="checkbox" name="az"%s(oaz) />%s(B('z'))Download Zip
   490    495     @   </td>
   491    496     @ </tr>
   492    497     @ <tr>
   493    498     @   <td align="right">Password:</td>
   494    499     if( zPw[0] ){
   495    500       /* Obscure the password for all users */

Changes to src/sync.c.

    76     76       ** autosync, or something?
    77     77       */
    78     78       configSync = CONFIGSET_SHUN;
    79     79     }
    80     80   #endif
    81     81     printf("Autosync:  %s\n", g.urlCanonical);
    82     82     url_enable_proxy("via proxy: ");
    83         -  rc = client_sync((flags & AUTOSYNC_PUSH)!=0, 1, 0, configSync, 0);
           83  +  rc = client_sync((flags & AUTOSYNC_PUSH)!=0, 1, 0, 0, configSync, 0);
    84     84     if( rc ) fossil_warning("Autosync failed");
    85     85     return rc;
    86     86   }
    87     87   
    88     88   /*
    89     89   ** This routine processes the command-line argument for push, pull,
    90     90   ** and sync.  If a command-line argument is given, that is the URL
    91     91   ** of a server to sync against.  If no argument is given, use the
    92     92   ** most recently synced URL.  Remember the current URL for next time.
    93     93   */
    94         -static int process_sync_args(void){
           94  +static void process_sync_args(int *pConfigSync, int *pPrivate){
    95     95     const char *zUrl = 0;
    96     96     const char *zPw = 0;
    97     97     int configSync = 0;
    98     98     int urlOptional = find_option("autourl",0,0)!=0;
    99     99     g.dontKeepUrl = find_option("once",0,0)!=0;
          100  +  *pPrivate = find_option("private",0,0)!=0;
   100    101     url_proxy_options();
   101    102     db_find_and_open_repository(0, 0);
   102    103     db_open_config(0);
   103    104     if( g.argc==2 ){
   104    105       zUrl = db_get("last-sync-url", 0);
   105    106       zPw = unobscure(db_get("last-sync-pw", 0));
   106    107       if( db_get_boolean("auto-sync",1) ) configSync = CONFIGSET_SHUN;
................................................................................
   124    125       if( g.urlPasswd ) db_set("last-sync-pw", obscure(g.urlPasswd), 0);
   125    126     }
   126    127     user_select();
   127    128     if( g.argc==2 ){
   128    129       printf("Server:    %s\n", g.urlCanonical);
   129    130     }
   130    131     url_enable_proxy("via proxy: ");
   131         -  return configSync;
          132  +  *pConfigSync = configSync;
   132    133   }
   133    134   
   134    135   /*
   135    136   ** COMMAND: pull
   136    137   **
   137    138   ** Usage: %fossil pull ?URL? ?options?
   138    139   **
................................................................................
   143    144   ** If the URL is not specified, then the URL from the most recent
   144    145   ** clone, push, pull, remote-url, or sync command is used.
   145    146   **
   146    147   ** The URL specified normally becomes the new "remote-url" used for
   147    148   ** subsequent push, pull, and sync operations.  However, the "--once"
   148    149   ** command-line option makes the URL a one-time-use URL that is not
   149    150   ** saved.
          151  +**
          152  +** Use the --private option to pull private branches from the
          153  +** remote repository.
   150    154   **
   151    155   ** See also: clone, push, sync, remote-url
   152    156   */
   153    157   void pull_cmd(void){
   154         -  int syncFlags = process_sync_args();
   155         -  client_sync(0,1,0,syncFlags,0);
          158  +  int syncFlags;
          159  +  int bPrivate;
          160  +  process_sync_args(&syncFlags, &bPrivate);
          161  +  client_sync(0,1,0,bPrivate,syncFlags,0);
   156    162   }
   157    163   
   158    164   /*
   159    165   ** COMMAND: push
   160    166   **
   161    167   ** Usage: %fossil push ?URL? ?options?
   162    168   **
................................................................................
   167    173   ** If the URL is not specified, then the URL from the most recent
   168    174   ** clone, push, pull, remote-url, or sync command is used.
   169    175   **
   170    176   ** The URL specified normally becomes the new "remote-url" used for
   171    177   ** subsequent push, pull, and sync operations.  However, the "--once"
   172    178   ** command-line option makes the URL a one-time-use URL that is not
   173    179   ** saved.
          180  +**
          181  +** Use the --private option to push private branches to the
          182  +** remote repository.
   174    183   **
   175    184   ** See also: clone, pull, sync, remote-url
   176    185   */
   177    186   void push_cmd(void){
   178         -  process_sync_args();
   179         -  client_sync(1,0,0,0,0);
          187  +  int syncFlags;
          188  +  int bPrivate;
          189  +  process_sync_args(&syncFlags, &bPrivate);
          190  +  client_sync(1,0,0,bPrivate,0,0);
   180    191   }
   181    192   
   182    193   
   183    194   /*
   184    195   ** COMMAND: sync
   185    196   **
   186    197   ** Usage: %fossil sync ?URL? ?options?
................................................................................
   197    208   ** If the URL is not specified, then the URL from the most recent successful
   198    209   ** clone, push, pull, remote-url, or sync command is used.
   199    210   **
   200    211   ** The URL specified normally becomes the new "remote-url" used for
   201    212   ** subsequent push, pull, and sync operations.  However, the "--once"
   202    213   ** command-line option makes the URL a one-time-use URL that is not
   203    214   ** saved.
          215  +**
          216  +** Use the --private option to sync private branches with the
          217  +** remote repository.
   204    218   **
   205    219   ** See also:  clone, push, pull, remote-url
   206    220   */
   207    221   void sync_cmd(void){
   208         -  int syncFlags = process_sync_args();
   209         -  client_sync(1,1,0,syncFlags,0);
          222  +  int syncFlags;
          223  +  int bPrivate;
          224  +  process_sync_args(&syncFlags, &bPrivate);
          225  +  client_sync(1,1,0,bPrivate,syncFlags,0);
   210    226   }
   211    227   
   212    228   /*
   213    229   ** COMMAND: remote-url
   214    230   **
   215    231   ** Usage: %fossil remote-url ?URL|off?
   216    232   **

Changes to src/tag.c.

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

Changes to src/xfer.c.

    36     36     int nGimmeSent;     /* Number of gimme cards sent */
    37     37     int nFileSent;      /* Number of files sent */
    38     38     int nDeltaSent;     /* Number of deltas sent */
    39     39     int nFileRcvd;      /* Number of files received */
    40     40     int nDeltaRcvd;     /* Number of deltas received */
    41     41     int nDanglingFile;  /* Number of dangling deltas received */
    42     42     int mxSend;         /* Stop sending "file" with pOut reaches this size */
           43  +  u8 syncPrivate;     /* True to enable syncing private content */
           44  +  u8 nextIsPrivate;   /* If true, next "file" received is a private */
    43     45   };
    44     46   
    45     47   
    46     48   /*
    47     49   ** The input blob contains a UUID.  Convert it into a record ID.
    48     50   ** Create a phantom record if no prior record exists and
    49     51   ** phantomize is true.
    50     52   **
    51     53   ** Compare to uuid_to_rid().  This routine takes a blob argument
    52     54   ** and does less error checking.
    53     55   */
    54         -static int rid_from_uuid(Blob *pUuid, int phantomize){
           56  +static int rid_from_uuid(Blob *pUuid, int phantomize, int isPrivate){
    55     57     static Stmt q;
    56     58     int rid;
    57     59   
    58     60     db_static_prepare(&q, "SELECT rid FROM blob WHERE uuid=:uuid");
    59     61     db_bind_str(&q, ":uuid", pUuid);
    60     62     if( db_step(&q)==SQLITE_ROW ){
    61     63       rid = db_column_int(&q, 0);
    62     64     }else{
    63     65       rid = 0;
    64     66     }
    65     67     db_reset(&q);
    66     68     if( rid==0 && phantomize ){
    67         -    rid = content_new(blob_str(pUuid), 0);
           69  +    rid = content_new(blob_str(pUuid), isPrivate);
    68     70     }
    69     71     return rid;
    70     72   }
    71     73   
    72     74   /*
    73     75   ** Remember that the other side of the connection already has a copy
    74     76   ** of the file rid.
................................................................................
   104    106   ** be public and is therefore removed from the "private" table.
   105    107   */
   106    108   static void xfer_accept_file(Xfer *pXfer, int cloneFlag){
   107    109     int n;
   108    110     int rid;
   109    111     int srcid = 0;
   110    112     Blob content, hash;
          113  +  int isPriv;
   111    114     
          115  +  isPriv = pXfer->nextIsPrivate;
          116  +  pXfer->nextIsPrivate = 0;
   112    117     if( pXfer->nToken<3 
   113    118      || pXfer->nToken>4
   114    119      || !blob_is_uuid(&pXfer->aToken[1])
   115    120      || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &n)
   116    121      || n<0
   117    122      || (pXfer->nToken==4 && !blob_is_uuid(&pXfer->aToken[2]))
   118    123     ){
................................................................................
   121    126     }
   122    127     blob_zero(&content);
   123    128     blob_zero(&hash);
   124    129     blob_extract(pXfer->pIn, n, &content);
   125    130     if( !cloneFlag && uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
   126    131       /* Ignore files that have been shunned */
   127    132       return;
          133  +  }
          134  +  if( isPriv && !g.okPrivate ){
          135  +    /* Do not accept private files if not authorized */
          136  +    return;
   128    137     }
   129    138     if( cloneFlag ){
   130    139       if( pXfer->nToken==4 ){
   131         -      srcid = rid_from_uuid(&pXfer->aToken[2], 1);
          140  +      srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
   132    141         pXfer->nDeltaRcvd++;
   133    142       }else{
   134    143         srcid = 0;
   135    144         pXfer->nFileRcvd++;
   136    145       }
   137         -    rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid, 0, 0);
          146  +    rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid,
          147  +                         0, isPriv);
   138    148       remote_has(rid);
   139    149       blob_reset(&content);
   140    150       return;
   141    151     }
   142    152     if( pXfer->nToken==4 ){
   143    153       Blob src, next;
   144         -    srcid = rid_from_uuid(&pXfer->aToken[2], 1);
          154  +    srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
   145    155       if( content_get(srcid, &src)==0 ){
   146         -      rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid, 0, 0);
          156  +      rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid,
          157  +                           0, isPriv);
   147    158         pXfer->nDanglingFile++;
   148    159         db_multi_exec("DELETE FROM phantom WHERE rid=%d", rid);
   149         -      content_make_public(rid);
          160  +      if( !isPriv ) content_make_public(rid);
   150    161         return;
   151    162       }
   152    163       pXfer->nDeltaRcvd++;
   153    164       blob_delta_apply(&src, &content, &next);
   154    165       blob_reset(&src);
   155    166       blob_reset(&content);
   156    167       content = next;
................................................................................
   157    168     }else{
   158    169       pXfer->nFileRcvd++;
   159    170     }
   160    171     sha1sum_blob(&content, &hash);
   161    172     if( !blob_eq_str(&pXfer->aToken[1], blob_str(&hash), -1) ){
   162    173       blob_appendf(&pXfer->err, "content does not match sha1 hash");
   163    174     }
   164         -  rid = content_put_ex(&content, blob_str(&hash), 0, 0, 0);
          175  +  rid = content_put_ex(&content, blob_str(&hash), 0, 0, isPriv);
   165    176     blob_reset(&hash);
   166    177     if( rid==0 ){
   167    178       blob_appendf(&pXfer->err, "%s", g.zErrMsg);
   168    179       blob_reset(&content);
   169    180     }else{
   170         -    content_make_public(rid);
          181  +    if( !isPriv ) content_make_public(rid);
   171    182       manifest_crosslink(rid, &content);
   172    183     }
   173    184     assert( blob_is_reset(&content) );
   174    185     remote_has(rid);
   175    186   }
   176    187   
   177    188   /*
................................................................................
   199    210   */
   200    211   static void xfer_accept_compressed_file(Xfer *pXfer){
   201    212     int szC;   /* CSIZE */
   202    213     int szU;   /* USIZE */
   203    214     int rid;
   204    215     int srcid = 0;
   205    216     Blob content;
          217  +  int isPriv;
   206    218     
          219  +  isPriv = pXfer->nextIsPrivate;
          220  +  pXfer->nextIsPrivate = 0;
   207    221     if( pXfer->nToken<4 
   208    222      || pXfer->nToken>5
   209    223      || !blob_is_uuid(&pXfer->aToken[1])
   210    224      || !blob_is_int(&pXfer->aToken[pXfer->nToken-2], &szU)
   211    225      || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &szC)
   212    226      || szC<0 || szU<0
   213    227      || (pXfer->nToken==5 && !blob_is_uuid(&pXfer->aToken[2]))
   214    228     ){
   215    229       blob_appendf(&pXfer->err, "malformed cfile line");
   216    230       return;
          231  +  }
          232  +  if( isPriv && !g.okPrivate ){
          233  +    /* Do not accept private files if not authorized */
          234  +    return;
   217    235     }
   218    236     blob_zero(&content);
   219    237     blob_extract(pXfer->pIn, szC, &content);
   220    238     if( uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
   221    239       /* Ignore files that have been shunned */
   222    240       return;
   223    241     }
   224    242     if( pXfer->nToken==5 ){
   225         -    srcid = rid_from_uuid(&pXfer->aToken[2], 1);
          243  +    srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
   226    244       pXfer->nDeltaRcvd++;
   227    245     }else{
   228    246       srcid = 0;
   229    247       pXfer->nFileRcvd++;
   230    248     }
   231         -  rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid, szC, 0);
          249  +  rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid,
          250  +                       szC, isPriv);
   232    251     remote_has(rid);
   233    252     blob_reset(&content);
   234    253   }
   235    254   
   236    255   /*
   237    256   ** Try to send a file as a delta against its parent.
   238    257   ** If successful, return the number of bytes in the delta.
................................................................................
   240    259   ** nothing and return zero.
   241    260   **
   242    261   ** Never send a delta against a private artifact.
   243    262   */
   244    263   static int send_delta_parent(
   245    264     Xfer *pXfer,            /* The transfer context */
   246    265     int rid,                /* record id of the file to send */
          266  +  int isPrivate,          /* True if rid is a private artifact */
   247    267     Blob *pContent,         /* The content of the file to send */
   248    268     Blob *pUuid             /* The UUID of the file to send */
   249    269   ){
   250    270     static const char *azQuery[] = {
   251    271       "SELECT pid FROM plink x"
   252    272       " WHERE cid=%d"
   253    273       "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
................................................................................
   264    284     Blob src, delta;
   265    285     int size = 0;
   266    286     int srcId = 0;
   267    287   
   268    288     for(i=0; srcId==0 && i<count(azQuery); i++){
   269    289       srcId = db_int(0, azQuery[i], rid);
   270    290     }
   271         -  if( srcId>0 && !content_is_private(srcId) && content_get(srcId, &src) ){
          291  +  if( srcId>0
          292  +   && (pXfer->syncPrivate || !content_is_private(srcId))
          293  +   && content_get(srcId, &src)
          294  +  ){
   272    295       char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", srcId);
   273    296       blob_delta_create(&src, pContent, &delta);
   274    297       size = blob_size(&delta);
   275    298       if( size>=blob_size(pContent)-50 ){
   276    299         size = 0;
   277    300       }else if( uuid_is_shunned(zUuid) ){
   278    301         size = 0;
   279    302       }else{
          303  +      if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
   280    304         blob_appendf(pXfer->pOut, "file %b %s %d\n", pUuid, zUuid, size);
   281    305         blob_append(pXfer->pOut, blob_buffer(&delta), size);
   282         -      /* blob_appendf(pXfer->pOut, "\n", 1); */
   283    306       }
   284    307       blob_reset(&delta);
   285    308       free(zUuid);
   286    309       blob_reset(&src);
   287    310     }
   288    311     return size;
   289    312   }
................................................................................
   295    318   ** nothing and return zero.
   296    319   **
   297    320   ** Never send a delta against a private artifact.
   298    321   */
   299    322   static int send_delta_native(
   300    323     Xfer *pXfer,            /* The transfer context */
   301    324     int rid,                /* record id of the file to send */
          325  +  int isPrivate,          /* True if rid is a private artifact */
   302    326     Blob *pUuid             /* The UUID of the file to send */
   303    327   ){
   304    328     Blob src, delta;
   305    329     int size = 0;
   306    330     int srcId;
   307    331   
   308    332     srcId = db_int(0, "SELECT srcid FROM delta WHERE rid=%d", rid);
   309         -  if( srcId>0 && !content_is_private(srcId) ){
          333  +  if( srcId>0
          334  +   && (pXfer->syncPrivate || !content_is_private(srcId))
          335  +  ){
   310    336       blob_zero(&src);
   311    337       db_blob(&src, "SELECT uuid FROM blob WHERE rid=%d", srcId);
   312    338       if( uuid_is_shunned(blob_str(&src)) ){
   313    339         blob_reset(&src);
   314    340         return 0;
   315    341       }
   316    342       blob_zero(&delta);
   317    343       db_blob(&delta, "SELECT content FROM blob WHERE rid=%d", rid);
   318    344       blob_uncompress(&delta, &delta);
          345  +    if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
   319    346       blob_appendf(pXfer->pOut, "file %b %b %d\n",
   320    347                   pUuid, &src, blob_size(&delta));
   321    348       blob_append(pXfer->pOut, blob_buffer(&delta), blob_size(&delta));
   322    349       size = blob_size(&delta);
   323    350       blob_reset(&delta);
   324    351       blob_reset(&src);
   325    352     }else{
................................................................................
   340    367   ** It should never be the case that rid is a private artifact.  But
   341    368   ** as a precaution, this routine does check on rid and if it is private
   342    369   ** this routine becomes a no-op.
   343    370   */
   344    371   static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int nativeDelta){
   345    372     Blob content, uuid;
   346    373     int size = 0;
          374  +  int isPriv = content_is_private(rid);
   347    375   
   348         -  if( content_is_private(rid) ) return;
          376  +  if( pXfer->syncPrivate==0 && isPriv ) return;
   349    377     if( db_exists("SELECT 1 FROM onremote WHERE rid=%d", rid) ){
   350    378        return;
   351    379     }
   352    380     blob_zero(&uuid);
   353    381     db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d AND size>=0", rid);
   354    382     if( blob_size(&uuid)==0 ){
   355    383       return;
................................................................................
   363    391       pUuid = &uuid;
   364    392     }
   365    393     if( uuid_is_shunned(blob_str(pUuid)) ){
   366    394       blob_reset(&uuid);
   367    395       return;
   368    396     }
   369    397     if( pXfer->mxSend<=blob_size(pXfer->pOut) ){
   370         -    blob_appendf(pXfer->pOut, "igot %b\n", pUuid);
          398  +    const char *zFormat = isPriv ? "igot %b 1\n" : "igot %b\n";
          399  +    blob_appendf(pXfer->pOut, zFormat, pUuid);
   371    400       pXfer->nIGotSent++;
   372    401       blob_reset(&uuid);
   373    402       return;
   374    403     }
   375    404     if( nativeDelta ){
   376         -    size = send_delta_native(pXfer, rid, pUuid);
          405  +    size = send_delta_native(pXfer, rid, isPriv, pUuid);
   377    406       if( size ){
   378    407         pXfer->nDeltaSent++;
   379    408       }
   380    409     }
   381    410     if( size==0 ){
   382    411       content_get(rid, &content);
   383    412   
   384    413       if( !nativeDelta && blob_size(&content)>100 ){
   385         -      size = send_delta_parent(pXfer, rid, &content, pUuid);
          414  +      size = send_delta_parent(pXfer, rid, isPriv, &content, pUuid);
   386    415       }
   387    416       if( size==0 ){
   388    417         int size = blob_size(&content);
          418  +      if( isPriv ) blob_append(pXfer->pOut, "private\n", -1);
   389    419         blob_appendf(pXfer->pOut, "file %b %d\n", pUuid, size);
   390    420         blob_append(pXfer->pOut, blob_buffer(&content), size);
   391    421         pXfer->nFileSent++;
   392    422       }else{
   393    423         pXfer->nDeltaSent++;
   394    424       }
   395    425     }
   396    426     remote_has(rid);
   397    427     blob_reset(&uuid);
          428  +#if 0
          429  +  if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){
          430  +    blob_appendf(pXfer->pOut, "\n", 1);
          431  +  }
          432  +#endif
   398    433   }
   399    434   
   400    435   /*
   401    436   ** Send the file identified by rid as a compressed artifact.  Basically,
   402    437   ** send the content exactly as it appears in the BLOB table using 
   403    438   ** a "cfile" card.
   404    439   */
................................................................................
   405    440   static void send_compressed_file(Xfer *pXfer, int rid){
   406    441     const char *zContent;
   407    442     const char *zUuid;
   408    443     const char *zDelta;
   409    444     int szU;
   410    445     int szC;
   411    446     int rc;
          447  +  int isPrivate;
   412    448     static Stmt q1;
   413    449   
          450  +  isPrivate = content_is_private(rid);
          451  +  if( isPrivate && pXfer->syncPrivate==0 ) return;
   414    452     db_static_prepare(&q1,
   415    453       "SELECT uuid, size, content,"
   416    454            "  (SELECT uuid FROM delta, blob"
   417    455            "    WHERE delta.rid=:rid AND delta.srcid=blob.rid)"
   418    456       " FROM blob"
   419    457       " WHERE rid=:rid"
   420    458       "   AND size>=0"
   421    459       "   AND uuid NOT IN shun"
   422         -    "   AND rid NOT IN private",
   423         -    rid
   424    460     );
   425    461     db_bind_int(&q1, ":rid", rid);
   426    462     rc = db_step(&q1);
   427    463     if( rc==SQLITE_ROW ){
   428    464       zUuid = db_column_text(&q1, 0);
   429    465       szU = db_column_int(&q1, 1);
   430    466       szC = db_column_bytes(&q1, 2);
   431    467       zContent = db_column_raw(&q1, 2);
   432    468       zDelta = db_column_text(&q1, 3);
          469  +    if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
   433    470       blob_appendf(pXfer->pOut, "cfile %s ", zUuid);
   434         -    if( zDelta ){
          471  +     if( zDelta ){
   435    472         blob_appendf(pXfer->pOut, "%s ", zDelta);
   436    473         pXfer->nDeltaSent++;
   437    474       }else{
   438    475         pXfer->nFileSent++;
   439    476       }
   440    477       blob_appendf(pXfer->pOut, "%d %d\n", szU, szC);
   441    478       blob_append(pXfer->pOut, zContent, szC);
   442         -    blob_append(pXfer->pOut, "\n", 1);
          479  +    if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){
          480  +      blob_appendf(pXfer->pOut, "\n", 1);
          481  +    }
   443    482     }
   444    483     db_reset(&q1);
   445    484   }
   446    485   
   447    486   /*
   448    487   ** Send a gimme message for every phantom.
   449    488   **
   450         -** It should not be possible to have a private phantom.  But just to be
   451         -** sure, take care not to send any "gimme" messagse on private artifacts.
          489  +** Except: do not request shunned artifacts.  And do not request
          490  +** private artifacts if we are not doing a private transfer.
   452    491   */
   453    492   static void request_phantoms(Xfer *pXfer, int maxReq){
   454    493     Stmt q;
   455    494     db_prepare(&q, 
   456    495       "SELECT uuid FROM phantom JOIN blob USING(rid)"
   457         -    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
   458         -    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
          496  +    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid) %s",
          497  +    (pXfer->syncPrivate ? "" :
          498  +         "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)")
   459    499     );
   460    500     while( db_step(&q)==SQLITE_ROW && maxReq-- > 0 ){
   461    501       const char *zUuid = db_column_text(&q, 0);
   462    502       blob_appendf(pXfer->pOut, "gimme %s\n", zUuid);
   463    503       pXfer->nGimmeSent++;
   464    504     }
   465    505     db_finalize(&q);
................................................................................
   639    679         blob_appendf(&cluster, "Z %b\n", &cksum);
   640    680         blob_reset(&cksum);
   641    681         content_put(&cluster);
   642    682         blob_reset(&cluster);
   643    683       }
   644    684     }
   645    685   }
          686  +
          687  +/*
          688  +** Send igot messages for every private artifact
          689  +*/
          690  +static int send_private(Xfer *pXfer){
          691  +  int cnt = 0;
          692  +  Stmt q;
          693  +  if( pXfer->syncPrivate ){
          694  +    db_prepare(&q, "SELECT uuid FROM private JOIN blob USING(rid)");
          695  +    while( db_step(&q)==SQLITE_ROW ){
          696  +      blob_appendf(pXfer->pOut, "igot %s 1\n", db_column_text(&q,0));
          697  +      cnt++;
          698  +    }
          699  +    db_finalize(&q);
          700  +  }
          701  +  return cnt;
          702  +}
   646    703   
   647    704   /*
   648    705   ** Send an igot message for every entry in unclustered table.
   649    706   ** Return the number of cards sent.
   650    707   */
   651    708   static int send_unclustered(Xfer *pXfer){
   652    709     Stmt q;
   653    710     int cnt = 0;
   654    711     db_prepare(&q, 
   655    712       "SELECT uuid FROM unclustered JOIN blob USING(rid)"
   656    713       " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
   657         -    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
   658    714       "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
          715  +    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
   659    716     );
   660    717     while( db_step(&q)==SQLITE_ROW ){
   661    718       blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
   662    719       cnt++;
   663    720     }
   664    721     db_finalize(&q);
   665    722     return cnt;
................................................................................
   702    759       configure_render_special_name(zName, &content);
   703    760       blob_appendf(pXfer->pOut, "config %s %d\n%s\n", zName,
   704    761                    blob_size(&content), blob_str(&content));
   705    762       blob_reset(&content);
   706    763     }
   707    764   }
   708    765   
          766  +
          767  +/*
          768  +** Called when there is an attempt to transfer private content to and
          769  +** from a server without authorization.
          770  +*/
          771  +static void server_private_xfer_not_authorized(void){
          772  +  @ error not\sauthorized\sto\ssync\sprivate\scontent
          773  +}
          774  +
   709    775   
   710    776   /*
   711    777   ** If this variable is set, disable login checks.  Used for debugging
   712    778   ** only.
   713    779   */
   714    780   static int disableLogin = 0;
   715    781   
................................................................................
   755    821     db_begin_transaction();
   756    822     db_multi_exec(
   757    823        "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
   758    824     );
   759    825     manifest_crosslink_begin();
   760    826     while( blob_line(xfer.pIn, &xfer.line) ){
   761    827       if( blob_buffer(&xfer.line)[0]=='#' ) continue;
          828  +    if( blob_size(&xfer.line)==0 ) continue;
   762    829       xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
   763    830   
   764    831       /*   file UUID SIZE \n CONTENT
   765    832       **   file UUID DELTASRC SIZE \n CONTENT
   766    833       **
   767    834       ** Accept a file from the client.
   768    835       */
................................................................................
   809    876       */
   810    877       if( blob_eq(&xfer.aToken[0], "gimme")
   811    878        && xfer.nToken==2
   812    879        && blob_is_uuid(&xfer.aToken[1])
   813    880       ){
   814    881         nGimme++;
   815    882         if( isPull ){
   816         -        int rid = rid_from_uuid(&xfer.aToken[1], 0);
          883  +        int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
   817    884           if( rid ){
   818    885             send_file(&xfer, rid, &xfer.aToken[1], deltaFlag);
   819    886           }
   820    887         }
   821    888       }else
   822    889   
   823         -    /*   igot UUID
          890  +    /*   igot UUID ?ISPRIVATE?
   824    891       **
   825         -    ** Client announces that it has a particular file.
          892  +    ** Client announces that it has a particular file.  If the ISPRIVATE
          893  +    ** argument exists and is non-zero, then the file is a private file.
   826    894       */
   827         -    if( xfer.nToken==2
          895  +    if( xfer.nToken>=2
   828    896        && blob_eq(&xfer.aToken[0], "igot")
   829    897        && blob_is_uuid(&xfer.aToken[1])
   830    898       ){
   831    899         if( isPush ){
   832         -        rid_from_uuid(&xfer.aToken[1], 1);
          900  +        if( xfer.nToken==2 || blob_eq(&xfer.aToken[2],"1")==0 ){
          901  +          rid_from_uuid(&xfer.aToken[1], 1, 0);
          902  +        }else if( g.okPrivate ){
          903  +          rid_from_uuid(&xfer.aToken[1], 1, 1);
          904  +        }else{
          905  +          server_private_xfer_not_authorized();
          906  +        }
   833    907         }
   834    908       }else
   835    909     
   836    910       
   837    911       /*    pull  SERVERCODE  PROJECTCODE
   838    912       **    push  SERVERCODE  PROJECTCODE
   839    913       **
................................................................................
   927   1001       ** Check for a valid login.  This has to happen before anything else.
   928   1002       ** The client can send multiple logins.  Permissions are cumulative.
   929   1003       */
   930   1004       if( blob_eq(&xfer.aToken[0], "login")
   931   1005        && xfer.nToken==4
   932   1006       ){
   933   1007         if( disableLogin ){
   934         -        g.okRead = g.okWrite = 1;
         1008  +        g.okRead = g.okWrite = g.okPrivate = 1;
   935   1009         }else{
   936   1010           if( check_tail_hash(&xfer.aToken[2], xfer.pIn)
   937   1011            || check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3])
   938   1012           ){
   939   1013             cgi_reset_content();
   940   1014             @ error login\sfailed
   941   1015             nErr++;
................................................................................
  1027   1101       ** back several different cookies to the server.  The server should be
  1028   1102       ** prepared to sift through the cookies and pick the one that it wants.
  1029   1103       */
  1030   1104       if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
  1031   1105         /* Process the cookie */
  1032   1106       }else
  1033   1107   
         1108  +
         1109  +    /*    private
         1110  +    **
         1111  +    ** This card indicates that the next "file" or "cfile" will contain
         1112  +    ** private content.
         1113  +    */
         1114  +    if( blob_eq(&xfer.aToken[0], "private") ){
         1115  +      if( !g.okPrivate ){
         1116  +        server_private_xfer_not_authorized();
         1117  +      }else{
         1118  +        xfer.nextIsPrivate = 1;
         1119  +      }
         1120  +    }else
         1121  +
         1122  +
         1123  +    /*    pragma NAME VALUE...
         1124  +    **
         1125  +    ** The client issue pragmas to try to influence the behavior of the
         1126  +    ** server.  These are requests only.  Unknown pragmas are silently
         1127  +    ** ignored.
         1128  +    */
         1129  +    if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
         1130  +      /*   pragma send-private
         1131  +      **
         1132  +      ** If the user has the "x" privilege (which must be set explicitly -
         1133  +      ** it is not automatic with "a" or "s") then this pragma causes
         1134  +      ** private information to be pulled in addition to public records.
         1135  +      */
         1136  +      if( blob_eq(&xfer.aToken[1], "send-private") ){
         1137  +        login_check_credentials();
         1138  +        if( !g.okPrivate ){
         1139  +          server_private_xfer_not_authorized();
         1140  +        }else{
         1141  +          xfer.syncPrivate = 1;
         1142  +        }
         1143  +      }
         1144  +    }else
         1145  +
  1034   1146       /* Unknown message
  1035   1147       */
  1036   1148       {
  1037   1149         cgi_reset_content();
  1038   1150         @ error bad\scommand:\s%F(blob_str(&xfer.line))
  1039   1151       }
  1040   1152       blobarray_reset(xfer.aToken, xfer.nToken);
................................................................................
  1047   1159       ** "gimme" cards. On that initial message, send the client an "igot"
  1048   1160       ** card for every artifact currently in the respository.  This will
  1049   1161       ** cause the client to create phantoms for all artifacts, which will
  1050   1162       ** in turn make sure that the entire repository is sent efficiently
  1051   1163       ** and expeditiously.
  1052   1164       */
  1053   1165       send_all(&xfer);
         1166  +    if( xfer.syncPrivate ) send_private(&xfer);
  1054   1167     }else if( isPull ){
  1055   1168       create_cluster();
  1056   1169       send_unclustered(&xfer);
         1170  +    if( xfer.syncPrivate ) send_private(&xfer);
  1057   1171     }
  1058   1172     if( recvConfig ){
  1059   1173       configure_finalize_receive();
  1060   1174     }
  1061   1175     manifest_crosslink_end();
  1062   1176   
  1063   1177     /* Send the server timestamp last, in case prior processing happened
................................................................................
  1118   1232   ** are pulled if pullFlag is true.  A full sync occurs if both are
  1119   1233   ** true.
  1120   1234   */
  1121   1235   int client_sync(
  1122   1236     int pushFlag,           /* True to do a push (or a sync) */
  1123   1237     int pullFlag,           /* True to do a pull (or a sync) */
  1124   1238     int cloneFlag,          /* True if this is a clone */
         1239  +  int privateFlag,        /* True to exchange private branches */
  1125   1240     int configRcvMask,      /* Receive these configuration items */
  1126   1241     int configSendMask      /* Send these configuration items */
  1127   1242   ){
  1128   1243     int go = 1;             /* Loop until zero */
  1129   1244     int nCardSent = 0;      /* Number of cards sent */
  1130   1245     int nCardRcvd = 0;      /* Number of cards received */
  1131   1246     int nCycle = 0;         /* Number of round trips to the server */
................................................................................
  1153   1268   
  1154   1269     transport_stats(0, 0, 1);
  1155   1270     socket_global_init();
  1156   1271     memset(&xfer, 0, sizeof(xfer));
  1157   1272     xfer.pIn = &recv;
  1158   1273     xfer.pOut = &send;
  1159   1274     xfer.mxSend = db_get_int("max-upload", 250000);
         1275  +  if( privateFlag ){
         1276  +    g.okPrivate = 1;
         1277  +    xfer.syncPrivate = 1;
         1278  +  }
  1160   1279   
  1161   1280     assert( pushFlag | pullFlag | cloneFlag | configRcvMask | configSendMask );
  1162   1281     db_begin_transaction();
  1163   1282     db_record_repository_filename(0);
  1164   1283     db_multi_exec(
  1165   1284       "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
  1166   1285     );
................................................................................
  1167   1286     blobarray_zero(xfer.aToken, count(xfer.aToken));
  1168   1287     blob_zero(&send);
  1169   1288     blob_zero(&recv);
  1170   1289     blob_zero(&xfer.err);
  1171   1290     blob_zero(&xfer.line);
  1172   1291     origConfigRcvMask = 0;
  1173   1292   
         1293  +
         1294  +  /* Send the send-private pragma if we are trying to sync private data */
         1295  +  if( privateFlag ) blob_append(&send, "pragma send-private\n", -1);
         1296  +
  1174   1297     /*
  1175   1298     ** Always begin with a clone, pull, or push message
  1176   1299     */
  1177   1300     if( cloneFlag ){
  1178   1301       blob_appendf(&send, "clone 3 %d\n", cloneSeqno);
  1179   1302       pushFlag = 0;
  1180   1303       pullFlag = 0;
................................................................................
  1210   1333       */
  1211   1334       if( pullFlag || (cloneFlag && cloneSeqno==1) ){
  1212   1335         request_phantoms(&xfer, mxPhantomReq);
  1213   1336       }
  1214   1337       if( pushFlag ){
  1215   1338         send_unsent(&xfer);
  1216   1339         nCardSent += send_unclustered(&xfer);
         1340  +      if( privateFlag ) send_private(&xfer);
  1217   1341       }
  1218   1342   
  1219   1343       /* Send configuration parameter requests.  On a clone, delay sending
  1220   1344       ** this until the second cycle since the login card might fail on 
  1221   1345       ** the first cycle.
  1222   1346       */
  1223   1347       if( configRcvMask && (cloneFlag==0 || nCycle>0) ){
................................................................................
  1273   1397       if( http_exchange(&send, &recv, cloneFlag==0 || nCycle>0) ){
  1274   1398         nErr++;
  1275   1399         break;
  1276   1400       }
  1277   1401       lastPctDone = -1;
  1278   1402       blob_reset(&send);
  1279   1403       rArrivalTime = db_double(0.0, "SELECT julianday('now')");
         1404  +
         1405  +    /* Send the send-private pragma if we are trying to sync private data */
         1406  +    if( privateFlag ) blob_append(&send, "pragma send-private\n", -1);
  1280   1407   
  1281   1408       /* Begin constructing the next message (which might never be
  1282   1409       ** sent) by beginning with the pull or push cards
  1283   1410       */
  1284   1411       if( pullFlag ){
  1285   1412         blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
  1286   1413         nCardSent++;
................................................................................
  1347   1474         ** associated with the manifest and send those too.
  1348   1475         */
  1349   1476         if( blob_eq(&xfer.aToken[0], "gimme")
  1350   1477          && xfer.nToken==2
  1351   1478          && blob_is_uuid(&xfer.aToken[1])
  1352   1479         ){
  1353   1480           if( pushFlag ){
  1354         -          int rid = rid_from_uuid(&xfer.aToken[1], 0);
         1481  +          int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
  1355   1482             if( rid ) send_file(&xfer, rid, &xfer.aToken[1], 0);
  1356   1483           }
  1357   1484         }else
  1358   1485     
  1359         -      /*   igot UUID
         1486  +      /*   igot UUID  ?PRIVATEFLAG?
  1360   1487         **
  1361   1488         ** Server announces that it has a particular file.  If this is
  1362   1489         ** not a file that we have and we are pulling, then create a
  1363   1490         ** phantom to cause this file to be requested on the next cycle.
  1364   1491         ** Always remember that the server has this file so that we do
  1365   1492         ** not transmit it by accident.
         1493  +      **
         1494  +      ** If the PRIVATE argument exists and is 1, then the file is 
         1495  +      ** private.  Pretend it does not exists if we are not pulling
         1496  +      ** private files.
  1366   1497         */
  1367         -      if( xfer.nToken==2
         1498  +      if( xfer.nToken>=2
  1368   1499          && blob_eq(&xfer.aToken[0], "igot")
  1369   1500          && blob_is_uuid(&xfer.aToken[1])
  1370   1501         ){
  1371         -        int rid = rid_from_uuid(&xfer.aToken[1], 0);
         1502  +        int rid;
         1503  +        int isPriv = xfer.nToken>=3 && blob_eq(&xfer.aToken[2],"1");
         1504  +        rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
  1372   1505           if( rid>0 ){
  1373         -          content_make_public(rid);
         1506  +          if( !isPriv ) content_make_public(rid);
         1507  +        }else if( isPriv && !g.okPrivate ){
         1508  +          /* ignore private files */
  1374   1509           }else if( pullFlag || cloneFlag ){
  1375         -          rid = content_new(blob_str(&xfer.aToken[1]), 0);
         1510  +          rid = content_new(blob_str(&xfer.aToken[1]), isPriv);
  1376   1511             if( rid ) newPhantom = 1;
  1377   1512           }
  1378   1513           remote_has(rid);
  1379   1514         }else
  1380   1515       
  1381   1516         
  1382   1517         /*   push  SERVERCODE  PRODUCTCODE
................................................................................
  1452   1587         **
  1453   1588         ** Each cookie received overwrites the prior cookie from the
  1454   1589         ** same server.
  1455   1590         */
  1456   1591         if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
  1457   1592           db_set("cookie", blob_str(&xfer.aToken[1]), 0);
  1458   1593         }else
         1594  +
         1595  +
         1596  +      /*    private
         1597  +      **
         1598  +      ** This card indicates that the next "file" or "cfile" will contain
         1599  +      ** private content.
         1600  +      */
         1601  +      if( blob_eq(&xfer.aToken[0], "private") ){
         1602  +        xfer.nextIsPrivate = 1;
         1603  +      }else
         1604  +
  1459   1605   
  1460   1606         /*    clone_seqno N
  1461   1607         **
  1462   1608         ** When doing a clone, the server tries to send all of its artifacts
  1463   1609         ** in sequence.  This card indicates the sequence number of the next
  1464   1610         ** blob that needs to be sent.  If N<=0 that indicates that all blobs
  1465   1611         ** have been sent.