]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Control-character escaping
authordrh <>
Tue, 21 Oct 2025 13:07:51 +0000 (13:07 +0000)
committerdrh <>
Tue, 21 Oct 2025 13:07:51 +0000 (13:07 +0000)
FossilOrigin-Name: fbb69764559f4333890860f4f3c010efa9cc994a82743766b948e7635fd079e8

ext/misc/resfmt-tester.c
ext/misc/resfmt.c
ext/misc/resfmt.h
manifest
manifest.uuid

index 89c59b4f30364d925b22097ff0f2dab229c01df7..81bcc91b75713756e971af0e7704a758548a2384 100644 (file)
@@ -245,9 +245,9 @@ int main(int argc, char **argv){
     }else
     if( strncmp(zLine, "--eEscape=", 10)==0 ){
       const struct { const char *z; int e; } aEscape[] = {
+         { "ascii",     RESFMT_E_Ascii   },
          { "off",       RESFMT_E_Off     },
-         { "unicode",   RESFMT_E_Unicode },
-         { "unix",      RESFMT_E_Unix    },
+         { "symbol",    RESFMT_E_Symbol  },
       };
       int i;
       for(i=0; i<COUNT(aEscape); i++){
index b2614ce16fd3e3487e4f2025fa26fbd477465123..274a4807d8d050fef46212dba9820a76f422f4dc 100644 (file)
@@ -54,14 +54,104 @@ static void resfmtWrite(sqlite3_resfmt *p){
   }
 }
 
+/*
+** Escape the input string if it is needed and in accordance with
+** eEscape, which is either RESFMT_E_Ascii or RESFMT_E_Symbol.
+**
+** Escaping is needed if the string contains any control characters
+** other than \t, \n, and \r\n
+**
+** If no escaping is needed (the common case) then set *ppOut to NULL
+** and return 0.  If escaping is needed, write the escaped string into
+** memory obtained from sqlite3_malloc64() and make *ppOut point to that
+** memory and return 0.  If an error occurs, return non-zero.
+**
+** The caller is responsible for freeing *ppFree if it is non-NULL in order
+** to reclaim memory.
+*/
+static void resfmtEscape(
+  int eEscape,            /* RESFMT_E_Ascii or RESFMT_E_Symbol */
+  sqlite3_str *pStr,      /* String to be escaped */
+  int iStart              /* Begin escapding on this byte of pStr */
+){
+  sqlite3_int64 i, j;     /* Loop counters */
+  sqlite3_int64 sz;       /* Size of the string prior to escaping */
+  sqlite3_int64 nCtrl = 0;/* Number of control characters to escape */
+  unsigned char *zIn;     /* Text to be escaped */
+  unsigned char c;        /* A single character of the text */
+  unsigned char *zOut;    /* Where to write the results */
+
+  /* Find the text to be escaped */
+  zIn = (unsigned char*)sqlite3_str_value(pStr);
+  if( zIn==0 ) return;
+  zIn += iStart;
+
+  /* Count the control characters */
+  for(i=0; (c = zIn[i])!=0; i++){
+    if( c<=0x1f
+     && c!='\t'
+     && c!='\n'
+     && (c!='\r' || zIn[i+1]!='\n')
+    ){
+      nCtrl++;
+    }
+  }
+  if( nCtrl==0 ) return;  /* Early out if no control characters */
+
+  /* Make space to hold the escapes.  Copy the original text to the end
+  ** of the available space. */
+  sz = sqlite3_str_length(pStr) - iStart;
+  if( eEscape==RESFMT_E_Symbol ) nCtrl *= 2;
+  sqlite3_str_appendchar(pStr, nCtrl, ' ');
+  zOut = (unsigned char*)sqlite3_str_value(pStr);
+  if( zOut==0 ) return;
+  zOut += iStart;
+  zIn = zOut + nCtrl;
+  memmove(zIn,zOut,sz);
+
+  /* Convert the control characters */
+  for(i=j=0; (c = zIn[i])!=0; i++){
+    if( c>0x1f
+     || c=='\t'
+     || c=='\n'
+     || (c=='\r' && zIn[i+1]=='\n')
+    ){
+      continue;
+    }
+    if( i>0 ){
+      memmove(&zOut[j], zIn, i);
+      j += i;
+    }
+    zIn += i+1;
+    i = -1;
+    if( eEscape==RESFMT_E_Symbol ){
+      zOut[j++] = 0xe2;
+      zOut[j++] = 0x90;
+      zOut[j++] = 0x80+c;
+    }else{
+      zOut[j++] = '^';
+      zOut[j++] = 0x40+c;
+    }
+  }
+}
+
 /*
 ** Encode text appropriately and append it to p->pOut.
 */
 static void resfmtEncodeText(sqlite3_resfmt *p, const char *zTxt){
-  if( p->spec.eQuote ){
-    sqlite3_str_appendf(p->pOut, "%Q", zTxt);
-  }else{
-    sqlite3_str_appendall(p->pOut, zTxt);
+  int iStart = sqlite3_str_length(p->pOut);
+  switch( p->spec.eQuote ){
+    case RESFMT_Q_Sql: {
+      sqlite3_str_appendf(p->pOut, "%Q", zTxt);
+      break;
+    }
+    default: {
+      sqlite3_str_appendall(p->pOut, zTxt);
+      break;
+    }
+  }
+  if( p->spec.eEscape!=RESFMT_E_Off ){
+    resfmtEscape(p->spec.eEscape, p->pOut, iStart);
   }
 }
 
@@ -97,26 +187,30 @@ static void resfmtRenderValue(sqlite3_resfmt *p, int iCol){
       break;
     }
     case SQLITE_BLOB: {
-      if( p->spec.eQuote ){
-        int iStart = sqlite3_str_length(p->pOut);
-        int nBlob = sqlite3_column_bytes(p->pStmt,iCol);
-        int i, j;
-        char *zVal;
-        const unsigned char *a = sqlite3_column_blob(p->pStmt,iCol);
-        sqlite3_str_append(p->pOut, "x'", 2);
-        sqlite3_str_appendchar(p->pOut, nBlob, ' ');
-        sqlite3_str_appendchar(p->pOut, nBlob, ' ');
-        sqlite3_str_appendchar(p->pOut, 1, '\'');
-        if( sqlite3_str_errcode(p->pOut) ) return;
-        zVal = sqlite3_str_value(p->pOut);
-        for(i=0, j=iStart+2; i<nBlob; i++, j+=2){
-          unsigned char c = a[i];
-          zVal[j] = "0123456789abcdef"[(c>>4)&0xf];
-          zVal[j+1] = "0123456789abcdef"[(c)&0xf];
+      switch( p->spec.eQuote ){
+        case RESFMT_Q_Sql: {
+          int iStart = sqlite3_str_length(p->pOut);
+          int nBlob = sqlite3_column_bytes(p->pStmt,iCol);
+          int i, j;
+          char *zVal;
+          const unsigned char *a = sqlite3_column_blob(p->pStmt,iCol);
+          sqlite3_str_append(p->pOut, "x'", 2);
+          sqlite3_str_appendchar(p->pOut, nBlob, ' ');
+          sqlite3_str_appendchar(p->pOut, nBlob, ' ');
+          sqlite3_str_appendchar(p->pOut, 1, '\'');
+          if( sqlite3_str_errcode(p->pOut) ) return;
+          zVal = sqlite3_str_value(p->pOut);
+          for(i=0, j=iStart+2; i<nBlob; i++, j+=2){
+            unsigned char c = a[i];
+            zVal[j] = "0123456789abcdef"[(c>>4)&0xf];
+            zVal[j+1] = "0123456789abcdef"[(c)&0xf];
+          }
+          break;
+        }
+        default: {
+          const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol);
+          resfmtEncodeText(p, zTxt);
         }
-      }else{
-        const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol);
-        sqlite3_str_appendall(p->pOut, zTxt);
       }
       break;
     }
index ea4dbab6de087ae2c07b3b1146684da0faa78b5a..1ac93d5e2af14bdfc67069dca3834a0f51bab0f3 100644 (file)
@@ -92,5 +92,5 @@ int sqlite3_resfmt_finish(sqlite3_resfmt*,int*,char**);
 ** Allowed values for sqlite3_resfmt_spec.eEscape
 */
 #define RESFMT_E_Off     0 /* Do not escape control characters */
-#define RESFMT_E_Unix    1 /* Unix-style escapes.  Ex: U+0007 shows ^G */
-#define RESFMT_E_Unicode 2 /* Unicode escapes. Ex: U+0007 shows U+2407 */
+#define RESFMT_E_Ascii   1 /* Unix-style escapes.  Ex: U+0007 shows ^G */
+#define RESFMT_E_Symbol  2 /* Unicode escapes. Ex: U+0007 shows U+2407 */
index 570d8780442f3796672f23d3a18d50e1d201c209..4f9f28a0133d447a7834a24cc60729d5acc0eaf3 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Recognize\sthe\svarious\squoting\sstyles\sand\sescape\smodes\sin\sthe\sresfmt-test\nprogram.
-D 2025-10-21T11:00:04.169
+C Control-character\sescaping
+D 2025-10-21T13:07:51.412
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -389,9 +389,9 @@ F ext/misc/qpvtab.c fc189e127f68f791af90a487f4460ec91539a716daf45a0c357e963fd47c
 F ext/misc/randomjson.c ef835fc64289e76ac4873b85fe12f9463a036168d7683cf2b773e36e6262c4ed
 F ext/misc/regexp.c 548151f3e57506fda678e6a65e85a763f4eece653287e1ad44e167f9485e0c6b
 F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c
-F ext/misc/resfmt-tester.c 90a13ed2d4b61e147c0aa976c5b7c184c5e782ddbcfd6667a11431f26001cfc0
-F ext/misc/resfmt.c b12901db493acb62beb382f4174d66824f1311148b293f7736d74bfcfed0984d
-F ext/misc/resfmt.h da70079e9b543fbdd5bc2fef525b1d257f8e0e25d2550a0258540a377f42f4f3
+F ext/misc/resfmt-tester.c 1cb1dae3f31caa13e4b7ef635eec05a5b1981aaa2202a72ca74cd995ceb5c677
+F ext/misc/resfmt.c cae8cff21efa71d0779999f715023488e96739d690c1f5bff17f947b7f43a10a
+F ext/misc/resfmt.h 54937535c8d7bfda506ec3ecd9c9e6fe247f3621c0645d067809f2eb9d4bb70e
 F ext/misc/resfmt.md 6f6cefd95fa11ce30e4f34ea84052e7a8291dd48b7e666352bd7cf2e22c22ec4
 F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c
 F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946
@@ -2175,8 +2175,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd
 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 9adaf791f88875c5afeba7e7aa72efb59df42c6052898f8d7e2f83aede00a044
-R 41bf090f4cb07015b0636c2cbe7ec8eb
+P 3ee2a8775fcc557a53c36240ddc039024ff52e21d9ad9021d7cf5bf79f8b9ed4
+R 78c35db5d2e47632e3afeaff2a301639
 U drh
-Z 6c096d7b8b0de8560d49dc8950bacfdd
+Z dd3d4f12786ad3019ddd480d2d6b5f14
 # Remove this line to create a well-formed Fossil manifest.
index a2392f9da52723a11173a0231ba1c9c385677290..b62a9bd40291cdde3b4dc9ec23fdf1cd60c23507 100644 (file)
@@ -1 +1 @@
-3ee2a8775fcc557a53c36240ddc039024ff52e21d9ad9021d7cf5bf79f8b9ed4
+fbb69764559f4333890860f4f3c010efa9cc994a82743766b948e7635fd079e8