/* ** Copyright (c) 2002 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public ** License as published by the Free Software Foundation; either ** version 2 of the License, or (at your option) any later version. ** ** 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. See the GNU ** General Public License for more details. ** ** You should have received a copy of the GNU General Public ** License along with this library; if not, write to the ** Free Software Foundation, Inc., 59 Temple Place - Suite 330, ** Boston, MA 02111-1307, USA. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** Code for handling attachments */ #include "config.h" #include "attach.h" #include /* ** The default maximum size of an attachment. This value can be changed ** by the setup users. */ #define MX_ATTACH_SIZE "102400" /* ** Return the maximum size of an attachment in bytes */ int attachment_max(void){ return atoi(db_config("max_attach_size",MX_ATTACH_SIZE)); } static int is_integer(const char *zString){ if( zString==0 ) return 0; while( *zString ){ if( !isdigit(*zString) ) return 0; zString ++; } return 1; } /* ** WEBPAGE: /attach_add ** ** This web-page gives the user the opportunity to add an attachment to ** an existing ticket. The "tn" query parameter specifies the ticket ** number. A tn of zero (an invalid ticket number) means attachments to ** the setup page (i.e. stylesheets, logos, etc). ** ** This routine has been extended so that "tn" can now be the name of ** a Wiki page. This allows attachments to be added to wiki. */ void attachment_add(void){ const char *zPage; char *zTitle = NULL; char *zErr = 0; const char *zBack; int mxSize = attachment_max(); int tn = atoi(PD("tn","0")); int isHome = 0; zPage = P("tn"); if( zPage==0 ){ common_err("Invalid or missing \"tn\" query parameter"); } login_check_credentials(); throttle(1,1); if( is_integer(zPage) ){ if( tn ){ zBack = mprintf("tktview?tn=%d", tn); if( !g.okWrite ){ cgi_redirect(zBack); } }else{ zBack = "setup_style"; if( !g.okSetup ){ cgi_redirect("index"); } } }else{ isHome = is_user_page(zPage) && is_home_page(zPage); if( !isHome && is_wiki_name(zPage)!=strlen(zPage) ){ common_err("Invalid wiki page name \"tn=%h\"", zPage); } zBack = mprintf("wiki?p=%t", zPage); if( !g.okWiki ){ cgi_redirect(zBack); } } common_add_help_item("CvstracAttachment"); common_add_action_item(zBack, "Cancel"); if( P("can") || mxSize<=0 ){ cgi_redirect(zBack); } if( P("ok") ){ const char *zData = P("f"); const char *zDescription = PD("d",""); const char *zName = P("f:filename"); int size = atoi(PD("f:bytes","0")); const char *zType = PD("f:mimetype","text/plain"); const char *z; time_t now = time(0); char **az; int atn; if( zData==0 || zName==0 || size==0 || zType==0 ){ common_err("Attachment information is missing from the query content"); } if( size>mxSize ){ zErr = mprintf("Your attachment is too big. The maximum allowed size " "is %dKB but your attachment was %dKB", mxSize/1024, (size+1023)/1024); }else{ sqlite3 *pDb; sqlite3_stmt *pStmt; const char *zTail; int rc; for(z=zName; *z; z++){ if( (*z=='/' || *z=='\\') && z[1]!=0 ){ zName = &z[1]; } } /* ** In order to insert a blob, we need to drop down to raw SQLite 3 ** calls. */ pDb = db_open(); rc = sqlite3_prepare( pDb, "INSERT INTO " " attachment(atn,tn,size,date,user,description,mime,fname,content) " "VALUES(NULL,?,?,?,?,?,?,?,?);", -1, &pStmt, &zTail); if( rc!=SQLITE_OK ) { db_err( sqlite3_errmsg(pDb), 0, "/attach_add: unable to add \"%h\"", zName ); } sqlite3_bind_text(pStmt, 1, zPage, -1, SQLITE_STATIC); sqlite3_bind_int(pStmt, 2, size); sqlite3_bind_int(pStmt, 3, now); sqlite3_bind_text(pStmt, 4, g.zUser, -1, SQLITE_STATIC); sqlite3_bind_text(pStmt, 5, zDescription, -1, SQLITE_STATIC); sqlite3_bind_text(pStmt, 6, zType, -1, SQLITE_STATIC); sqlite3_bind_text(pStmt, 7, zName, -1, SQLITE_STATIC); sqlite3_bind_blob(pStmt, 8, zData, size, SQLITE_STATIC); rc = sqlite3_step(pStmt); if( rc!=SQLITE_DONE ) { db_err( sqlite3_errmsg(pDb), 0, "/attach_add: unable to add \"%h\"", zName ); } sqlite3_finalize(pStmt); az = db_query( "SELECT MAX(ROWID) FROM attachment" ); atn = atoi(az[0]); ticket_notify(atoi(zPage), 0, 0, atn); cgi_redirect(zBack); } } if( is_integer(zPage) ){ if( tn==0 ){ /* FIXME: Not sure we need a separate page unless there's an error... */ common_header("Attachments To Setup"); }else{ zTitle = db_short_query("SELECT title FROM ticket WHERE tn=%d", tn); if( zTitle==0 ){ common_err("No such ticket: #%d", tn); } common_header("Attachments To Ticket #%d", tn); } }else{ common_header("Attachments to %h", wiki_expand_name(zPage)); } if( zErr ){ cgi_printf("

