Diff
Not logged in

Differences From Artifact [c2218ffaf1]:

To Artifact [a43bdb4395]:


25
26
27
28
29
30
31
32
33
34
35
36
37






38
39



40
41
42
43
44
45
46
25
26
27
28
29
30
31






32
33
34
35
36
37
38

39
40
41
42
43
44
45
46
47
48







-
-
-
-
-
-
+
+
+
+
+
+

-
+
+
+







#include <assert.h>

#if INTERFACE
/*
** Configuration transfers occur in groups.  These are the allowed
** groupings:
*/
#define CONFIGSET_SKIN   0x000001     /* WWW interface appearance */
#define CONFIGSET_TKT    0x000002     /* Ticket configuration */
#define CONFIGSET_PROJ   0x000004     /* Project name */
#define CONFIGSET_SHUN   0x000008     /* Shun settings */
#define CONFIGSET_USER   0x000010     /* The USER table */
#define CONFIGSET_ADDR   0x000020     /* The CONCEALED table */
#define CONFIGSET_SKIN      0x000001     /* WWW interface appearance */
#define CONFIGSET_TKT       0x000002     /* Ticket configuration */
#define CONFIGSET_PROJ      0x000004     /* Project name */
#define CONFIGSET_SHUN      0x000008     /* Shun settings */
#define CONFIGSET_USER      0x000010     /* The USER table */
#define CONFIGSET_ADDR      0x000020     /* The CONCEALED table */

#define CONFIGSET_ALL    0xffffff     /* Everything */
#define CONFIGSET_ALL       0x0000ff     /* Everything */

#define CONFIGSET_OVERWRITE 0x100000     /* Causes overwrite instead of merge */

#endif /* INTERFACE */

