Add (optional) base64 and base85 UDF extensions.

FossilOrigin-Name: b44ab10c49bc2895483a9d40813be3798710ee713cc4bf04e449dce55a68452a
This commit is contained in:
larrybr
2022-11-28 14:51:50 +00:00
9 changed files with 995 additions and 10 deletions
+1
View File
@@ -446,6 +446,7 @@ TESTSRC += \
$(TOP)/ext/expert/test_expert.c \
$(TOP)/ext/misc/amatch.c \
$(TOP)/ext/misc/appendvfs.c \
$(TOP)/ext/misc/basexx.c \
$(TOP)/ext/misc/carray.c \
$(TOP)/ext/misc/cksumvfs.c \
$(TOP)/ext/misc/closure.c \
+269
View File
@@ -0,0 +1,269 @@
/*
** 2022-11-18
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
**
** This is a SQLite extension for converting in either direction
** between a (binary) blob and base64 text. Base64 can transit a
** sane USASCII channel unmolested. It also plays nicely in CSV or
** written as TCL brace-enclosed literals or SQL string literals,
** and can be used unmodified in XML-like documents.
**
** This is an independent implementation of conversions specified in
** RFC 4648, done on the above date by the author (Larry Brasfield)
** who thereby has the right to put this into the public domain.
**
** The conversions meet RFC 4648 requirements, provided that this
** C source specifies that line-feeds are included in the encoded
** data to limit visible line lengths to 72 characters and to
** terminate any encoded blob having non-zero length.
**
** Length limitations are not imposed except that the runtime
** SQLite string or blob length limits are respected. Otherwise,
** any length binary sequence can be represented and recovered.
** Generated base64 sequences, with their line-feeds included,
** can be concatenated; the result converted back to binary will
** be the concatenation of the represented binary sequences.
**
** This SQLite3 extension creates a function, base64(x), which
** either: converts text x containing base64 to a returned blob;
** or converts a blob x to returned text containing base64. An
** error will be thrown for other input argument types.
**
** This code relies on UTF-8 encoding only with respect to the
** meaning of the first 128 (7-bit) codes matching that of USASCII.
** It will fail miserably if somehow made to try to convert EBCDIC.
** Because it is table-driven, it could be enhanced to handle that,
** but the world and SQLite have moved on from that anachronism.
**
** To build the extension:
** Set shell variable SQDIR=<your favorite SQLite checkout directory>
** *Nix: gcc -O2 -shared -I$SQDIR -fPIC -o base64.so base64.c
** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR -o base64.dylib base64.c
** Win32: gcc -O2 -shared -I%SQDIR% -o base64.dll base64.c
** Win32: cl /Os -I%SQDIR% base64.c -link -dll -out:base64.dll
*/
#include <assert.h>
#ifndef SQLITE_SHELL_EXTFUNCS /* Guard for #include as built-in extension. */
#include "sqlite3ext.h"
#endif
SQLITE_EXTENSION_INIT1;
#define PC 0x80 /* pad character */
#define WS 0x81 /* whitespace */
#define ND 0x82 /* Not above or digit-value */
#define PAD_CHAR '='
#ifndef UBYTE_TYPEDEF
typedef unsigned char ubyte;
# define UBYTE_TYPEDEF
#endif
static const ubyte b64DigitValues[128] = {
/* HT LF VT FF CR */
ND,ND,ND,ND, ND,ND,ND,ND, ND,WS,WS,WS, WS,WS,ND,ND,
/* US */
ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND,
/*sp + / */
WS,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,62, ND,ND,ND,63,
/* 0 1 5 9 = */
52,53,54,55, 56,57,58,59, 60,61,ND,ND, ND,PC,ND,ND,
/* A O */
ND, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
/* P Z */
15,16,17,18, 19,20,21,22, 23,24,25,ND, ND,ND,ND,ND,
/* a o */
ND,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
/* p z */
41,42,43,44, 45,46,47,48, 49,50,51,ND, ND,ND,ND,ND
};
static const char b64Numerals[64+1]
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#define BX_DV_PROTO(c) ((((ubyte)(c))<0x80)? b64DigitValues[c] : 0x80)
#define IS_BX_DIGIT(bdp) (((ubyte)(bdp))<0x80)
#define IS_BX_WS(bdp) ((bdp)==WS)
#define IS_BX_PAD(bdp) ((bdp)==PC)
#define BX_NUMERAL(dv) (b64Numerals[(ubyte)(dv)])
/* Width of base64 lines. Should be an integer multiple of 4. */
#define B64_DARK_MAX 72
/* Encode a byte buffer into base64 text with linefeeds appended to limit
** encoded group lengths to B64_DARK_MAX or to terminate the last group.
*/
static char* toBase64( ubyte *pIn, int nbIn, char *pOut ){
int nCol = 0;
while( nbIn >= 3 ){
/* Do the bit-shuffle, exploiting unsigned input to avoid masking. */
pOut[0] = BX_NUMERAL(pIn[0]>>2);
pOut[1] = BX_NUMERAL(((pIn[0]<<4)|(pIn[1]>>4))&0x3f);
pOut[2] = BX_NUMERAL(((pIn[1]&0xf)<<2)|(pIn[2]>>6));
pOut[3] = BX_NUMERAL(pIn[2]&0x3f);
pOut += 4;
nbIn -= 3;
pIn += 3;
if( (nCol += 4)>=B64_DARK_MAX || nbIn<=0 ){
*pOut++ = '\n';
nCol = 0;
}
}
if( nbIn > 0 ){
signed char nco = nbIn+1;
int nbe;
unsigned long qv = *pIn++;
for( nbe=1; nbe<3; ++nbe ){
qv <<= 8;
if( nbe<nbIn ) qv |= *pIn++;
}
for( nbe=3; nbe>=0; --nbe ){
char ce = (nbe<nco)? BX_NUMERAL((ubyte)(qv & 0x3f)) : PAD_CHAR;
qv >>= 6;
pOut[nbe] = ce;
}
pOut += 4;
*pOut++ = '\n';
}
*pOut = 0;
return pOut;
}
/* Skip over text which is not base64 numeral(s). */
static char * skipNonB64( char *s ){
char c;
while( (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s;
return s;
}
/* Decode base64 text into a byte buffer. */
static ubyte* fromBase64( char *pIn, int ncIn, ubyte *pOut ){
if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn;
while( ncIn>0 && *pIn!=PAD_CHAR ){
static signed char nboi[] = { 0, 0, 1, 2, 3 };
char *pUse = skipNonB64(pIn);
unsigned long qv = 0L;
int nti, nbo, nac;
ncIn -= (pUse - pIn);
pIn = pUse;
nti = (ncIn>4)? 4 : ncIn;
ncIn -= nti;
nbo = nboi[nti];
if( nbo==0 ) break;
for( nac=0; nac<4; ++nac ){
char c = (nac<nti)? *pIn++ : b64Numerals[0];
ubyte bdp = BX_DV_PROTO(c);
switch( bdp ){
case ND:
/* Treat dark non-digits as pad, but they terminate decode too. */
ncIn = 0;
/* fall thru */
case WS:
/* Treat whitespace as pad and terminate this group.*/
nti = nac;
/* fall thru */
case PC:
bdp = 0;
--nbo;
/* fall thru */
default: /* bdp is the digit value. */
qv = qv<<6 | bdp;
break;
}
}
switch( nbo ){
case 3:
pOut[2] = (qv) & 0xff;
case 2:
pOut[1] = (qv>>8) & 0xff;
case 1:
pOut[0] = (qv>>16) & 0xff;
}
pOut += nbo;
}
return pOut;
}
/* This function does the work for the SQLite base64(x) UDF. */
static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){
int nb, nc, nv = sqlite3_value_bytes(av[0]);
int nvMax = sqlite3_limit(sqlite3_context_db_handle(context),
SQLITE_LIMIT_LENGTH, -1);
char *cBuf;
ubyte *bBuf;
assert(na==1);
switch( sqlite3_value_type(av[0]) ){
case SQLITE_BLOB:
nb = nv;
nc = 4*(nv+2/3); /* quads needed */
nc += (nc+(B64_DARK_MAX-1))/B64_DARK_MAX + 1; /* LFs and a 0-terminator */
if( nvMax < nc ){
sqlite3_result_error(context, "blob expanded to base64 too big", -1);
return;
}
cBuf = sqlite3_malloc(nc);
if( !cBuf ) goto memFail;
bBuf = (ubyte*)sqlite3_value_blob(av[0]);
nc = (int)(toBase64(bBuf, nb, cBuf) - cBuf);
sqlite3_result_text(context, cBuf, nc, sqlite3_free);
break;
case SQLITE_TEXT:
nc = nv;
nb = 3*((nv+3)/4); /* may overestimate due to LF and padding */
if( nvMax < nb ){
sqlite3_result_error(context, "blob from base64 may be too big", -1);
return;
}else if( nb<1 ){
nb = 1;
}
bBuf = sqlite3_malloc(nb);
if( !bBuf ) goto memFail;
cBuf = (char *)sqlite3_value_text(av[0]);
nb = (int)(fromBase64(cBuf, nc, bBuf) - bBuf);
sqlite3_result_blob(context, bBuf, nb, sqlite3_free);
break;
default:
sqlite3_result_error(context, "base64 accepts only blob or text", -1);
return;
}
return;
memFail:
sqlite3_result_error(context, "base64 OOM", -1);
}
/*
** Establish linkage to running SQLite library.
*/
#ifndef SQLITE_SHELL_EXTFUNCS
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_base_init
#else
static int sqlite3_base64_init
#endif
(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErr;
return sqlite3_create_function
(db, "base64", 1,
SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8,
0, base64, 0, 0);
}
/*
** Define some macros to allow this extension to be built into the shell
** conveniently, in conjunction with use of SQLITE_SHELL_EXTFUNCS. This
** allows shell.c, as distributed, to have this extension built in.
*/
#define BASE64_INIT(db) sqlite3_base64_init(db, 0, 0)
#define BASE64_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */
+437
View File
@@ -0,0 +1,437 @@
/*
** 2022-11-16
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
**
** This is a utility for converting binary to base85 or vice-versa.
** It can be built as a standalone program or an SQLite3 extension.
**
** Much like base64 representations, base85 can be sent through a
** sane USASCII channel unmolested. It also plays nicely in CSV or
** written as TCL brace-enclosed literals or SQL string literals.
** It is not suited for unmodified use in XML-like documents.
**
** The encoding used resembles Ascii85, but was devised by the author
** (Larry Brasfield) before Mozilla, Adobe, ZMODEM or other Ascii85
** variant sources existed, in the 1984 timeframe on a VAX mainframe.
** Further, this is an independent implementation of a base85 system.
** Hence, the author has rightfully put this into the public domain.
**
** Base85 numerals are taken from the set of 7-bit USASCII codes,
** excluding control characters and Space ! " ' ( ) { | } ~ Del
** in code order representing digit values 0 to 84 (base 10.)
**
** Groups of 4 bytes, interpreted as big-endian 32-bit values,
** are represented as 5-digit base85 numbers with MS to LS digit
** order. Groups of 1-3 bytes are represented with 2-4 digits,
** still big-endian but 8-24 bit values. (Using big-endian yields
** the simplest transition to byte groups smaller than 4 bytes.
** These byte groups can also be considered base-256 numbers.)
** Groups of 0 bytes are represented with 0 digits and vice-versa.
** No pad characters are used; Encoded base85 numeral sequence
** (aka "group") length maps 1-to-1 to the decoded binary length.
**
** Any character not in the base85 numeral set delimits groups.
** When base85 is streamed or stored in containers of indefinite
** size, newline is used to separate it into sub-sequences of no
** more than 80 digits so that fgets() can be used to read it.
**
** Length limitations are not imposed except that the runtime
** SQLite string or blob length limits are respected. Otherwise,
** any length binary sequence can be represented and recovered.
** Base85 sequences can be concatenated by separating them with
** a non-base85 character; the conversion to binary will then
** be the concatenation of the represented binary sequences.
** The standalone program either converts base85 on stdin to create
** a binary file or converts a binary file to base85 on stdout.
** Read or make it blurt its help for invocation details.
**
** The SQLite3 extension creates a function, base85(x), which will
** either convert text base85 to a blob or a blob to text base85
** and return the result (or throw an error for other types.)
** Unless built with OMIT_BASE85_CHECKER defined, it also creates a
** function, is_base85(t), which returns 1 iff the text t contains
** nothing other than base85 numerals and whitespace, or 0 otherwise.
**
** To build the extension:
** Set shell variable SQDIR=<your favorite SQLite checkout directory>
** and variable OPTS to -DOMIT_BASE85_CHECKER if is_base85() unwanted.
** *Nix: gcc -O2 -shared -I$SQDIR $OPTS -fPIC -o base85.so base85.c
** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR $OPTS -o base85.dylib base85.c
** Win32: gcc -O2 -shared -I%SQDIR% %OPTS% -o base85.dll base85.c
** Win32: cl /Os -I%SQDIR% %OPTS% base85.c -link -dll -out:base85.dll
**
** To build the standalone program, define PP symbol BASE85_STANDALONE. Eg.
** *Nix or OSX: gcc -O2 -DBASE85_STANDALONE base85.c -o base85
** Win32: gcc -O2 -DBASE85_STANDALONE -o base85.exe base85.c
** Win32: cl /Os /MD -DBASE85_STANDALONE base85.c
*/
#include <stdio.h>
#include <memory.h>
#include <string.h>
#include <assert.h>
#ifndef OMIT_BASE85_CHECKER
# include <ctype.h>
#endif
#ifndef BASE85_STANDALONE
#ifndef SQLITE_SHELL_EXTFUNCS /* Guard for #include as built-in extension. */
# include "sqlite3ext.h"
#endif
SQLITE_EXTENSION_INIT1;
#else
# ifdef _WIN32
# include <io.h>
# include <fcntl.h>
# else
# define setmode(fd,m)
# endif
static char *zHelp =
"Usage: base85 <dirFlag> <binFile>\n"
" <dirFlag> is either -r to read or -w to write <binFile>,\n"
" content to be converted to/from base85 on stdout/stdin.\n"
" <binFile> names a binary file to be rendered or created.\n"
" Or, the name '-' refers to the stdin or stdout stream.\n"
;
static void sayHelp(){
printf("%s", zHelp);
}
#endif
#ifndef UBYTE_TYPEDEF
typedef unsigned char ubyte;
# define UBYTE_TYPEDEF
#endif
/* Classify c according to interval within USASCII set w.r.t. base85
* Values of 1 and 3 are base85 numerals. Values of 0, 2, or 4 are not.
*/
#define B85_CLASS( c ) (((c)>='#')+((c)>'&')+((c)>='*')+((c)>'z'))
/* Provide digitValue to b85Numeral offset as a function of above class. */
static ubyte b85_cOffset[] = { 0, '#', 0, '*'-4, 0 };
#define B85_DNOS( c ) b85_cOffset[B85_CLASS(c)]
/* Say whether c is a base85 numeral. */
#define IS_B85( c ) (B85_CLASS(c) & 1)
#if 0 /* Not used, */
static ubyte base85DigitValue( char c ){
ubyte dv = (ubyte)(c - '#');
if( dv>87 ) return 0xff;
return (dv > 3)? dv-3 : dv;
}
#endif
/* Width of base64 lines. Should be an integer multiple of 5. */
#define B85_DARK_MAX 80
static char * skipNonB85( char *s ){
char c;
while( (c = *s) && !IS_B85(c) ) ++s;
return s;
}
/* Convert small integer, known to be in 0..84 inclusive, to base85 numeral.
* Do not use the macro form with argument expression having a side-effect.*/
#if 0
static char base85Numeral( ubyte b ){
return (b < 4)? (char)(b + '#') : (char)(b - 4 + '*');
}
#else
# define base85Numeral( dn )\
((char)(((dn) < 4)? (char)((dn) + '#') : (char)((dn) - 4 + '*')))
#endif
static char *putcs(char *pc, char *s){
char c;
while( (c = *s++)!=0 ) *pc++ = c;
return pc;
}
/* Encode a byte buffer into base85 text. If pSep!=0, it's a C string
** to be appended to encoded groups to limit their length to B85_DARK_MAX
** or to terminate the last group (to aid concatenation.)
*/
static char* toBase85( ubyte *pIn, int nbIn, char *pOut, char *pSep ){
int nCol = 0;
while( nbIn >= 4 ){
int nco = 5;
unsigned long qbv = (pIn[0]<<24)|(pIn[1]<<16)|(pIn[2]<<8)|pIn[3];
while( nco > 0 ){
unsigned nqv = (unsigned)(qbv/85UL);
unsigned char dv = qbv - 85UL*nqv;
qbv = nqv;
pOut[--nco] = base85Numeral(dv);
}
nbIn -= 4;
pIn += 4;
pOut += 5;
if( pSep && (nCol += 5)>=B85_DARK_MAX ){
pOut = putcs(pOut, pSep);
nCol = 0;
}
}
if( nbIn > 0 ){
int nco = nbIn + 1;
unsigned long qv = *pIn++;
int nbe = 1;
while( nbe++ < nbIn ){
qv = (qv<<8) | *pIn++;
}
nCol += nco;
while( nco > 0 ){
ubyte dv = (ubyte)(qv % 85);
qv /= 85;
pOut[--nco] = base85Numeral(dv);
}
pOut += (nbIn+1);
}
if( pSep && nCol>0 ) pOut = putcs(pOut, pSep);
*pOut = 0;
return pOut;
}
/* Decode base85 text into a byte buffer. */
static ubyte* fromBase85( char *pIn, int ncIn, ubyte *pOut ){
if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn;
while( ncIn>0 ){
static signed char nboi[] = { 0, 0, 1, 2, 3, 4 };
char *pUse = skipNonB85(pIn);
unsigned long qv = 0L;
int nti, nbo;
ncIn -= (pUse - pIn);
pIn = pUse;
nti = (ncIn>5)? 5 : ncIn;
nbo = nboi[nti];
if( nbo==0 ) break;
while( nti>0 ){
char c = *pIn++;
ubyte cdo = B85_DNOS(c);
--ncIn;
if( cdo==0 ) break;
qv = 85 * qv + (c - cdo);
--nti;
}
nbo -= nti; /* Adjust for early (non-digit) end of group. */
switch( nbo ){
case 4:
*pOut++ = (qv >> 24)&0xff;
case 3:
*pOut++ = (qv >> 16)&0xff;
case 2:
*pOut++ = (qv >> 8)&0xff;
case 1:
*pOut++ = qv&0xff;
case 0:
break;
}
}
return pOut;
}
#ifndef OMIT_BASE85_CHECKER
/* Say whether input char sequence is all (base85 and/or whitespace).*/
static int allBase85( char *p, int len ){
char c;
while( len-- > 0 && (c = *p++) != 0 ){
if( !IS_B85(c) && !isspace(c) ) return 0;
}
return 1;
}
#endif
#ifndef BASE85_STANDALONE
# ifndef OMIT_BASE85_CHECKER
/* This function does the work for the SQLite is_base85(t) UDF. */
static void is_base85(sqlite3_context *context, int na, sqlite3_value *av[]){
assert(na==1);
switch( sqlite3_value_type(av[0]) ){
case SQLITE_TEXT:
{
int rv = allBase85( (char *)sqlite3_value_text(av[0]),
sqlite3_value_bytes(av[0]) );
sqlite3_result_int(context, rv);
}
break;
case SQLITE_NULL:
sqlite3_result_null(context);
break;
default:
sqlite3_result_error(context, "is_base85 accepts only text or NULL", -1);
return;
}
}
# endif
/* This function does the work for the SQLite base85(x) UDF. */
static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){
int nb, nc, nv = sqlite3_value_bytes(av[0]);
int nvMax = sqlite3_limit(sqlite3_context_db_handle(context),
SQLITE_LIMIT_LENGTH, -1);
char *cBuf;
ubyte *bBuf;
assert(na==1);
switch( sqlite3_value_type(av[0]) ){
case SQLITE_BLOB:
nb = nv;
/* ulongs tail newlines tailenc+nul*/
nc = 5*(nv/4) + nv%4 + nv/64+1 + 2;
if( nvMax < nc ){
sqlite3_result_error(context, "blob expanded to base85 too big", -1);
return;
}
cBuf = sqlite3_malloc(nc);
if( !cBuf ) goto memFail;
bBuf = (ubyte*)sqlite3_value_blob(av[0]);
nc = (int)(toBase85(bBuf, nb, cBuf, "\n") - cBuf);
sqlite3_result_text(context, cBuf, nc, sqlite3_free);
break;
case SQLITE_TEXT:
nc = nv;
nb = 4*(nv/5) + nv%5; /* may overestimate */
if( nvMax < nb ){
sqlite3_result_error(context, "blob from base85 may be too big", -1);
return;
}else if( nb<1 ){
nb = 1;
}
bBuf = sqlite3_malloc(nb);
if( !bBuf ) goto memFail;
cBuf = (char *)sqlite3_value_text(av[0]);
nb = (int)(fromBase85(cBuf, nc, bBuf) - bBuf);
sqlite3_result_blob(context, bBuf, nb, sqlite3_free);
break;
default:
sqlite3_result_error(context, "base85 accepts only blob or text.", -1);
return;
}
return;
memFail:
sqlite3_result_error(context, "base85 OOM", -1);
}
/*
** Establish linkage to running SQLite library.
*/
#ifndef SQLITE_SHELL_EXTFUNCS
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_base_init
#else
static int sqlite3_base85_init
#endif
(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErr;
# ifndef OMIT_BASE85_CHECKER
{
int rc = sqlite3_create_function
(db, "is_base85", 1,
SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_UTF8,
0, is_base85, 0, 0);
if( rc!=SQLITE_OK ) return rc;
}
# endif
return sqlite3_create_function
(db, "base85", 1,
SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8,
0, base85, 0, 0);
}
/*
** Define some macros to allow this extension to be built into the shell
** conveniently, in conjunction with use of SQLITE_SHELL_EXTFUNCS. This
** allows shell.c, as distributed, to have this extension built in.
*/
# define BASE85_INIT(db) sqlite3_base85_init(db, 0, 0)
# define BASE85_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */
#else /* standalone program */
int main(int na, char *av[]){
int cin;
int rc = 0;
ubyte bBuf[4*(B85_DARK_MAX/5)];
char cBuf[5*(sizeof(bBuf)/4)+2];
size_t nio;
# ifndef OMIT_BASE85_CHECKER
int b85Clean = 1;
# endif
char rw;
FILE *fb = 0, *foc = 0;
char fmode[3] = "xb";
if( na < 3 || av[1][0]!='-' || (rw = av[1][1])==0 || (rw!='r' && rw!='w') ){
sayHelp();
return 0;
}
fmode[0] = rw;
if( av[2][0]=='-' && av[2][1]==0 ){
switch( rw ){
case 'r':
fb = stdin;
setmode(fileno(stdin), O_BINARY);
break;
case 'w':
fb = stdout;
setmode(fileno(stdout), O_BINARY);
break;
}
}else{
fb = fopen(av[2], fmode);
foc = fb;
}
if( !fb ){
fprintf(stderr, "Cannot open %s for %c\n", av[2], rw);
rc = 1;
}else{
switch( rw ){
case 'r':
while( (nio = fread( bBuf, 1, sizeof(bBuf), fb))>0 ){
toBase85( bBuf, (int)nio, cBuf, 0 );
fprintf(stdout, "%s\n", cBuf);
}
break;
case 'w':
while( 0 != fgets(cBuf, sizeof(cBuf), stdin) ){
int nc = strlen(cBuf);
size_t nbo = fromBase85( cBuf, nc, bBuf ) - bBuf;
if( 1 != fwrite(bBuf, nbo, 1, fb) ) rc = 1;
# ifndef OMIT_BASE85_CHECKER
b85Clean &= allBase85( cBuf, nc );
# endif
}
break;
default:
sayHelp();
rc = 1;
}
if( foc ) fclose(foc);
}
# ifndef OMIT_BASE85_CHECKER
if( !b85Clean ){
fprintf(stderr, "Base85 input had non-base85 dark or control content.\n");
}
# endif
return rc;
}
#endif
+84
View File
@@ -0,0 +1,84 @@
/*
** 2022-11-20
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
**
** This source allows multiple SQLite extensions to be either: combined
** into a single runtime-loadable library; or built into the SQLite shell
** using a preprocessing convention set by src/shell.c.in (and shell.c).
**
** Presently, it combines the base64.c and base85.c extensions. However,
** it can be used as a template for other combinations.
**
** Example usages:
**
** - Build a runtime-loadable extension from SQLite checkout directory:
** *Nix, OSX: gcc -O2 -shared -I. -fPIC -o basexx.so ext/misc/basexx.c
** Win32: cl /Os -I. ext/misc/basexx.c -link -dll -out:basexx.dll
**
** - Incorporate as built-in in sqlite3 shell:
** *Nix, OSX with gcc on a like platform:
** export mop1=-DSQLITE_SHELL_EXTSRC=ext/misc/basexx.c
** export mop2=-DSQLITE_SHELL_EXTFUNCS=BASEXX
** make sqlite3 "OPTS=$mop1 $mop2"
** Win32 with Microsoft toolset on Windows:
** set mop1=-DSQLITE_SHELL_EXTSRC=ext/misc/basexx.c
** set mop2=-DSQLITE_SHELL_EXTFUNCS=BASEXX
** set mops="OPTS=%mop1% %mop2%"
** nmake -f Makefile.msc sqlite3.exe %mops%
*/
#ifndef SQLITE_SHELL_EXTFUNCS /* Guard for #include as built-in extension. */
# include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1;
#endif
static void init_api_ptr(const sqlite3_api_routines *pApi){
SQLITE_EXTENSION_INIT2(pApi);
}
#undef SQLITE_EXTENSION_INIT1
#define SQLITE_EXTENSION_INIT1 /* */
#undef SQLITE_EXTENSION_INIT2
#define SQLITE_EXTENSION_INIT2(v) (void)v
/* These next 2 undef's are only needed because the entry point names
* collide when formulated per the rules stated for loadable extension
* entry point names that will be deduced from the file basenames.
*/
#undef sqlite3_base_init
#define sqlite3_base_init sqlite3_base64_init
#include "base64.c"
#undef sqlite3_base_init
#define sqlite3_base_init sqlite3_base85_init
#include "base85.c"
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_basexx_init(sqlite3 *db, char **pzErr,
const sqlite3_api_routines *pApi){
init_api_ptr(pApi);
int rc1 = BASE64_INIT(db);
int rc2 = BASE85_INIT(db);
int rc = SQLITE_OK;
if( rc1==SQLITE_OK && rc2==SQLITE_OK ){
BASE64_EXPOSE(db, pzErr);
BASE64_EXPOSE(db, pzErr);
return SQLITE_OK;
}else{
return SQLITE_ERROR;
}
}
# define BASEXX_INIT(db) sqlite3_basexx_init(db, 0, 0)
# define BASEXX_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */
+13 -9
View File
@@ -1,9 +1,9 @@
C Ensure\sthe\sRETURNING\sclause\sis\shonoured\swhen\sa\srow\sof\sa\stemp\stable\sis\supdated\sby\san\sON\sCONFLICT\sclause.
D 2022-11-28T13:47:27.194
C Add\s(optional)\sbase64\sand\sbase85\sUDF\sextensions.
D 2022-11-28T14:51:50.439
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
F Makefile.in 9a1228fd6406cc607958ccf1386a9a8f74f8255a7e98bc9c0a2792b56bd3a309
F Makefile.in c223963d7b0828f26cb62ea3e0f583d26839b7d3ef0d1cca87f35c4b222ff01b
F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
F Makefile.msc e7a564ceec71f0d9666031d5638cf0d4f88b050b44e8df5d32125137cd259ac0
F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e
@@ -289,6 +289,9 @@ F ext/misc/README.md d6dd0fe1d8af77040216798a6a2b0c46c73054d2f0ea544fbbcdccf6f23
F ext/misc/amatch.c e3ad5532799cee9a97647f483f67f43b38796b84b5a8c60594fe782a4338f358
F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a22525eddb
F ext/misc/appendvfs.c 9642c7a194a2a25dca7ad3e36af24a0a46d7702168c4ad7e59c9f9b0e16a3824
F ext/misc/base64.c fb140039d4e15710298e28cafe12d2bdff1b927fc57540cbcd07996f82bf9800 x
F ext/misc/base85.c ace591246855806a70e63eb6705ce8736f4b782da2137bdbb5d39cc337346e0d
F ext/misc/basexx.c b37ea21f00deea1029d2ddc670f5701d9370ae06f7d173c5bed79d0f26a67ec1
F ext/misc/blobio.c a867c4c4617f6ec223a307ebfe0eabb45e0992f74dd47722b96f3e631c0edb2a
F ext/misc/btreeinfo.c d28ce349b40054eaa9473e835837bad7a71deec33ba13e39f963d50933bfa0f9
F ext/misc/carray.c b752f46411e4e47e34dce6f0c88bc8e51bb821ba9e49bfcd882506451c928f69
@@ -642,7 +645,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
F src/resolve.c efea4e5fbecfd6d0a9071b0be0d952620991673391b6ffaaf4c277b0bb674633
F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
F src/select.c c1eb8f3ee25152327f2e7e87db8cea549e57c104b63638bff4fc584d479c33f0
F src/shell.c.in 334b1661abb6453e4f5d1778ce9c80dddef83470867f1fba07e5ee400792adbe
F src/shell.c.in 9fda74d40b206a707aaa69fc5dc38e2c6a9137a3f4a1dcd7af581d59d92c063c
F src/sqlite.h.in 100fc660c2f19961b8ed8437b9d53d687de2f8eb2b96437ec6da216adcb643ca
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h c4b9fa7a7e2bcdf850cfeb4b8a91d5ec47b7a00033bc996fd2ee96cbf2741f5f
@@ -651,7 +654,7 @@ F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a3
F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
F src/tclsqlite.c 4e64ba300a5a26e0f1170e09032429faeb65e45e8f3d1a7833e8edb69fc2979e
F src/test1.c 40c9a40975512985762f87b83d0c63e4904833a9fe78cbcca664a37095301b1d
F src/test1.c 09ad3f2bf5f0efe70eb16e460054954b36be8ed09dfcafa0642b37d25d379f6f
F src/test2.c 827446e259a3b7ab949da1542953edda7b5117982576d3e6f1c24a0dd20a5cef
F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644
F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664
@@ -813,6 +816,7 @@ F test/backup_ioerr.test 4c3c7147cee85b024ecf6e150e090c32fdbb5135
F test/backup_malloc.test 0c9abdf74c51e7bedb66d504cd684f28d4bd4027
F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f
F test/badutf2.test f310fd3b24a491b6b77bccdf14923b85d6ebcce751068c180d93a6b8ff854399
F test/basexx1.test d8a50f0744b93dca656625597bcd3499ff4b9a4ea2a82432b119b7d46e3e0c08
F test/bc_common.tcl b5e42d80305be95697e6370e015af571e5333a1c
F test/bestindex1.test 856a453dff8c68b4568601eed5a8b5e20b4763af9229f3947c215729ed878db0
F test/bestindex2.test 394ff8fbf34703391247116d6a44e1c50ee7282236ee77909044573cefc37bc0
@@ -2060,8 +2064,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 31546ea320e8daa020f8a9f491718ffc3dde0e32954018afed88e8494a6aff6c
R cab683454f2ed9db2c50c1dc1f9667b9
U dan
Z f330cc4347042764fbf120efeed28ded
P a2449bcc2c71d0f4c3289621fbf1cb97f0f407c9f7b5bf18245b7854a07c6cfa b8345630a2a322234bda49ee4b996f6ba20e2b080621e229a2ec5e820892a663
R b25d12f698cc565856f0ef09be44e05e
U larrybr
Z dca654d0507bf9deef15f6de6a6be007
# Remove this line to create a well-formed Fossil manifest.
+1 -1
View File
@@ -1 +1 @@
a2449bcc2c71d0f4c3289621fbf1cb97f0f407c9f7b5bf18245b7854a07c6cfa
b44ab10c49bc2895483a9d40813be3798710ee713cc4bf04e449dce55a68452a
+33
View File
@@ -1072,6 +1072,9 @@ INCLUDE ../ext/recover/dbdata.c
INCLUDE ../ext/recover/sqlite3recover.c
# endif
#endif
#ifdef SQLITE_SHELL_EXTSRC
# include SHELL_STRINGIFY(SQLITE_SHELL_EXTSRC)
#endif
#if defined(SQLITE_ENABLE_SESSION)
/*
@@ -5117,6 +5120,7 @@ static void open_db(ShellState *p, int openFlags){
}
exit(1);
}
#ifndef SQLITE_OMIT_LOAD_EXTENSION
sqlite3_enable_load_extension(p->db, 1);
#endif
@@ -5139,6 +5143,34 @@ static void open_db(ShellState *p, int openFlags){
sqlite3_sqlar_init(p->db, 0, 0);
}
#endif
#ifdef SQLITE_SHELL_EXTFUNCS
/* Create a preprocessing mechanism for extensions to make
* their own provisions for being built into the shell.
* This is a short-span macro. See further below for usage.
*/
#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant
#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant)
/* Let custom-included extensions get their ..._init() called.
* The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause
* the extension's sqlite3_*_init( db, pzErrorMsg, pApi )
* inititialization routine to be called.
*/
{
int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db);
/* Let custom-included extensions expose their functionality.
* The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause
* the SQL functions, virtual tables, collating sequences or
* VFS's implemented by the extension to be registered.
*/
if( irc==SQLITE_OK
|| irc==SQLITE_OK_LOAD_PERMANENTLY ){
SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0);
}
#undef SHELL_SUB_MACRO
#undef SHELL_SUBMACRO
}
#endif
sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0,
shellAddSchemaName, 0, 0);
sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0,
@@ -5159,6 +5191,7 @@ static void open_db(ShellState *p, int openFlags){
sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0,
editFunc, 0, 0);
#endif
if( p->openMode==SHELL_OPEN_ZIPFILE ){
char *zSql = sqlite3_mprintf(
"CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename);
+2
View File
@@ -7709,6 +7709,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd(
){
extern int sqlite3_amatch_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_appendvfs_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_basexx_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_carray_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_closure_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_csv_init(sqlite3*,char**,const sqlite3_api_routines*);
@@ -7740,6 +7741,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd(
} aExtension[] = {
{ "amatch", sqlite3_amatch_init },
{ "appendvfs", sqlite3_appendvfs_init },
{ "basexx", sqlite3_basexx_init },
{ "carray", sqlite3_carray_init },
{ "closure", sqlite3_closure_init },
{ "csv", sqlite3_csv_init },
+155
View File
@@ -0,0 +1,155 @@
# 2022 November 22
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix basexx
if {[catch {load_static_extension db basexx} error]} {
puts "Skipping basexx tests, hit load error: $error"
finish_test; return
}
# Empty blobs encode to empty strings.
do_execsql_test 100 {
SELECT base64(x'')||base85(x'');
} {{}}
# Empty strings decode to empty blobs.
do_execsql_test 101 {
SELECT hex(x'01'||base64('')||base85('')||x'02');
} {0102}
# Basic base64 encoding
do_execsql_test 102 {
SELECT base64(x'000102030405');
SELECT base64(x'0001020304');
SELECT base64(x'00010203');
} {{AAECAwQF
} {AAECAwQ=
} {AAECAw==
}}
# Basic base64 decoding with pad chars
do_execsql_test 103 {
SELECT hex(base64('AAECAwQF'));
SELECT hex(base64('AAECAwQ='));
SELECT hex(base64('AAECAw=='));
} {000102030405 0001020304 00010203}
# Basic base64 decoding without pad chars and with whitespace
do_execsql_test 104 {
SELECT hex(base64(' AAECAwQF '));
SELECT hex(base64(' AAECAwQ'));
SELECT hex(base64('AAECAw '));
} {000102030405 0001020304 00010203}
# Basic base85 encoding
do_execsql_test 105 {
SELECT base85(x'000102030405');
SELECT base85(x'0001020304');
SELECT base85(x'00010203');
} {{##/2,#2/
} {##/2,#*
} {##/2,
}}
# Basic base85 decoding with and without whitespace
do_execsql_test 106 {
SELECT hex(base85('##/2,#2/'));
SELECT hex(base85('##/2,#*'));
SELECT hex(base85('##/2,'));
SELECT hex(base85(' ##/2,#2/ '));
SELECT hex(base85(' ##/2,#*'));
SELECT hex(base85('##/2, '));
} {000102030405 0001020304 00010203 000102030405 0001020304 00010203}
# Round-trip some random blobs.
do_execsql_test 107 {
CREATE TEMP TABLE rb( len int, b blob ) STRICT;
INSERT INTO rb(len) VALUES (1),(2),(3),(4),(5),(150),(151),(152),(153),(1054);
UPDATE rb SET b = randomblob(len);
SELECT len, base64(base64(b))=b, base85(base85(b))=b
FROM rb ORDER BY len;
} {1 1 1 2 1 1 3 1 1 4 1 1 5 1 1 150 1 1 151 1 1 152 1 1 153 1 1 1054 1 1}
# Same round-trip but with space or junk prepended and/or appended or not.
do_execsql_test 108 {
CREATE TEMP TABLE junk(j text, rank int);
INSERT INTO junk VALUES ('',0),(' ',1),('~',2);
SELECT len, base64(j.j||base64(b)||j.j)=b, base85(j.j||base85(b)||j.j)=b
FROM rb r, junk j WHERE j.rank=(r.len+r.len/25)%3 ORDER BY len;
} {1 1 1 2 1 1 3 1 1 4 1 1 5 1 1 150 1 1 151 1 1 152 1 1 153 1 1 1054 1 1}
# Exercise the fail-on-too-large result feature.
set inLimit [sqlite3_limit db SQLITE_LIMIT_LENGTH -1]
sqlite3_limit db SQLITE_LIMIT_LENGTH 1300
do_catchsql_test 109 {
SELECT len, base64(b) FROM rb WHERE len>200;
} {1 {blob expanded to base64 too big}}
do_catchsql_test 110 {
SELECT len, base85(b) FROM rb WHERE len>200;
} {1 {blob expanded to base85 too big}}
do_catchsql_test 111 {
SELECT length(base85(b))=1335 FROM rb WHERE len=1054;
} {1 {blob expanded to base85 too big}}
sqlite3_limit db SQLITE_LIMIT_LENGTH $inLimit
# Exercise is_base85(t)
do_execsql_test 112 {
SELECT is_base85(' '||base85(x'123456')||char(10)),
is_base85('#$%&*+,-./0123456789:;<=>?@'
||'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
||'[\]^_`'
||'abcdefghijklmnopqrstuvwxyz'),
is_base85('!'), is_base85('"'), is_base85(''''), is_base85('('),
is_base85(')'), is_base85(char(123)), is_base85('|'), is_base85(char(125)),
is_base85('~'), is_base85(char(127));
} {1 1 0 0 0 0 0 0 0 0 0 0}
do_execsql_test 113 {
SELECT is_base85(NULL) IS NULL;
} {1}
do_catchsql_test 114 {
SELECT is_base85(1);
} {1 {is_base85 accepts only text or NULL}}
do_catchsql_test 115 {
SELECT is_base85(1.1);
} {1 {is_base85 accepts only text or NULL}}
do_catchsql_test 116 {
SELECT is_base85(x'00');
} {1 {is_base85 accepts only text or NULL}}
# Round-trip many bigger random blobs.
do_execsql_test 117 {
CREATE TABLE bs(b blob, num);
INSERT INTO bs SELECT randomblob(4000 + n%3), n
FROM (
WITH RECURSIVE seq(n) AS (
VALUES(1) UNION ALL SELECT n+1
FROM seq WHERE n<100
) SELECT n FROM seq);
SELECT num FROM bs WHERE base64(base64(b))!=b;
SELECT num FROM bs WHERE base85(base85(b))!=b;
} {}
finish_test