Fossil SCM
Begin implementing the protocol changes for configuration sync.
Commit
f99e3fa9e6b2715e3c5656612a86b1ce6608e51f
Parent
e17fc71319e0534…
1 file changed
+131
-21
+131
-21
| --- src/configure.c | ||
| +++ src/configure.c | ||
| @@ -296,10 +296,50 @@ | ||
| 296 | 296 | flag_clear_function, 0, 0); |
| 297 | 297 | flag_value = 0xffff; |
| 298 | 298 | db_multi_exec(zSQL2); |
| 299 | 299 | } |
| 300 | 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 | +} | |
| 301 | 341 | |
| 302 | 342 | /* |
| 303 | 343 | ** Process a single "config" card received from the other side of a |
| 304 | 344 | ** sync session. |
| 305 | 345 | ** |
| @@ -345,32 +385,102 @@ | ||
| 345 | 385 | ** ahead of time using configure_prepare_to_receive(). Then after multiple |
| 346 | 386 | ** calls to this routine, configure_finalize_receive() to transfer the |
| 347 | 387 | ** information received into the true target table. |
| 348 | 388 | */ |
| 349 | 389 | 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); | |
| 367 | 457 | }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 | + } | |
| 372 | 482 | } |
| 373 | 483 | } |
| 374 | 484 | |
| 375 | 485 | |
| 376 | 486 | /* |
| 377 | 487 |
| --- 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 |