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 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 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.