|
1
|
/* |
|
2
|
** Copyright (c) 2014 D. Richard Hipp |
|
3
|
** |
|
4
|
** This program is free software; you can redistribute it and/or |
|
5
|
** modify it under the terms of the Simplified BSD License (also |
|
6
|
** known as the "2-Clause License" or "FreeBSD License".) |
|
7
|
|
|
8
|
** This program is distributed in the hope that it will be useful, |
|
9
|
** but without any warranty; without even the implied warranty of |
|
10
|
** merchantability or fitness for a particular purpose. |
|
11
|
** |
|
12
|
** Author contact information: |
|
13
|
** [email protected] |
|
14
|
** http://www.hwaci.com/drh/ |
|
15
|
** |
|
16
|
******************************************************************************* |
|
17
|
** |
|
18
|
** This file contains built-in string and BLOB resources packaged as |
|
19
|
** byte arrays. |
|
20
|
*/ |
|
21
|
#include "config.h" |
|
22
|
#include "builtin.h" |
|
23
|
#include <assert.h> |
|
24
|
|
|
25
|
/* |
|
26
|
** The resources provided by this file are packaged by the "mkbuiltin.c" |
|
27
|
** utility program during the built process and stored in the |
|
28
|
** builtin_data.h file. Include that information here: |
|
29
|
*/ |
|
30
|
#include "builtin_data.h" |
|
31
|
|
|
32
|
/* |
|
33
|
** Return the index in the aBuiltinFiles[] array for the file |
|
34
|
** whose name is zFilename. Or return -1 if the file is not |
|
35
|
** found. |
|
36
|
*/ |
|
37
|
static int builtin_file_index(const char *zFilename){ |
|
38
|
int lwr, upr, i, c; |
|
39
|
lwr = 0; |
|
40
|
upr = count(aBuiltinFiles) - 1; |
|
41
|
while( upr>=lwr ){ |
|
42
|
i = (upr+lwr)/2; |
|
43
|
c = strcmp(aBuiltinFiles[i].zName,zFilename); |
|
44
|
if( c<0 ){ |
|
45
|
lwr = i+1; |
|
46
|
}else if( c>0 ){ |
|
47
|
upr = i-1; |
|
48
|
}else{ |
|
49
|
return i; |
|
50
|
} |
|
51
|
} |
|
52
|
return -1; |
|
53
|
} |
|
54
|
|
|
55
|
/* |
|
56
|
** Return a pointer to built-in content |
|
57
|
** |
|
58
|
** If the filename contains "-vNNNNNNNN" just before the final file |
|
59
|
** suffix, where each N is a random digit, then omit that part of the |
|
60
|
** filename before doing the lookup. The extra -vNNNNNNNN was added |
|
61
|
** to defeat overly aggressive caching by web browsers. There must be |
|
62
|
** at least 8 digits in NNNNNNNN but more than 8 are allowed. |
|
63
|
*/ |
|
64
|
const unsigned char *builtin_file(const char *zFilename, int *piSize){ |
|
65
|
int i = builtin_file_index(zFilename); |
|
66
|
if( i>=0 ){ |
|
67
|
if( piSize ) *piSize = aBuiltinFiles[i].nByte; |
|
68
|
return aBuiltinFiles[i].pData; |
|
69
|
}else{ |
|
70
|
char *zV = strstr(zFilename, "-v"); |
|
71
|
if( zV!=0 ){ |
|
72
|
for(i=0; fossil_isdigit(zV[i+2]); i++){} |
|
73
|
if( i>=8 && zV[i+2]=='.' ){ |
|
74
|
char *zNew = mprintf("%.*s%s", (int)(zV-zFilename), zFilename, zV+i+2); |
|
75
|
const unsigned char *pRes = builtin_file(zNew, piSize); |
|
76
|
fossil_free(zNew); |
|
77
|
return pRes; |
|
78
|
} |
|
79
|
} |
|
80
|
if( piSize ) *piSize = 0; |
|
81
|
return 0; |
|
82
|
} |
|
83
|
} |
|
84
|
const char *builtin_text(const char *zFilename){ |
|
85
|
return (char*)builtin_file(zFilename, 0); |
|
86
|
} |
|
87
|
|
|
88
|
/* |
|
89
|
** COMMAND: test-builtin-list |
|
90
|
** |
|
91
|
** If -verbose is used, it outputs a line at the end |
|
92
|
** with the total item count and size. |
|
93
|
** |
|
94
|
** List the names and sizes of all built-in resources. |
|
95
|
*/ |
|
96
|
void test_builtin_list(void){ |
|
97
|
int i, size = 0;; |
|
98
|
for(i=0; i<count(aBuiltinFiles); i++){ |
|
99
|
const int n = aBuiltinFiles[i].nByte; |
|
100
|
fossil_print("%3d. %-45s %6d\n", i+1, aBuiltinFiles[i].zName,n); |
|
101
|
size += n; |
|
102
|
} |
|
103
|
if(find_option("verbose","v",0)!=0){ |
|
104
|
fossil_print("%d entries totaling %d bytes\n", i, size); |
|
105
|
} |
|
106
|
} |
|
107
|
|
|
108
|
/* |
|
109
|
** WEBPAGE: test-builtin-files |
|
110
|
** |
|
111
|
** Show all built-in text files. |
|
112
|
*/ |
|
113
|
void test_builtin_list_page(void){ |
|
114
|
int i; |
|
115
|
style_set_current_feature("test"); |
|
116
|
style_header("Built-in Text Files"); |
|
117
|
@ <ol> |
|
118
|
for(i=0; i<count(aBuiltinFiles); i++){ |
|
119
|
const char *z = aBuiltinFiles[i].zName; |
|
120
|
char *zUrl = href("%R/builtin?name=%T&id=%.8s&mimetype=text/plain", |
|
121
|
z,fossil_exe_id()); |
|
122
|
@ <li>%z(zUrl)%h(z)</a> |
|
123
|
} |
|
124
|
@ </ol> |
|
125
|
style_finish_page(); |
|
126
|
} |
|
127
|
|
|
128
|
/* |
|
129
|
** COMMAND: test-builtin-get |
|
130
|
** |
|
131
|
** Usage: %fossil test-builtin-get NAME ?OUTPUT-FILE? |
|
132
|
*/ |
|
133
|
void test_builtin_get(void){ |
|
134
|
const unsigned char *pData; |
|
135
|
int nByte; |
|
136
|
Blob x; |
|
137
|
if( g.argc!=3 && g.argc!=4 ){ |
|
138
|
usage("NAME ?OUTPUT-FILE?"); |
|
139
|
} |
|
140
|
pData = builtin_file(g.argv[2], &nByte); |
|
141
|
if( pData==0 ){ |
|
142
|
fossil_fatal("no such built-in file: [%s]", g.argv[2]); |
|
143
|
} |
|
144
|
blob_init(&x, (const char*)pData, nByte); |
|
145
|
blob_write_to_file(&x, g.argc==4 ? g.argv[3] : "-"); |
|
146
|
blob_reset(&x); |
|
147
|
} |
|
148
|
|
|
149
|
/* |
|
150
|
** Input zList is a list of numeric identifiers for files in |
|
151
|
** aBuiltinFiles[]. Return the concatenation of all of those files |
|
152
|
** using mimetype zType, or as text/javascript if zType is 0. |
|
153
|
*/ |
|
154
|
static void builtin_deliver_multiple_js_files( |
|
155
|
const char *zList, /* List of numeric identifiers */ |
|
156
|
const char *zType /* Override mimetype */ |
|
157
|
){ |
|
158
|
Blob *pOut; |
|
159
|
if( zType==0 ) zType = "text/javascript"; |
|
160
|
cgi_set_content_type(zType); |
|
161
|
pOut = cgi_output_blob(); |
|
162
|
while( zList[0] ){ |
|
163
|
int i = atoi(zList); |
|
164
|
if( i>0 && i<=count(aBuiltinFiles) ){ |
|
165
|
blob_appendf(pOut, "/* %s */\n", aBuiltinFiles[i-1].zName); |
|
166
|
blob_append(pOut, (const char*)aBuiltinFiles[i-1].pData, |
|
167
|
aBuiltinFiles[i-1].nByte); |
|
168
|
} |
|
169
|
while( zList[0] && fossil_isdigit(zList[0]) ) zList++; |
|
170
|
while( zList[0] && !fossil_isdigit(zList[0]) ) zList++; |
|
171
|
} |
|
172
|
return; |
|
173
|
} |
|
174
|
|
|
175
|
/* |
|
176
|
** WEBPAGE: builtin loadavg-exempt |
|
177
|
** |
|
178
|
** Return one of many built-in content files. Query parameters: |
|
179
|
** |
|
180
|
** name=FILENAME Return the single file whose name is FILENAME. |
|
181
|
** mimetype=TYPE Override the mimetype in the returned file to |
|
182
|
** be TYPE. If this query parameter is omitted |
|
183
|
** (the usual case) then the mimetype is inferred |
|
184
|
** from the suffix on FILENAME |
|
185
|
** m=IDLIST IDLIST is a comma-separated list of integers |
|
186
|
** that specify multiple javascript files to be |
|
187
|
** concatenated and returned all at once. |
|
188
|
** id=UNIQUEID Version number of the "builtin" files. Used |
|
189
|
** for cache control only. |
|
190
|
** |
|
191
|
** At least one of the name= or m= query parameters must be present. |
|
192
|
** |
|
193
|
** If the id= query parameter is present, then Fossil assumes that the |
|
194
|
** result is immutable and sets a very large cache retention time (1 year). |
|
195
|
*/ |
|
196
|
void builtin_webpage(void){ |
|
197
|
Blob out; |
|
198
|
const char *zName = P("name"); |
|
199
|
const char *zContent = 0; |
|
200
|
int nContent = 0; |
|
201
|
const char *zId = P("id"); |
|
202
|
const char *zType = P("mimetype"); |
|
203
|
int nId; |
|
204
|
if( zName ) zContent = (const char *)builtin_file(zName, &nContent); |
|
205
|
if( zContent==0 ){ |
|
206
|
const char *zM = P("m"); |
|
207
|
if( zM ){ |
|
208
|
if( zId && (nId = (int)strlen(zId))>=8 |
|
209
|
&& strncmp(zId,fossil_exe_id(),nId)==0 |
|
210
|
){ |
|
211
|
g.isConst = 1; |
|
212
|
} |
|
213
|
etag_check(0,0); |
|
214
|
builtin_deliver_multiple_js_files(zM, zType); |
|
215
|
return; |
|
216
|
} |
|
217
|
cgi_set_status(404, "Not Found"); |
|
218
|
@ File "%h(zName)" not found |
|
219
|
return; |
|
220
|
} |
|
221
|
if( zType==0 ){ |
|
222
|
if( sqlite3_strglob("*.js", zName)==0 ){ |
|
223
|
zType = "text/javascript"; |
|
224
|
}else{ |
|
225
|
zType = mimetype_from_name(zName); |
|
226
|
} |
|
227
|
} |
|
228
|
cgi_set_content_type(zType); |
|
229
|
if( zId |
|
230
|
&& (nId = (int)strlen(zId))>=8 |
|
231
|
&& strncmp(zId,fossil_exe_id(),nId)==0 |
|
232
|
){ |
|
233
|
g.isConst = 1; |
|
234
|
} |
|
235
|
etag_check(0,0); |
|
236
|
blob_init(&out, zContent, nContent); |
|
237
|
cgi_set_content(&out); |
|
238
|
} |
|
239
|
|
|
240
|
/* Variables controlling the JS cache. |
|
241
|
*/ |
|
242
|
static struct { |
|
243
|
int aReq[30]; /* Indexes of all requested built-in JS files */ |
|
244
|
int nReq; /* Number of slots in aReq[] currently used */ |
|
245
|
int nSent; /* Number of slots in aReq[] fulfilled */ |
|
246
|
int eDelivery; /* Delivery mechanism */ |
|
247
|
} builtin; |
|
248
|
|
|
249
|
#if INTERFACE |
|
250
|
/* Various delivery mechanisms. The 0 option is the default. |
|
251
|
** MAINTENANCE NOTE: Review/update the builtin_set_js_delivery_mode() and |
|
252
|
** builtin_get_js_delivery_mode_name() functions if values are changed/added. |
|
253
|
*/ |
|
254
|
#define JS_INLINE 0 /* inline, batched together at end of file */ |
|
255
|
#define JS_SEPARATE 1 /* Separate HTTP request for each JS file */ |
|
256
|
#define JS_BUNDLED 2 /* One HTTP request to load all JS files */ |
|
257
|
/* concatenated together into a bundle */ |
|
258
|
#endif /* INTERFACE */ |
|
259
|
|
|
260
|
/* |
|
261
|
** The argument is a request to change the javascript delivery mode. |
|
262
|
** The argument is a string which is a command-line option or CGI |
|
263
|
** parameter. Try to match it against one of the delivery options |
|
264
|
** and set things up accordingly. Throw an error if no match unless |
|
265
|
** bSilent is true. |
|
266
|
*/ |
|
267
|
void builtin_set_js_delivery_mode(const char *zMode, int bSilent){ |
|
268
|
if( zMode==0 ) return; |
|
269
|
if( strcmp(zMode, "inline")==0 ){ |
|
270
|
builtin.eDelivery = JS_INLINE; |
|
271
|
}else |
|
272
|
if( strcmp(zMode, "separate")==0 ){ |
|
273
|
builtin.eDelivery = JS_SEPARATE; |
|
274
|
}else |
|
275
|
if( strcmp(zMode, "bundled")==0 ){ |
|
276
|
builtin.eDelivery = JS_BUNDLED; |
|
277
|
}else if( !bSilent ){ |
|
278
|
fossil_fatal("unknown javascript delivery mode \"%s\" - should be" |
|
279
|
" one of: inline separate bundled", zMode); |
|
280
|
} |
|
281
|
} |
|
282
|
|
|
283
|
/* |
|
284
|
** Returns the current JS delivery mode: one of JS_INLINE, |
|
285
|
** JS_SEPARATE, JS_BUNDLED. |
|
286
|
*/ |
|
287
|
int builtin_get_js_delivery_mode(void){ |
|
288
|
return builtin.eDelivery; |
|
289
|
} |
|
290
|
|
|
291
|
/* |
|
292
|
** Returns the name of the current JS delivery mode for reuse with the --jsmode |
|
293
|
** option, i.e. the other way around than builtin_set_js_delivery_mode(). |
|
294
|
*/ |
|
295
|
const char *builtin_get_js_delivery_mode_name(void){ |
|
296
|
switch( builtin.eDelivery ){ |
|
297
|
case JS_SEPARATE: { |
|
298
|
return "separate"; |
|
299
|
} |
|
300
|
case JS_BUNDLED: { |
|
301
|
return "bundled"; |
|
302
|
} |
|
303
|
case JS_INLINE: |
|
304
|
/*FALLTHROUGH*/ |
|
305
|
default: { |
|
306
|
return "inline"; |
|
307
|
} |
|
308
|
} |
|
309
|
} |
|
310
|
|
|
311
|
/* |
|
312
|
** The caller wants the Javascript file named by zFilename to be |
|
313
|
** included in the generated page. Add the file to the queue of |
|
314
|
** requested javascript resources, if it is not there already. |
|
315
|
** |
|
316
|
** The current implementation queues the file to be included in the |
|
317
|
** output later. However, the caller should not depend on that |
|
318
|
** behavior. In the future, this routine might decide to insert |
|
319
|
** the requested javascript inline, immedaitely, or to insert |
|
320
|
** a <script src=..> element to reference the javascript as a |
|
321
|
** separate resource. The exact behavior might change in the future |
|
322
|
** so pages that use this interface must not rely on any particular |
|
323
|
** behavior. |
|
324
|
** |
|
325
|
** All this routine guarantees is that the named javascript file |
|
326
|
** will be requested by the browser at some point. This routine |
|
327
|
** does not guarantee when the javascript will be included, and it |
|
328
|
** does not guarantee whether the javascript will be added inline or |
|
329
|
** delivered as a separate resource. |
|
330
|
*/ |
|
331
|
void builtin_request_js(const char *zFilename){ |
|
332
|
int i = builtin_file_index(zFilename); |
|
333
|
int j; |
|
334
|
if( i<0 ){ |
|
335
|
fossil_panic("unknown javascript file: \"%s\"", zFilename); |
|
336
|
} |
|
337
|
for(j=0; j<builtin.nReq; j++){ |
|
338
|
if( builtin.aReq[j]==i ) return; /* Already queued or sent */ |
|
339
|
} |
|
340
|
if( builtin.nReq>=count(builtin.aReq) ){ |
|
341
|
fossil_panic("too many javascript files requested"); |
|
342
|
} |
|
343
|
builtin.aReq[builtin.nReq++] = i; |
|
344
|
} |
|
345
|
|
|
346
|
/* |
|
347
|
** Fulfill all pending requests for javascript files. |
|
348
|
** |
|
349
|
** The current implementation delivers all javascript in-line. However, |
|
350
|
** the caller should not depend on this. Future changes to this routine |
|
351
|
** might choose to deliver javascript as separate resources. |
|
352
|
*/ |
|
353
|
void builtin_fulfill_js_requests(void){ |
|
354
|
if( builtin.nSent>=builtin.nReq ) return; /* nothing to do */ |
|
355
|
switch( builtin.eDelivery ){ |
|
356
|
case JS_INLINE: { |
|
357
|
CX("<script nonce='%h'>\n",style_nonce()); |
|
358
|
do{ |
|
359
|
int i = builtin.aReq[builtin.nSent++]; |
|
360
|
CX("/* %s %.60c*/\n", aBuiltinFiles[i].zName, '*'); |
|
361
|
cgi_append_content((const char*)aBuiltinFiles[i].pData, |
|
362
|
aBuiltinFiles[i].nByte); |
|
363
|
}while( builtin.nSent<builtin.nReq ); |
|
364
|
CX("</script>\n"); |
|
365
|
break; |
|
366
|
} |
|
367
|
case JS_BUNDLED: { |
|
368
|
if( builtin.nSent+1<builtin.nReq ){ |
|
369
|
Blob aList; |
|
370
|
blob_init(&aList,0,0); |
|
371
|
while( builtin.nSent<builtin.nReq ){ |
|
372
|
blob_appendf(&aList, ",%d", builtin.aReq[builtin.nSent++]+1); |
|
373
|
} |
|
374
|
CX("<script src='%R/builtin?m=%s&id=%.8s'></script>\n", |
|
375
|
blob_str(&aList)+1, fossil_exe_id()); |
|
376
|
blob_reset(&aList); |
|
377
|
break; |
|
378
|
} |
|
379
|
/* If there is only one JS file, fall through into the |
|
380
|
** JS_SEPARATE case below. */ |
|
381
|
/*FALLTHROUGH*/ |
|
382
|
} |
|
383
|
case JS_SEPARATE: { |
|
384
|
/* Each JS file as a separate resource */ |
|
385
|
while( builtin.nSent<builtin.nReq ){ |
|
386
|
int i = builtin.aReq[builtin.nSent++]; |
|
387
|
CX("<script src='%R/builtin?name=%t&id=%.8s'></script>\n", |
|
388
|
aBuiltinFiles[i].zName, fossil_exe_id()); |
|
389
|
} |
|
390
|
break; |
|
391
|
} |
|
392
|
} |
|
393
|
} |
|
394
|
|
|
395
|
/***************************************************************************** |
|
396
|
** A virtual table for accessing the information in aBuiltinFiles[]. |
|
397
|
*/ |
|
398
|
|
|
399
|
/* builtinVtab_vtab is a subclass of sqlite3_vtab which is |
|
400
|
** underlying representation of the virtual table |
|
401
|
*/ |
|
402
|
typedef struct builtinVtab_vtab builtinVtab_vtab; |
|
403
|
struct builtinVtab_vtab { |
|
404
|
sqlite3_vtab base; /* Base class - must be first */ |
|
405
|
/* Add new fields here, as necessary */ |
|
406
|
}; |
|
407
|
|
|
408
|
/* builtinVtab_cursor is a subclass of sqlite3_vtab_cursor which will |
|
409
|
** serve as the underlying representation of a cursor that scans |
|
410
|
** over rows of the result |
|
411
|
*/ |
|
412
|
typedef struct builtinVtab_cursor builtinVtab_cursor; |
|
413
|
struct builtinVtab_cursor { |
|
414
|
sqlite3_vtab_cursor base; /* Base class - must be first */ |
|
415
|
/* Insert new fields here. For this builtinVtab we only keep track |
|
416
|
** of the rowid */ |
|
417
|
sqlite3_int64 iRowid; /* The rowid */ |
|
418
|
}; |
|
419
|
|
|
420
|
/* |
|
421
|
** The builtinVtabConnect() method is invoked to create a new |
|
422
|
** builtin virtual table. |
|
423
|
** |
|
424
|
** Think of this routine as the constructor for builtinVtab_vtab objects. |
|
425
|
** |
|
426
|
** All this routine needs to do is: |
|
427
|
** |
|
428
|
** (1) Allocate the builtinVtab_vtab object and initialize all fields. |
|
429
|
** |
|
430
|
** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the |
|
431
|
** result set of queries against the virtual table will look like. |
|
432
|
*/ |
|
433
|
static int builtinVtabConnect( |
|
434
|
sqlite3 *db, |
|
435
|
void *pAux, |
|
436
|
int argc, const char *const*argv, |
|
437
|
sqlite3_vtab **ppVtab, |
|
438
|
char **pzErr |
|
439
|
){ |
|
440
|
builtinVtab_vtab *pNew; |
|
441
|
int rc; |
|
442
|
|
|
443
|
rc = sqlite3_declare_vtab(db, |
|
444
|
"CREATE TABLE x(name,size,data)" |
|
445
|
); |
|
446
|
if( rc==SQLITE_OK ){ |
|
447
|
pNew = sqlite3_malloc( sizeof(*pNew) ); |
|
448
|
*ppVtab = (sqlite3_vtab*)pNew; |
|
449
|
if( pNew==0 ) return SQLITE_NOMEM; |
|
450
|
memset(pNew, 0, sizeof(*pNew)); |
|
451
|
} |
|
452
|
return rc; |
|
453
|
} |
|
454
|
|
|
455
|
/* |
|
456
|
** This method is the destructor for builtinVtab_vtab objects. |
|
457
|
*/ |
|
458
|
static int builtinVtabDisconnect(sqlite3_vtab *pVtab){ |
|
459
|
builtinVtab_vtab *p = (builtinVtab_vtab*)pVtab; |
|
460
|
sqlite3_free(p); |
|
461
|
return SQLITE_OK; |
|
462
|
} |
|
463
|
|
|
464
|
/* |
|
465
|
** Constructor for a new builtinVtab_cursor object. |
|
466
|
*/ |
|
467
|
static int builtinVtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ |
|
468
|
builtinVtab_cursor *pCur; |
|
469
|
pCur = sqlite3_malloc( sizeof(*pCur) ); |
|
470
|
if( pCur==0 ) return SQLITE_NOMEM; |
|
471
|
memset(pCur, 0, sizeof(*pCur)); |
|
472
|
*ppCursor = &pCur->base; |
|
473
|
return SQLITE_OK; |
|
474
|
} |
|
475
|
|
|
476
|
/* |
|
477
|
** Destructor for a builtinVtab_cursor. |
|
478
|
*/ |
|
479
|
static int builtinVtabClose(sqlite3_vtab_cursor *cur){ |
|
480
|
builtinVtab_cursor *pCur = (builtinVtab_cursor*)cur; |
|
481
|
sqlite3_free(pCur); |
|
482
|
return SQLITE_OK; |
|
483
|
} |
|
484
|
|
|
485
|
|
|
486
|
/* |
|
487
|
** Advance a builtinVtab_cursor to its next row of output. |
|
488
|
*/ |
|
489
|
static int builtinVtabNext(sqlite3_vtab_cursor *cur){ |
|
490
|
builtinVtab_cursor *pCur = (builtinVtab_cursor*)cur; |
|
491
|
pCur->iRowid++; |
|
492
|
return SQLITE_OK; |
|
493
|
} |
|
494
|
|
|
495
|
/* |
|
496
|
** Return values of columns for the row at which the builtinVtab_cursor |
|
497
|
** is currently pointing. |
|
498
|
*/ |
|
499
|
static int builtinVtabColumn( |
|
500
|
sqlite3_vtab_cursor *cur, /* The cursor */ |
|
501
|
sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ |
|
502
|
int i /* Which column to return */ |
|
503
|
){ |
|
504
|
builtinVtab_cursor *pCur = (builtinVtab_cursor*)cur; |
|
505
|
const struct BuiltinFileTable *pFile = aBuiltinFiles + pCur->iRowid - 1; |
|
506
|
switch( i ){ |
|
507
|
case 0: /* name */ |
|
508
|
sqlite3_result_text(ctx, pFile->zName, -1, SQLITE_STATIC); |
|
509
|
break; |
|
510
|
case 1: /* size */ |
|
511
|
sqlite3_result_int(ctx, pFile->nByte); |
|
512
|
break; |
|
513
|
case 2: /* data */ |
|
514
|
sqlite3_result_blob(ctx, pFile->pData, pFile->nByte, SQLITE_STATIC); |
|
515
|
break; |
|
516
|
} |
|
517
|
return SQLITE_OK; |
|
518
|
} |
|
519
|
|
|
520
|
/* |
|
521
|
** Return the rowid for the current row. In this implementation, the |
|
522
|
** rowid is the same as the output value. |
|
523
|
*/ |
|
524
|
static int builtinVtabRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ |
|
525
|
builtinVtab_cursor *pCur = (builtinVtab_cursor*)cur; |
|
526
|
*pRowid = pCur->iRowid; |
|
527
|
return SQLITE_OK; |
|
528
|
} |
|
529
|
|
|
530
|
/* |
|
531
|
** Return TRUE if the cursor has been moved off of the last |
|
532
|
** row of output. |
|
533
|
*/ |
|
534
|
static int builtinVtabEof(sqlite3_vtab_cursor *cur){ |
|
535
|
builtinVtab_cursor *pCur = (builtinVtab_cursor*)cur; |
|
536
|
return pCur->iRowid>count(aBuiltinFiles); |
|
537
|
} |
|
538
|
|
|
539
|
/* |
|
540
|
** This method is called to "rewind" the builtinVtab_cursor object back |
|
541
|
** to the first row of output. This method is always called at least |
|
542
|
** once prior to any call to builtinVtabColumn() or builtinVtabRowid() or |
|
543
|
** builtinVtabEof(). |
|
544
|
*/ |
|
545
|
static int builtinVtabFilter( |
|
546
|
sqlite3_vtab_cursor *pVtabCursor, |
|
547
|
int idxNum, const char *idxStr, |
|
548
|
int argc, sqlite3_value **argv |
|
549
|
){ |
|
550
|
builtinVtab_cursor *pCur = (builtinVtab_cursor *)pVtabCursor; |
|
551
|
pCur->iRowid = 1; |
|
552
|
return SQLITE_OK; |
|
553
|
} |
|
554
|
|
|
555
|
/* |
|
556
|
** SQLite will invoke this method one or more times while planning a query |
|
557
|
** that uses the virtual table. This routine needs to create |
|
558
|
** a query plan for each invocation and compute an estimated cost for that |
|
559
|
** plan. |
|
560
|
*/ |
|
561
|
static int builtinVtabBestIndex( |
|
562
|
sqlite3_vtab *tab, |
|
563
|
sqlite3_index_info *pIdxInfo |
|
564
|
){ |
|
565
|
pIdxInfo->estimatedCost = (double)count(aBuiltinFiles); |
|
566
|
pIdxInfo->estimatedRows = count(aBuiltinFiles); |
|
567
|
return SQLITE_OK; |
|
568
|
} |
|
569
|
|
|
570
|
/* |
|
571
|
** This following structure defines all the methods for the |
|
572
|
** virtual table. |
|
573
|
*/ |
|
574
|
static sqlite3_module builtinVtabModule = { |
|
575
|
/* iVersion */ 0, |
|
576
|
/* xCreate */ 0, /* The builtin vtab is eponymous and read-only */ |
|
577
|
/* xConnect */ builtinVtabConnect, |
|
578
|
/* xBestIndex */ builtinVtabBestIndex, |
|
579
|
/* xDisconnect */ builtinVtabDisconnect, |
|
580
|
/* xDestroy */ 0, |
|
581
|
/* xOpen */ builtinVtabOpen, |
|
582
|
/* xClose */ builtinVtabClose, |
|
583
|
/* xFilter */ builtinVtabFilter, |
|
584
|
/* xNext */ builtinVtabNext, |
|
585
|
/* xEof */ builtinVtabEof, |
|
586
|
/* xColumn */ builtinVtabColumn, |
|
587
|
/* xRowid */ builtinVtabRowid, |
|
588
|
/* xUpdate */ 0, |
|
589
|
/* xBegin */ 0, |
|
590
|
/* xSync */ 0, |
|
591
|
/* xCommit */ 0, |
|
592
|
/* xRollback */ 0, |
|
593
|
/* xFindMethod */ 0, |
|
594
|
/* xRename */ 0, |
|
595
|
/* xSavepoint */ 0, |
|
596
|
/* xRelease */ 0, |
|
597
|
/* xRollbackTo */ 0, |
|
598
|
/* xShadowName */ 0, |
|
599
|
/* xIntegrity */ 0 |
|
600
|
}; |
|
601
|
|
|
602
|
|
|
603
|
/* |
|
604
|
** Register the builtin virtual table |
|
605
|
*/ |
|
606
|
int builtin_vtab_register(sqlite3 *db){ |
|
607
|
int rc = sqlite3_create_module(db, "builtin", &builtinVtabModule, 0); |
|
608
|
return rc; |
|
609
|
} |
|
610
|
/* End of the builtin virtual table |
|
611
|
******************************************************************************/ |
|
612
|
|
|
613
|
|
|
614
|
/* |
|
615
|
** The first time this is called, it emits code to install and |
|
616
|
** bootstrap the window.fossil object, using the built-in file |
|
617
|
** fossil.bootstrap.js (not to be confused with bootstrap.js). |
|
618
|
** |
|
619
|
** Subsequent calls are no-ops. |
|
620
|
** |
|
621
|
** It emits 2 parts: |
|
622
|
** |
|
623
|
** 1) window.fossil core object, some of which depends on C-level |
|
624
|
** runtime data. That part of the script is always emitted inline. If |
|
625
|
** addScriptTag is true then it is wrapped in its own SCRIPT tag, else |
|
626
|
** it is assumed that the caller already opened a tag. |
|
627
|
** |
|
628
|
** 2) Emits the static fossil.bootstrap.js using builtin_request_js(). |
|
629
|
*/ |
|
630
|
void builtin_emit_script_fossil_bootstrap(int addScriptTag){ |
|
631
|
static int once = 0; |
|
632
|
if(0==once++){ |
|
633
|
char * zName; |
|
634
|
/* Set up the generic/app-agnostic parts of window.fossil |
|
635
|
** which require C-level state... */ |
|
636
|
if(addScriptTag!=0){ |
|
637
|
style_script_begin(__FILE__,__LINE__); |
|
638
|
} |
|
639
|
CX("(function(){\n"); |
|
640
|
CX(/*MSIE NodeList.forEach polyfill, courtesy of Mozilla: |
|
641
|
https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill |
|
642
|
*/ |
|
643
|
"if(window.NodeList && !NodeList.prototype.forEach){" |
|
644
|
"NodeList.prototype.forEach = Array.prototype.forEach;" |
|
645
|
"}\n"); |
|
646
|
CX("if(!window.fossil) window.fossil={};\n" |
|
647
|
"window.fossil.version = %!j;\n" |
|
648
|
/* fossil.rootPath is the top-most CGI/server path, |
|
649
|
** including a trailing slash. */ |
|
650
|
"window.fossil.rootPath = %!j+'/';\n", |
|
651
|
get_version(), g.zTop); |
|
652
|
/* fossil.config = {...various config-level options...} */ |
|
653
|
CX("window.fossil.config = {"); |
|
654
|
zName = db_get("project-name", ""); |
|
655
|
CX("projectName: %!j,\n", zName); |
|
656
|
fossil_free(zName); |
|
657
|
zName = db_get("short-project-name", ""); |
|
658
|
CX("shortProjectName: %!j,\n", zName); |
|
659
|
fossil_free(zName); |
|
660
|
zName = db_get("project-code", ""); |
|
661
|
CX("projectCode: %!j,\n", zName); |
|
662
|
fossil_free(zName); |
|
663
|
CX("/* Length of UUID hashes for display purposes. */"); |
|
664
|
CX("hashDigits: %d, hashDigitsUrl: %d,\n", |
|
665
|
hash_digits(0), hash_digits(1)); |
|
666
|
CX("diffContextLines: %d,\n", |
|
667
|
diff_context_lines(0)); |
|
668
|
CX("editStateMarkers: {" |
|
669
|
"/*Symbolic markers to denote certain edit states.*/" |
|
670
|
"isNew:'[+]', isModified:'[*]', isDeleted:'[-]'},\n"); |
|
671
|
CX("confirmerButtonTicks: 3 " |
|
672
|
"/*default fossil.confirmer tick count.*/,\n"); |
|
673
|
/* Inject certain info about the current skin... */ |
|
674
|
CX("skin:{"); |
|
675
|
/* can leak a local filesystem path: |
|
676
|
CX("name: %!j,", skin_in_use());*/ |
|
677
|
CX("isDark: %s" |
|
678
|
"/*true if the current skin has the 'white-foreground' detail*/", |
|
679
|
skin_detail_boolean("white-foreground") ? "true" : "false"); |
|
680
|
CX("}\n"/*fossil.config.skin*/); |
|
681
|
CX("};\n"/* fossil.config */); |
|
682
|
CX("window.fossil.user = {"); |
|
683
|
CX("name: %!j,", (g.zLogin&&*g.zLogin) ? g.zLogin : "guest"); |
|
684
|
CX("isAdmin: %s", (g.perm.Admin || g.perm.Setup) ? "true" : "false"); |
|
685
|
CX("};\n"/*fossil.user*/); |
|
686
|
CX("if(fossil.config.skin.isDark) " |
|
687
|
"document.body.classList.add('fossil-dark-style');\n"); |
|
688
|
/* |
|
689
|
** fossil.page holds info about the current page. This is also |
|
690
|
** where the current page "should" store any of its own |
|
691
|
** page-specific state, and it is reserved for that purpose. |
|
692
|
*/ |
|
693
|
CX("window.fossil.page = {" |
|
694
|
"name:\"%T\"" |
|
695
|
"};\n", g.zPath); |
|
696
|
CX("})();\n"); |
|
697
|
if(addScriptTag!=0){ |
|
698
|
style_script_end(); |
|
699
|
} |
|
700
|
/* The remaining window.fossil bootstrap code is not dependent on |
|
701
|
** C-runtime state... */ |
|
702
|
builtin_request_js("fossil.bootstrap.js"); |
|
703
|
} |
|
704
|
} |
|
705
|
|
|
706
|
/* |
|
707
|
** Given the NAME part of fossil.NAME.js, this function checks whether |
|
708
|
** that module has been emitted by this function before. If it has, |
|
709
|
** it returns -1 with no side effects. If it has not, it queues up |
|
710
|
** (via builtin_request_js()) an emit of the module via and all of its |
|
711
|
** known (by this function) fossil.XYZ.js dependencies (in their |
|
712
|
** dependency order) and returns 1. If it does not find the given |
|
713
|
** module name it returns 0. |
|
714
|
** |
|
715
|
** As a special case, if passed 0 then it queues up all known modules |
|
716
|
** and returns -1. |
|
717
|
** |
|
718
|
** The very first time this is called, it unconditionally calls |
|
719
|
** builtin_emit_script_fossil_bootstrap(). |
|
720
|
** |
|
721
|
** Any given module is only queued once, whether it is explicitly |
|
722
|
** passed to the function or resolved as a dependency. Any attempts to |
|
723
|
** re-queue them later are harmless no-ops. |
|
724
|
*/ |
|
725
|
static int builtin_emit_fossil_js_once(const char * zName){ |
|
726
|
static int once = 0; |
|
727
|
int i; |
|
728
|
static struct FossilJs { |
|
729
|
const char * zName; /* NAME part of fossil.NAME.js */ |
|
730
|
int emitted; /* True if already emitted. */ |
|
731
|
const char * zDeps; /* \0-delimited list of other FossilJs |
|
732
|
** entries: all known deps of this one. Each |
|
733
|
** REQUIRES an EXPLICIT trailing \0, including |
|
734
|
** the final one! */ |
|
735
|
} fjs[] = { |
|
736
|
/* This list ordering isn't strictly important. */ |
|
737
|
{"confirmer", 0, 0}, |
|
738
|
{"copybutton", 0, "dom\0"}, |
|
739
|
{"diff", 0, "dom\0fetch\0storage\0" |
|
740
|
/* maintenance note: "diff" needs "storage" for storing the |
|
741
|
** sbs-sync-scroll toggle. */}, |
|
742
|
{"dom", 0, 0}, |
|
743
|
{"fetch", 0, 0}, |
|
744
|
{"numbered-lines", 0, "popupwidget\0copybutton\0"}, |
|
745
|
{"pikchr", 0, "dom\0"}, |
|
746
|
{"popupwidget", 0, "dom\0"}, |
|
747
|
{"storage", 0, 0}, |
|
748
|
{"tabs", 0, "dom\0"} |
|
749
|
}; |
|
750
|
const int nFjs = sizeof(fjs) / sizeof(fjs[0]); |
|
751
|
if(0==once){ |
|
752
|
++once; |
|
753
|
builtin_emit_script_fossil_bootstrap(1); |
|
754
|
} |
|
755
|
if(0==zName){ |
|
756
|
for( i = 0; i < nFjs; ++i ){ |
|
757
|
builtin_emit_fossil_js_once(fjs[i].zName); |
|
758
|
} |
|
759
|
return -1; |
|
760
|
} |
|
761
|
for( i = 0; i < nFjs; ++i ){ |
|
762
|
if(0==strcmp(zName, fjs[i].zName)){ |
|
763
|
if(fjs[i].emitted){ |
|
764
|
return -1; |
|
765
|
}else{ |
|
766
|
char nameBuffer[50]; |
|
767
|
if(fjs[i].zDeps){ |
|
768
|
const char * zDep = fjs[i].zDeps; |
|
769
|
while(*zDep!=0){ |
|
770
|
builtin_emit_fossil_js_once(zDep); |
|
771
|
zDep += strlen(zDep)+1/*NUL delimiter*/; |
|
772
|
} |
|
773
|
} |
|
774
|
sqlite3_snprintf(sizeof(nameBuffer)-1, nameBuffer, |
|
775
|
"fossil.%s.js", fjs[i].zName); |
|
776
|
builtin_request_js(nameBuffer); |
|
777
|
fjs[i].emitted = 1; |
|
778
|
return 1; |
|
779
|
} |
|
780
|
} |
|
781
|
} |
|
782
|
return 0; |
|
783
|
} |
|
784
|
|
|
785
|
/* |
|
786
|
** COMMAND: test-js-once |
|
787
|
** |
|
788
|
** Tester for builtin_emit_fossil_js_once(). |
|
789
|
** |
|
790
|
** Usage: %fossil test-js-once filename |
|
791
|
*/ |
|
792
|
void test_js_once(void){ |
|
793
|
int i; |
|
794
|
if(g.argc<2){ |
|
795
|
usage("?FILENAME...?"); |
|
796
|
} |
|
797
|
if(2==g.argc){ |
|
798
|
builtin_emit_fossil_js_once(0); |
|
799
|
assert(builtin.nReq>8); |
|
800
|
}else{ |
|
801
|
for(i = 2; i < g.argc; ++i){ |
|
802
|
builtin_emit_fossil_js_once(g.argv[i]); |
|
803
|
} |
|
804
|
assert(builtin.nReq>1 && "don't forget implicit fossil.bootstrap.js"); |
|
805
|
} |
|
806
|
for(i = 0; i < builtin.nReq; ++i){ |
|
807
|
fossil_print("ndx#%d = %d = %s\n", i, builtin.aReq[i], |
|
808
|
aBuiltinFiles[builtin.aReq[i]].zName); |
|
809
|
} |
|
810
|
} |
|
811
|
|
|
812
|
/* |
|
813
|
** Convenience wrapper which calls builtin_request_js() for a series |
|
814
|
** of builtin scripts named fossil.NAME.js. The first time it is |
|
815
|
** called, it also calls builtin_emit_script_fossil_bootstrap() to |
|
816
|
** initialize the window.fossil JS API. The first argument is the NAME |
|
817
|
** part of the first API to emit. All subsequent arguments must be |
|
818
|
** strings of the NAME part of additional fossil.NAME.js files, |
|
819
|
** followed by a NULL argument to terminate the list. |
|
820
|
** |
|
821
|
** e.g. pass it ("fetch", "dom", "tabs", NULL) to load those 3 APIs (or |
|
822
|
** pass it ("fetch","tabs",NULL), as "dom" is a dependency of "tabs", so |
|
823
|
** it will be automatically loaded). Do not forget the trailing NULL, |
|
824
|
** and do not pass 0 instead, since that isn't always equivalent to NULL |
|
825
|
** in this context. |
|
826
|
** |
|
827
|
** If it is JS_BUNDLED then this routine queues up an emit of ALL of |
|
828
|
** the JS fossil.XYZ.js APIs which are not strictly specific to a |
|
829
|
** single page, and then calls builtin_fulfill_js_requests(). The idea |
|
830
|
** is that we can get better bundle caching and reduced HTTP requests |
|
831
|
** by including all JS, rather than creating separate bundles on a |
|
832
|
** per-page basis. In this case, all arguments are ignored! |
|
833
|
** |
|
834
|
** This function has an internal mapping of the dependencies for each |
|
835
|
** of the known fossil.XYZ.js modules and ensures that the |
|
836
|
** dependencies also get queued (recursively) and that each module is |
|
837
|
** queued only once. |
|
838
|
** |
|
839
|
** If passed a name which is not a base fossil module name then it |
|
840
|
** will fail fatally! |
|
841
|
** |
|
842
|
** DO NOT use this for loading fossil.page.*.js: use |
|
843
|
** builtin_request_js() for those. |
|
844
|
** |
|
845
|
** If the current JS delivery mode is *not* JS_BUNDLED then this |
|
846
|
** function queues up a request for each given module and its known |
|
847
|
** dependencies, but does not immediately fulfill the request, thus it |
|
848
|
** can be called multiple times. |
|
849
|
** |
|
850
|
** If a given module is ever passed to this more than once, either in |
|
851
|
** a single invocation or multiples, it is only queued for emit a |
|
852
|
** single time (i.e. the 2nd and subsequent ones become |
|
853
|
** no-ops). Likewise, if a module is requested but was already |
|
854
|
** automatically queued to fulfill a dependency, the explicit request |
|
855
|
** becomes a no-op. |
|
856
|
** |
|
857
|
** Bundled mode is the only mode in which this API greatly improves |
|
858
|
** aggregate over-the-wire and HTTP request costs. For other modes, |
|
859
|
** reducing the inclusion of fossil.XYZ APIs to their bare minimum |
|
860
|
** provides the lowest aggregate costs. For debate and details, see |
|
861
|
** the discussion at: |
|
862
|
** |
|
863
|
** https://fossil-scm.org/forum/forumpost/3fa2633f3e |
|
864
|
** |
|
865
|
** In practice it is normally necessary (or preferred) to call |
|
866
|
** builtin_fulfill_js_requests() after calling this, before proceeding |
|
867
|
** to call builtin_request_js() for page-specific JS, in order to |
|
868
|
** improve cachability. |
|
869
|
** |
|
870
|
** Minor caveat: the purpose of emitting all of the fossil.XYZ JS APIs |
|
871
|
** at once is to reduce over-the-wire transfers by enabling cross-page |
|
872
|
** caching, but if there are other JS scripts pending via |
|
873
|
** builtin_request_js() when this is called then they will be included |
|
874
|
** in the JS request emitted by this routine, resulting in a different |
|
875
|
** script URL than if they were not included. Thus, if a given page |
|
876
|
** has its own scripts to install via builtin_request_js(), they |
|
877
|
** should, if possible, be delayed until after this is called OR the |
|
878
|
** page should call builtin_fulfill_js_requests() to flush the request |
|
879
|
** queue before calling this routine. |
|
880
|
** |
|
881
|
** Achtung: the fossil.page.XYZ.js files are page-specific, containing |
|
882
|
** the app-level logic for that specific page, and loading more than |
|
883
|
** one of them in a single page will break that page. Each of those |
|
884
|
** expects to "own" the page it is loaded in, and it should be loaded |
|
885
|
** as late in the JS-loading process as feasible, ideally bundled (via |
|
886
|
** builtin_request_js()) with any other app-/page-specific JS it may |
|
887
|
** need. |
|
888
|
** |
|
889
|
** Example usage: |
|
890
|
** |
|
891
|
** builtin_fossil_js_bundle_or("dom", "fetch", NULL); |
|
892
|
** |
|
893
|
** In bundled mode, that will (the first time it is called) emit all |
|
894
|
** builtin fossil JS APIs and "fulfill" the queue immediately. In |
|
895
|
** non-bundled mode it will queue up the "dom" and "fetch" APIs to be |
|
896
|
** emitted the next time builtin_fulfill_js_requests() is called. |
|
897
|
*/ |
|
898
|
NULL_SENTINEL void builtin_fossil_js_bundle_or( const char * zApi, ... ) { |
|
899
|
static int bundled = 0; |
|
900
|
const char *zArg; |
|
901
|
va_list vargs; |
|
902
|
|
|
903
|
if(JS_BUNDLED == builtin_get_js_delivery_mode()){ |
|
904
|
if(!bundled){ |
|
905
|
bundled = 1; |
|
906
|
builtin_emit_fossil_js_once(0); |
|
907
|
builtin_fulfill_js_requests(); |
|
908
|
} |
|
909
|
return; |
|
910
|
} |
|
911
|
va_start(vargs,zApi); |
|
912
|
for( zArg = zApi; zArg!=NULL; (zArg = va_arg (vargs, const char *))){ |
|
913
|
if(0==builtin_emit_fossil_js_once(zArg)){ |
|
914
|
fossil_fatal("Unknown fossil JS module: %s\n", zArg); |
|
915
|
} |
|
916
|
} |
|
917
|
va_end(vargs); |
|
918
|
} |
|
919
|
|