Fossil SCM

fossil-scm / src / attach.c
Blame History Raw 842 lines
1
/*
2
** Copyright (c) 2010 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 for dealing with attachments.
19
*/
20
#include "config.h"
21
#include "attach.h"
22
#include <assert.h>
23
24
/*
25
** WEBPAGE: attachlist
26
** List attachments.
27
**
28
** tkt=HASH
29
** page=WIKIPAGE
30
** technote=HASH
31
**
32
** At most one of technote=, tkt= or page= may be supplied.
33
**
34
** If none are given, all attachments are listed. If one is given, only
35
** attachments for the designated technote, ticket or wiki page are shown.
36
**
37
** HASH may be just a prefix of the relevant technical note or ticket
38
** artifact hash, in which case all attachments of all technical notes or
39
** tickets with the prefix will be listed.
40
*/
41
void attachlist_page(void){
42
const char *zPage = P("page");
43
const char *zTkt = P("tkt");
44
const char *zTechNote = P("technote");
45
Blob sql;
46
Stmt q;
47
48
if( zPage && zTkt ) zTkt = 0;
49
login_check_credentials();
50
style_set_current_feature("attach");
51
blob_zero(&sql);
52
blob_append_sql(&sql,
53
"SELECT datetime(mtime,toLocal()), src, target, filename,"
54
" comment, user,"
55
" (SELECT uuid FROM blob WHERE rid=attachid), attachid,"
56
" (CASE WHEN 'tkt-'||target IN (SELECT tagname FROM tag)"
57
" THEN 1"
58
" WHEN 'event-'||target IN (SELECT tagname FROM tag)"
59
" THEN 2"
60
" ELSE 0 END)"
61
" FROM attachment"
62
);
63
if( zPage ){
64
if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
65
style_header("Attachments To %h", zPage);
66
blob_append_sql(&sql, " WHERE target=%Q", zPage);
67
}else if( zTkt ){
68
if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
69
style_header("Attachments To Ticket %S", zTkt);
70
blob_append_sql(&sql, " WHERE target GLOB '%q*'", zTkt);
71
}else if( zTechNote ){
72
if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
73
style_header("Attachments to Tech Note %S", zTechNote);
74
blob_append_sql(&sql, " WHERE target GLOB '%q*'",
75
zTechNote);
76
}else{
77
if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ){
78
login_needed(g.anon.RdTkt || g.anon.RdWiki);
79
return;
80
}
81
style_header("All Attachments");
82
}
83
blob_append_sql(&sql, " ORDER BY mtime DESC");
84
db_prepare(&q, "%s", blob_sql_text(&sql));
85
@ <ol>
86
while( db_step(&q)==SQLITE_ROW ){
87
const char *zDate = db_column_text(&q, 0);
88
const char *zSrc = db_column_text(&q, 1);
89
const char *zTarget = db_column_text(&q, 2);
90
const char *zFilename = db_column_text(&q, 3);
91
const char *zComment = db_column_text(&q, 4);
92
const char *zUser = db_column_text(&q, 5);
93
const char *zUuid = db_column_text(&q, 6);
94
int attachid = db_column_int(&q, 7);
95
/* type 0 is a wiki page, 1 is a ticket, 2 is a tech note */
96
int type = db_column_int(&q, 8);
97
const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
98
int i;
99
char *zUrlTail;
100
for(i=0; zFilename[i]; i++){
101
if( zFilename[i]=='/' && zFilename[i+1]!=0 ){
102
zFilename = &zFilename[i+1];
103
i = -1;
104
}
105
}
106
if( type==1 ){
107
zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename);
108
}else if( type==2 ){
109
zUrlTail = mprintf("technote=%s&file=%t", zTarget, zFilename);
110
}else{
111
zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename);
112
}
113
@ <li><p>
114
@ Attachment %z(href("%R/ainfo/%!S",zUuid))%S(zUuid)</a>
115
moderation_pending_www(attachid);
116
@ <br><a href="%R/attachview?%s(zUrlTail)">%h(zFilename)</a>
117
@ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br>
118
if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++;
119
if( zComment && zComment[0] ){
120
@ %!W(zComment)<br>
121
}
122
if( zPage==0 && zTkt==0 && zTechNote==0 ){
123
if( zSrc==0 || zSrc[0]==0 ){
124
zSrc = "Deleted from";
125
}else {
126
zSrc = "Added to";
127
}
128
if( type==1 ){
129
@ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)">
130
@ %S(zTarget)</a>
131
}else if( type==2 ){
132
@ %s(zSrc) tech note <a href="%R/technote/%s(zTarget)">
133
@ %S(zTarget)</a>
134
}else{
135
@ %s(zSrc) wiki page <a href="%R/wiki?name=%t(zTarget)">
136
@ %h(zTarget)</a>
137
}
138
}else{
139
if( zSrc==0 || zSrc[0]==0 ){
140
@ Deleted
141
}else {
142
@ Added
143
}
144
}
145
@ by %h(zDispUser) on
146
hyperlink_to_date(zDate, ".");
147
free(zUrlTail);
148
}
149
db_finalize(&q);
150
@ </ol>
151
style_finish_page();
152
return;
153
}
154
155
/*
156
** WEBPAGE: attachdownload
157
** WEBPAGE: attachimage
158
** WEBPAGE: attachview
159
**
160
** Download or display an attachment.
161
**
162
** Query parameters:
163
**
164
** tkt=HASH
165
** page=WIKIPAGE
166
** technote=HASH
167
** file=FILENAME
168
** attachid=ID
169
**
170
*/
171
void attachview_page(void){
172
const char *zPage = P("page");
173
const char *zTkt = P("tkt");
174
const char *zTechNote = P("technote");
175
const char *zFile = P("file");
176
const char *zTarget = 0;
177
int attachid = atoi(PD("attachid","0"));
178
char *zUUID;
179
180
if( zFile==0 ) fossil_redirect_home();
181
login_check_credentials();
182
style_set_current_feature("attach");
183
if( zPage ){
184
if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
185
zTarget = zPage;
186
}else if( zTkt ){
187
if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
188
zTarget = zTkt;
189
}else if( zTechNote ){
190
if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
191
zTarget = zTechNote;
192
}else{
193
fossil_redirect_home();
194
}
195
if( attachid>0 ){
196
zUUID = db_text(0,
197
"SELECT coalesce(src,'x') FROM attachment"
198
" WHERE target=%Q AND attachid=%d",
199
zTarget, attachid
200
);
201
}else{
202
zUUID = db_text(0,
203
"SELECT coalesce(src,'x') FROM attachment"
204
" WHERE target=%Q AND filename=%Q"
205
" ORDER BY mtime DESC LIMIT 1",
206
zTarget, zFile
207
);
208
}
209
if( zUUID==0 || zUUID[0]==0 ){
210
style_header("No Such Attachment");
211
@ No such attachment....
212
style_finish_page();
213
return;
214
}else if( zUUID[0]=='x' ){
215
style_header("Missing");
216
@ Attachment has been deleted
217
style_finish_page();
218
return;
219
}else{
220
g.perm.Read = 1;
221
cgi_replace_parameter("name",zUUID);
222
if( fossil_strcmp(g.zPath,"attachview")==0 ){
223
artifact_page();
224
}else{
225
cgi_replace_parameter("m", mimetype_from_name(zFile));
226
rawartifact_page();
227
}
228
}
229
}
230
231
/*
232
** Save an attachment control artifact into the repository
233
*/
234
static void attach_put(
235
Blob *pAttach, /* Text of the Attachment record */
236
int attachRid, /* RID for the file that is being attached */
237
int needMod /* True if the attachment is subject to moderation */
238
){
239
int rid;
240
if( needMod ){
241
rid = content_put_ex(pAttach, 0, 0, 0, 1);
242
moderation_table_create();
243
db_multi_exec(
244
"INSERT INTO modreq(objid,attachRid) VALUES(%d,%d);",
245
rid, attachRid
246
);
247
}else{
248
rid = content_put(pAttach);
249
db_add_unsent(rid);
250
db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid);
251
}
252
manifest_crosslink(rid, pAttach, MC_NONE);
253
}
254
255
256
/*
257
** Commit a new attachment into the repository
258
*/
259
void attach_commit(
260
const char *zName, /* The filename of the attachment */
261
const char *zTarget, /* The artifact hash to attach to */
262
const char *aContent, /* The content of the attachment */
263
int szContent, /* The length of the attachment */
264
int needModerator, /* Moderate the attachment? */
265
const char *zComment /* The comment for the attachment */
266
){
267
Blob content;
268
Blob manifest;
269
Blob cksum;
270
char *zUUID;
271
char *zDate;
272
int rid;
273
int i, n;
274
int addCompress = 0;
275
Manifest *pManifest;
276
277
db_begin_transaction();
278
blob_init(&content, aContent, szContent);
279
pManifest = manifest_parse(&content, 0, 0);
280
manifest_destroy(pManifest);
281
blob_init(&content, aContent, szContent);
282
if( pManifest ){
283
blob_compress(&content, &content);
284
addCompress = 1;
285
}
286
rid = content_put_ex(&content, 0, 0, 0, needModerator);
287
zUUID = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
288
blob_zero(&manifest);
289
for(i=n=0; zName[i]; i++){
290
if( zName[i]=='/' || zName[i]=='\\' ) n = i+1;
291
}
292
zName += n;
293
if( zName[0]==0 ) zName = "unknown";
294
blob_appendf(&manifest, "A %F%s %F %s\n",
295
zName, addCompress ? ".gz" : "", zTarget, zUUID);
296
while( fossil_isspace(zComment[0]) ) zComment++;
297
n = strlen(zComment);
298
while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
299
if( n>0 ){
300
blob_appendf(&manifest, "C %#F\n", n, zComment);
301
}
302
zDate = date_in_standard_format("now");
303
blob_appendf(&manifest, "D %s\n", zDate);
304
blob_appendf(&manifest, "U %F\n", login_name());
305
md5sum_blob(&manifest, &cksum);
306
blob_appendf(&manifest, "Z %b\n", &cksum);
307
attach_put(&manifest, rid, needModerator);
308
assert( blob_is_reset(&manifest) );
309
db_end_transaction(0);
310
}
311
312
/*
313
** WEBPAGE: attachadd
314
** Add a new attachment.
315
**
316
** tkt=HASH
317
** page=WIKIPAGE
318
** technote=HASH
319
** from=URL
320
**
321
*/
322
void attachadd_page(void){
323
const char *zPage = P("page");
324
const char *zTkt = P("tkt");
325
const char *zTechNote = P("technote");
326
const char *zFrom = P("from");
327
const char *aContent = P("f");
328
const char *zName = PD("f:filename","unknown");
329
const char *zTarget;
330
char *zTargetType;
331
int szContent = atoi(PD("f:bytes","0"));
332
int goodCaptcha = 1;
333
334
if( P("cancel") ) cgi_redirect(zFrom);
335
if( (zPage && zTkt)
336
|| (zPage && zTechNote)
337
|| (zTkt && zTechNote)
338
){
339
fossil_redirect_home();
340
}
341
if( zPage==0 && zTkt==0 && zTechNote==0) fossil_redirect_home();
342
login_check_credentials();
343
if( zPage ){
344
if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){
345
login_needed(g.anon.ApndWiki && g.anon.Attach);
346
return;
347
}
348
if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){
349
fossil_redirect_home();
350
}
351
zTarget = zPage;
352
zTargetType = mprintf("Wiki Page <a href=\"%R/wiki?name=%h\">%h</a>",
353
zPage, zPage);
354
}else if ( zTechNote ){
355
if( g.perm.Write==0 || g.perm.ApndWiki==0 || g.perm.Attach==0 ){
356
login_needed(g.anon.Write && g.anon.ApndWiki && g.anon.Attach);
357
return;
358
}
359
if( !db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'", zTechNote) ){
360
zTechNote = db_text(0, "SELECT substr(tagname,7) FROM tag"
361
" WHERE tagname GLOB 'event-%q*'", zTechNote);
362
if( zTechNote==0) fossil_redirect_home();
363
}
364
zTarget = zTechNote;
365
zTargetType = mprintf("Tech Note <a href=\"%R/technote/%s\">%S</a>",
366
zTechNote, zTechNote);
367
368
}else{
369
if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){
370
login_needed(g.anon.ApndTkt && g.anon.Attach);
371
return;
372
}
373
if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){
374
zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag"
375
" WHERE tagname GLOB 'tkt-%q*'", zTkt);
376
if( zTkt==0 ) fossil_redirect_home();
377
}
378
zTarget = zTkt;
379
zTargetType = mprintf("Ticket <a href=\"%R/tktview/%s\">%S</a>",
380
zTkt, zTkt);
381
}
382
if( zFrom==0 ) zFrom = mprintf("%R/home");
383
if( P("cancel") ){
384
cgi_redirect(zFrom);
385
}
386
if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct(0)) ){
387
int needModerator = (zTkt!=0 && ticket_need_moderation(0)) ||
388
(zPage!=0 && wiki_need_moderation(0));
389
const char *zComment = PD("comment", "");
390
attach_commit(zName, zTarget, aContent, szContent, needModerator, zComment);
391
cgi_redirect(zFrom);
392
}
393
style_set_current_feature("attach");
394
style_header("Add Attachment");
395
if( !goodCaptcha ){
396
@ <p class="generalError">Error: Incorrect security code.</p>
397
}
398
@ <h2>Add Attachment To %s(zTargetType)</h2>
399
form_begin("enctype='multipart/form-data'", "%R/attachadd");
400
@ <div>
401
@ File to Attach:
402
@ <input type="file" name="f" size="60"><br>
403
@ Description:<br>
404
@ <textarea name="comment" cols="80" rows="5" wrap="virtual"></textarea><br>
405
if( zTkt ){
406
@ <input type="hidden" name="tkt" value="%h(zTkt)">
407
}else if( zTechNote ){
408
@ <input type="hidden" name="technote" value="%h(zTechNote)">
409
}else{
410
@ <input type="hidden" name="page" value="%h(zPage)">
411
}
412
@ <input type="hidden" name="from" value="%h(zFrom)">
413
@ <input type="submit" name="ok" value="Add Attachment">
414
@ <input type="submit" name="cancel" value="Cancel">
415
@ </div>
416
captcha_generate(0);
417
@ </form>
418
style_finish_page();
419
fossil_free(zTargetType);
420
}
421
422
/*
423
** WEBPAGE: ainfo
424
** URL: /ainfo?name=ARTIFACTID
425
**
426
** Show the details of an attachment artifact.
427
*/
428
void ainfo_page(void){
429
int rid; /* RID for the control artifact */
430
int ridSrc; /* RID for the attached file */
431
char *zDate; /* Date attached */
432
const char *zUuid; /* Hash of the control artifact */
433
Manifest *pAttach; /* Parse of the control artifact */
434
const char *zTarget; /* Wiki, ticket or tech note attached to */
435
const char *zSrc; /* Hash of the attached file */
436
const char *zName; /* Name of the attached file */
437
const char *zDesc; /* Description of the attached file */
438
const char *zWikiName = 0; /* Wiki page name when attached to Wiki */
439
const char *zTNUuid = 0; /* Tech Note ID when attached to tech note */
440
const char *zTktUuid = 0; /* Ticket ID when attached to a ticket */
441
int modPending; /* True if awaiting moderation */
442
const char *zModAction; /* Moderation action or NULL */
443
int isModerator; /* TRUE if user is the moderator */
444
const char *zMime; /* MIME Type */
445
Blob attach; /* Content of the attachment */
446
int fShowContent = 0;
447
const char *zLn = P("ln");
448
449
login_check_credentials();
450
if( !g.perm.RdTkt && !g.perm.RdWiki ){
451
login_needed(g.anon.RdTkt || g.anon.RdWiki);
452
return;
453
}
454
rid = name_to_rid_www("name");
455
if( rid==0 ){ fossil_redirect_home(); }
456
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
457
pAttach = manifest_get(rid, CFTYPE_ATTACHMENT, 0);
458
if( pAttach==0 ) fossil_redirect_home();
459
zTarget = pAttach->zAttachTarget;
460
zSrc = pAttach->zAttachSrc;
461
ridSrc = db_int(0,"SELECT rid FROM blob WHERE uuid='%q'", zSrc);
462
zName = pAttach->zAttachName;
463
zDesc = pAttach->zComment;
464
zMime = mimetype_from_name(zName);
465
fShowContent = zMime ? strncmp(zMime,"text/", 5)==0 : 0;
466
if( validate16(zTarget, strlen(zTarget))
467
&& db_exists("SELECT 1 FROM ticket WHERE tkt_uuid='%q'", zTarget)
468
){
469
zTktUuid = zTarget;
470
if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
471
if( g.perm.WrTkt ){
472
style_submenu_element("Delete", "%R/ainfo/%s?del", zUuid);
473
}
474
}else if( db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'",zTarget) ){
475
zWikiName = zTarget;
476
if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
477
if( g.perm.WrWiki ){
478
style_submenu_element("Delete", "%R/ainfo/%s?del", zUuid);
479
}
480
}else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",zTarget) ){
481
zTNUuid = zTarget;
482
if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
483
if( g.perm.Write && g.perm.WrWiki ){
484
style_submenu_element("Delete", "%R/ainfo/%s?del", zUuid);
485
}
486
}
487
zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate);
488
489
if( P("confirm")
490
&& ((zTktUuid && g.perm.WrTkt) ||
491
(zWikiName && g.perm.WrWiki) ||
492
(zTNUuid && g.perm.Write && g.perm.WrWiki))
493
){
494
int i, n, rid;
495
char *zDate;
496
Blob manifest;
497
Blob cksum;
498
const char *zFile = zName;
499
500
db_begin_transaction();
501
blob_zero(&manifest);
502
for(i=n=0; zFile[i]; i++){
503
if( zFile[i]=='/' || zFile[i]=='\\' ) n = i;
504
}
505
zFile += n;
506
if( zFile[0]==0 ) zFile = "unknown";
507
blob_appendf(&manifest, "A %F %F\n", zFile, zTarget);
508
zDate = date_in_standard_format("now");
509
blob_appendf(&manifest, "D %s\n", zDate);
510
blob_appendf(&manifest, "U %F\n", login_name());
511
md5sum_blob(&manifest, &cksum);
512
blob_appendf(&manifest, "Z %b\n", &cksum);
513
rid = content_put(&manifest);
514
manifest_crosslink(rid, &manifest, MC_NONE);
515
db_end_transaction(0);
516
@ <p>The attachment below has been deleted.</p>
517
}
518
519
if( P("del")
520
&& ((zTktUuid && g.perm.WrTkt) ||
521
(zWikiName && g.perm.WrWiki) ||
522
(zTNUuid && g.perm.Write && g.perm.WrWiki))
523
){
524
form_begin(0, "%R/ainfo/%!S", zUuid);
525
@ <p>Confirm you want to delete the attachment shown below.
526
@ <input type="submit" name="confirm" value="Confirm">
527
@ </form>
528
}
529
530
isModerator = g.perm.Admin ||
531
(zTktUuid && g.perm.ModTkt) ||
532
(zWikiName && g.perm.ModWiki);
533
if( isModerator && (zModAction = P("modaction"))!=0 ){
534
if( strcmp(zModAction,"delete")==0 ){
535
moderation_disapprove(rid);
536
if( zTktUuid ){
537
cgi_redirectf("%R/tktview/%!S", zTktUuid);
538
}else{
539
cgi_redirectf("%R/wiki?name=%t", zWikiName);
540
}
541
return;
542
}
543
if( strcmp(zModAction,"approve")==0 ){
544
moderation_approve('a', rid);
545
}
546
}
547
style_set_current_feature("attach");
548
style_header("Attachment Details");
549
style_submenu_element("Raw", "%R/artifact/%s", zUuid);
550
if(fShowContent){
551
style_submenu_element("Line Numbers", "%R/ainfo/%s%s", zUuid,
552
((zLn&&*zLn) ? "" : "?ln=0"));
553
}
554
555
@ <div class="section">Overview</div>
556
@ <p><table class="label-value">
557
@ <tr><th>Artifact&nbsp;ID:</th>
558
@ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
559
if( g.perm.Setup ){
560
@ (%d(rid))
561
}
562
modPending = moderation_pending_www(rid);
563
if( zTktUuid ){
564
@ <tr><th>Ticket:</th>
565
@ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr>
566
}
567
if( zTNUuid ){
568
@ <tr><th>Tech Note:</th>
569
@ <td>%z(href("%R/technote/%s",zTNUuid))%s(zTNUuid)</a></td></tr>
570
}
571
if( zWikiName ){
572
@ <tr><th>Wiki&nbsp;Page:</th>
573
@ <td>%z(href("%R/wiki?name=%t",zWikiName))%h(zWikiName)</a></td></tr>
574
}
575
@ <tr><th>Date:</th><td>
576
hyperlink_to_date(zDate, "</td></tr>");
577
@ <tr><th>User:</th><td>
578
hyperlink_to_user(pAttach->zUser, zDate, "</td></tr>");
579
@ <tr><th>Artifact&nbsp;Attached:</th>
580
@ <td>%z(href("%R/artifact/%s",zSrc))%s(zSrc)</a>
581
if( g.perm.Setup ){
582
@ (%d(ridSrc))
583
}
584
@ <tr><th>Filename:</th><td>%h(zName)</td></tr>
585
if( g.perm.Setup ){
586
@ <tr><th>MIME-Type:</th><td>%h(zMime)</td></tr>
587
}
588
@ <tr><th valign="top">Description:</th><td valign="top">%h(zDesc)</td></tr>
589
@ </table>
590
591
if( isModerator && modPending ){
592
@ <div class="section">Moderation</div>
593
@ <blockquote>
594
form_begin(0, "%R/ainfo/%s", zUuid);
595
@ <label><input type="radio" name="modaction" value="delete">
596
@ Delete this change</label><br>
597
@ <label><input type="radio" name="modaction" value="approve">
598
@ Approve this change</label><br>
599
@ <input type="submit" value="Submit">
600
@ </form>
601
@ </blockquote>
602
}
603
604
@ <div class="section">Content Appended</div>
605
@ <blockquote>
606
blob_zero(&attach);
607
if( fShowContent ){
608
const char *z;
609
content_get(ridSrc, &attach);
610
blob_to_utf8_no_bom(&attach, 0);
611
z = blob_str(&attach);
612
if( zLn ){
613
output_text_with_line_numbers(z, blob_size(&attach), zName, zLn, 1);
614
}else{
615
@ <pre>
616
@ %h(z)
617
@ </pre>
618
}
619
}else if( strncmp(zMime, "image/", 6)==0 ){
620
int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc);
621
@ <i>(file is %d(sz) bytes of image data)</i><br>
622
@ <img src="%R/raw/%s(zSrc)?m=%s(zMime)"></img>
623
style_submenu_element("Image", "%R/raw/%s?m=%s", zSrc, zMime);
624
}else{
625
int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc);
626
@ <i>(file is %d(sz) bytes of binary data)</i>
627
}
628
@ </blockquote>
629
manifest_destroy(pAttach);
630
blob_reset(&attach);
631
style_finish_page();
632
}
633
634
/*
635
** Output HTML to show a list of attachments.
636
*/
637
void attachment_list(
638
const char *zTarget, /* Object that things are attached to */
639
const char *zHeader, /* Header to display with attachments */
640
int fHorizontalRule /* Insert <hr> separator above header */
641
){
642
int cnt = 0;
643
Stmt q;
644
db_prepare(&q,
645
"SELECT datetime(mtime,toLocal()), filename, user,"
646
" (SELECT uuid FROM blob WHERE rid=attachid), src"
647
" FROM attachment"
648
" WHERE isLatest AND src!='' AND target=%Q"
649
" ORDER BY mtime DESC",
650
zTarget
651
);
652
while( db_step(&q)==SQLITE_ROW ){
653
const char *zDate = db_column_text(&q, 0);
654
const char *zFile = db_column_text(&q, 1);
655
const char *zUser = db_column_text(&q, 2);
656
const char *zUuid = db_column_text(&q, 3);
657
const char *zSrc = db_column_text(&q, 4);
658
const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
659
if( cnt==0 ){
660
@ <section class='attachlist'>
661
if( fHorizontalRule ){
662
@ <hr>
663
}
664
@ %s(zHeader)
665
@ <ul>
666
}
667
cnt++;
668
@ <li>
669
@ %z(href("%R/artifact/%!S",zSrc))%h(zFile)</a>
670
@ [<a href="%R/attachdownload/%t(zFile)?page=%t(zTarget)&file=%t(zFile)">download</a>]
671
@ added by %h(zDispUser) on
672
hyperlink_to_date(zDate, ".");
673
@ [%z(href("%R/ainfo/%!S",zUuid))details</a>]
674
@ </li>
675
}
676
if( cnt ){
677
@ </ul>
678
@ </section>
679
}
680
db_finalize(&q);
681
682
}
683
684
/*
685
** COMMAND: attachment*
686
**
687
** Usage: %fossil attachment add ?PAGENAME? FILENAME ?OPTIONS?
688
**
689
** Add an attachment to an existing wiki page or tech note.
690
**
691
** Options:
692
** -t|--technote DATETIME Specifies the timestamp of
693
** the technote to which the attachment
694
** is to be made. The attachment will be
695
** to the most recently modified tech note
696
** with the specified timestamp.
697
** -t|--technote TECHNOTE-ID Specifies the technote to be
698
** updated by its technote id
699
**
700
** One of PAGENAME, DATETIME or TECHNOTE-ID must be specified.
701
**
702
** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in
703
** year-month-day form, it may be truncated, the "T" may be replaced by
704
** a space, and it may also name a timezone offset from UTC as "-HH:MM"
705
** (westward) or "+HH:MM" (eastward). Either no timezone suffix or "Z"
706
** means UTC.
707
*/
708
void attachment_cmd(void){
709
int n;
710
db_find_and_open_repository(0, 0);
711
if( g.argc<3 ){
712
goto attachment_cmd_usage;
713
}
714
n = strlen(g.argv[2]);
715
if( n==0 ){
716
goto attachment_cmd_usage;
717
}
718
719
if( strncmp(g.argv[2],"add",n)==0 ){
720
const char *zPageName = 0; /* Name of the wiki page to attach to */
721
const char *zFile; /* Name of the file to be attached */
722
const char *zETime; /* The name of the technote to attach to */
723
Manifest *pWiki = 0; /* Parsed wiki page content */
724
char *zBody = 0; /* Wiki page content */
725
int rid;
726
const char *zTarget; /* Target of the attachment */
727
Blob content; /* The content of the attachment */
728
zETime = find_option("technote","t",1);
729
if( !zETime ){
730
if( g.argc!=5 ){
731
usage("add PAGENAME FILENAME");
732
}
733
zPageName = g.argv[3];
734
rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
735
" WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
736
" ORDER BY x.mtime DESC LIMIT 1",
737
zPageName
738
);
739
if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
740
zBody = pWiki->zWiki;
741
}
742
if( zBody==0 ){
743
fossil_fatal("wiki page [%s] not found",zPageName);
744
}
745
zTarget = zPageName;
746
zFile = g.argv[4];
747
}else{
748
if( g.argc!=4 ){
749
usage("add FILENAME --technote DATETIME|TECHNOTE-ID");
750
}
751
rid = wiki_technote_to_rid(zETime);
752
if( rid<0 ){
753
fossil_fatal("ambiguous tech note id: %s", zETime);
754
}
755
if( (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0 ){
756
zBody = pWiki->zWiki;
757
}
758
if( zBody==0 ){
759
fossil_fatal("technote [%s] not found",zETime);
760
}
761
zTarget = db_text(0,
762
"SELECT substr(tagname,7) FROM tag "
763
" WHERE tagid=(SELECT tagid FROM event WHERE objid='%d')",
764
rid
765
);
766
zFile = g.argv[3];
767
}
768
blob_read_from_file(&content, zFile, ExtFILE);
769
user_select();
770
attach_commit(
771
zFile, /* The filename of the attachment */
772
zTarget, /* The artifact hash to attach to */
773
blob_buffer(&content), /* The content of the attachment */
774
blob_size(&content), /* The length of the attachment */
775
0, /* No need to moderate the attachment */
776
"" /* Empty attachment comment */
777
);
778
if( !zETime ){
779
fossil_print("Attached %s to wiki page %s.\n", zFile, zPageName);
780
}else{
781
fossil_print("Attached %s to tech note %s.\n", zFile, zETime);
782
}
783
}else{
784
goto attachment_cmd_usage;
785
}
786
return;
787
788
attachment_cmd_usage:
789
usage("add ?PAGENAME? FILENAME [-t|--technote DATETIME ]");
790
}
791
792
793
/*
794
** COMMAND: test-list-attachments
795
**
796
** Usage: %fossil test-list-attachments ?-latest? ?TargetName(s)...?
797
**
798
** List attachments for one or more attachment targets. The target
799
** name arguments are glob prefixes for the attachment.target
800
** field. If no names are provided then a prefix of [a-zA-Z] is used,
801
** which will match most wiki page names and some ticket hashes.
802
**
803
** Options:
804
** -latest List only the latest version of a given attachment
805
**
806
*/
807
void test_list_attachments(void){
808
Stmt q;
809
int i;
810
const int fLatest = find_option("latest", 0, 0) != 0;
811
812
db_find_and_open_repository(0, 0);
813
verify_all_options();
814
db_prepare(&q,
815
"SELECT datetime(mtime,toLocal()), src, target, filename,"
816
" comment, user "
817
" FROM attachment"
818
" WHERE target GLOB :tgtname ||'*'"
819
" AND (isLatest OR %d)"
820
" ORDER BY target, isLatest DESC, mtime DESC",
821
!fLatest
822
);
823
if(g.argc<3){
824
static char * argv[3] = {0,0,"[a-zA-Z]"};
825
g.argc = 3;
826
g.argv = argv;
827
}
828
for(i = 2; i < g.argc; ++i){
829
const char *zPage = g.argv[i];
830
db_bind_text(&q, ":tgtname", zPage);
831
while(SQLITE_ROW == db_step(&q)){
832
const char * zTime = db_column_text(&q, 0);
833
const char * zSrc = db_column_text(&q, 1);
834
const char * zTarget = db_column_text(&q, 2);
835
const char * zName = db_column_text(&q, 3);
836
printf("%-20s %s %.12s %s\n", zTarget, zTime, zSrc, zName);
837
}
838
db_reset(&q);
839
}
840
db_finalize(&q);
841
}
842

Keyboard Shortcuts

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