Fossil

Check-in [98cc8782]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Improve quoting of match tags and patterns. Ignore empty match tags and patterns.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | andygoth-timeline-ms
Files: files | file ages | folders
SHA1:98cc8782e11f94d1dbd19958a2d068c547e94f65
User & Date: andygoth 2016-11-04 21:12:27
References
2016-11-05
03:42
Rework [98cc8782e1] to more thoroughly ignore empty match tags and patterns so that the "Related" submenu item does not appear due to an empty tag filter text entry adding "t=" to the query string check-in: 3bcdc077 user: andygoth tags: andygoth-timeline-ms
Context
2016-11-04
21:53
Use count(x) instead of sizeof(x)/sizeof(*x) check-in: cda8c14c user: andygoth tags: andygoth-timeline-ms
21:12
Improve quoting of match tags and patterns. Ignore empty match tags and patterns. check-in: 98cc8782 user: andygoth tags: andygoth-timeline-ms
20:49
Integrate andygoth-quote-apostrophe. Needed because single quotes can be used in the tag filter entry, and these single quotes would otherwise be passed through unprotected to the output HTML. check-in: 68bd2e7b user: andygoth tags: andygoth-timeline-ms
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/timeline.c.

1225
1226
1227
1228
1229
1230
1231




























1232
1233
1234
1235
1236
1237
1238
....
1239
1240
1241
1242
1243
1244
1245





1246
1247
1248
1249
1250

1251
1252

1253
1254
1255
1256
1257



1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275

1276
1277
1278
1279
1280
1281

1282
1283
1284
1285
1286
1287

1288
1289
1290
1291
1292

1293
1294
1295
1296
1297
1298
1299
....
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329














1330
1331
1332
1333
1334
1335
1336
1337







1338
1339

1340
1341
1342
1343
1344
1345
1346
1347
1348
....
1402
1403
1404
1405
1406
1407
1408

1409
1410
1411
1412
1413
1414
1415
1416
1417
....
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
....
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
....
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
*/
typedef enum {
  MS_EXACT,   /* Matches a single tag by exact string comparison. */
  MS_GLOB,    /* Matches tags against a list of GLOB patterns. */
  MS_LIKE,    /* Matches tags against a list of LIKE patterns. */
  MS_REGEXP   /* Matches tags against a list of regular expressions. */
} MatchStyle;





