/*
** Names of the configuration sets
*/
static struct {
125
126
127
128
129
130
131





132
133

134
135
136
137
138
139
140
127
128
129
130
131
132
133
134
135
136
137
138
139

140
141
142
143
144
145
146
147







+
+
+
+
+

-
+







** "Safe" in the previous paragraph means the permission is created to
** export the property.  In other words, the requesting side has presented
** login credentials and has sufficient capabilities to access the requested
** information.
*/
int configure_is_exportable(const char *zName){
  int i;
  int n = strlen(zName);
  if( n>2 && zName[0]=='\'' && zName[n-1]=='\'' ){
    zName++;
    n -= 2;
  }
  for(i=0; i<count(aConfig); i++){
    if( fossil_strcmp(zName, aConfig[i].zName)==0 ){
    if( memcmp(zName, aConfig[i].zName, n)==0 && aConfig[i].zName[n]==0 ){
      int m = aConfig[i].groupMask;
      if( !g.okAdmin ){
        m &= ~CONFIGSET_USER;
      }
      if( !g.okRdAddr ){
        m &= ~CONFIGSET_ADDR;
      }
198
199
200
201
202
203
204
205
206


207
208
209
210
211





212
213
214
215
216
217
218


219
220
221
222
223
224

225
226

227
228
229
230
231
232

233
234
235
236
237
238
239
205
206
207
208
209
210
211


212
213
214




215
216
217
218
219
220
221
222
223
224


225
226
227
228
229
230
231

232
233

234
235
236
237
238
239

240
241
242
243
244
245
246
247







-
-
+
+

-
-
-
-
+
+
+
+
+





-
-
+
+





-
+

-
+





-
+







    db_finalize(&q);
  }
}

/*
** Two SQL functions:
**
**        flag_test(int)
**        flag_clear(int)
**        config_is_reset(int)
**        config_reset(int)
**
** The flag_test() function takes the integer valued argument and
** ANDs it against the static variable "flag_value" below.  The
** function returns TRUE or false depending on the result.  The
** flag_clear() function masks off the bits from "flag_value" that
** The config_is_reset() function takes the integer valued argument and
** ANDs it against the static variable "configHasBeenReset" below.  The
** function returns TRUE or FALSE depending on the result depending on 
** whether or not the corresponding configuration table has been reset.  The
** config_reset() function adds the bits to "configHasBeenReset" that
** are given in the argument.
**
** These functions are used below in the WHEN clause of a trigger to
** get the trigger to fire exactly once.
*/
static int flag_value = 0xffff;
static void flag_test_function(
static int configHasBeenReset = 0;
static void config_is_reset_function(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  int m = sqlite3_value_int(argv[0]);
  sqlite3_result_int(context, (flag_value&m)!=0 );
  sqlite3_result_int(context, (configHasBeenReset&m)!=0 );
}
static void flag_clear_function(
static void config_reset_function(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  int m = sqlite3_value_int(argv[0]);
  flag_value &= ~m;
  configHasBeenReset |= m;
}

/*
** Create the temporary _xfer_reportfmt and _xfer_user tables that are
** necessary in order to evalute the SQL text generated by the
** configure_render_special_name() routine.
**
271
272
273
274
275
276
277
278

279
280

281
282
283

284
285

286
287
288

289
290

291
292
293
294
295
296
297





298
299
300
301
302
303
304
279
280
281
282
283
284
285

286
287

288
289
290

291
292

293
294
295

296
297

298
299
300





301
302
303
304
305
306
307
308
309
310
311
312







-
+

-
+


-
+

-
+


-
+

-
+


-
-
-
-
-
+
+
+
+
+







  /* When the replace flag is set, add triggers that run the first time
  ** that new data is seen.  The triggers run only once and delete all the
  ** existing data.
  */
  if( replaceFlag ){
    static const char zSQL2[] =
      @ CREATE TRIGGER _xfer_r1 BEFORE INSERT ON _xfer_reportfmt
      @ WHEN flag_test(1) BEGIN
      @ WHEN NOT config_is_reset(2) BEGIN
      @   DELETE FROM _xfer_reportfmt;
      @   SELECT flag_clear(1);
      @   SELECT config_reset(2);
      @ END;
      @ CREATE TRIGGER _xfer_r2 BEFORE INSERT ON _xfer_user
      @ WHEN flag_test(2) BEGIN
      @ WHEN NOT config_is_reset(16) BEGIN
      @   DELETE FROM _xfer_user;
      @   SELECT flag_clear(2);
      @   SELECT config_reset(16);
      @ END;
      @ CREATE TEMP TRIGGER _xfer_r3 BEFORE INSERT ON shun
      @ WHEN flag_test(4) BEGIN
      @ WHEN NOT config_is_reset(8) BEGIN
      @   DELETE FROM shun;
      @   SELECT flag_clear(4);
      @   SELECT config_reset(8);
      @ END;
    ;
    sqlite3_create_function(g.db, "flag_test", 1, SQLITE_UTF8, 0,
         flag_test_function, 0, 0);
    sqlite3_create_function(g.db, "flag_clear", 1, SQLITE_UTF8, 0,
         flag_clear_function, 0, 0);
    flag_value = 0xffff;
    sqlite3_create_function(g.db, "config_is_reset", 1, SQLITE_UTF8, 0,
         config_is_reset_function, 0, 0);
    sqlite3_create_function(g.db, "config_reset", 1, SQLITE_UTF8, 0,
         config_reset_function, 0, 0);
    configHasBeenReset = 0;
    db_multi_exec(zSQL2);
  }
}

/*
** Return true if z[] is not a "safe" SQL token.  A safe token is one of:
**
382
383
384
385
386
387
388
389

390
391
392
393
394

395
396
397
398
399
400
401
390
391
392
393
394
395
396

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







-
+





+







** SQL that is evaluated.  Note that the raw SQL in CONTENT might not
** insert directly into the target table but might instead use a proxy
** table like _fer_reportfmt or _xfer_user.  Such tables must be created
** ahead of time using configure_prepare_to_receive().  Then after multiple
** calls to this routine, configure_finalize_receive() to transfer the
** information received into the true target table.
*/
void configure_receive(const char *zName, Blob *pContent, int mask){
void configure_receive(const char *zName, Blob *pContent, int groupMask){
  if( zName[0]=='/' ){
    /* The new format */
    char *azToken[12];
    int nToken = 0;
    int ii, jj;
    int thisMask;
    Blob name, value, sql;
    static const struct receiveType {
      const char *zName;
      const char *zPrimKey;
      int nField;
      const char *azField[4];
    } aType[] = {
423
424
425
426
427
428
429
430

431
432

433

434
435
436
437











438
439
440
441
442
443
444
445

446
447
448

449
450
451
452
453
454
455
456
457
458
459

460
461
462
463

464
465
466
467
468
469
470
471
472
473
474
475
476
477
478

479
480
481
482
483
484






























485
486
487
488
489
490
491
432
433
434
435
436
437
438

439
440

441
442
443
444



445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462

463
464
465

466
467
468
469
470
471
472
473
474
475
476

477
478
479
480

481
482
483
484
485
486
487
488
489
490
491
492
493
494
495

496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539







-
+

-
+

+

-
-
-
+
+
+
+
+
+
+
+
+
+
+







-
+


-
+










-
+



-
+














-
+






+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







      azToken[nToken++] = z;
      azToken[nToken++] = z = blob_terminate(&value);
      if( !safeSql(z) ) return;
      if( nToken>=count(azToken) ) break;
    }
    if( nToken<2 ) return;
    if( aType[ii].zName[0]=='/' ){
      if( (configure_is_exportable(azToken[1]) & mask)==0 ) return;
      thisMask = configure_is_exportable(azToken[1]);
    }else{
      if( (configure_is_exportable(aType[ii].zName) & mask)==0 ) return;
      thisMask = configure_is_exportable(aType[ii].zName);
    }
    if( (thisMask & groupMask)==0 ) return;
    
    blob_reset(&sql);
    blob_appendf(&sql, "INSERT OR IGNORE INTO %s(%s, mtime",
                 &zName[1], aType[ii].zPrimKey);
    blob_zero(&sql);
    if( groupMask & CONFIGSET_OVERWRITE ){
      if( (thisMask & configHasBeenReset)==0 && aType[ii].zName[0]!='/' ){
        db_multi_exec("DELETE FROM %s", &aType[ii].zName[1]);
        configHasBeenReset |= thisMask;
      }
      blob_append(&sql, "REPLACE INTO ", -1);
    }else{
      blob_append(&sql, "INSERT OR IGNORE INTO ", -1);
    }
    blob_appendf(&sql, "%s(%s, mtime", &zName[1], aType[ii].zPrimKey);
    for(jj=2; jj<nToken; jj+=2){
       blob_appendf(&sql, ",%s", azToken[jj]);
    }
    blob_appendf(&sql,") VALUES(%s,%s", azToken[1], azToken[0]);
    for(jj=2; jj<nToken; jj+=2){
       blob_appendf(&sql, ",%s", azToken[jj+1]);
    }
    db_multi_exec("%s", blob_str(&sql));
    db_multi_exec("%s)", blob_str(&sql));
    if( db_changes()==0 ){
      blob_reset(&sql);
      blob_appendf(&sql, "UPDATE %s SET mtime=%s,", &zName[1], azToken[0]);
      blob_appendf(&sql, "UPDATE %s SET mtime=%s", &zName[1], azToken[0]);
      for(jj=2; jj<nToken; jj+=2){
        blob_appendf(&sql, ", %s=%s", azToken[jj], azToken[jj+1]);
      }
      blob_appendf(&sql, " WHERE %s=%s AND mtime<%s",
                   aType[ii].zPrimKey, azToken[1], azToken[0]);
      db_multi_exec("%s", blob_str(&sql));
    }
    blob_reset(&sql);
  }else{
    /* Otherwise, the old format */
    if( (configure_is_exportable(zName) & mask)==0 ) return;
    if( (configure_is_exportable(zName) & groupMask)==0 ) return;
    if( strcmp(zName, "logo-image")==0 ){
      Stmt ins;
      db_prepare(&ins,
        "REPLACE INTO config(name, value) VALUES(:name, :value)"
        "REPLACE INTO config(name, value, mtime) VALUES(:name, :value, now())"
      );
      db_bind_text(&ins, ":name", zName);
      db_bind_blob(&ins, ":value", pContent);
      db_step(&ins);
      db_finalize(&ins);
    }else if( zName[0]=='@' ){
      /* Notice that we are evaluating arbitrary SQL received from the
      ** client.  But this can only happen if the client has authenticated
      ** as an administrator, so presumably we trust the client at this
      ** point.
      */
      db_multi_exec("%s", blob_str(pContent));
    }else{
      db_multi_exec(
         "REPLACE INTO config(name,value) VALUES(%Q,%Q)",
         "REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now())",
         zName, blob_str(pContent)
      );
    }
  }
}

/*
** Process a file full of "config" cards.
*/
void configure_receive_all(Blob *pIn, int groupMask){
  Blob line;
  int nToken;
  int size;
  Blob aToken[4];

  configHasBeenReset = 0;
  while( blob_line(pIn, &line) ){
    if( blob_buffer(&line)[0]=='#' ) continue;
    nToken = blob_tokenize(&line, aToken, count(aToken));
    if( blob_eq(&aToken[0],"config")
     && nToken==3
     && blob_is_int(&aToken[2], &size)
    ){
      const char *zName = blob_str(&aToken[1]);
      Blob content;
      blob_zero(&content);
      blob_extract(pIn, size, &content);
      g.okAdmin = g.okRdAddr = 1;
      configure_receive(zName, &content, groupMask);
      blob_reset(&content);
      blob_seek(pIn, 1, BLOB_SEEK_CUR);
    }
  }
}
    

/*
** Send "config" cards using the new format for all elements of a group
** that have recently changed.
**
** Output goes into pOut.  The groupMask identifies the group(s) to be sent.
** Send only entries whose timestamp is later than or equal to iStart.
*/
646
647
648
649
650
651
652
653

654

655
656
657
658
659
660
661
662
663



664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679

680
681
682
683
684
685
686
687
688
689
694
695
696
697
698
699
700

701
702
703
704
705

706
707
708



709
710
711
712
713
714













715



716
717
718
719
720
721
722







-
+

+


-



-
-
-
+
+
+



-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-







}

/*
** Write SQL text into file zFilename that will restore the configuration
** area identified by mask to its current state from any other state.
*/
static void export_config(
  int mask,                 /* Mask indicating which configuration to export */
  int groupMask,            /* Mask indicating which configuration to export */
  const char *zMask,        /* Name of the configuration */
  sqlite3_int64 iStart,     /* Start date */
  const char *zFilename     /* Write into this file */
){
  int i;
  Blob out;
  blob_zero(&out);
  blob_appendf(&out, 
    "-- The \"%s\" configuration exported from\n"
    "-- repository \"%s\"\n"
    "-- on %s\n",
    "# The \"%s\" configuration exported from\n"
    "# repository \"%s\"\n"
    "# on %s\n",
    zMask, g.zRepositoryName,
    db_text(0, "SELECT datetime('now')")
  );
  for(i=0; i<count(aConfig); i++){
    if( (aConfig[i].groupMask & mask)!=0 ){
      const char *zName = aConfig[i].zName;
      if( zName[0]!='@' ){
        char *zValue = db_text(0, 
            "SELECT quote(value) FROM config WHERE name=%Q", zName);
        if( zValue ){
          blob_appendf(&out,"REPLACE INTO config VALUES(%Q,%s);\n", 
                       zName, zValue);
        }
        free(zValue);
      }else{
        configure_render_special_name(zName, &out);
  configure_send_group(&out, groupMask, iStart);
      }
    }
  }
  blob_write_to_file(&out, zFilename);
  blob_reset(&out);
}


/*
** COMMAND: configuration
736
737
738
739
740
741
742


743
744
745
746








747

748
749
750
751

752
753
754
755
756
757






758
759
760
761
762
763
764
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789

790
791
792
793
794
795
796
797
798



799
800
801
802
803
804
805
806
807
808
809
810
811







+
+




+
+
+
+
+
+
+
+
-
+




+



-
-
-
+
+
+
+
+
+







    usage("export|import|merge|pull|reset ...");
  }
  db_find_and_open_repository(0, 0);
  zMethod = g.argv[2];
  n = strlen(zMethod);
  if( strncmp(zMethod, "export", n)==0 ){
    int mask;
    const char *zSince = find_option("since",0,1);
    sqlite3_int64 iStart;
    if( g.argc!=5 ){
      usage("export AREA FILENAME");
    }
    mask = find_area(g.argv[3]);
    if( zSince ){
      iStart = db_multi_exec(
         "SELECT coalesce(strftime('%%s',%Q),strftime('%%s','now',%Q))+0",
         zSince, zSince
      );
    }else{
      iStart = 0;
    }
    export_config(mask, g.argv[3], g.argv[4]);
    export_config(mask, g.argv[3], iStart, g.argv[4]);
  }else
  if( strncmp(zMethod, "import", n)==0 
       || strncmp(zMethod, "merge", n)==0 ){
    Blob in;
    int groupMask;
    if( g.argc!=4 ) usage(mprintf("%s FILENAME",zMethod));
    blob_read_from_file(&in, g.argv[3]);
    db_begin_transaction();
    configure_prepare_to_receive(zMethod[0]=='i');
    db_multi_exec("%s", blob_str(&in));
    configure_finalize_receive();
    if( zMethod[0]=='i' ){
      groupMask = CONFIGSET_ALL | CONFIGSET_OVERWRITE;
    }else{
      groupMask = CONFIGSET_ALL;
    }
    configure_receive_all(&in, groupMask);
    db_end_transaction(0);
  }else
  if( strncmp(zMethod, "pull", n)==0 || strncmp(zMethod, "push", n)==0 ){
    int mask;
    const char *zServer;
    const char *zPw;
    url_proxy_options();
791
792
793
794
795
796
797
798

799
800
801
802
803
804
805
838
839
840
841
842
843
844

845
846
847
848
849
850
851
852







-
+







    int mask, i;
    char *zBackup;
    if( g.argc!=4 ) usage("reset AREA");
    mask = find_area(g.argv[3]);
    zBackup = db_text(0, 
       "SELECT strftime('config-backup-%%Y%%m%%d%%H%%M%%f','now')");
    db_begin_transaction();
    export_config(mask, g.argv[3], zBackup);
    export_config(mask, g.argv[3], 0, zBackup);
    for(i=0; i<count(aConfig); i++){
      const char *zName = aConfig[i].zName;
      if( (aConfig[i].groupMask & mask)==0 ) continue;
      if( zName[0]!='@' ){
        db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
      }else if( fossil_strcmp(zName,"@user")==0 ){
        db_multi_exec("DELETE FROM user");