Fossil SCM

fossil-scm / src / shun.c
Blame History Raw 616 lines
1
/*
2
** Copyright (c) 2008 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 code used to manage SHUN table of the repository
19
*/
20
#include "config.h"
21
#include "shun.h"
22
#include <assert.h>
23
24
/*
25
** Return true if the given artifact ID should be shunned.
26
*/
27
int uuid_is_shunned(const char *zUuid){
28
static Stmt q;
29
int rc;
30
if( zUuid==0 || zUuid[0]==0 ) return 0;
31
if( g.eHashPolicy==HPOLICY_SHUN_SHA1 && zUuid[HNAME_LEN_SHA1]==0 ) return 1;
32
db_static_prepare(&q, "SELECT 1 FROM shun WHERE uuid=:uuid");
33
db_bind_text(&q, ":uuid", zUuid);
34
rc = db_step(&q);
35
db_reset(&q);
36
return rc==SQLITE_ROW;
37
}
38
39
/*
40
** WEBPAGE: shun
41
**
42
** View the hashes of all shunned artifacts. Add new hashes
43
** to the shun set. Requires Admin privilege.
44
*/
45
void shun_page(void){
46
Stmt q;
47
int cnt = 0;
48
const char *zUuid = P("uuid");
49
const char *zShun = P("shun");
50
const char *zAccept = P("accept");
51
const char *zRcvid = P("rcvid");
52
int reviewList = P("review")!=0;
53
int nRcvid = 0;
54
int numRows = 3;
55
char *zCanonical = 0;
56
57
login_check_credentials();
58
if( !g.perm.Admin ){
59
login_needed(0);
60
return;
61
}
62
if( P("rebuild") ){
63
db_close(1);
64
db_open_repository(g.zRepositoryName);
65
db_begin_transaction();
66
rebuild_db(0, 0);
67
admin_log("Rebuilt database.");
68
db_end_transaction(0);
69
}
70
if( zUuid ){
71
char *p;
72
int i = 0;
73
int j = 0;
74
zCanonical = fossil_malloc(strlen(zUuid)+2);
75
while( zUuid[i] ){
76
if( fossil_isspace(zUuid[i]) ){
77
if( j && zCanonical[j-1] ){
78
zCanonical[j] = 0;
79
j++;
80
}
81
}else{
82
zCanonical[j] = zUuid[i];
83
j++;
84
}
85
i++;
86
}
87
zCanonical[j+1] = zCanonical[j] = 0;
88
p = zCanonical;
89
while( *p ){
90
int nUuid = strlen(p);
91
if( !(reviewList || hname_validate(p, nUuid)) ){
92
@ <p class="generalError">Error: Bad artifact IDs.</p>
93
fossil_free(zCanonical);
94
zCanonical = 0;
95
break;
96
}else{
97
canonical16(p, nUuid);
98
p += nUuid+1;
99
}
100
}
101
zUuid = zCanonical;
102
}
103
style_header("Shunned Artifacts");
104
if( zUuid && P("sub") && cgi_csrf_safe(2) ){
105
const char *p = zUuid;
106
int allExist = 1;
107
while( *p ){
108
db_multi_exec("DELETE FROM shun WHERE uuid=%Q", p);
109
if( !db_exists("SELECT 1 FROM blob WHERE uuid=%Q", p) ){
110
allExist = 0;
111
}
112
admin_log("Unshunned %Q", p);
113
p += strlen(p)+1;
114
}
115
if( allExist ){
116
@ <p class="noMoreShun">Artifact(s)<br>
117
for( p = zUuid ; *p ; p += strlen(p)+1 ){
118
@ <a href="%R/artifact/%s(p)">%s(p)</a><br>
119
}
120
@ are no longer being shunned.</p>
121
}else{
122
@ <p class="noMoreShun">Artifact(s)<br>
123
for( p = zUuid ; *p ; p += strlen(p)+1 ){
124
@ %s(p)<br>
125
}
126
@ will no longer be shunned but they may not exist in the repository.
127
@ It may be necessary to rebuild the repository
128
@ before the artifact content can be pulled in
129
@ from other repositories.</p>
130
}
131
}
132
if( zUuid && P("add") && cgi_csrf_safe(2) ){
133
const char *p = zUuid;
134
int rid, tagid;
135
while( *p ){
136
db_multi_exec(
137
"INSERT OR IGNORE INTO shun(uuid,mtime)"
138
" VALUES(%Q, now())", p);
139
db_multi_exec("DELETE FROM attachment WHERE src=%Q", p);
140
rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", p);
141
if( rid ){
142
db_multi_exec("DELETE FROM event WHERE objid=%d", rid);
143
}
144
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='tkt-%q'", p);
145
if( tagid ){
146
db_multi_exec("DELETE FROM ticket WHERE tkt_uuid=%Q", p);
147
db_multi_exec("DELETE FROM tag WHERE tagid=%d", tagid);
148
db_multi_exec("DELETE FROM tagxref WHERE tagid=%d", tagid);
149
}
150
admin_log("Shunned %Q", p);
151
p += strlen(p)+1;
152
}
153
@ <p class="shunned">Artifact(s)<br>
154
for( p = zUuid ; *p ; p += strlen(p)+1 ){
155
@ <a href="%R/artifact/%s(p)">%s(p)</a><br>
156
}
157
@ have been shunned. They will no longer be pushed.
158
@ They will be removed from the repository the next time the repository
159
@ is rebuilt using the <b>fossil rebuild</b> command-line</p>
160
}
161
if( zUuid && reviewList ){
162
const char *p;
163
int nTotal = 0;
164
int nOk = 0;
165
@ <table class="shun-review"><tbody><tr><td>
166
for( p = zUuid ; *p ; p += strlen(p)+1 ){
167
int rid = symbolic_name_to_rid(p, 0);
168
nTotal++;
169
if( rid < 0 ){
170
@ Ambiguous<br>
171
}else if( rid == 0 ){
172
if( !hname_validate(p, strlen(p)) ){
173
@ Bad artifact<br>
174
}else if(db_int(0, "SELECT 1 FROM shun WHERE uuid=%Q", p)){
175
@ Already shunned<br>
176
}else{
177
@ Unknown<br>
178
}
179
}else{
180
char *zCmpUuid = db_text(0,
181
"SELECT uuid"
182
" FROM blob, rcvfrom"
183
" WHERE rid=%d"
184
" AND rcvfrom.rcvid=blob.rcvid",
185
rid);
186
if( fossil_strcmp(p, zCmpUuid)==0 ){
187
nOk++;
188
@ OK</br>
189
}else{
190
@ Abbreviated<br>
191
}
192
}
193
}
194
@ </td><td>
195
for( p = zUuid ; *p ; p += strlen(p)+1 ){
196
int rid = symbolic_name_to_rid(p, 0);
197
if( rid > 0 ){
198
@ <a href="%R/artifact/%s(p)">%s(p)</a><br>
199
}else{
200
@ %s(p)<br>
201
}
202
}
203
@ </td></tr></tbody></table>
204
@ <p class="shunned">
205
if( nOk < nTotal){
206
@ <b>Warning:</b> Not all artifacts
207
}else if( nTotal==1 ){
208
@ The artifact is present and
209
}else{
210
@ All %i(nOk) artifacts are present and
211
}
212
@ can be shunned with its hash above.</p>
213
}
214
if( zRcvid ){
215
nRcvid = atoi(zRcvid);
216
numRows = db_int(0, "SELECT min(count(), 10) FROM blob WHERE rcvid=%d",
217
nRcvid);
218
}
219
@ <p>A shunned artifact will not be pushed nor accepted in a pull and the
220
@ artifact content will be purged from the repository the next time the
221
@ repository is rebuilt. A list of shunned artifacts can be seen at the
222
@ bottom of this page.</p>
223
@
224
@ <a name="addshun"></a>
225
@ <p>To shun artifacts, enter their artifact hashes (the 40- or
226
@ 64-character lowercase hexadecimal hash of the artifact content) in the
227
@ following box and press the "Shun" button. This will cause the artifacts
228
@ to be removed from the repository and will prevent the artifacts from being
229
@ readded to the repository by subsequent sync operation.</p>
230
@
231
@ <p>Note that you must enter full artifact hashes, not abbreviations
232
@ or symbolic tags.</p>
233
@
234
@ <p>Warning: Shunning should only be used to remove inappropriate content
235
@ from the repository. Inappropriate content includes such things as
236
@ spam added to Wiki, files that violate copyright or patent agreements,
237
@ or artifacts that by design or accident interfere with the processing
238
@ of the repository. Do not shun artifacts merely to remove them from
239
@ sight - set the "hidden" tag on such artifacts instead.</p>
240
@
241
@ <blockquote>
242
@ <form method="post" action="%R/%s(g.zPath)"><div>
243
login_insert_csrf_secret();
244
@ <textarea class="fullsize-text" cols="70" rows="%d(numRows)" name="uuid">
245
if( zShun ){
246
if( strlen(zShun) ){
247
@ %h(zShun)
248
}else if( nRcvid ){
249
db_prepare(&q, "SELECT uuid FROM blob WHERE rcvid=%d", nRcvid);
250
while( db_step(&q)==SQLITE_ROW ){
251
@ %s(db_column_text(&q, 0))
252
}
253
db_finalize(&q);
254
}
255
}else if( zUuid && reviewList ){
256
const char *p;
257
for( p = zUuid ; *p ; p += strlen(p)+1 ){
258
@ %s(p)
259
}
260
}
261
@ </textarea>
262
@ <input type="submit" name="add" value="Shun">
263
@ <input type="submit" name="review" value="Review">
264
@ </div></form>
265
@ </blockquote>
266
@
267
@ <a name="delshun"></a>
268
@ <p>Enter the UUIDs of previously shunned artifacts to cause them to be
269
@ accepted again in the repository. The artifacts content is not
270
@ restored because the content is unknown. The only change is that
271
@ the formerly shunned artifacts will be accepted on subsequent sync
272
@ operations.</p>
273
@
274
@ <blockquote>
275
@ <form method="post" action="%R/%s(g.zPath)"><div>
276
login_insert_csrf_secret();
277
@ <textarea class="fullsize-text" cols="70" rows="%d(numRows)" name="uuid">
278
if( zAccept ){
279
if( strlen(zAccept) ){
280
@ %h(zAccept)
281
}else if( nRcvid ){
282
db_prepare(&q, "SELECT uuid FROM blob WHERE rcvid=%d", nRcvid);
283
while( db_step(&q)==SQLITE_ROW ){
284
@ %s(db_column_text(&q, 0))
285
}
286
db_finalize(&q);
287
}
288
}
289
@ </textarea>
290
@ <input type="submit" name="sub" value="Accept">
291
@ </div></form>
292
@ </blockquote>
293
@
294
@ <p>Press the Rebuild button below to rebuild the repository. The
295
@ content of newly shunned artifacts is not purged until the repository
296
@ is rebuilt. On larger repositories, the rebuild may take minute or
297
@ two, so be patient after pressing the button.</p>
298
@
299
@ <blockquote>
300
@ <form method="post" action="%R/%s(g.zPath)"><div>
301
login_insert_csrf_secret();
302
@ <input type="submit" name="rebuild" value="Rebuild">
303
@ </div></form>
304
@ </blockquote>
305
@
306
@ <hr><p>Shunned Artifacts:</p>
307
@ <blockquote><p>
308
db_prepare(&q,
309
"SELECT uuid, EXISTS(SELECT 1 FROM blob WHERE blob.uuid=shun.uuid)"
310
" FROM shun ORDER BY uuid");
311
while( db_step(&q)==SQLITE_ROW ){
312
const char *zUuid = db_column_text(&q, 0);
313
int stillExists = db_column_int(&q, 1);
314
cnt++;
315
if( stillExists ){
316
@ <b><a href="%R/artifact/%s(zUuid)">%s(zUuid)</a></b><br>
317
}else{
318
@ <b>%s(zUuid)</b><br>
319
}
320
}
321
if( cnt==0 ){
322
@ <i>no artifacts are shunned on this server</i>
323
}
324
db_finalize(&q);
325
@ </p></blockquote>
326
style_finish_page();
327
fossil_free(zCanonical);
328
}
329
330
/*
331
** Remove from the BLOB table all artifacts that are in the SHUN table.
332
*/
333
void shun_artifacts(void){
334
Stmt q;
335
db_multi_exec(
336
"CREATE TEMP TABLE toshun(rid INTEGER PRIMARY KEY);"
337
"INSERT INTO toshun SELECT rid FROM blob, shun WHERE blob.uuid=shun.uuid;"
338
);
339
db_prepare(&q,
340
"SELECT rid FROM delta WHERE srcid IN toshun"
341
);
342
while( db_step(&q)==SQLITE_ROW ){
343
int srcid = db_column_int(&q, 0);
344
content_undelta(srcid);
345
}
346
db_finalize(&q);
347
db_multi_exec(
348
"DELETE FROM delta WHERE rid IN toshun;"
349
"DELETE FROM blob WHERE rid IN toshun;"
350
"DROP TABLE toshun;"
351
"DELETE FROM private "
352
" WHERE NOT EXISTS (SELECT 1 FROM blob WHERE rid=private.rid);"
353
);
354
}
355
356
/*
357
** WEBPAGE: rcvfromlist
358
**
359
** Show a listing of RCVFROM table entries.
360
**
361
** The RCVFROM table records where this repository received each
362
** artifact, including the time of receipt, user, and IP address.
363
**
364
** Access requires Admin privilege.
365
*/
366
void rcvfromlist_page(void){
367
int ofst = atoi(PD("ofst","0"));
368
int showAll = P("all")!=0;
369
int cnt;
370
Stmt q;
371
const int perScreen = 500; /* RCVIDs per page */
372
373
login_check_credentials();
374
if( !g.perm.Admin ){
375
login_needed(0);
376
return;
377
}
378
style_header("Xfer Log");
379
style_submenu_element("Log-Menu", "setup-logmenu");
380
if( showAll ){
381
ofst = 0;
382
}else{
383
style_submenu_element("All", "rcvfromlist?all=1");
384
}
385
if( ofst>0 ){
386
style_submenu_element("Newer", "rcvfromlist?ofst=%d",
387
ofst>perScreen ? ofst-perScreen : 0);
388
}
389
style_submenu_element("Artifacts", "bloblist");
390
style_submenu_element("Top-250", "bigbloblist");
391
db_multi_exec(
392
"CREATE TEMP TABLE rcvidUsed(x INTEGER PRIMARY KEY);"
393
"CREATE TEMP TABLE rcvidSha1(x INTEGER PRIMARY KEY);"
394
"CREATE TEMP TABLE rcvidSha3(x INTEGER PRIMARY KEY);"
395
"INSERT OR IGNORE INTO rcvidUsed(x) SELECT rcvid FROM blob;"
396
"INSERT OR IGNORE INTO rcvidSha1(x)"
397
" SELECT rcvid FROM blob WHERE length(uuid)==40;"
398
"INSERT OR IGNORE INTO rcvidSha3(x)"
399
" SELECT rcvid FROM blob WHERE length(uuid)==64;"
400
);
401
if( db_table_exists("repository","unversioned") ){
402
db_multi_exec(
403
"INSERT OR IGNORE INTO rcvidUsed(x) SELECT rcvid FROM unversioned;"
404
"INSERT OR IGNORE INTO rcvidSha1(x)"
405
" SELECT rcvid FROM unversioned WHERE length(hash)==40;"
406
"INSERT OR IGNORE INTO rcvidSha3(x)"
407
" SELECT rcvid FROM unversioned WHERE length(hash)==64;"
408
);
409
}
410
db_prepare(&q,
411
"SELECT rcvid, login, datetime(rcvfrom.mtime), rcvfrom.ipaddr,"
412
" EXISTS(SELECT 1 FROM rcvidUsed WHERE x=rcvfrom.rcvid),"
413
" EXISTS(SELECT 1 FROM rcvidSha1 WHERE x=rcvfrom.rcvid),"
414
" EXISTS(SELECT 1 FROM rcvidSha3 WHERE x=rcvfrom.rcvid)"
415
" FROM rcvfrom LEFT JOIN user USING(uid)"
416
" ORDER BY rcvid DESC LIMIT %d OFFSET %d",
417
showAll ? -1 : perScreen+1, ofst
418
);
419
@ <p>Whenever new artifacts are added to the repository, either by
420
@ push or using the web interface or by "fossil commit" or similar,
421
@ an entry is made in the RCVFROM table
422
@ to record the source of those artifacts. This log facilitates
423
@ finding and fixing attempts to inject illicit content into the
424
@ repository.</p>
425
@
426
@ <p>Click on the "rcvid" to show a list of specific artifacts received
427
@ by a transaction. After identifying illicit artifacts, remove them
428
@ using the "Shun" button. If an "rcvid" is not hyperlinked, that means
429
@ all artifacts associated with that rcvid have already been shunned
430
@ or purged.</p>
431
@
432
@ <table cellpadding="0" cellspacing="0" border="0">
433
@ <tr><th style="padding-right: 15px;text-align: right;">rcvid</th>
434
@ <th style="padding-right: 15px;text-align: left;">Date</th>
435
@ <th style="padding-right: 15px;text-align: left;">User</th>
436
@ <th style="padding-right: 15px;text-align: left;">Hash</th>
437
@ <th style="text-align: left;">IP&nbsp;Address</th></tr>
438
cnt = 0;
439
while( db_step(&q)==SQLITE_ROW ){
440
int rcvid = db_column_int(&q, 0);
441
const char *zUser = db_column_text(&q, 1);
442
const char *zDate = db_column_text(&q, 2);
443
const char *zIpAddr = db_column_text(&q, 3);
444
int usesSha1 = db_column_int(&q, 5)!=0;
445
int usesSha3 = db_column_int(&q, 6)!=0;
446
static const char *const zHashType[] = { "", "sha1", "sha3", "both" };
447
const char *zHash = zHashType[usesSha1+usesSha3*2];
448
if( cnt==perScreen && !showAll ){
449
style_submenu_element("Older", "rcvfromlist?ofst=%d", ofst+perScreen);
450
}else{
451
cnt++;
452
@ <tr>
453
if( db_column_int(&q,4) ){
454
@ <td style="padding-right: 15px;text-align: right;">
455
@ <a href="rcvfrom?rcvid=%d(rcvid)">%d(rcvid)</a></td>
456
}else{
457
@ <td style="padding-right: 15px;text-align: right;">%d(rcvid)</td>
458
}
459
@ <td style="padding-right: 15px;text-align: left;">%s(zDate)</td>
460
@ <td style="padding-right: 15px;text-align: left;">%h(zUser)</td>
461
@ <td style="padding-right: 15px;text-align: left;">%s(zHash)</td>
462
@ <td style="text-align: left;">%s(zIpAddr)</td>
463
@ </tr>
464
}
465
}
466
db_finalize(&q);
467
@ </table>
468
style_finish_page();
469
}
470
471
/*
472
** WEBPAGE: rcvfrom
473
**
474
** Show a single RCVFROM table entry identified by the rcvid= query
475
** parameters. Requires Admin privilege.
476
*/
477
void rcvfrom_page(void){
478
int rcvid = atoi(PD("rcvid","0"));
479
Stmt q;
480
int cnt;
481
482
login_check_credentials();
483
if( !g.perm.Admin ){
484
login_needed(0);
485
return;
486
}
487
style_header("Artifact Receipt %d", rcvid);
488
if( db_exists(
489
"SELECT 1 FROM blob WHERE rcvid=%d AND"
490
" NOT EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid)
491
){
492
style_submenu_element("Shun All", "shun?shun&rcvid=%d#addshun", rcvid);
493
}
494
if( db_exists(
495
"SELECT 1 FROM blob WHERE rcvid=%d AND"
496
" EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid)
497
){
498
style_submenu_element("Unshun All", "shun?accept&rcvid=%d#delshun", rcvid);
499
}
500
db_prepare(&q,
501
"SELECT login, datetime(rcvfrom.mtime), rcvfrom.ipaddr"
502
" FROM rcvfrom LEFT JOIN user USING(uid)"
503
" WHERE rcvid=%d",
504
rcvid
505
);
506
@ <table cellspacing="15" cellpadding="0" border="0">
507
@ <tr><th valign="top" align="right">rcvid:</th>
508
@ <td valign="top">%d(rcvid)</td></tr>
509
if( db_step(&q)==SQLITE_ROW ){
510
const char *zUser = db_column_text(&q, 0);
511
const char *zDate = db_column_text(&q, 1);
512
const char *zIpAddr = db_column_text(&q, 2);
513
@ <tr><th valign="top" align="right">User:</th>
514
@ <td valign="top">%s(zUser)</td></tr>
515
@ <tr><th valign="top" align="right">Date:</th>
516
@ <td valign="top">%s(zDate)</td></tr>
517
@ <tr><th valign="top" align="right">IP&nbsp;Address:</th>
518
@ <td valign="top">%s(zIpAddr)</td></tr>
519
}
520
db_finalize(&q);
521
db_multi_exec(
522
"CREATE TEMP TABLE toshow(rid INTEGER PRIMARY KEY);"
523
"INSERT INTO toshow SELECT rid FROM blob WHERE rcvid=%d", rcvid
524
);
525
describe_artifacts("IN toshow");
526
db_prepare(&q,
527
"SELECT blob.rid, blob.uuid, blob.size, description.summary\n"
528
" FROM blob LEFT JOIN description ON (blob.rid=description.rid)"
529
" WHERE blob.rcvid=%d", rcvid
530
);
531
cnt = 0;
532
while( db_step(&q)==SQLITE_ROW ){
533
const char *zUuid = db_column_text(&q, 1);
534
int size = db_column_int(&q, 2);
535
const char *zDesc = db_column_text(&q, 3);
536
if( zDesc==0 ) zDesc = "";
537
if( cnt==0 ){
538
@ <tr><th valign="top" align="right">Artifacts:</th>
539
@ <td valign="top">
540
}
541
cnt++;
542
@ <a href="%R/info/%s(zUuid)">%s(zUuid)</a>
543
@ %h(zDesc) (size: %d(size))<br>
544
}
545
if( cnt>0 ){
546
@ <p>
547
if( db_exists(
548
"SELECT 1 FROM blob WHERE rcvid=%d AND"
549
" NOT EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid)
550
){
551
@ <form action='%R/shun'>
552
@ <input type="hidden" name="shun">
553
@ <input type="hidden" name="rcvid" value='%d(rcvid)'>
554
@ <input type="submit" value="Shun All These Artifacts">
555
@ </form>
556
}
557
if( db_exists(
558
"SELECT 1 FROM blob WHERE rcvid=%d AND"
559
" EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid)
560
){
561
@ <form action='%R/shun'>
562
@ <input type="hidden" name="unshun">
563
@ <input type="hidden" name="rcvid" value='%d(rcvid)'>
564
@ <input type="submit" value="Unshun All These Artifacts">
565
@ </form>
566
}
567
@ </td></tr>
568
}
569
if( db_table_exists("repository","unversioned") ){
570
cnt = 0;
571
if( PB("uvdelete") && PB("confirmdelete") ){
572
db_multi_exec(
573
"DELETE FROM unversioned WHERE rcvid=%d", rcvid
574
);
575
}
576
db_finalize(&q);
577
db_prepare(&q,
578
"SELECT name, hash, sz\n"
579
" FROM unversioned "
580
" WHERE rcvid=%d", rcvid
581
);
582
while( db_step(&q)==SQLITE_ROW ){
583
const char *zName = db_column_text(&q,0);
584
const char *zHash = db_column_text(&q,1);
585
int size = db_column_int(&q,2);
586
int isDeleted = zHash==0;
587
if( cnt==0 ){
588
@ <tr><th valign="top" align="right">Unversioned&nbsp;Files:</th>
589
@ <td valign="top">
590
}
591
cnt++;
592
if( isDeleted ){
593
@ %h(zName) (deleted)<br>
594
}else{
595
@ <a href="%R/uv/%h(zName)">%h(zName)</a> (size: %d(size))<br>
596
}
597
}
598
if( cnt>0 ){
599
@ <p><form action='%R/rcvfrom'>
600
@ <input type="hidden" name="rcvid" value='%d(rcvid)'>
601
@ <input type="hidden" name="uvdelete" value="1">
602
if( PB("uvdelete") ){
603
@ <input type="hidden" name="confirmdelete" value="1">
604
@ <input type="submit" value="Confirm Deletion of These Files">
605
}else{
606
@ <input type="submit" value="Delete These Unversioned Files">
607
}
608
@ </form>
609
@ </td></tr>
610
}
611
}
612
@ </table>
613
db_finalize(&q);
614
style_finish_page();
615
}
616

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button