/* ** Copyright (c) 2010 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file contains code for dealing with attachments. */ #include "config.h" #include "attach.h" #include /* ** WEBPAGE: attachlist ** ** tkt=TICKETUUID ** page=WIKIPAGE ** ** List attachments. ** Either one of tkt= or page= are supplied or neither. If neither ** are given, all attachments are listed. If one is given, only ** attachments for the designated ticket or wiki page are shown. ** TICKETUUID must be complete */ void attachlist_page(void){ const char *zPage = P("page"); const char *zTkt = P("tkt"); Blob sql; Stmt q; if( zPage && zTkt ) zTkt = 0; login_check_credentials(); blob_zero(&sql); blob_append(&sql, "SELECT datetime(mtime,'localtime'), src, target, filename," " comment, user," " (SELECT uuid FROM blob WHERE rid=attachid), attachid" " FROM attachment", -1 ); if( zPage ){ if( g.perm.RdWiki==0 ) login_needed(); style_header("Attachments To %h", zPage); blob_appendf(&sql, " WHERE target=%Q", zPage); }else if( zTkt ){ if( g.perm.RdTkt==0 ) login_needed(); style_header("Attachments To Ticket %.10s", zTkt); blob_appendf(&sql, " WHERE target GLOB '%q*'", zTkt); }else{ if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ) login_needed(); style_header("All Attachments"); } blob_appendf(&sql, " ORDER BY mtime DESC"); db_prepare(&q, "%s", blob_str(&sql)); @
    while( db_step(&q)==SQLITE_ROW ){ const char *zDate = db_column_text(&q, 0); const char *zSrc = db_column_text(&q, 1); const char *zTarget = db_column_text(&q, 2); const char *zFilename = db_column_text(&q, 3); const char *zComment = db_column_text(&q, 4); const char *zUser = db_column_text(&q, 5); const char *zUuid = db_column_text(&q, 6); int attachid = db_column_int(&q, 7); int i; char *zUrlTail; for(i=0; zFilename[i]; i++){ if( zFilename[i]=='/' && zFilename[i+1]!=0 ){ zFilename = &zFilename[i+1]; i = -1; } } if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){ zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename); }else{ zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename); } @
  1. @ Attachment %z(href("%R/ainfo/%s",zUuid))%S(zUuid) if( moderation_pending(attachid) ){ @ *** Awaiting Moderator Approval *** } @
    %h(zFilename) @ [download]
    if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++; if( zComment && zComment[0] ){ @ %w(zComment)
    } if( zPage==0 && zTkt==0 ){ if( zSrc==0 || zSrc[0]==0 ){ zSrc = "Deleted from"; }else { zSrc = "Added to"; } if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){ @ %s(zSrc) ticket @ %S(zTarget) }else{ @ %s(zSrc) wiki page @ %h(zTarget) } }else{ if( zSrc==0 || zSrc[0]==0 ){ @ Deleted }else { @ Added } } @ by %h(zUser) on hyperlink_to_date(zDate, "."); free(zUrlTail); } db_finalize(&q); @

