diff --git a/TODO b/TODO index adcfcf8..4f9e33d 100644 --- a/TODO +++ b/TODO @@ -25,17 +25,27 @@ Lots! statements in the case of joined queries (no need to SELECT table.column AS name). -- Getting conferencing in there - but still not there yet. We need topic and - post implementations, and UI. +- Functions still to do on conferencing "posts" page: + Hide/Show Topic + Next & Keep New (make it actually Keep New) + Freeze/Unfreeze Topic + Archive/Unarchive Topic + Delete Topic + Make number of "viewable" posts per page a config option + Display the message locator (i.e. ) above each message + Hide/Show Post + Scribble Post + Nuke Post + Put the HTML Guide in (for all pages w/post boxes) + +- Slippage during posting is still untested. + +- Functions still to do on conferencing "topics" page: + Manage Conference + Add Conference To Hotlist - Implement conference hotlist for users. -- The HTML checker is back together and almost all integrated into the - Venice engine, but I still need to initialize the dictionary. It's going - to require configuration entries in the Venice XML config file, and the - use of LazyLexicon (to load the dictionary in the background while people - log in). - - Not everybody likes purple. Provide a way to change the default colors. Probably via entries in render-config.xml. Of course, if we go to a different rendering system eventually, we won't need this. diff --git a/etc/web.xml b/etc/web.xml index 89401ff..d77b804 100644 --- a/etc/web.xml +++ b/etc/web.xml @@ -150,6 +150,14 @@ com.silverwrist.venice.servlets.PostMessage + + attachment + + Andles downloading and uploading attachments. + + com.silverwrist.venice.servlets.Attachment + + @@ -213,6 +221,11 @@ /post + + attachment + /attachment + + testformdata diff --git a/setup/database.sql b/setup/database.sql index e2b242e..32bfe4d 100644 --- a/setup/database.sql +++ b/setup/database.sql @@ -420,6 +420,7 @@ INSERT INTO refaudit (type, descr) VALUES (311, 'Hide Message'), (312, 'Scribble Message'), (313, 'Nuke Message'), + (314, 'Upload Message Attachment'), (9999999, 'DUMMY'); # The ISO 3166 two-letter country codes. Source is diff --git a/src/com/silverwrist/util/ServletMultipartHandler.java b/src/com/silverwrist/util/ServletMultipartHandler.java index a19d95b..67b9796 100644 --- a/src/com/silverwrist/util/ServletMultipartHandler.java +++ b/src/com/silverwrist/util/ServletMultipartHandler.java @@ -7,7 +7,7 @@ * WARRANTY OF ANY KIND, either express or implied. See the License for the specific * language governing rights and limitations under the License. * - * The Original Code is the Venice Web Community System. + * The Original Code is the Venice Web Communities System. * * The Initial Developer of the Original Code is Eric J. Bowersox , * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are @@ -32,9 +32,10 @@ import javax.servlet.*; public class ServletMultipartHandler { - private MimeMultipart multipart; // holds all the multipart data - private Hashtable param_byname; // parameters by name - private Vector param_order; // parameters in order + /*-------------------------------------------------------------------------------- + * Internal wrapper around the ServletRequest that implements DataSource + *-------------------------------------------------------------------------------- + */ class ServletDataSource implements DataSource { @@ -75,6 +76,11 @@ public class ServletMultipartHandler } // end class ServletDataSource + /*-------------------------------------------------------------------------------- + * Internal class representing a data value + *-------------------------------------------------------------------------------- + */ + static class MultipartDataValue implements Blob { private byte[] actual_data; // the actual data we contain @@ -83,7 +89,7 @@ public class ServletMultipartHandler { InputStream in = part.getInputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] copybuf = new byte[1024]; + byte[] copybuf = new byte[4096]; int ct = in.read(copybuf); while (ct>=0) { // do a simple read and write @@ -133,6 +139,11 @@ public class ServletMultipartHandler } // end class MultipartDataValue + /*-------------------------------------------------------------------------------- + * Internal class representing a request parameter + *-------------------------------------------------------------------------------- + */ + class MultipartParameter { private MimeBodyPart part; // the actual body part data @@ -178,7 +189,8 @@ public class ServletMultipartHandler public String getValue() { if (filename!=null) - return filename; + return filename; // "value" for file parts is the filename + try { // Retrieve the part's actual content and convert it to a String. (Since non-file // fields are of type text/plain, the Object "val" should actually be a String, in @@ -228,7 +240,7 @@ public class ServletMultipartHandler public MultipartDataValue getContent() throws ServletMultipartException { if (filename==null) - return null; + return null; // not a file parameter if (cached_value==null) { // we don't have the value cached yet @@ -256,6 +268,20 @@ public class ServletMultipartHandler } // end class MultipartParameter + /*-------------------------------------------------------------------------------- + * Attributes + *-------------------------------------------------------------------------------- + */ + + private MimeMultipart multipart; // holds all the multipart data + private Hashtable param_byname; // parameters by name + private Vector param_order; // parameters in order + + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + public ServletMultipartHandler(ServletRequest request) throws ServletMultipartException { if (!canHandle(request)) @@ -286,6 +312,11 @@ public class ServletMultipartHandler } // end constructor + /*-------------------------------------------------------------------------------- + * External static operations + *-------------------------------------------------------------------------------- + */ + /** * Returns true if the given ServletRequest can be handled by * the ServletMultipartHandler, false if not. @@ -301,6 +332,11 @@ public class ServletMultipartHandler } // end canHandle + /*-------------------------------------------------------------------------------- + * External operations + *-------------------------------------------------------------------------------- + */ + public Enumeration getNames() { Vector tmp_vector = new Vector(); diff --git a/src/com/silverwrist/venice/core/ConferenceContext.java b/src/com/silverwrist/venice/core/ConferenceContext.java index 04aa1d3..91838d3 100644 --- a/src/com/silverwrist/venice/core/ConferenceContext.java +++ b/src/com/silverwrist/venice/core/ConferenceContext.java @@ -112,4 +112,6 @@ public interface ConferenceContext public abstract TopicContext addTopic(String title, String zp_pseud, String zp_text) throws DataException, AccessError; + public abstract TopicMessageContext getMessageByPostID(long postid) throws DataException, AccessError; + } // end interface ConferenceContext diff --git a/src/com/silverwrist/venice/core/TopicContext.java b/src/com/silverwrist/venice/core/TopicContext.java index b95e37a..ee350f3 100644 --- a/src/com/silverwrist/venice/core/TopicContext.java +++ b/src/com/silverwrist/venice/core/TopicContext.java @@ -66,6 +66,8 @@ public interface TopicContext public abstract List getMessages(int low, int high) throws DataException, AccessError; + public abstract TopicMessageContext getMessage(int number) throws DataException, AccessError; + public abstract TopicMessageContext postNewMessage(long parent, String pseud, String text) throws DataException, AccessError; diff --git a/src/com/silverwrist/venice/core/TopicMessageContext.java b/src/com/silverwrist/venice/core/TopicMessageContext.java index 6e8a01a..b5c934e 100644 --- a/src/com/silverwrist/venice/core/TopicMessageContext.java +++ b/src/com/silverwrist/venice/core/TopicMessageContext.java @@ -17,6 +17,7 @@ */ package com.silverwrist.venice.core; +import java.io.InputStream; import java.util.Date; public interface TopicMessageContext @@ -49,6 +50,14 @@ public interface TopicMessageContext public abstract boolean hasAttachment(); + public abstract String getAttachmentType(); + + public abstract String getAttachmentFilename(); + + public abstract int getAttachmentLength(); + + public abstract InputStream getAttachmentData() throws AccessError, DataException; + public abstract boolean canHide(); public abstract boolean canScribble(); @@ -61,4 +70,7 @@ public interface TopicMessageContext public abstract void nuke() throws DataException, AccessError; + public abstract void attachData(String m_type, String file, int length, InputStream data) + throws AccessError, DataException; + } // end interface TopicMessageContext diff --git a/src/com/silverwrist/venice/core/impl/ConferenceUserContextImpl.java b/src/com/silverwrist/venice/core/impl/ConferenceUserContextImpl.java index 8ce6ef0..887af51 100644 --- a/src/com/silverwrist/venice/core/impl/ConferenceUserContextImpl.java +++ b/src/com/silverwrist/venice/core/impl/ConferenceUserContextImpl.java @@ -884,6 +884,20 @@ class ConferenceUserContextImpl implements ConferenceContext, ConferenceBackend } // end addTopic + public TopicMessageContext getMessageByPostID(long postid) throws DataException, AccessError + { + if (!(getConferenceData().canReadConference(level))) + { // the luser can't even read the conference... + logger.error("user not permitted to change membership"); + throw new AccessError("You are not permitted to read this conference."); + + } // end if + + // call down to the static function level + return TopicMessageUserContextImpl.getMessage(engine,this,datapool,postid); + + } // end getMessageByPostID + /*-------------------------------------------------------------------------------- * Implementations from interface UserBackend *-------------------------------------------------------------------------------- diff --git a/src/com/silverwrist/venice/core/impl/TopicMessageUserContextImpl.java b/src/com/silverwrist/venice/core/impl/TopicMessageUserContextImpl.java index 7d66e34..6086613 100644 --- a/src/com/silverwrist/venice/core/impl/TopicMessageUserContextImpl.java +++ b/src/com/silverwrist/venice/core/impl/TopicMessageUserContextImpl.java @@ -17,9 +17,11 @@ */ package com.silverwrist.venice.core.impl; +import java.io.*; import java.sql.*; import java.util.*; import org.apache.log4j.*; +import com.silverwrist.util.StringUtil; import com.silverwrist.venice.db.*; import com.silverwrist.venice.security.AuditRecord; import com.silverwrist.venice.core.*; @@ -33,6 +35,8 @@ class TopicMessageUserContextImpl implements TopicMessageContext private static Category logger = Category.getInstance(TopicMessageUserContextImpl.class.getName()); + private static final int MAX_ATTACH = 1048576; // TODO: should be a configurable parameter + /*-------------------------------------------------------------------------------- * Attributes *-------------------------------------------------------------------------------- @@ -329,6 +333,113 @@ class TopicMessageUserContextImpl implements TopicMessageContext } // end hasAttachment + public String getAttachmentType() + { + return mimetype; + + } // end getAttachmentType + + public String getAttachmentFilename() + { + return ((mimetype!=null) ? filename : null); + + } // end getAttachmentFilename + + public int getAttachmentLength() + { + return ((mimetype!=null) ? datalen : 0); + + } // end getAttachmentLength + + public InputStream getAttachmentData() throws AccessError, DataException + { + if (nuked || (scribble_date!=null)) + { // this would be an exercise in futility! + logger.error("cannot attach to a nuked or scribbled message"); + throw new AccessError("You cannot attach data to a message that no longer exists."); + + } // end if + + Connection conn = null; + InputStream rc = null; + try + { // open up a database connection + conn = datapool.getConnection(); + + // make sure we have current data + refresh(conn); + if (nuked || (scribble_date!=null)) + { // this would be an exercise in futility! + logger.error("cannot attach to a nuked or scribbled message"); + throw new AccessError("You cannot attach data to a message that no longer exists."); + + } // end if + + if (mimetype==null) + { // there is no attachment data! + logger.error("no attachment data to get"); + throw new AccessError("There is no attachment data for this message."); + + } // end if + + // Create the statement and the SQL we need to retrieve the attachment. + Statement stmt = conn.createStatement(); + StringBuffer sql = new StringBuffer("SELECT data FROM postattach WHERE postid = "); + sql.append(postid).append(';'); + + // Execute the query! + ResultSet rs = stmt.executeQuery(sql.toString()); + if (!(rs.next())) + { // there is no attachment data! + logger.error("no attachment data to get"); + throw new AccessError("There is no attachment data for this message."); + + } // end if + + // Since the InputStream we get from JDBC will probably go away when the connection is dropped, we + // need to make a temporary copy of it, to a ByteArrayOutputStream. (Attachments can only be + // 1 Mb in size, so this shouldn't be a big problem.) + InputStream sqldata = rs.getBinaryStream(1); + ByteArrayOutputStream copy = new ByteArrayOutputStream(datalen); + byte[] buffer = new byte[4096]; + int rd = sqldata.read(buffer); + while (rd>=0) + { // write, then read again + if (rd>0) + copy.write(buffer,0,rd); + rd = sqldata.read(buffer); + + } // end while + + // Close both our streams, making sure we create the stream we need for the return value. + sqldata.close(); + rc = new ByteArrayInputStream(copy.toByteArray()); + copy.close(); + + } // end try + catch (SQLException e) + { // turn this into a DataException + logger.error("DB error retrieving attachment: " + e.getMessage(),e); + throw new DataException("unable to retrieve attachment data: " + e.getMessage(),e); + + } // end catch + catch (IOException e) + { // turn this into a DataException too + logger.error("I/O error copying attachment data: " + e.getMessage(),e); + throw new DataException("unable to retrieve attachment data: " + e.getMessage(),e); + + } // end catch + finally + { // make sure we release the connection before we go + if (conn!=null) + datapool.releaseConnection(conn); + + } // end finally + + return rc; + + } // end getAttachmentData + public boolean canHide() { return ((creator_uid==conf.realUID()) || conf.userCanHide()); @@ -641,6 +752,125 @@ class TopicMessageUserContextImpl implements TopicMessageContext } // end nuke + public void attachData(String m_type, String file, int length, InputStream data) + throws AccessError, DataException + { + if (mimetype!=null) + { // the message already has an attachment + logger.error("tried to attach data to a message that already has it!"); + throw new AccessError("This message already has an attachment."); + + } // end if + + if (creator_uid!=conf.realUID()) + { // you can't attach to this message! + logger.error("tried to attach data to a message that's not yours!"); + throw new AccessError("You are not permitted to add an attachment to this message."); + + } // end if + + if (nuked || (scribble_date!=null)) + { // this would be an exercise in futility! + logger.error("cannot attach to a nuked or scribbled message"); + throw new AccessError("You cannot attach data to a message that no longer exists."); + + } // end if + + if (StringUtil.isStringEmpty(m_type)) + { // no MIME type specified + logger.error("no MIME type specified for attachment"); + throw new AccessError("MIME type of attachment data not specified."); + + } // end if + + if (StringUtil.isStringEmpty(file)) + { // no MIME type specified + logger.error("no filename specified for attachment"); + throw new AccessError("Filename of attachment data not specified."); + + } // end if + + if (length<=0) + { // a length of 0 or less is just WRONG + logger.error("non-positive length specified for attachment"); + throw new AccessError("Invalid attachment length."); + + } // end if + else if (length>MAX_ATTACH) + { // the attachment is too damn long! + logger.error("attachment is too long (" + String.valueOf(length) + " bytes)"); + throw new AccessError("The attachment is too long to store. Maximum available length is " + + String.valueOf(MAX_ATTACH) + " bytes."); + + } // end else if + + Connection conn = null; + AuditRecord ar = null; + + try + { // open up a database connection + conn = datapool.getConnection(); + + // make sure we have the right status to upload + refresh(conn); + if (nuked || (scribble_date!=null)) + { // this would be an exercise in futility! + logger.error("cannot attach to a nuked or scribbled message"); + throw new AccessError("You cannot attach data to a message that no longer exists."); + + } // end if + + // Build the SQL statement that inserts the attachment. Note the use of the "?" to specify the + // BLOB parameter (the attachment data itself); this comes later. + StringBuffer sql = + new StringBuffer("INSERT INTO postattach (postid, datalen, filename, mimetype, data) VALUES ("); + sql.append(postid).append(", ").append(length).append(", '").append(SQLUtil.encodeString(file)); + sql.append("', '").append(SQLUtil.encodeString(m_type)).append("', ?);"); + + // Prepare the statement, set the BLOB parameter, and execute it. + PreparedStatement stmt = conn.prepareStatement(sql.toString()); + stmt.setBinaryStream(1,data,length); + stmt.executeUpdate(); + + // Save off the local attachment values. + datalen = length; + filename = file; + mimetype = m_type; + + // Generate an audit record indicating what we did. + ar = new AuditRecord(AuditRecord.UPLOAD_ATTACHMENT,conf.realUID(),conf.userRemoteAddress(), + conf.realSIGID(),"conf=" + String.valueOf(conf.realConfID()) + ",post=" + + String.valueOf(postid),"len=" + String.valueOf(length) + ",type=" + m_type + + ",name=" + file); + + } // end try + catch (SQLException e) + { // turn this into a DataException + logger.error("DB error saving attachment: " + e.getMessage(),e); + throw new DataException("unable to save attachment data: " + e.getMessage(),e); + + } // end catch + finally + { // make sure we release the connection before we go + try + { // save off the audit record before we go, though + if ((ar!=null) && (conn!=null)) + ar.store(conn); + + } // end try + catch (SQLException e) + { // we couldn't store the audit record! + logger.error("DB error saving audit record: " + e.getMessage(),e); + + } // end catch + + if (conn!=null) + datapool.releaseConnection(conn); + + } // end finally + + } // end attachData + /*-------------------------------------------------------------------------------- * External static operations *-------------------------------------------------------------------------------- @@ -703,4 +933,105 @@ class TopicMessageUserContextImpl implements TopicMessageContext } // end loadMessageRange + static TopicMessageContext loadMessage(EngineBackend engine, ConferenceBackend conf, DataPool datapool, + int topicid, int message_num) throws DataException + { + if (logger.isDebugEnabled()) + logger.debug("loadMessage for conf # " + String.valueOf(conf.realConfID()) + ", topic #" + + String.valueOf(topicid) + ", message " + String.valueOf(message_num)); + + Connection conn = null; // pooled database connection + + try + { // get a database connection + conn = datapool.getConnection(); + Statement stmt = conn.createStatement(); + + // run a query to get all the posts in a particular topic + StringBuffer sql = + new StringBuffer("SELECT p.postid, p.parent, p.num, p.linecount, p.creator_uid, p.posted, " + + "p.hidden, p.scribble_uid, p.scribble_date, p.pseud, a.datalen, a.filename, " + + "a.mimetype FROM posts p LEFT JOIN postattach a ON p.postid = a.postid " + + "WHERE p.topicid = "); + sql.append(topicid).append(" AND p.num = ").append(message_num).append(';'); + if (logger.isDebugEnabled()) + logger.debug("SQL: " + sql.toString()); + ResultSet rs = stmt.executeQuery(sql.toString()); + + if (rs.next()) // create an object reference and return it + return new TopicMessageUserContextImpl(engine,conf,datapool,rs.getLong(1),rs.getLong(2),rs.getInt(3), + rs.getInt(4),rs.getInt(5),SQLUtil.getFullDateTime(rs,6), + rs.getBoolean(7),rs.getInt(8),SQLUtil.getFullDateTime(rs,9), + rs.getString(10),rs.getInt(11),rs.getString(12), + rs.getString(13)); + + // indicates an error... + throw new DataException("Message not found."); + + } // end try + catch (SQLException e) + { // turn SQLException into data exception + logger.error("DB error reading message entry: " + e.getMessage(),e); + throw new DataException("unable to retrieve message: " + e.getMessage(),e); + + } // end catch + finally + { // make sure we release the connection before we go + if (conn!=null) + datapool.releaseConnection(conn); + + } // end finally + + } // end loadMessage + + static TopicMessageContext getMessage(EngineBackend engine, ConferenceBackend conf, DataPool datapool, + long postid) throws DataException + { + if (logger.isDebugEnabled()) + logger.debug("getMessage for conf # " + String.valueOf(conf.realConfID()) + ", post #" + + String.valueOf(postid)); + + Connection conn = null; // pooled database connection + + try + { // get a database connection + conn = datapool.getConnection(); + Statement stmt = conn.createStatement(); + + StringBuffer sql = + new StringBuffer("SELECT p.postid, p.parent, p.num, p.linecount, p.creator_uid, p.posted, " + + "p.hidden, p.scribble_uid, p.scribble_date, p.pseud, a.datalen, a.filename, " + + "a.mimetype FROM topics t, posts p LEFT JOIN postattach a ON p.postid = a.postid " + + "WHERE t.topicid = p.topicid AND t.confid = "); + sql.append(conf.realConfID()).append(" AND p.postid = ").append(postid).append(';'); + if (logger.isDebugEnabled()) + logger.debug("SQL: " + sql.toString()); + ResultSet rs = stmt.executeQuery(sql.toString()); + + if (rs.next()) // create an object reference and return it + return new TopicMessageUserContextImpl(engine,conf,datapool,rs.getLong(1),rs.getLong(2),rs.getInt(3), + rs.getInt(4),rs.getInt(5),SQLUtil.getFullDateTime(rs,6), + rs.getBoolean(7),rs.getInt(8),SQLUtil.getFullDateTime(rs,9), + rs.getString(10),rs.getInt(11),rs.getString(12), + rs.getString(13)); + + // indicates an error... + throw new DataException("Message not found."); + + } // end try + catch (SQLException e) + { // turn SQLException into data exception + logger.error("DB error reading message entries: " + e.getMessage(),e); + throw new DataException("unable to retrieve messages: " + e.getMessage(),e); + + } // end catch + finally + { // make sure we release the connection before we go + if (conn!=null) + datapool.releaseConnection(conn); + + } // end finally + + } // end getMessage + } // end class TopicMessageUserContextImpl diff --git a/src/com/silverwrist/venice/core/impl/TopicUserContextImpl.java b/src/com/silverwrist/venice/core/impl/TopicUserContextImpl.java index 9331c16..d9041f2 100644 --- a/src/com/silverwrist/venice/core/impl/TopicUserContextImpl.java +++ b/src/com/silverwrist/venice/core/impl/TopicUserContextImpl.java @@ -569,6 +569,20 @@ class TopicUserContextImpl implements TopicContext } // end getMessages + public TopicMessageContext getMessage(int number) throws DataException, AccessError + { + if (!(conf.userCanRead())) + { // they can't read messages in this topic! + logger.error("trying to read postings w/o permission!"); + throw new AccessError("You do not have permission to read messages in this conference."); + + } // end if + + // pass down to one of our static functiions to return this + return TopicMessageUserContextImpl.loadMessage(engine,conf,datapool,topicid,number); + + } // end getMessage + public TopicMessageContext postNewMessage(long parent, String pseud, String text) throws DataException, AccessError { diff --git a/src/com/silverwrist/venice/security/Audit.java b/src/com/silverwrist/venice/security/Audit.java index 67f0851..282fe1e 100644 --- a/src/com/silverwrist/venice/security/Audit.java +++ b/src/com/silverwrist/venice/security/Audit.java @@ -58,5 +58,6 @@ public interface Audit public static final int HIDE_MESSAGE = 311; public static final int SCRIBBLE_MESSAGE = 312; public static final int NUKE_MESSAGE = 313; + public static final int UPLOAD_ATTACHMENT = 314; } // end interface Audit diff --git a/src/com/silverwrist/venice/servlets/Attachment.java b/src/com/silverwrist/venice/servlets/Attachment.java new file mode 100644 index 0000000..8ef7acd --- /dev/null +++ b/src/com/silverwrist/venice/servlets/Attachment.java @@ -0,0 +1,427 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.servlets; + +import java.io.*; +import java.util.*; +import javax.servlet.*; +import javax.servlet.http.*; +import org.apache.log4j.*; +import com.silverwrist.util.StringUtil; +import com.silverwrist.util.ServletMultipartHandler; +import com.silverwrist.util.ServletMultipartException; +import com.silverwrist.venice.ValidationException; +import com.silverwrist.venice.core.*; +import com.silverwrist.venice.servlets.format.*; + +public class Attachment extends VeniceServlet +{ + /*-------------------------------------------------------------------------------- + * Static data members + *-------------------------------------------------------------------------------- + */ + + private static Category logger = Category.getInstance(Attachment.class.getName()); + + /*-------------------------------------------------------------------------------- + * Internal functions + *-------------------------------------------------------------------------------- + */ + + private static SIGContext getSIGParameter(String str, UserContext user) + throws ValidationException, DataException + { + if (str==null) + { // no SIG parameter - bail out now! + logger.error("SIG parameter not specified!"); + throw new ValidationException("No SIG specified."); + + } // end if + + try + { // turn the string into a SIGID, and thence to a SIGContext + int sigid = Integer.parseInt(str); + return user.getSIGContext(sigid); + + } // end try + catch (NumberFormatException nfe) + { // error in Integer.parseInt + logger.error("Cannot convert SIG parameter '" + str + "'!"); + throw new ValidationException("Invalid SIG parameter."); + + } // end catch + + } // end getSIGParameter + + private static SIGContext getSIGParameter(ServletRequest request, UserContext user) + throws ValidationException, DataException + { + return getSIGParameter(request.getParameter("sig"),user); + + } // end getSIGParameter + + private static SIGContext getSIGParameter(ServletMultipartHandler mphandler, UserContext user) + throws ValidationException, DataException + { + if (mphandler.isFileParam("sig")) + throw new ValidationException("Internal Error: SIG should be a normal param"); + return getSIGParameter(mphandler.getValue("sig"),user); + + } // end getSIGParameter + + private static ConferenceContext getConferenceParameter(String str, SIGContext sig) + throws ValidationException, DataException, AccessError + { + if (str==null) + { // no conference parameter - bail out now! + logger.error("Conference parameter not specified!"); + throw new ValidationException("No conference specified."); + + } // end if + + try + { // turn the string into a ConfID, and thence to a ConferenceContext + int confid = Integer.parseInt(str); + return sig.getConferenceContext(confid); + + } // end try + catch (NumberFormatException nfe) + { // error in Integer.parseInt + logger.error("Cannot convert conference parameter '" + str + "'!"); + throw new ValidationException("Invalid conference parameter."); + + } // end catch + + } // end getConferenceParameter + + private static ConferenceContext getConferenceParameter(ServletRequest request, SIGContext sig) + throws ValidationException, DataException, AccessError + { + return getConferenceParameter(request.getParameter("conf"),sig); + + } // end getConferenceParameter + + private static ConferenceContext getConferenceParameter(ServletMultipartHandler mphandler, SIGContext sig) + throws ValidationException, DataException, AccessError + { + if (mphandler.isFileParam("conf")) + throw new ValidationException("Internal Error: conference should be a normal param"); + return getConferenceParameter(mphandler.getValue("conf"),sig); + + } // end getConferenceParameter + + private static TopicMessageContext getMessageParameter(String str, ConferenceContext conf) + throws ValidationException, DataException, AccessError + { + if (str==null) + { // no conference parameter - bail out now! + logger.error("Message parameter not specified!"); + throw new ValidationException("No message specified."); + + } // end if + + try + { // turn the string into a postid, and thence to a TopicMessageContext + long postid = Long.parseLong(str); + return conf.getMessageByPostID(postid); + + } // end try + catch (NumberFormatException nfe) + { // error in Integer.parseInt + logger.error("Cannot convert message parameter '" + str + "'!"); + throw new ValidationException("Invalid message parameter."); + + } // end catch + + } // end getMessageParameter + + private static TopicMessageContext getMessageParameter(ServletRequest request, ConferenceContext conf) + throws ValidationException, DataException, AccessError + { + return getMessageParameter(request.getParameter("msg"),conf); + + } // end getMessageParameter + + private static TopicMessageContext getMessageParameter(ServletMultipartHandler mphandler, + ConferenceContext conf) + throws ValidationException, DataException, AccessError + { + if (mphandler.isFileParam("msg")) + throw new ValidationException("Internal Error: message should be a normal param"); + return getMessageParameter(mphandler.getValue("msg"),conf); + + } // end getMessageParameter + + /*-------------------------------------------------------------------------------- + * Overrides from class HttpServlet + *-------------------------------------------------------------------------------- + */ + + public String getServletInfo() + { + String rc = "Attachment servlet - Handles uploading and downloading attachments\n" + + "Part of the Venice Web Communities System\n"; + return rc; + + } // end getServletInfo + + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + UserContext user = getUserContext(request); + RenderData rdat = createRenderData(request,response); + String page_title = null; + Object content = null; + SIGContext sig = null; // SIG context + ConferenceContext conf = null; // conference context + TopicMessageContext msg = null; // message context + + try + { // this outer try is to catch ValidationException + try + { // all commands require a SIG parameter + sig = getSIGParameter(request,user); + changeMenuSIG(request,sig); + if (logger.isDebugEnabled()) + logger.debug("found SIG #" + String.valueOf(sig.getSIGID())); + + } // end try + catch (DataException de) + { // error looking up the SIG + page_title = "Database Error"; + content = new ErrorBox(page_title,"Database error finding SIG: " + de.getMessage(),"top"); + + } // end catch + + if (content==null) + { // we got the SIG parameter OK + try + { // all commands require a conference parameter + conf = getConferenceParameter(request,sig); + if (logger.isDebugEnabled()) + logger.debug("found conf #" + String.valueOf(conf.getConfID())); + + } // end try + catch (DataException de) + { // error looking up the conference + page_title = "Database Error"; + content = new ErrorBox(page_title,"Database error finding conference: " + de.getMessage(),"top"); + + } // end catch + + } // end if + + if (content==null) + { // we got the conference parameter OK + try + { // now we need a message parameter + msg = getMessageParameter(request,conf); + if (logger.isDebugEnabled()) + logger.debug("found post #" + String.valueOf(msg.getPostID())); + + } // end try + catch (DataException de) + { // error looking up the conference + page_title = "Database Error"; + content = new ErrorBox(page_title,"Database error finding message: " + de.getMessage(),"top"); + + } // end catch + + } // end if + + } // end try + catch (ValidationException ve) + { // these all get handled in pretty much the same way + page_title = "Error"; + content = new ErrorBox(null,ve.getMessage(),"top"); + + } // end catch + catch (AccessError ae) + { // these all get handled in pretty much the same way + page_title = "Access Error"; + content = new ErrorBox(page_title,ae.getMessage(),"top"); + + } // end catch + + if (content==null) + { // extract the attachment data from the message and send it out + try + { // extract the attachment data and send it out! + rdat.sendBinaryData(msg.getAttachmentType(),msg.getAttachmentFilename(), + msg.getAttachmentLength(),msg.getAttachmentData()); + return; // now we're all done! + + } // end try + catch (AccessError ae) + { // these all get handled in pretty much the same way + page_title = "Access Error"; + content = new ErrorBox(page_title,ae.getMessage(),"top"); + + } // end catch + catch (DataException de) + { // error looking up the conference + page_title = "Database Error"; + content = new ErrorBox(page_title,"Database error retrieving attachment: " + de.getMessage(),"top"); + + } // end catch + + } // end if + + // we only get here if there were an error + BaseJSPData basedat = new BaseJSPData(page_title,"top",content); + basedat.transfer(getServletContext(),rdat); + + } // end doGet + + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + UserContext user = getUserContext(request); + RenderData rdat = createRenderData(request,response); + ServletMultipartHandler mphandler = null; + String target = "top"; + String page_title = null; + Object content = null; + SIGContext sig = null; // SIG context + ConferenceContext conf = null; // conference context + TopicMessageContext msg = null; // message context + + try + { // this outer try is to catch ValidationException + mphandler = new ServletMultipartHandler(request); + + if (mphandler.isFileParam("target")) + throw new ValidationException("Internal Error: 'target' should be a normal param"); + target = mphandler.getValue("target"); + + try + { // all commands require a SIG parameter + sig = getSIGParameter(mphandler,user); + changeMenuSIG(request,sig); + if (logger.isDebugEnabled()) + logger.debug("found SIG #" + String.valueOf(sig.getSIGID())); + + } // end try + catch (DataException de) + { // error looking up the SIG + page_title = "Database Error"; + content = new ErrorBox(page_title,"Database error finding SIG: " + de.getMessage(),target); + + } // end catch + + if (content==null) + { // we got the SIG parameter OK + try + { // all commands require a conference parameter + conf = getConferenceParameter(mphandler,sig); + if (logger.isDebugEnabled()) + logger.debug("found conf #" + String.valueOf(conf.getConfID())); + + } // end try + catch (DataException de) + { // error looking up the conference + page_title = "Database Error"; + content = new ErrorBox(page_title,"Database error finding conference: " + de.getMessage(),target); + + } // end catch + + } // end if + + if (content==null) + { // we got the conference parameter OK + try + { // now we need a message parameter + msg = getMessageParameter(mphandler,conf); + if (logger.isDebugEnabled()) + logger.debug("found post #" + String.valueOf(msg.getPostID())); + + } // end try + catch (DataException de) + { // error looking up the conference + page_title = "Database Error"; + content = new ErrorBox(page_title,"Database error finding message: " + de.getMessage(),target); + + } // end catch + + } // end if + + // also check on file and target parameter status + if (!(mphandler.isFileParam("thefile"))) + throw new ValidationException("Internal error: 'thefile' should be a file param"); + + + } // end try + catch (ValidationException ve) + { // these all get handled in pretty much the same way + page_title = "Error"; + content = new ErrorBox(null,ve.getMessage(),target); + + } // end catch + catch (AccessError ae) + { // these all get handled in pretty much the same way + page_title = "Access Error"; + content = new ErrorBox(page_title,ae.getMessage(),target); + + } // end catch + catch (ServletMultipartException smpe) + { // this is kind of a special case + page_title = "Error"; + content = new ErrorBox(page_title,"Internal Error: " + smpe.getMessage(),target); + + } // end catch + + if (content==null) + { // we're ready to get the data and attach it + try + { // attach the data to the message! + msg.attachData(mphandler.getContentType("thefile"),mphandler.getValue("thefile"), + mphandler.getContentSize("thefile"),mphandler.getFileContentStream("thefile")); + + // go back to where we should have gone before we uploaded the message + rdat.redirectTo(target); + return; + + } // end try + catch (ServletMultipartException smpe) + { // this is kind of a special case + page_title = "Error"; + content = new ErrorBox(page_title,"Internal Error: " + smpe.getMessage(),target); + + } // end catch + catch (AccessError ae) + { // these all get handled in pretty much the same way + page_title = "Access Error"; + content = new ErrorBox(page_title,ae.getMessage(),target); + + } // end catch + catch (DataException de) + { // error looking up the conference + page_title = "Database Error"; + content = new ErrorBox(page_title,"Database error storing attachment: " + de.getMessage(),target); + + } // end catch + + } // end if (we got all the parameters OK) + + // we only get here if there were an error + BaseJSPData basedat = new BaseJSPData(page_title,target,content); + basedat.transfer(getServletContext(),rdat); + + } // end doPost + +} // end class Attachment diff --git a/src/com/silverwrist/venice/servlets/ConfOperations.java b/src/com/silverwrist/venice/servlets/ConfOperations.java index ebdded0..bbab418 100644 --- a/src/com/silverwrist/venice/servlets/ConfOperations.java +++ b/src/com/silverwrist/venice/servlets/ConfOperations.java @@ -453,9 +453,10 @@ public class ConfOperations extends VeniceServlet final String yes = "Y"; if (yes.equals(request.getParameter("attach"))) { // we need to upload an attachment for this post - // TODO: jump somewhere to upload an attachment - rdat.redirectTo(on_error); - return; + TopicMessageContext msg = topic.getMessage(0); // load the "zero post" + + content = new AttachmentForm(sig,conf,msg,on_error); + page_title = "Upload Attachment"; } // end if else diff --git a/src/com/silverwrist/venice/servlets/PostMessage.java b/src/com/silverwrist/venice/servlets/PostMessage.java index 7e73f83..9d68c1e 100644 --- a/src/com/silverwrist/venice/servlets/PostMessage.java +++ b/src/com/silverwrist/venice/servlets/PostMessage.java @@ -276,12 +276,12 @@ public class PostMessage extends VeniceServlet { // no slippage - post the message!!! TopicMessageContext msg = topic.postNewMessage(0,request.getParameter("pseud"),raw_postdata); if (yes.equals(request.getParameter("attach"))) - { // we have an attachment to upload... - // TODO: do something to upload the attachment - rdat.redirectTo("confdisp?sig=" + String.valueOf(sig.getSIGID()) + "&conf=" - + String.valueOf(conf.getConfID()) + "&top=" - + String.valueOf(topic.getTopicNumber()) + "&rnm=1"); - return; + { // we have an attachment to upload...display the "Upload Attachment" form + String target = "confdisp?sig=" + String.valueOf(sig.getSIGID()) + "&conf=" + + String.valueOf(conf.getConfID()) + "&top=" + + String.valueOf(topic.getTopicNumber()) + "&rnm=1"; + content = new AttachmentForm(sig,conf,msg,target); + page_title = "Upload Attachment"; } // end if else diff --git a/src/com/silverwrist/venice/servlets/format/AttachmentForm.java b/src/com/silverwrist/venice/servlets/format/AttachmentForm.java new file mode 100644 index 0000000..a837d5a --- /dev/null +++ b/src/com/silverwrist/venice/servlets/format/AttachmentForm.java @@ -0,0 +1,118 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.servlets.format; + +import java.util.*; +import javax.servlet.*; +import javax.servlet.http.*; +import com.silverwrist.util.StringUtil; +import com.silverwrist.venice.htmlcheck.*; +import com.silverwrist.venice.core.*; + +public class AttachmentForm implements JSPRender +{ + /*-------------------------------------------------------------------------------- + * Static data members + *-------------------------------------------------------------------------------- + */ + + // Attribute name for request attribute + protected static final String ATTR_NAME = "com.silverwrist.venice.content.AttachmentForm"; + + /*-------------------------------------------------------------------------------- + * Attributes + *-------------------------------------------------------------------------------- + */ + + private int sigid; + private int confid; + private long postid; + private String target; + + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + public AttachmentForm(SIGContext sig, ConferenceContext conf, TopicMessageContext msg, String target) + { + this.sigid = sig.getSIGID(); + this.confid = conf.getConfID(); + this.postid = msg.getPostID(); + this.target = target; + + } // end constructor + + /*-------------------------------------------------------------------------------- + * External static functions + *-------------------------------------------------------------------------------- + */ + + public static AttachmentForm retrieve(ServletRequest request) + { + return (AttachmentForm)(request.getAttribute(ATTR_NAME)); + + } // end retrieve + + /*-------------------------------------------------------------------------------- + * Implementations from interface JSPRender + *-------------------------------------------------------------------------------- + */ + + public void store(ServletRequest request) + { + request.setAttribute(ATTR_NAME,this); + + } // end store + + public String getTargetJSPName() + { + return "attach_form.jsp"; + + } // end getTargetJSPName + + /*-------------------------------------------------------------------------------- + * External operations + *-------------------------------------------------------------------------------- + */ + + public int getSIGID() + { + return sigid; + + } // end getSIGID + + public int getConfID() + { + return confid; + + } // end getConfID + + public long getPostID() + { + return postid; + + } // end getPostID + + public String getTarget() + { + return target; + + } // end getTarget + +} // end class AttachmentForm diff --git a/src/com/silverwrist/venice/servlets/format/RenderData.java b/src/com/silverwrist/venice/servlets/format/RenderData.java index 4fa7772..360c01f 100644 --- a/src/com/silverwrist/venice/servlets/format/RenderData.java +++ b/src/com/silverwrist/venice/servlets/format/RenderData.java @@ -299,4 +299,25 @@ public class RenderData } // end nullResponse + public void sendBinaryData(String type, String filename, int length, InputStream data) throws IOException + { + response.setContentType(type); + response.setContentLength(length); + if (filename!=null) // make sure we pass the filename along, too + response.setHeader("Content-Disposition","attachment; filename=\"" + filename + "\";"); + + // Copy the contents of the "data" stream to the output. + ServletOutputStream stm = response.getOutputStream(); + byte[] buffer = new byte[4096]; + int rd = data.read(buffer); + while (rd>=0) + { // simple read-write loop to shove data out the door + if (rd>0) + stm.write(buffer,0,rd); + rd = data.read(buffer); + + } // end while + + } // end sendBinaryData + } // end class RenderData diff --git a/web/format/attach_form.jsp b/web/format/attach_form.jsp new file mode 100644 index 0000000..c79b568 --- /dev/null +++ b/web/format/attach_form.jsp @@ -0,0 +1,41 @@ +<%-- + The contents of this file are subject to the Mozilla Public License Version 1.1 + (the "License"); you may not use this file except in compliance with the License. + You may obtain a copy of the License at . + + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + WARRANTY OF ANY KIND, either express or implied. See the License for the specific + language governing rights and limitations under the License. + + The Original Code is the Venice Web Communities System. + + The Initial Developer of the Original Code is Eric J. Bowersox , + for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + + Contributor(s): +--%> +<%@ page import = "java.util.*" %> +<%@ page import = "com.silverwrist.util.StringUtil" %> +<%@ page import = "com.silverwrist.venice.core.*" %> +<%@ page import = "com.silverwrist.venice.servlets.Variables" %> +<%@ page import = "com.silverwrist.venice.servlets.format.*" %> +<% + AttachmentForm data = AttachmentForm.retrieve(request); + Variables.failIfNull(data); + RenderData rdat = RenderConfig.createRenderData(application,request,response); +%> +<% rdat.writeContentHeader(out,"Upload Your Attachment",null); %> +<%= rdat.getStdFontTag(null,2) %> + Your attachment may be no more than 1 megabyte in size.

