support for CAPTCHAs
[mir.git] / source / mircoders / localizer / basic / MirBasicPostingSessionHandler.java
1 /*
2  * Copyright (C) 2001, 2002 The Mir-coders group
3  *
4  * This file is part of Mir.
5  *
6  * Mir is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Mir is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Mir; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * In addition, as a special exception, The Mir-coders gives permission to link
21  * the code of this program with  any library licensed under the Apache Software License,
22  * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library
23  * (or with modified versions of the above that use the same license as the above),
24  * and distribute linked combinations including the two.  You must obey the
25  * GNU General Public License in all respects for all of the code used other than
26  * the above mentioned libraries.  If you modify this file, you may extend this
27  * exception to your version of the file, but you are not obligated to do so.
28  * If you do not wish to do so, delete this exception statement from your version.
29  */
30 package mircoders.localizer.basic;
31
32 import mir.config.MirPropertiesConfiguration;
33 import mir.log.LoggerWrapper;
34 import mir.session.*;
35 import mir.storage.Database;
36 import mir.util.ExceptionRoutines;
37 import mir.util.FileRoutines;
38 import mir.util.IORoutines;
39 import mircoders.global.MirGlobal;
40 import mircoders.media.UnsupportedMediaTypeExc;
41 import mircoders.localizer.MirOpenPostingLocalizer;
42
43 import java.io.*;
44 import java.util.*;
45
46 /**
47  * Extensible handler for open postings.
48  * Behaviour can be altered by overriding methods.
49  */
50 public abstract class MirBasicPostingSessionHandler implements SessionHandler {
51   protected static LoggerWrapper logger = new LoggerWrapper("Localizer.OpenPosting");
52   protected MirPropertiesConfiguration configuration = MirPropertiesConfiguration.instance();
53
54   /** Previously uploaded files */
55   protected final List attachments = new ArrayList();
56   /** counter to generate unique field names for uploaded files */
57   protected int uploadedFileIndex = 0;
58
59   private String normalResponseGenerator;
60   private String dupeResponseGenerator;
61   private String unsupportedMediaTypeResponseGenerator;
62   private String finalResponseGenerator;
63
64   private boolean persistentUploadedFiles;
65
66   public MirBasicPostingSessionHandler(boolean aPersistentUploadedFiles) {
67     persistentUploadedFiles = aPersistentUploadedFiles;
68   }
69
70   protected void setNormalResponseGenerator(String aGenerator) {
71     normalResponseGenerator = aGenerator;
72   }
73
74   protected void setResponseGenerators(String aNormalResponseGenerator,
75         String aDupeResponseGenerator, String anUnsupportedMediaTypeResponseGenerator,
76         String aFinalResponseGenerator) {
77     setNormalResponseGenerator(aNormalResponseGenerator);
78     dupeResponseGenerator = aDupeResponseGenerator;
79     unsupportedMediaTypeResponseGenerator = anUnsupportedMediaTypeResponseGenerator;
80     finalResponseGenerator = aFinalResponseGenerator;
81   }
82
83   public void processRequest(Request aRequest, Session aSession, Response aResponse) throws SessionExc, SessionFailure {
84     if (MirGlobal.abuse().getOpenPostingDisabled()) {
85       makeOpenPostingDisabledResponse(aRequest, aSession, aResponse);
86       aSession.terminate();
87     }
88     else {
89       if (aSession.getAttribute("initialRequest") == null) {
90         initialRequest(aRequest, aSession, aResponse);
91         aSession.setAttribute("initialRequest", "no");
92       }
93       else {
94         subsequentRequest(aRequest, aSession, aResponse);
95       }
96     }
97   }
98
99   protected void initialRequest(Request aRequest, Session aSession, Response aResponse) throws SessionExc, SessionFailure {
100     initializeSession(aRequest, aSession);
101     initializeResponseData(aRequest, aSession, aResponse);
102     makeInitialResponse(aRequest, aSession, aResponse);
103   }
104
105   protected void processAttachments(Request aRequest, Session aSession, Response aResponse) {
106     Iterator i = attachments.iterator();
107     while (i.hasNext()) {
108       Attachment attachment = (Attachment) i.next();
109       try{
110         processAttachment(aRequest, aSession, attachment);
111       }
112       catch (Throwable t) {
113         try {
114           processAttachmentError(aRequest, aSession, attachment, t);
115         }
116         catch (Throwable u) {
117           logger.error("Error while processing attachment error", u);
118         }
119         logger.error("Error while processing attachment", t);
120       }
121     }
122   }
123
124   public void subsequentRequest(Request aRequest, Session aSession, Response aResponse) throws SessionExc, SessionFailure {
125     try {
126       try {
127         List validationErrors = new ArrayList();
128
129         preprocessPreviousAttachments(aRequest, aSession);
130         preProcessNewAttachments(aRequest, aSession);
131
132         if (!shouldProcessRequest(aRequest, aSession, validationErrors)) {
133           initializeResponseData(aRequest, aSession, aResponse);
134           makeResponse(aRequest, aSession, aResponse, validationErrors);
135         }
136         else {
137           preProcessRequest(aRequest, aSession);
138
139           processAttachments(aRequest, aSession, aResponse);
140           postProcessRequest(aRequest, aSession);
141           initializeResponseData(aRequest, aSession, aResponse);
142           makeFinalResponse(aRequest, aSession, aResponse);
143           aSession.terminate();
144         }
145       }
146       catch (Throwable t) {
147         initializeResponseData(aRequest, aSession, aResponse);
148         makeErrorResponse(aRequest, aSession, aResponse, t);
149         aSession.terminate();
150       }
151     }
152     catch (Throwable t) {
153       aSession.terminate();
154
155       throw new SessionFailure(t);
156     }
157   }
158
159   /**
160    * Initializes a session.
161    * This may happen in the case of a new session being initiated, but also
162    * when an older session gets re-initiated after a session timeout.
163    */
164   protected void initializeSession(Request aRequest, Session aSession) throws SessionExc, SessionFailure {
165     aSession.setAttribute("referer", aRequest.getHeader("Referer"));
166   }
167
168   /**
169    * Called every time a response is being prepared.
170    * This may be at the initial response, or on a subsequent one.
171    */
172   protected void initializeResponseData(Request aRequest, Session aSession, Response aResponse) throws SessionExc, SessionFailure {
173     int nrMediaItems = configuration.getInt("ServletModule.OpenIndy.DefaultMediaUploadItems", 5);
174
175     if (aSession.getAttribute("nrmediaitems")!=null) {
176       nrMediaItems = ((Integer) aSession.getAttribute("nrmediaitems")).intValue();
177     }
178
179     try {
180       nrMediaItems = Math.min(configuration.getInt("ServletModule.OpenIndy.MaxMediaUploadItems"), Integer.parseInt(aRequest.getParameter("nrmediaitems")));
181     }
182     catch (Throwable t) {
183       logger.warn("Error while retrieving configuration setting " +
184               "ServletModule.OpenIndy.MaxMediaUploadItems", t);
185     }
186
187     aSession.setAttribute("nrmediaitems", new Integer(nrMediaItems));
188
189     List mediaItems = new ArrayList();
190     for (int i=1; i<=nrMediaItems; i++) {
191       mediaItems.add(new Integer(i));
192     }
193
194     aResponse.setResponseValue("nrmediaitems", new Integer(nrMediaItems));
195     aResponse.setResponseValue("mediaitems", mediaItems);
196
197     if (MirGlobal.abuse().getRequireCaptcha()) {
198       aResponse.setResponseValue("password", Boolean.TRUE);
199     }
200     aResponse.setResponseValue("referer", aSession.getAttribute("referer"));
201     aResponse.setResponseValue("errors", null);
202
203     if (configuration.getBoolean("Localizer.OpenSession.AllowFTPUploads", false)) {
204       aResponse.setResponseValue("ftpfiles",
205           FileRoutines.getDirectoryContentsAsList(configuration.getFile("Localizer.OpenSession.FTPDirectory"),
206               new FilenameFilter() {
207                 public boolean accept(File aDir, String aName) {
208                   return !(new File(aDir, aName).isDirectory());
209                 }
210           }));
211     }
212     else {
213       aResponse.setResponseValue("ftpfiles", null);
214     }
215
216     initializeAttachmentResponseData(aRequest, aSession, aResponse);
217   }
218
219   /**
220    * Process possible changes to previously uploaded files
221    */
222   protected void initializeAttachmentResponseData(Request aRequest, Session aSession, Response aResponse) throws SessionExc, SessionFailure {
223     List result = new ArrayList();
224     if (persistentUploadedFiles) {
225       Iterator i = attachments.iterator();
226       while (i.hasNext()) {
227         Attachment attachment = (Attachment) i.next();
228         Map attachmentData = new HashMap();
229         attachmentData.putAll(attachment.getAllAttributes());
230         attachmentData.put("fieldname", attachment.getFieldName());
231         attachmentData.put("filename", attachment.getFileName());
232         result.add(attachmentData);
233       }
234     }
235
236     aResponse.setResponseValue("attachments", result);
237   }
238
239   /**
240    * Process possible changes to previously uploaded files
241    */
242   protected void preprocessPreviousAttachments(Request aRequest, Session aSession) throws SessionExc, SessionFailure {
243     synchronized (attachments) {
244       List previouslyUploadedFiles = new ArrayList(attachments);
245       attachments.clear();
246
247       if (persistentUploadedFiles) {
248         Iterator i = previouslyUploadedFiles.iterator();
249         while (i.hasNext()) {
250           Attachment uploadedFile = (Attachment) i.next();
251           if (!(aRequest.getParameter(uploadedFile.getFieldName()+"_cancel")!=null)) {
252             addAttachment(aRequest, aSession, uploadedFile, uploadedFile.getFieldName());
253           }
254         }
255       }
256     }
257   }
258
259   protected void addAttachment(Request aRequest, Session aSession, Attachment anAttachment, String aFieldName) throws SessionExc, SessionFailure {
260     List parameters = aRequest.getPrefixedParameterNames(aFieldName+"_");
261     Iterator j = parameters.iterator();
262     while (j.hasNext()) {
263       String parameter = ((String) j.next());
264       anAttachment.setAttribute(
265           parameter.substring(aFieldName.length()+1),
266           aRequest.getParameter(parameter));
267     }
268     attachments.add(anAttachment);
269   }
270
271   public void preprocessNewAttachment(Request aRequest, Session aSession, UploadedFile aFile) throws SessionExc, SessionFailure {
272       Attachment uploadedFile =
273           new Attachment(aFile, aFile.getFileName(), "attachment"+ ++uploadedFileIndex, aFile.getContentType());
274
275       addAttachment(aRequest, aSession, uploadedFile, aFile.getFieldName());
276   }
277
278
279   /**
280    * Process newly uploaded files
281    */
282   protected void preProcessNewAttachments(Request aRequest, Session aSession) throws SessionExc, SessionFailure {
283     Iterator i = aRequest.getUploadedFiles().iterator();
284     while (i.hasNext()) {
285       preprocessNewAttachment(aRequest, aSession, (UploadedFile) i.next());
286     }
287
288     if (configuration.getBoolean("Localizer.OpenSession.AllowFTPUploads", false)) {
289       File FTPDirectory = configuration.getFile("Localizer.OpenSession.FTPDirectory");
290
291       List ftpUploads = new ArrayList(aRequest.getPrefixedParameterNames("ftpupload"));
292       Collections.sort(ftpUploads, new Comparator() {
293         public int compare(Object o1, Object o2) {
294           if (o1 instanceof String && o2 instanceof String) {
295             return ((String) o1).compareTo((String) o2);
296           }
297           else {
298             return 0;
299           }
300         }
301       });
302
303       i = ftpUploads.iterator();
304       while (i.hasNext()) {
305         final String fieldName = (String) i.next();
306
307         if (fieldName.indexOf("_")<0) {
308           final String fileName = aRequest.getParameter(fieldName);
309
310           if (fileName!=null && fileName.trim().length()>0) {
311             final File sourceFile = new File(FTPDirectory, fileName);
312
313             if (sourceFile.getParentFile().equals(FTPDirectory)) {
314               preprocessNewAttachment(aRequest, aSession, new UploadedFile() {
315                 public void writeToFile(File aFile) throws SessionFailure {
316                   try {
317                     FileRoutines.move(sourceFile, aFile);
318                   }
319                   catch (IOException e) {
320                     throw new SessionFailure(e);
321                   }
322                 }
323
324                 public void writeToStream(OutputStream aStream) throws SessionFailure {
325                   try {
326                     IORoutines.copyStream(getInputStream(), aStream);
327                   }
328                   catch (IOException e) {
329                     throw new SessionFailure(e);
330                   }
331                 }
332
333                 public InputStream getInputStream() throws SessionFailure {
334                   try {
335                     return new FileInputStream(sourceFile);
336                   }
337                   catch (IOException e) {
338                     throw new SessionFailure(e);
339                   }
340                 }
341
342                 public String getFileName() {
343                   return fileName;
344                 }
345
346                 public String getFieldName() {
347                   return fieldName;
348                 }
349
350                 public String getContentType() {
351                   return null;
352                 }
353               });
354             }
355           }
356         }
357       }
358     }
359   }
360
361   protected void makeInitialResponse(Request aRequest, Session aSession, Response aResponse) throws SessionExc, SessionFailure {
362     aResponse.setResponseGenerator(normalResponseGenerator);
363   }
364
365   protected void makeResponse(Request aRequest, Session aSession, Response aResponse, List anErrors) throws SessionExc, SessionFailure {
366     aResponse.setResponseValue("errors", anErrors);
367     aResponse.setResponseGenerator(normalResponseGenerator);
368   }
369
370   protected void makeFinalResponse(Request aRequest, Session aSession, Response aResponse) throws SessionExc, SessionFailure {
371     aResponse.setResponseGenerator(finalResponseGenerator);
372   }
373
374   protected void makeErrorResponse(Request aRequest, Session aSession, Response aResponse, Throwable anError) throws SessionExc, SessionFailure {
375     Throwable rootCause = ExceptionRoutines.traceCauseException(anError);
376
377     if (rootCause instanceof DuplicatePostingExc)
378       aResponse.setResponseGenerator(dupeResponseGenerator);
379     if (rootCause instanceof UnsupportedMediaTypeExc) {
380       aResponse.setResponseValue("mimetype", ((UnsupportedMediaTypeExc) rootCause).getMimeType());
381       aResponse.setResponseGenerator(unsupportedMediaTypeResponseGenerator);
382     }
383     else {
384       List errors = new ArrayList();
385       errors.add(new ValidationError("", "general.unexpectederror",
386           new Object[] {anError.getMessage()}));
387       makeResponse(aRequest, aSession, aResponse, errors);
388     }
389   }
390
391   protected void makeOpenPostingDisabledResponse(Request aRequest, Session aSession, Response aResponse) {
392     aResponse.setResponseGenerator(configuration.getString("ServletModule.OpenIndy.PostingDisabledTemplate"));
393   }
394
395   /**
396    *
397    */
398   protected void preProcessRequest(Request aRequest, Session aSession) throws SessionExc, SessionFailure {
399   }
400   public void processAttachment(Request aRequest, Session aSession, Attachment aFile) throws SessionExc, SessionFailure {
401   }
402   public void processAttachmentError(Request aRequest, Session aSession, Attachment aFile, Throwable anError) {
403   }
404   protected void postProcessRequest(Request aRequest, Session aSession) throws SessionExc, SessionFailure {
405   }
406
407   /**
408    * Determine whether the request shoudl be processed: that is, the validate,
409    * preprocess, postprocess methods should be called to perform validations,
410    * store data, etc
411    */
412   protected boolean shouldProcessRequest(Request aRequest, Session aSession, List aValidationErrors) throws SessionExc, SessionFailure {
413     if (aRequest.getParameter("post")==null) {
414       return false;
415     }
416     validate(aValidationErrors, aRequest, aSession);
417     
418     return (aValidationErrors == null || aValidationErrors.size() == 0);
419   }
420
421   /**
422    * Method used to validate user input.
423    * Multiple {@link ValidationError}s may be added to the
424    * <code>aResults</code> parameter.
425    * The request is considered validated if, after calling this method,
426    * <code>aResults</code> is empty.
427    */
428   protected void validate(List aResults, Request aRequest, Session aSession) throws SessionExc, SessionFailure {
429     if (MirGlobal.abuse().getRequireCaptcha()) {
430       String submittedPassword = aRequest.getParameter("password");
431       MirOpenPostingLocalizer.Captcha captcha = (MirOpenPostingLocalizer.Captcha) aSession.getAttribute("captcha");
432
433       if (captcha == null || !captcha.validateAnswer(submittedPassword)) {
434         aResults.add(new ValidationError("password", "validationerror.passwordmismatch"));
435       }
436     }
437   }
438
439
440
441   /**
442    * Method to filter the attributes and their values of a request
443    * based on the fields of a database object.
444    */
445   protected static Map getIntersectingValues(Request aRequest, Database aStorage) throws SessionFailure {
446     Map result = new HashMap();
447
448     Iterator i = aStorage.getFieldNames().iterator();
449
450     while (i.hasNext()) {
451       String fieldName = (String) i.next();
452       Object value = aRequest.getParameter(fieldName);
453       if (value != null)
454         result.put(fieldName, value);
455     }
456
457     return result;
458   }
459
460   /**
461    * Exception to be thrown when an article or comment was already posted
462    */
463   protected static class DuplicatePostingExc extends SessionExc {
464     public DuplicatePostingExc(String aMessage) {
465       super(aMessage);
466     }
467   }
468
469   /**
470    * A file that has been attached to a session
471    */
472   protected static class Attachment implements UploadedFile {
473     private UploadedFile uploadedFile;
474     private String filename;
475     private String fieldName;
476     private String contentType;
477     private Map attributes;
478
479     public Attachment(UploadedFile anUploadedFile, String aFilename, String aFieldName, String aContentType) {
480       attributes = new HashMap();
481       filename = aFilename;
482       fieldName = aFieldName;
483       contentType = aContentType;
484       uploadedFile = anUploadedFile;
485     }
486
487     public void writeToFile(File aFile) throws SessionExc, SessionFailure {
488       uploadedFile.writeToFile(aFile);
489     }
490
491     public void writeToStream(OutputStream aStream) throws SessionExc, SessionFailure {
492       try {
493         IORoutines.copyStream(uploadedFile.getInputStream(), aStream);
494       }
495       catch (IOException e) {
496         throw new SessionFailure(e);
497       }
498     }
499
500     public InputStream getInputStream() throws SessionExc, SessionFailure {
501       return uploadedFile.getInputStream();
502     }
503
504     public String getFileName() {
505       return filename;
506     }
507
508     public String getFieldName() {
509       return fieldName;
510     }
511
512     public String getContentType() {
513       return contentType;
514     }
515
516     public String getAttribute(String anAttribute) {
517       return (String) attributes.get(anAttribute);
518     }
519
520     public void setAttribute(String anAttribute, String aValue) {
521       attributes.put(anAttribute, aValue);
522     }
523
524     public Map getAllAttributes() {
525       return Collections.unmodifiableMap(attributes);
526     }
527   }
528 }
529