style_footer(); return; } /* ** WEBPAGE: attachdownload ** WEBPAGE: attachimage ** WEBPAGE: attachview ** ** tkt=TICKETUUID ** page=WIKIPAGE ** file=FILENAME ** attachid=ID ** ** List attachments. */ void attachview_page(void){ const char *zPage = P("page"); const char *zTkt = P("tkt"); const char *zFile = P("file"); const char *zTarget = 0; int attachid = atoi(PD("attachid","0")); char *zUUID; if( zPage && zTkt ) zTkt = 0; if( zFile==0 ) fossil_redirect_home(); login_check_credentials(); if( zPage ){ if( g.perm.RdWiki==0 ) login_needed(); zTarget = zPage; }else if( zTkt ){ if( g.perm.RdTkt==0 ) login_needed(); zTarget = zTkt; }else{ fossil_redirect_home(); } if( attachid>0 ){ zUUID = db_text(0, "SELECT coalesce(src,'x') FROM attachment" " WHERE target=%Q AND attachid=%d", zTarget, attachid ); }else{ zUUID = db_text(0, "SELECT coalesce(src,'x') FROM attachment" " WHERE target=%Q AND filename=%Q" " ORDER BY mtime DESC LIMIT 1", zTarget, zFile ); } if( zUUID==0 || zUUID[0]==0 ){ style_header("No Such Attachment"); @ No such attachment.... style_footer(); return; }else if( zUUID[0]=='x' ){ style_header("Missing"); @ Attachment has been deleted style_footer(); return; } g.perm.Read = 1; cgi_replace_parameter("name",zUUID); if( fossil_strcmp(g.zPath,"attachview")==0 ){ artifact_page(); }else{ cgi_replace_parameter("m", mimetype_from_name(zFile)); rawartifact_page(); } } /* ** Save an attachment control artifact into the repository */ static void attach_put(Blob *pAttach, int attachRid, int needMod){ int rid; if( needMod ){ rid = content_put_ex(pAttach, 0, 0, 0, 1); moderation_table_create(); db_multi_exec( "INSERT INTO modreq(objid,attachRid) VALUES(%d,%d);", rid, attachRid ); }else{ content_put(pAttach); } manifest_crosslink(rid, pAttach); } /* ** WEBPAGE: attachadd ** ** tkt=TICKETUUID ** page=WIKIPAGE ** from=URL ** ** Add a new attachment. */ void attachadd_page(void){ const char *zPage = P("page"); const char *zTkt = P("tkt"); const char *zFrom = P("from"); const char *aContent = P("f"); const char *zName = PD("f:filename","unknown"); const char *zTarget; const char *zTargetType; int szContent = atoi(PD("f:bytes","0")); if( P("cancel") ) cgi_redirect(zFrom); if( zPage && zTkt ) fossil_redirect_home(); if( zPage==0 && zTkt==0 ) fossil_redirect_home(); login_check_credentials(); if( zPage ){ if( g.perm.ApndWiki==0 || g.perm.Attach==0 ) login_needed(); if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){ fossil_redirect_home(); } zTarget = zPage; zTargetType = mprintf("Wiki Page %h", g.zTop, zPage, zPage); }else{ if( g.perm.ApndTkt==0 || g.perm.Attach==0 ) login_needed(); if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){ zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag" " WHERE tagname GLOB 'tkt-%q*'", zTkt); if( zTkt==0 ) fossil_redirect_home(); } zTarget = zTkt; zTargetType = mprintf("Ticket %S", g.zTop, zTkt, zTkt); } if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop); if( P("cancel") ){ cgi_redirect(zFrom); } if( P("ok") && szContent>0 ){ Blob content; Blob manifest; Blob cksum; char *zUUID; const char *zComment; char *zDate; int rid; int i, n; int addCompress = 0; Manifest *pManifest; int isModerator; db_begin_transaction(); blob_init(&content, aContent, szContent); pManifest = manifest_parse(&content, 0, 0); manifest_destroy(pManifest); blob_init(&content, aContent, szContent); if( pManifest ){ blob_compress(&content, &content); addCompress = 1; } isModerator = (zTkt!=0 && g.perm.ModTkt) || (zPage!=0 && g.perm.ModWiki); rid = content_put_ex(&content, 0, 0, 0, !isModerator); zUUID = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); blob_zero(&manifest); for(i=n=0; zName[i]; i++){ if( zName[i]=='/' || zName[i]=='\\' ) n = i; } zName += n; if( zName[0]==0 ) zName = "unknown"; blob_appendf(&manifest, "A %F%s %F %s\n", zName, addCompress ? ".gz" : "", zTarget, zUUID); zComment = PD("comment", ""); while( fossil_isspace(zComment[0]) ) zComment++; n = strlen(zComment); while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; } if( n>0 ){ blob_appendf(&manifest, "C %F\n", zComment); } zDate = date_in_standard_format("now"); blob_appendf(&manifest, "D %s\n", zDate); blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody"); md5sum_blob(&manifest, &cksum); blob_appendf(&manifest, "Z %b\n", &cksum); attach_put(&manifest, rid, !isModerator); assert( blob_is_reset(&manifest) ); db_end_transaction(0); cgi_redirect(zFrom); } style_header("Add Attachment"); @

Add Attachment To %s(zTargetType)

@
@ File to Attach: @
@ Description:
@
if( zTkt ){ @ }else{ @ } @ @ @ @
style_footer(); } /* ** WEBPAGE: attachdelete ** ** tkt=TICKETUUID ** page=WIKIPAGE ** file=FILENAME ** ** "Delete" an attachment. Because objects in Fossil are immutable ** the attachment isn't really deleted. Instead, we change the content ** of the attachment to NULL, which the system understands as being ** deleted. Historical values of the attachment are preserved. */ void attachdel_page(void){ const char *zPage = P("page"); const char *zTkt = P("tkt"); const char *zFile = P("file"); const char *zFrom = P("from"); const char *zTarget; if( zPage && zTkt ) fossil_redirect_home(); if( zPage==0 && zTkt==0 ) fossil_redirect_home(); if( zFile==0 ) fossil_redirect_home(); login_check_credentials(); if( zPage ){ if( g.perm.WrWiki==0 || g.perm.Attach==0 ) login_needed(); zTarget = zPage; }else{ if( g.perm.WrTkt==0 || g.perm.Attach==0 ) login_needed(); zTarget = zTkt; } if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop); if( P("cancel") ){ cgi_redirect(zFrom); } if( P("confirm") ){ int i, n, rid; char *zDate; Blob manifest; Blob cksum; db_begin_transaction(); blob_zero(&manifest); for(i=n=0; zFile[i]; i++){ if( zFile[i]=='/' || zFile[i]=='\\' ) n = i; } zFile += n; if( zFile[0]==0 ) zFile = "unknown"; blob_appendf(&manifest, "A %F %F\n", zFile, zTarget); zDate = date_in_standard_format("now"); blob_appendf(&manifest, "D %s\n", zDate); blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody"); md5sum_blob(&manifest, &cksum); blob_appendf(&manifest, "Z %b\n", &cksum); rid = content_put(&manifest); manifest_crosslink(rid, &manifest); db_end_transaction(0); cgi_redirect(zFrom); } style_header("Delete Attachment"); @
@

Confirm that you want to delete the attachment named @ "%h(zFile)" on %s(zTkt?"ticket":"wiki page") %h(zTarget):

if( zTkt ){ @ }else{ @ } @ @ @ @ @
style_footer(); }