/*
** Construct the tag match SQL expression.
**
** This function is adapted from glob_expr() to support the MS_EXACT, MS_GLOB,
** MS_LIKE, and MS_REGEXP match styles.  For MS_EXACT, the returned expression
** checks for integer match against the tag ID which is looked up directly by
................................................................................
** this function.  For the other modes, the returned SQL expression performs
** string comparisons against the tag names, so it is necessary to join against
** the tag table to access the "tagname" column.
**
** Each pattern is adjusted to to start with "sym-" and be anchored at end.
**
** In MS_REGEXP mode, backslash can be used to protect delimiter characters.





*/
static const char *tagMatchExpression(
  MatchStyle matchStyle,  /* Match style code */
  const char *zTag,       /* Tag name, match pattern, or list of patterns */
  int *pCount             /* Pointer to match pattern count variable */

){
  Blob blob = BLOB_INITIALIZER;  /* SQL expression string assembly buffer */

  const char *zStart;            /* Text at start of expression */
  const char *zDelimiter;        /* Text between expression terms */
  const char *zEnd;              /* Text at end of expression */
  const char *zPrefix;           /* Text before each match pattern */
  const char *zSuffix;           /* Text after each match pattern */



  char cDel;                     /* Input delimiter character */
  int i;                         /* Input match pattern length counter */

  /* Optimize exact matches by looking up the ID in advance to create a simple
   * numeric comparison.  Bypass the remainder of this function. */
  if( matchStyle==MS_EXACT ){
    *pCount = 1;
    return mprintf("(tagid=%d)", db_int(-1,
        "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag));
  }

  /* Decide pattern prefix and suffix strings according to match style. */
  if( matchStyle==MS_GLOB ){
    zStart = "(";
    zDelimiter = " OR ";
    zEnd = ")";
    zPrefix = "tagname GLOB 'sym-";
    zSuffix = "'";

  }else if( matchStyle==MS_LIKE ){
    zStart = "(";
    zDelimiter = " OR ";
    zEnd = ")";
    zPrefix = "tagname LIKE 'sym-";
    zSuffix = "'";

  }else/* if( matchStyle==MS_REGEXP )*/{
    zStart = "(tagname REGEXP '^sym-(";
    zDelimiter = "|";
    zEnd = ")$')";
    zPrefix = "";
    zSuffix = "";

  }

  /* Convert the list of matches into an SQL expression. */
  *pCount = 0;
  blob_zero(&blob);

  while( 1 ){
    /* Skip leading delimiters. */
    for( ; fossil_isspace(*zTag) || *zTag==','; ++zTag );

    /* Next non-delimiter character determines quoting. */
    if( !*zTag ){
      /* Terminate loop at end of string. */
................................................................................
      if( matchStyle==MS_REGEXP && zTag[i]=='\\' && zTag[i+1] ){
        ++i;
      }
    }

    /* Incorporate the match word into the output expression.  The %q format is
     * used to protect against SQL injection attacks by replacing ' with ''. */
    blob_appendf(&blob, "%s%s%#q%s", *pCount ? zDelimiter : zStart,
        zPrefix, i, zTag, zSuffix);

    /* Keep track of the number of match expressions. */
    ++*pCount;















    /* Advance past all consumed input characters. */
    zTag += i;
    if( cDel!=',' && *zTag==cDel ){
      ++zTag;
    }
  }








  /* Finalize and extract the SQL expression. */
  if( *pCount ){

    blob_append(&blob, zEnd, -1);
    return blob_str(&blob);
  }

  /* If execution reaches this point, the pattern was empty.  Return NULL. */
  return 0;
}

/*
................................................................................
  const char *zBefore = P("b");      /* Events before this time */
  const char *zCirca = P("c");       /* Events near this time */
  const char *zMark = P("m");        /* Mark this event or an event this time */
  const char *zTagName = P("t");     /* Show events with this tag */
  const char *zBrName = P("r");      /* Show events related to this tag */
  const char *zMatchStyle = P("ms"); /* Tag/branch match style string */
  MatchStyle matchStyle = MS_EXACT;  /* Match style code */

  const char *zTagSql = 0;           /* Tag/branch match SQL expression */
  int tagMatchCount = 0;             /* Number of tag match patterns */
  const char *zSearch = P("s");      /* Search string */
  const char *zUses = P("uf");       /* Only show check-ins hold this file */
  const char *zYearMonth = P("ym");  /* Show check-ins for the given YYYY-MM */
  const char *zYearWeek = P("yw");   /* Check-ins for YYYY-WW (week-of-year) */
  const char *zDay = P("ymd");       /* Check-ins for the day YYYY-MM-DD */
  const char *zChng = P("chng");     /* List of GLOBs for files that changed */
  int useDividers = P("nd")==0;      /* Show dividers if "nd" is missing */
................................................................................
    login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
    return;
  }
  url_initialize(&url, "timeline");
  cgi_query_parameters_to_url(&url);

  /* Identify the tag or branch name or match pattern. */
  if( zTagName ){
    zThisTag = zTagName;
  }else if( zBrName ){
    zThisTag = zBrName;
  }

  /* Interpet the tag style string. */
  if( zThisTag ){
    if( fossil_stricmp(zMatchStyle, "glob")==0 ){
      matchStyle = MS_GLOB;
................................................................................
    }else if( fossil_stricmp(zMatchStyle, "regexp")==0 ){
      matchStyle = MS_REGEXP;
    }
  }

  /* Construct the tag match expression. */
  if( zThisTag ){
    zTagSql = tagMatchExpression(matchStyle, zThisTag, &tagMatchCount);
  }

  if( zTagName && g.perm.Read ){
    style_submenu_element("Related", "Related", "%s",
                          url_render(&url, "r", zTagName, "t", 0));
  }else if( zBrName && g.perm.Read ){
    style_submenu_element("Branch Only", "only", "%s",
................................................................................
    if( zUser ){
      blob_appendf(&desc, " by user %h", zUser);
      tmFlags |= TIMELINE_DISJOINT;
    }
    if( zThisTag ){
      if( matchStyle!=MS_EXACT ){
        if( zTagName ){
          blob_append(&desc, " with tags matching ", -1);
        }else{
          blob_append(&desc, " related to tags matching ", -1);
        }
        if( matchStyle==MS_GLOB ){
          blob_append(&desc, " glob pattern", -1);
        }else if( matchStyle==MS_LIKE ){
          blob_append(&desc, " SQL LIKE pattern", -1);
        }else/* if( matchStyle==MS_REGEXP )*/{
          blob_append(&desc, " regular expression", -1);
        }
        if( tagMatchCount!=1 ){
          blob_append(&desc, "s", 1);
        }
        blob_appendf(&desc, " (%h)", zThisTag);
      }else if( zTagName ){
        blob_appendf(&desc, " tagged with \"%h\"", zTagName);
      }else{
        blob_appendf(&desc, " related to \"%h\"", zBrName);
      }
      tmFlags |= TIMELINE_DISJOINT;
    }
    addFileGlobDescription(zChng, &desc);
    if( rAfter>0.0 ){
      if( rBefore>0.0 ){
        blob_appendf(&desc, " occurring between %h and %h.<br />",







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>


|
|
<
>

|
>
|
|
|
|
|
>
>
>
|
|




|











>






>






>


|
<
|
>







 







|


|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>








>
>
>
>
>
>
>

<
>
|
|







 







>

<







 







|

|







 







|







 







|

|

<
<
<
<
<
<
<
<
<
<
<

|

|







1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
....
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282

1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330

1331
1332
1333
1334
1335
1336
1337
1338
1339
....
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399

1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
....
1463
1464
1465
1466
1467
1468
1469
1470
1471

1472
1473
1474
1475
1476
1477
1478
....
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
....
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
....
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984











1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
*/
typedef enum {
  MS_EXACT,   /* Matches a single tag by exact string comparison. */
  MS_GLOB,    /* Matches tags against a list of GLOB patterns. */
  MS_LIKE,    /* Matches tags against a list of LIKE patterns. */
  MS_REGEXP   /* Matches tags against a list of regular expressions. */
} MatchStyle;

/*
** Quote a tag string by surrounding it with double quotes and preceding
** internal double quotes and backslashes with backslashes.
*/
static const char *tagQuote(
   int len,         /* Maximum length of zTag, or negative for unlimited */
   const char *zTag /* Tag string */
){
  Blob blob = BLOB_INITIALIZER;
  int i, j;
  blob_zero(&blob);
  blob_append(&blob, "\"", 1);
  for( i=j=0; zTag[j] && (len<0 || j<len); ++j ){
    if( zTag[j]=='\"' || zTag[j]=='\\' ){
      if( j>i ){
        blob_append(&blob, zTag+i, j-i);
      }
      blob_append(&blob, "\\", 1);
      i = j;
    }
  }
  if( j>i ){
    blob_append(&blob, zTag+i, j-i);
  }
  blob_append(&blob, "\"", 1);
  return blob_str(&blob);
}

/*
** Construct the tag match SQL expression.
**
** This function is adapted from glob_expr() to support the MS_EXACT, MS_GLOB,
** MS_LIKE, and MS_REGEXP match styles.  For MS_EXACT, the returned expression
** checks for integer match against the tag ID which is looked up directly by
................................................................................
** this function.  For the other modes, the returned SQL expression performs
** string comparisons against the tag names, so it is necessary to join against
** the tag table to access the "tagname" column.
**
** Each pattern is adjusted to to start with "sym-" and be anchored at end.
**
** In MS_REGEXP mode, backslash can be used to protect delimiter characters.
** The backslashes are not removed from the regular expression.
**
** In addition to assembling and returning an SQL expression, this function
** makes an English-language description of the patterns being matched, suitable
** for display in the web interface.
*/
static const char *tagMatchExpression(
  MatchStyle matchStyle,        /* Match style code */
  const char *zTag,             /* Tag name, match pattern, or pattern list */

  const char **zDesc            /* Output expression description string */
){
  Blob expr = BLOB_INITIALIZER; /* SQL expression string assembly buffer */
  Blob desc = BLOB_INITIALIZER; /* English description of match patterns */
  const char *zStart;           /* Text at start of expression */
  const char *zDelimiter;       /* Text between expression terms */
  const char *zEnd;             /* Text at end of expression */
  const char *zPrefix;          /* Text before each match pattern */
  const char *zSuffix;          /* Text after each match pattern */
  const char *zIntro;           /* Text introducing pattern description */
  const char *zPattern = 0;     /* Previous quoted pattern */
  const char *zOr = " or ";     /* Text before final quoted pattern */
  char cDel;                    /* Input delimiter character */
  int i;                        /* Input match pattern length counter */

  /* Optimize exact matches by looking up the ID in advance to create a simple
   * numeric comparison.  Bypass the remainder of this function. */
  if( matchStyle==MS_EXACT ){
    *zDesc = tagQuote(-1, zTag);
    return mprintf("(tagid=%d)", db_int(-1,
        "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag));
  }

  /* Decide pattern prefix and suffix strings according to match style. */
  if( matchStyle==MS_GLOB ){
    zStart = "(";
    zDelimiter = " OR ";
    zEnd = ")";
    zPrefix = "tagname GLOB 'sym-";
    zSuffix = "'";
    zIntro = "glob pattern ";
  }else if( matchStyle==MS_LIKE ){
    zStart = "(";
    zDelimiter = " OR ";
    zEnd = ")";
    zPrefix = "tagname LIKE 'sym-";
    zSuffix = "'";
    zIntro = "SQL LIKE pattern ";
  }else/* if( matchStyle==MS_REGEXP )*/{
    zStart = "(tagname REGEXP '^sym-(";
    zDelimiter = "|";
    zEnd = ")$')";
    zPrefix = "";
    zSuffix = "";
    zIntro = "regular expression ";
  }

  /* Convert the list of matches into an SQL expression and text description. */

  blob_zero(&expr);
  blob_zero(&desc);
  while( 1 ){
    /* Skip leading delimiters. */
    for( ; fossil_isspace(*zTag) || *zTag==','; ++zTag );

    /* Next non-delimiter character determines quoting. */
    if( !*zTag ){
      /* Terminate loop at end of string. */
................................................................................
      if( matchStyle==MS_REGEXP && zTag[i]=='\\' && zTag[i+1] ){
        ++i;
      }
    }

    /* Incorporate the match word into the output expression.  The %q format is
     * used to protect against SQL injection attacks by replacing ' with ''. */
    blob_appendf(&expr, "%s%s%#q%s", blob_size(&expr) ? zDelimiter : zStart,
        zPrefix, i, zTag, zSuffix);

    /* Build up the description string. */
    if( !blob_size(&desc) ){
      /* First tag: start with intro followed by first quoted tag. */
      blob_append(&desc, zIntro, -1);
      blob_append(&desc, tagQuote(i, zTag), -1);
    }else{
      if( zPattern ){
        /* Third and subsequent tags: append comma then previous tag. */
        blob_append(&desc, ", ", 2);
        blob_append(&desc, zPattern, -1);
        zOr = ", or ";
      }

      /* Second and subsequent tags: store quoted tag for next iteration. */
      zPattern = tagQuote(i, zTag);
    }

    /* Advance past all consumed input characters. */
    zTag += i;
    if( cDel!=',' && *zTag==cDel ){
      ++zTag;
    }
  }

  /* Finalize and extract the pattern description. */
  if( zPattern ){
    blob_append(&desc, zOr, -1);
    blob_append(&desc, zPattern, -1);
  }
  *zDesc = blob_str(&desc);

  /* Finalize and extract the SQL expression. */

  if( blob_size(&expr) ){
    blob_append(&expr, zEnd, -1);
    return blob_str(&expr);
  }

  /* If execution reaches this point, the pattern was empty.  Return NULL. */
  return 0;
}

/*
................................................................................
  const char *zBefore = P("b");      /* Events before this time */
  const char *zCirca = P("c");       /* Events near this time */
  const char *zMark = P("m");        /* Mark this event or an event this time */
  const char *zTagName = P("t");     /* Show events with this tag */
  const char *zBrName = P("r");      /* Show events related to this tag */
  const char *zMatchStyle = P("ms"); /* Tag/branch match style string */
  MatchStyle matchStyle = MS_EXACT;  /* Match style code */
  const char *zMatchDesc = 0;        /* Tag match expression description text */
  const char *zTagSql = 0;           /* Tag/branch match SQL expression */

  const char *zSearch = P("s");      /* Search string */
  const char *zUses = P("uf");       /* Only show check-ins hold this file */
  const char *zYearMonth = P("ym");  /* Show check-ins for the given YYYY-MM */
  const char *zYearWeek = P("yw");   /* Check-ins for YYYY-WW (week-of-year) */
  const char *zDay = P("ymd");       /* Check-ins for the day YYYY-MM-DD */
  const char *zChng = P("chng");     /* List of GLOBs for files that changed */
  int useDividers = P("nd")==0;      /* Show dividers if "nd" is missing */
................................................................................
    login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
    return;
  }
  url_initialize(&url, "timeline");
  cgi_query_parameters_to_url(&url);

  /* Identify the tag or branch name or match pattern. */
  if( zTagName && *zTagName ){
    zThisTag = zTagName;
  }else if( zBrName && *zBrName ){
    zThisTag = zBrName;
  }

  /* Interpet the tag style string. */
  if( zThisTag ){
    if( fossil_stricmp(zMatchStyle, "glob")==0 ){
      matchStyle = MS_GLOB;
................................................................................
    }else if( fossil_stricmp(zMatchStyle, "regexp")==0 ){
      matchStyle = MS_REGEXP;
    }
  }

  /* Construct the tag match expression. */
  if( zThisTag ){
    zTagSql = tagMatchExpression(matchStyle, zThisTag, &zMatchDesc);
  }

  if( zTagName && g.perm.Read ){
    style_submenu_element("Related", "Related", "%s",
                          url_render(&url, "r", zTagName, "t", 0));
  }else if( zBrName && g.perm.Read ){
    style_submenu_element("Branch Only", "only", "%s",
................................................................................
    if( zUser ){
      blob_appendf(&desc, " by user %h", zUser);
      tmFlags |= TIMELINE_DISJOINT;
    }
    if( zThisTag ){
      if( matchStyle!=MS_EXACT ){
        if( zTagName ){
          blob_appendf(&desc, " with tags matching %h", zMatchDesc);
        }else{
          blob_appendf(&desc, " related to tags matching %h", zMatchDesc);
        }











      }else if( zTagName ){
        blob_appendf(&desc, " tagged with %h", zMatchDesc);
      }else{
        blob_appendf(&desc, " related to %h", zMatchDesc);
      }
      tmFlags |= TIMELINE_DISJOINT;
    }
    addFileGlobDescription(zChng, &desc);
    if( rAfter>0.0 ){
      if( rBefore>0.0 ){
        blob_appendf(&desc, " occurring between %h and %h.<br />",