Fossil SCM

Begin implementing the protocol changes for configuration sync.

drh 2011-04-26 00:45 UTC trunk
Commit f99e3fa9e6b2715e3c5656612a86b1ce6608e51f
1 file changed +131 -21
+131 -21
--- src/configure.c
+++ src/configure.c
@@ -296,10 +296,50 @@
296296
flag_clear_function, 0, 0);
297297
flag_value = 0xffff;
298298
db_multi_exec(zSQL2);
299299
}
300300
}
301
+
302
+/*
303
+** Return true if z[] is not a "safe" SQL token. A safe token is one of:
304
+**
305
+** * A string literal
306
+** * A blob literal
307
+** * An integer literal (no floating point)
308
+** * NULL
309
+*/
310
+static int safeSql(const char *z){
311
+ int i;
312
+ if( z==0 || z[0]==0 ) return 0;
313
+ if( (z[0]=='x' || z[0]=='X') && z[1]=='\'' ) z++;
314
+ if( z[0]=='\'' ){
315
+ for(i=1; z[i]; i++){
316
+ if( z[i]=='\'' ){
317
+ i++;
318
+ if( z[i]=='\'' ){ continue; }
319
+ return z[i]==0;
320
+ }
321
+ }
322
+ return 0;
323
+ }else{
324
+ char c;
325
+ for(i=0; (c = z[i])!=0; i++){
326
+ if( !fossil_isalnum(c) ) return 0;
327
+ }
328
+ }
329
+ return 1;
330
+}
331
+
332
+/*
333
+** Return true if z[] consists of nothing but digits
334
+*/
335
+static int safeInt(const char *z){
336
+ int i;
337
+ if( z==0 || z[0]==0 ) return 0;
338
+ for(i=0; fossil_isdigit(z[i]); i++){}
339
+ return z[i]==0;
340
+}
301341
302342
/*
303343
** Process a single "config" card received from the other side of a
304344
** sync session.
305345
**
@@ -345,32 +385,102 @@
345385
** ahead of time using configure_prepare_to_receive(). Then after multiple
346386
** calls to this routine, configure_finalize_receive() to transfer the
347387
** information received into the true target table.
348388
*/
349389
void configure_receive(const char *zName, Blob *pContent, int mask){
350
- if( (configure_is_exportable(zName) & mask)==0 ) return;
351
- if( strcmp(zName, "logo-image")==0 ){
352
- Stmt ins;
353
- db_prepare(&ins,
354
- "REPLACE INTO config(name, value) VALUES(:name, :value)"
355
- );
356
- db_bind_text(&ins, ":name", zName);
357
- db_bind_blob(&ins, ":value", pContent);
358
- db_step(&ins);
359
- db_finalize(&ins);
360
- }else if( zName[0]=='@' ){
361
- /* Notice that we are evaluating arbitrary SQL received from the
362
- ** client. But this can only happen if the client has authenticated
363
- ** as an administrator, so presumably we trust the client at this
364
- ** point.
365
- */
366
- db_multi_exec("%s", blob_str(pContent));
390
+ if( zName[0]=='/' ){
391
+ /* The new format */
392
+ char *azToken[12];
393
+ int nToken = 0;
394
+ int ii, jj;
395
+ Blob name, value, sql;
396
+ static const struct receiveType {
397
+ const char *zName;
398
+ const char *zPrimKey;
399
+ int nField;
400
+ const char *azField[4];
401
+ } aType[] = {
402
+ { "/config", "name", 1, { "value", 0, 0, 0 } },
403
+ { "@user", "login", 4, { "pw", "cap", "info", "photo" } },
404
+ { "@shun", "uuid", 1, { "scom", 0, 0, 0 } },
405
+ { "@reportfmt", "title", 3, { "owner", "cols", "sqlcode", 0 } },
406
+ { "@concealed", "hash", 1, { "content", 0, 0, 0 } },
407
+ };
408
+ for(ii=0; ii<count(aType); ii++){
409
+ if( fossil_strcmp(&aType[ii].zName[1],&zName[1])==0 ) break;
410
+ }
411
+ if( ii>=count(aType) ) return;
412
+ while( blob_token(pContent, &name) && blob_sqltoken(pContent, &value) ){
413
+ char *z = blob_terminate(&name);
414
+ if( !safeSql(z) ) return;
415
+ if( nToken>0 ){
416
+ for(jj=0; jj<aType[ii].nField; jj++){
417
+ if( fossil_strcmp(aType[ii].azField[jj], z)==0 ) break;
418
+ }
419
+ if( jj>=aType[ii].nField ) continue;
420
+ }else{
421
+ if( !safeInt(z) ) return;
422
+ }
423
+ azToken[nToken++] = z;
424
+ azToken[nToken++] = z = blob_terminate(&value);
425
+ if( !safeSql(z) ) return;
426
+ if( nToken>=count(azToken) ) break;
427
+ }
428
+ if( nToken<2 ) return;
429
+ if( aType[ii].zName[0]=='/' ){
430
+ if( (configure_is_exportable(azToken[1]) & mask)==0 ) return;
431
+ }else{
432
+ if( (configure_is_exportable(aType[ii].zName) & mask)==0 ) return;
433
+ }
434
+
435
+ blob_reset(&sql);
436
+ blob_appendf(&sql, "INSERT OR IGNORE INTO %s(%s, mtime",
437
+ &zName[1], aType[ii].zPrimKey);
438
+ for(jj=2; jj<nToken; jj+=2){
439
+ blob_appendf(&sql, ",%s", azToken[jj]);
440
+ }
441
+ blob_appendf(&sql,") VALUES(%s,%s", azToken[1], azToken[0]);
442
+ for(jj=2; jj<nToken; jj+=2){
443
+ blob_appendf(&sql, ",%s", azToken[jj+1]);
444
+ }
445
+ db_multi_exec("%s", blob_str(&sql));
446
+ if( db_changes()==0 ){
447
+ blob_reset(&sql);
448
+ blob_appendf(&sql, "UPDATE %s SET mtime=%s,", &zName[1], azToken[0]);
449
+ for(jj=2; jj<nToken; jj+=2){
450
+ blob_appendf(&sql, ", %s=%s", azToken[jj], azToken[jj+1]);
451
+ }
452
+ blob_appendf(&sql, " WHERE %s=%s AND mtime<%s",
453
+ aType[ii].zPrimKey, azToken[1], azToken[0]);
454
+ db_multi_exec("%s", blob_str(&sql));
455
+ }
456
+ blob_reset(&sql);
367457
}else{
368
- db_multi_exec(
369
- "REPLACE INTO config(name,value) VALUES(%Q,%Q)",
370
- zName, blob_str(pContent)
371
- );
458
+ /* Otherwise, the old format */
459
+ if( (configure_is_exportable(zName) & mask)==0 ) return;
460
+ if( strcmp(zName, "logo-image")==0 ){
461
+ Stmt ins;
462
+ db_prepare(&ins,
463
+ "REPLACE INTO config(name, value) VALUES(:name, :value)"
464
+ );
465
+ db_bind_text(&ins, ":name", zName);
466
+ db_bind_blob(&ins, ":value", pContent);
467
+ db_step(&ins);
468
+ db_finalize(&ins);
469
+ }else if( zName[0]=='@' ){
470
+ /* Notice that we are evaluating arbitrary SQL received from the
471
+ ** client. But this can only happen if the client has authenticated
472
+ ** as an administrator, so presumably we trust the client at this
473
+ ** point.
474
+ */
475
+ db_multi_exec("%s", blob_str(pContent));
476
+ }else{
477
+ db_multi_exec(
478
+ "REPLACE INTO config(name,value) VALUES(%Q,%Q)",
479
+ zName, blob_str(pContent)
480
+ );
481
+ }
372482
}
373483
}
374484
375485
376486
/*
377487
--- src/configure.c
+++ src/configure.c
@@ -296,10 +296,50 @@
296 flag_clear_function, 0, 0);
297 flag_value = 0xffff;
298 db_multi_exec(zSQL2);
299 }
300 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
302 /*
303 ** Process a single "config" card received from the other side of a
304 ** sync session.
305 **
@@ -345,32 +385,102 @@
345 ** ahead of time using configure_prepare_to_receive(). Then after multiple
346 ** calls to this routine, configure_finalize_receive() to transfer the
347 ** information received into the true target table.
348 */
349 void configure_receive(const char *zName, Blob *pContent, int mask){
350 if( (configure_is_exportable(zName) & mask)==0 ) return;
351 if( strcmp(zName, "logo-image")==0 ){
352 Stmt ins;
353 db_prepare(&ins,
354 "REPLACE INTO config(name, value) VALUES(:name, :value)"
355 );
356 db_bind_text(&ins, ":name", zName);
357 db_bind_blob(&ins, ":value", pContent);
358 db_step(&ins);
359 db_finalize(&ins);
360 }else if( zName[0]=='@' ){
361 /* Notice that we are evaluating arbitrary SQL received from the
362 ** client. But this can only happen if the client has authenticated
363 ** as an administrator, so presumably we trust the client at this
364 ** point.
365 */
366 db_multi_exec("%s", blob_str(pContent));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367 }else{
368 db_multi_exec(
369 "REPLACE INTO config(name,value) VALUES(%Q,%Q)",
370 zName, blob_str(pContent)
371 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372 }
373 }
374
375
376 /*
377
--- src/configure.c
+++ src/configure.c
@@ -296,10 +296,50 @@
296 flag_clear_function, 0, 0);
297 flag_value = 0xffff;
298 db_multi_exec(zSQL2);
299 }
300 }
301
302 /*
303 ** Return true if z[] is not a "safe" SQL token. A safe token is one of:
304 **
305 ** * A string literal
306 ** * A blob literal
307 ** * An integer literal (no floating point)
308 ** * NULL
309 */
310 static int safeSql(const char *z){
311 int i;
312 if( z==0 || z[0]==0 ) return 0;
313 if( (z[0]=='x' || z[0]=='X') && z[1]=='\'' ) z++;
314 if( z[0]=='\'' ){
315 for(i=1; z[i]; i++){
316 if( z[i]=='\'' ){
317 i++;
318 if( z[i]=='\'' ){ continue; }
319 return z[i]==0;
320 }
321 }
322 return 0;
323 }else{
324 char c;
325 for(i=0; (c = z[i])!=0; i++){
326 if( !fossil_isalnum(c) ) return 0;
327 }
328 }
329 return 1;
330 }
331
332 /*
333 ** Return true if z[] consists of nothing but digits
334 */
335 static int safeInt(const char *z){
336 int i;
337 if( z==0 || z[0]==0 ) return 0;
338 for(i=0; fossil_isdigit(z[i]); i++){}
339 return z[i]==0;
340 }
341
342 /*
343 ** Process a single "config" card received from the other side of a
344 ** sync session.
345 **
@@ -345,32 +385,102 @@
385 ** ahead of time using configure_prepare_to_receive(). Then after multiple
386 ** calls to this routine, configure_finalize_receive() to transfer the
387 ** information received into the true target table.
388 */
389 void configure_receive(const char *zName, Blob *pContent, int mask){
390 if( zName[0]=='/' ){
391 /* The new format */
392 char *azToken[12];
393 int nToken = 0;
394 int ii, jj;
395 Blob name, value, sql;
396 static const struct receiveType {
397 const char *zName;
398 const char *zPrimKey;
399 int nField;
400 const char *azField[4];
401 } aType[] = {
402 { "/config", "name", 1, { "value", 0, 0, 0 } },
403 { "@user", "login", 4, { "pw", "cap", "info", "photo" } },
404 { "@shun", "uuid", 1, { "scom", 0, 0, 0 } },
405 { "@reportfmt", "title", 3, { "owner", "cols", "sqlcode", 0 } },
406 { "@concealed", "hash", 1, { "content", 0, 0, 0 } },
407 };
408 for(ii=0; ii<count(aType); ii++){
409 if( fossil_strcmp(&aType[ii].zName[1],&zName[1])==0 ) break;
410 }
411 if( ii>=count(aType) ) return;
412 while( blob_token(pContent, &name) && blob_sqltoken(pContent, &value) ){
413 char *z = blob_terminate(&name);
414 if( !safeSql(z) ) return;
415 if( nToken>0 ){
416 for(jj=0; jj<aType[ii].nField; jj++){
417 if( fossil_strcmp(aType[ii].azField[jj], z)==0 ) break;
418 }
419 if( jj>=aType[ii].nField ) continue;
420 }else{
421 if( !safeInt(z) ) return;
422 }
423 azToken[nToken++] = z;
424 azToken[nToken++] = z = blob_terminate(&value);
425 if( !safeSql(z) ) return;
426 if( nToken>=count(azToken) ) break;
427 }
428 if( nToken<2 ) return;
429 if( aType[ii].zName[0]=='/' ){
430 if( (configure_is_exportable(azToken[1]) & mask)==0 ) return;
431 }else{
432 if( (configure_is_exportable(aType[ii].zName) & mask)==0 ) return;
433 }
434
435 blob_reset(&sql);
436 blob_appendf(&sql, "INSERT OR IGNORE INTO %s(%s, mtime",
437 &zName[1], aType[ii].zPrimKey);
438 for(jj=2; jj<nToken; jj+=2){
439 blob_appendf(&sql, ",%s", azToken[jj]);
440 }
441 blob_appendf(&sql,") VALUES(%s,%s", azToken[1], azToken[0]);
442 for(jj=2; jj<nToken; jj+=2){
443 blob_appendf(&sql, ",%s", azToken[jj+1]);
444 }
445 db_multi_exec("%s", blob_str(&sql));
446 if( db_changes()==0 ){
447 blob_reset(&sql);
448 blob_appendf(&sql, "UPDATE %s SET mtime=%s,", &zName[1], azToken[0]);
449 for(jj=2; jj<nToken; jj+=2){
450 blob_appendf(&sql, ", %s=%s", azToken[jj], azToken[jj+1]);
451 }
452 blob_appendf(&sql, " WHERE %s=%s AND mtime<%s",
453 aType[ii].zPrimKey, azToken[1], azToken[0]);
454 db_multi_exec("%s", blob_str(&sql));
455 }
456 blob_reset(&sql);
457 }else{
458 /* Otherwise, the old format */
459 if( (configure_is_exportable(zName) & mask)==0 ) return;
460 if( strcmp(zName, "logo-image")==0 ){
461 Stmt ins;
462 db_prepare(&ins,
463 "REPLACE INTO config(name, value) VALUES(:name, :value)"
464 );
465 db_bind_text(&ins, ":name", zName);
466 db_bind_blob(&ins, ":value", pContent);
467 db_step(&ins);
468 db_finalize(&ins);
469 }else if( zName[0]=='@' ){
470 /* Notice that we are evaluating arbitrary SQL received from the
471 ** client. But this can only happen if the client has authenticated
472 ** as an administrator, so presumably we trust the client at this
473 ** point.
474 */
475 db_multi_exec("%s", blob_str(pContent));
476 }else{
477 db_multi_exec(
478 "REPLACE INTO config(name,value) VALUES(%Q,%Q)",
479 zName, blob_str(pContent)
480 );
481 }
482 }
483 }
484
485
486 /*
487

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button