Error: %h

\n",zErr); } if( is_integer(zPage) && tn ){ cgi_printf("

Ticket #%d: %h

\n",tn,zTitle); } if( attachment_html(zPage, "

Existing attachments:

", "")==0 ){ cgi_printf("

There are currently no attachments on this document.

\n"); } cgi_printf("

To add a new attachment \n" "select the file to attach below an press the \"Add Attachment\" button.\n" "Attachments may not be larger than %dKB.

\n" "
\n" "File to attach:
\n" "Description:\n" "(See formatting hints)
\n" "
\n" "\n" "\n" "\n" "
\n" "
\n" "\n" "

Formatting Hints:

\n",mxSize/1024,zPage); append_formatting_hints(); common_footer(); } static int output_attachment_callback( int *nGot, /* Set if we got results */ int nArg, /* Number of columns in this result row */ char **azArg, /* Text of data in all columns */ char **azName /* Names of the columns */ ){ if( nArg != 4 ) return 0; (*nGot) ++; cgi_set_content_type(azArg[1]); cgi_modified_since(atoi(azArg[3])); cgi_append_header( mprintf("Last-Modified: %s\r\n",cgi_rfc822_datestamp(atoi(azArg[3])))); cgi_append_content(azArg[2], atoi(azArg[0])); g.isConst = 1; return 0; } void attachment_output(int atn){ /* ** We need to use a callback here since the content is a BLOB type object ** and the usual db_query() won't handle NUL characters in a returned ** row. The callback has the full row buffer available and will handle ** all the output duties. got will be set if we get a row. */ int got = 0; db_callback_query( output_attachment_callback, &got, "SELECT size, mime, content, date " "FROM attachment " "WHERE atn=%d", atn); if( !got ){ common_err("No such attachment: %d", atn); } } /* ** WEBPAGE: /attach_get ** ** Retrieve an attachment. g.zExtra looks something like "90/file.gif", which ** the atoi() call turns into just the integer "90". The filename is ignored, ** although some browsers use it as an initial name when saving to disk. */ void attachment_get(void){ int atn = g.zExtra ? atoi(g.zExtra) : 0; char *z; login_check_credentials(); throttle(1,0); if( atn==0 ) common_err("No attachment specified"); z = db_short_query("SELECT tn FROM attachment WHERE atn=%d", atn); if( z && z[0] ){ if( is_integer(z) ){ if( !g.okRead ){ login_needed(); return; } }else{ if( !g.okRdWiki ){ login_needed(); return; } } attachment_output(atn); }else{ common_err("No attachment specified"); } } /* ** Return true if it is ok to delete an attachment created by zUser ** at time addTime. Rules: ** ** * The Setup user can delete any attachment no matter who added ** it or how old it is. ** ** * Any registered user can delete an attachment that they ** themselves created less than 24 hours ago. ** ** * Users with Delete privilege can delete an attachment added ** by anonymous within the past 24 hours. ** */ int ok_to_delete_attachment(int addTime, const char *zUser){ if( g.okSetup ){ return 1; } if( addTimeAre you sure you want to delete this attachments?

\n" "
\n" "%h %h bytes added by %h on %h UTC.\n",az[5],az[1],az[3],zDate); if(az[6] && az[6][0]){ cgi_printf("
\n"); output_formatted(az[6], NULL); cgi_printf("
\n"); } cgi_printf("
\n" "\n" "
\n" "\n" "    \n" "\n" "    \n" "\n" "
\n",atn); common_footer(); } /* ** This routine generates HTML that shows a list of attachments for ** the given ticket number or wiki page. If there are no attachments, ** nothing is generated. Return the number of attachments. */ int attachment_html(const char *zPage, const char *zBefore, const char *zAfter){ char **az; int i = 0; time_t now; if( is_integer(zPage) ){ if( !g.okRead ) return 0; }else{ if( !g.okRdWiki ) return 0; } az = db_query("SELECT atn, size, date, user, mime, fname, description " "FROM attachment WHERE tn='%q' ORDER BY date", zPage); time(&now); if( az[0] ){ cgi_printf("%s\n" "
\n" "%s\n",zAfter); } db_query_free(az); return i/7; }