+

"> + + + + + File to attach:
+ " NAME="upload" ALT="Upload" + WIDTH=80 HEIGHT=24 BORDER=0> +

+ +<% rdat.writeFooter(out); %> diff --git a/web/format/posts.jsp b/web/format/posts.jsp index f86c349..3842571 100644 --- a/web/format/posts.jsp +++ b/web/format/posts.jsp @@ -171,7 +171,13 @@ " TARGET="_blank"><%= poster %>, <%= rdat.formatDateForDisplay(msg.getPostDate()) %> ) - <%-- TODO: paperclip goes here if we have an attachment --%> + <% if (msg.hasAttachment()) { %> + " + ALT="(Attachment <%= msg.getAttachmentFilename() %> - <%= msg.getAttachmentLength() %> bytes)" + WIDTH=16 HEIGHT=16 BORDER=0> + <% } // end if %>

<% if (msg.isScribbled()) { %> diff --git a/web/images/attachment.gif b/web/images/attachment.gif new file mode 100644 index 0000000..5623b1d Binary files /dev/null and b/web/images/attachment.gif differ diff --git a/web/images/bn_upload.gif b/web/images/bn_upload.gif new file mode 100644 index 0000000..84bd828 Binary files /dev/null and b/web/images/bn_upload.gif differ