001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki.attachment;
021:
022: import javax.servlet.*;
023: import javax.servlet.http.*;
024:
025: import java.util.*;
026: import java.io.*;
027: import java.text.SimpleDateFormat;
028: import java.text.DateFormat;
029:
030: import org.apache.log4j.Logger;
031:
032: import com.ecyrd.jspwiki.*;
033: import com.ecyrd.jspwiki.util.HttpUtil;
034: import com.ecyrd.jspwiki.auth.UserProfile;
035: import com.ecyrd.jspwiki.auth.AuthorizationManager;
036: import com.ecyrd.jspwiki.providers.ProviderException;
037: import com.ecyrd.jspwiki.dav.WebdavServlet;
038: import com.ecyrd.jspwiki.filters.RedirectException;
039:
040: // multipartrequest.jar imports:
041: import http.utils.multipartrequest.*;
042:
043: /**
044: * This is a simple file upload servlet customized for JSPWiki. It receives
045: * a mime/multipart POST message, as sent by an Attachment page, stores it
046: * temporarily, figures out what WikiName to use to store it, checks for
047: * previously existing versions.
048: *
049: * <p>This servlet does not worry about authentication; we leave that to the
050: * container, or a previous servlet that chains to us.
051: *
052: * @author Erik Bunn
053: * @author Janne Jalkanen
054: *
055: * @since 1.9.45.
056: */
057: public class AttachmentServlet extends WebdavServlet {
058: private WikiEngine m_engine;
059: Logger log = Logger.getLogger(this .getClass().getName());
060:
061: public static final String HDR_VERSION = "version";
062: public static final String HDR_NAME = "page";
063:
064: /** Default expiry period is 1 day */
065: protected static final long DEFAULT_EXPIRY = 1 * 24 * 60 * 60
066: * 1000;
067:
068: private String m_tmpDir;
069:
070: /**
071: * The maximum size that an attachment can be.
072: */
073: private int m_maxSize = Integer.MAX_VALUE;
074:
075: //
076: // Not static as DateFormat objects are not thread safe.
077: // Used to handle the RFC date format = Sat, 13 Apr 2002 13:23:01 GMT
078: //
079: private final DateFormat rfcDateFormat = new SimpleDateFormat(
080: "EEE, dd MMM yyyy HH:mm:ss z");
081:
082: /**
083: * Initializes the servlet from WikiEngine properties.
084: */
085: public void init(ServletConfig config) throws ServletException {
086: super .init(config);
087:
088: m_engine = WikiEngine.getInstance(config);
089: Properties props = m_engine.getWikiProperties();
090:
091: m_tmpDir = m_engine.getWorkDir() + File.separator
092: + "attach-tmp";
093:
094: m_maxSize = TextUtil.getIntegerProperty(props,
095: AttachmentManager.PROP_MAXSIZE, Integer.MAX_VALUE);
096:
097: File f = new File(m_tmpDir);
098: if (!f.exists()) {
099: f.mkdirs();
100: } else if (!f.isDirectory()) {
101: log
102: .fatal("A file already exists where the temporary dir is supposed to be: "
103: + m_tmpDir + ". Please remove it.");
104: }
105:
106: log.debug("UploadServlet initialized. Using " + m_tmpDir
107: + " for temporary storage.");
108: }
109:
110: /*
111: public void doPropFind( HttpServletRequest req, HttpServletResponse res )
112: throws IOException, ServletException
113: {
114: DavMethod dm = new PropFindMethod( m_engine );
115:
116: dm.execute( req, res );
117: }
118: */
119: protected void doOptions(HttpServletRequest req,
120: HttpServletResponse res) {
121: res.setHeader("DAV", "1"); // We support only Class 1
122: res
123: .setHeader("Allow",
124: "GET, PUT, POST, OPTIONS, PROPFIND, PROPPATCH, MOVE, COPY, DELETE");
125: res.setStatus(HttpServletResponse.SC_OK);
126: }
127:
128: /**
129: * Serves a GET with two parameters: 'wikiname' specifying the wikiname
130: * of the attachment, 'version' specifying the version indicator.
131: */
132:
133: // FIXME: Messages would need to be localized somehow.
134: public void doGet(HttpServletRequest req, HttpServletResponse res)
135: throws IOException, ServletException {
136: String version = m_engine.safeGetParameter(req, HDR_VERSION);
137: String nextPage = m_engine.safeGetParameter(req, "nextpage");
138:
139: String msg = "An error occurred. Ouch.";
140: int ver = WikiProvider.LATEST_VERSION;
141:
142: AttachmentManager mgr = m_engine.getAttachmentManager();
143: AuthorizationManager authmgr = m_engine
144: .getAuthorizationManager();
145:
146: UserProfile wup = m_engine.getUserManager().getUserProfile(req);
147:
148: WikiContext context = m_engine.createContext(req,
149: WikiContext.ATTACH);
150: String page = context.getPage().getName();
151:
152: if (page == null) {
153: log.info("Invalid attachment name.");
154: res.sendError(HttpServletResponse.SC_BAD_REQUEST);
155: return;
156: } else {
157: OutputStream out = null;
158: InputStream in = null;
159:
160: try {
161: log.debug("Attempting to download att " + page
162: + ", version " + version);
163: if (version != null) {
164: ver = Integer.parseInt(version);
165: }
166:
167: Attachment att = mgr.getAttachmentInfo(page, ver);
168:
169: if (att != null) {
170: //
171: // Check if the user has permission for this attachment
172: //
173:
174: if (!authmgr.checkPermission(att, wup, "view")) {
175: log
176: .debug("User does not have permission for this");
177: res.sendError(HttpServletResponse.SC_FORBIDDEN);
178: return;
179: }
180:
181: //
182: // Check if the client already has a version of this attachment.
183: //
184: if (HttpUtil.checkFor304(req, att)) {
185: log
186: .debug("Client has latest version already, sending 304...");
187: res
188: .sendError(HttpServletResponse.SC_NOT_MODIFIED);
189: return;
190: }
191:
192: String mimetype = getServletConfig()
193: .getServletContext().getMimeType(
194: att.getFileName().toLowerCase());
195:
196: if (mimetype == null) {
197: mimetype = "application/binary";
198: }
199:
200: res.setContentType(mimetype);
201:
202: //
203: // We use 'inline' instead of 'attachment' so that user agents
204: // can try to automatically open the file.
205: //
206:
207: res.addHeader("Content-Disposition",
208: "inline; filename=\"" + att.getFileName()
209: + "\";");
210: // long expires = new Date().getTime() + DEFAULT_EXPIRY;
211: // res.addDateHeader("Expires",expires);
212: res.addDateHeader("Last-Modified", att
213: .getLastModified().getTime());
214:
215: // If a size is provided by the provider, report it.
216: if (att.getSize() >= 0) {
217: // log.info("size:"+att.getSize());
218: res.setContentLength((int) att.getSize());
219: }
220:
221: out = res.getOutputStream();
222: in = mgr.getAttachmentStream(att);
223:
224: int read = 0;
225: byte buffer[] = new byte[8192];
226:
227: while ((read = in.read(buffer)) > -1) {
228: out.write(buffer, 0, read);
229: }
230:
231: if (log.isDebugEnabled()) {
232: msg = "Attachment " + att.getFileName()
233: + " sent to " + req.getRemoteUser()
234: + " on " + req.getRemoteAddr();
235: log.debug(msg);
236: }
237: if (nextPage != null)
238: res.sendRedirect(nextPage);
239:
240: return;
241: } else {
242: msg = "Attachment '" + page + "', version " + ver
243: + " does not exist.";
244:
245: log.info(msg);
246: res
247: .sendError(
248: HttpServletResponse.SC_NOT_FOUND,
249: msg);
250: return;
251: }
252:
253: } catch (ProviderException pe) {
254: msg = "Provider error: " + pe.getMessage();
255: res.sendError(
256: HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
257: msg);
258: return;
259: } catch (NumberFormatException nfe) {
260: msg = "Invalid version number (" + version + ")";
261: res.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
262: return;
263: } catch (IOException ioe) {
264: msg = "Error: " + ioe.getMessage();
265: res.sendError(
266: HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
267: msg);
268: return;
269: } finally {
270: if (in != null)
271: in.close();
272: if (out != null)
273: out.close();
274: }
275: }
276: }
277:
278: /**
279: * Grabs mime/multipart data and stores it into the temporary area.
280: * Uses other parameters to determine which name to store as.
281: *
282: * <p>The input to this servlet is generated by an HTML FORM with
283: * two parts. The first, named 'page', is the WikiName identifier
284: * for the parent file. The second, named 'content', is the binary
285: * content of the file.
286: */
287: public void doPost(HttpServletRequest req, HttpServletResponse res)
288: throws IOException, ServletException {
289: try {
290: String nextPage = upload(req);
291: req.getSession().removeAttribute("msg");
292: res.sendRedirect(nextPage);
293: } catch (RedirectException e) {
294: req.getSession().setAttribute("msg", e.getMessage());
295: res.sendRedirect(e.getRedirect());
296: }
297: }
298:
299: /**
300: * Uploads a specific mime multipart input set, intercepts exceptions.
301: *
302: * @return The page to which we should go next.
303: */
304: protected String upload(HttpServletRequest req)
305: throws RedirectException, IOException {
306: String msg = "";
307: String attName = "(unknown)";
308: String errorPage = m_engine.getURL(WikiContext.ERROR, "", null,
309: false); // If something bad happened, Upload should be able to take care of most stuff
310: String nextPage = errorPage;
311:
312: try {
313: MultipartRequest multi;
314:
315: multi = new MultipartRequest(
316: null, // no debugging
317: req.getContentType(), req.getContentLength(), req
318: .getInputStream(), m_tmpDir,
319: Integer.MAX_VALUE, m_engine.getContentEncoding());
320:
321: nextPage = multi.getURLParameter("nextpage");
322: String wikipage = multi.getURLParameter("page");
323:
324: WikiContext context = m_engine.createContext(req,
325: WikiContext.UPLOAD);
326: errorPage = context.getURL(WikiContext.UPLOAD, wikipage);
327:
328: errorPage = nextPage; // XXX to pick up porlet context if present, since uploads are not called through portlet
329:
330: //
331: // FIXME: This has the unfortunate side effect that it will receive the
332: // contents. But we can't figure out the page to redirect to
333: // before we receive the file, due to the stupid constructor of MultipartRequest.
334: //
335: if (req.getContentLength() > m_maxSize) {
336: // FIXME: Does not delete the received files.
337: throw new RedirectException(
338: "File exceeds maximum size (" + m_maxSize
339: + " bytes)", errorPage);
340: }
341:
342: UserProfile user = context.getCurrentUser();
343:
344: //
345: // Go through all files being uploaded.
346: //
347: Enumeration files = multi.getFileParameterNames();
348:
349: while (files.hasMoreElements()) {
350: String part = (String) files.nextElement();
351: File f = multi.getFile(part);
352: AttachmentManager mgr = m_engine.getAttachmentManager();
353: InputStream in;
354:
355: try {
356: //
357: // Is a file to be uploaded.
358: //
359:
360: String filename = multi.getFileSystemName(part);
361:
362: if (filename == null
363: || filename.trim().length() == 0) {
364: log.error("Empty file name given.");
365:
366: throw new RedirectException(
367: "Empty file name given.", errorPage);
368: }
369:
370: //
371: // Should help with IE 5.22 on OSX
372: //
373: filename = filename.trim();
374:
375: log.debug("file=" + filename);
376: //
377: // Attempt to open the input stream
378: //
379: if (f != null) {
380: in = new FileInputStream(f);
381: } else {
382: //
383: // This happens onl when the size of the
384: // file is small enough to be cached in memory
385: //
386: in = multi.getFileContents(part);
387: }
388:
389: if (in == null) {
390: log.error("File could not be opened.");
391:
392: throw new RedirectException(
393: "File could not be opened.", errorPage);
394: }
395:
396: //
397: // Check whether we already have this kind of a page.
398: // If the "page" parameter already defines an attachment
399: // name for an update, then we just use that file.
400: // Otherwise we create a new attachment, and use the
401: // filename given. Incidentally, this will also mean
402: // that if the user uploads a file with the exact
403: // same name than some other previous attachment,
404: // then that attachment gains a new version.
405: //
406:
407: Attachment att = mgr.getAttachmentInfo(wikipage);
408:
409: if (att == null) {
410: att = new Attachment(wikipage, filename);
411: }
412:
413: //
414: // Check if we're allowed to do this?
415: //
416:
417: if (m_engine.getAuthorizationManager()
418: .checkPermission(att, user, "upload")) {
419: if (user != null) {
420: att.setAuthor(user.getName());
421: }
422:
423: try {
424: m_engine.getAttachmentManager()
425: .storeAttachment(att, in);
426: } catch (Exception e) {
427: log.error(
428: "Could not save attachment data: ",
429: e);
430:
431: throw new RedirectException(
432: "Failed to store attachment.",
433: errorPage);
434: }
435:
436: log.info("User " + user
437: + " uploaded attachment to " + wikipage
438: + " called " + filename + ", size "
439: + multi.getFileSize(part));
440: } else {
441: throw new RedirectException(
442: "No permission to upload a file",
443: errorPage);
444: }
445: } finally {
446: if (f != null)
447: f.delete();
448: }
449: }
450:
451: // Inform the JSP page of which file we are handling:
452: // req.setAttribute( ATTR_ATTACHMENT, wikiname );
453: } catch (ProviderException e) {
454: msg = "Upload failed because the provider failed: "
455: + e.getMessage();
456: log.warn(msg + " (attachment: " + attName + ")", e);
457:
458: throw new IOException(msg);
459: } catch (IOException e) {
460: // Show the submit page again, but with a bit more
461: // intimidating output.
462: msg = "Upload failure: " + e.getMessage();
463: log.warn(msg + " (attachment: " + attName + ")", e);
464:
465: throw e;
466: } finally {
467: // FIXME: In case of exceptions should absolutely
468: // remove the uploaded file.
469: }
470:
471: return nextPage;
472: }
473:
474: /**
475: * Produces debug output listing parameters and files.
476: */
477: /*
478: private void debugContentList( MultipartRequest multi )
479: {
480: StringBuffer sb = new StringBuffer();
481:
482: sb.append( "Upload information: parameters: [" );
483:
484: Enumeration params = multi.getParameterNames();
485: while( params.hasMoreElements() )
486: {
487: String name = (String)params.nextElement();
488: String value = multi.getURLParameter( name );
489: sb.append( "[" + name + " = " + value + "]" );
490: }
491:
492: sb.append( " files: [" );
493: Enumeration files = multi.getFileParameterNames();
494: while( files.hasMoreElements() )
495: {
496: String name = (String)files.nextElement();
497: String filename = multi.getFileSystemName( name );
498: String type = multi.getContentType( name );
499: File f = multi.getFile( name );
500: sb.append( "[name: " + name );
501: sb.append( " temp_file: " + filename );
502: sb.append( " type: " + type );
503: if (f != null)
504: {
505: sb.append( " abs: " + f.getPath() );
506: sb.append( " size: " + f.length() );
507: }
508: sb.append( "]" );
509: }
510: sb.append( "]" );
511:
512:
513: log.debug( sb.toString() );
514: }
515: */
516:
517: }
|