001 /*
002 Copyright (c) 1996-2012, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029 package org.hd.d.pg2k.webSvr.upload;
030
031 import java.io.File;
032 import java.io.IOException;
033
034 import javax.servlet.ServletConfig;
035 import javax.servlet.ServletInputStream;
036 import javax.servlet.ServletOutputStream;
037 import javax.servlet.http.HttpServlet;
038 import javax.servlet.http.HttpServletRequest;
039 import javax.servlet.http.HttpServletResponse;
040
041 import org.hd.d.pg2k.svrCore.AllExhibitProperties;
042 import org.hd.d.pg2k.svrCore.CoreConsts;
043 import org.hd.d.pg2k.svrCore.ExhibitName;
044 import org.hd.d.pg2k.svrCore.Rnd;
045 import org.hd.d.pg2k.svrCore.props.GenProps;
046 import org.hd.d.pg2k.svrCore.props.LocalProps;
047 import org.hd.d.pg2k.svrCore.props.SimplepassProps;
048 import org.hd.d.pg2k.svrCore.uploader.UploaderConsts;
049 import org.hd.d.pg2k.webSvr.exhibit.DataSourceBean;
050 import org.hd.d.pg2k.webSvr.util.WebUtils;
051
052 /**This is the servlet that accepts uploads from non-browser sources.
053 * This is used by the JWS-based uploader.
054 * <p>
055 * This should coexist with the HTML/JSP/browser-based uploader,
056 * and respect at least the same security and safety restrictions.
057 * <p>
058 * This will respond only to POST requests for efficiency and security.
059 * <p>
060 * The general form of the protocol for upload is:
061 * <ol>
062 * <li>The client sends authentication credentials.
063 * <li>The server sends a reject and drops the connection,
064 * else an OK
065 * [and the current space available]
066 * [and the hashes of all items in the upload area].
067 * <li>The client then sends, one per exhibit:
068 * <ul>
069 * <li>The full proposed exhibit name.
070 * <li>The exhibit hash.
071 * <li>The exhibit description, if any.
072 * <li>The exhibit data.
073 * </ul>
074 * <li>The server responds with an OK
075 * (naming the accepted exhibit to avoid ambiguity)
076 * or reject, dropping the connection.
077 * <li>If the connection remains idle too long,
078 * or for any other reason,
079 * the server may asynchronously send a "reject" and drop the connection.
080 * <li>The client, when it has no more to upload,
081 * may send a "close" request, wait for a response,
082 * and then drop the connection.
083 * </ol>
084 */
085 public final class UploadServlet extends HttpServlet
086 {
087 /**Get singleton (per-servlet-context) data pipeline/cache instance.
088 * The config parameter must not be null, but for some operations
089 * (such as calling destroy()) request can be null.
090 */
091 private static DataSourceBean getDataSource(
092 final ServletConfig config,
093 final HttpServletRequest request)
094 {
095 // Fetches/creates the data source...
096 final DataSourceBean dataSource =
097 DataSourceBean.getApplicationInstance(config.getServletContext());
098
099 // Ensure that the essential details are set up.
100 dataSource.setServletContext(config.getServletContext());
101 if(request != null)
102 { dataSource.setContextPath(request.getContextPath()); }
103
104 return(dataSource);
105 }
106
107
108 /**Respond to a POST request for the content served by this servlet.
109 *
110 * @param request The servlet request we are processing
111 * @param response The servlet response we are producing
112 *
113 * @exception java.io.IOException if an input/output error occurs
114 */
115 @Override
116 public void doPost(final HttpServletRequest request,
117 final HttpServletResponse response)
118 throws IOException // , ServletException
119 {
120 // Make this page uncacheable.
121 response.setHeader("Cache-Control", "no-cache");
122 response.setHeader("Pragma", "no-cache");
123 response.setHeader("Expires", "0");
124
125 // Reject requests immediately if not prepared to accept any uploads.
126 final String uploadDirS = LocalProps.getUploadDir();
127 if((uploadDirS == null) || !(new File(uploadDirS)).isDirectory() ||
128 (LocalProps.getUploadMaxBytesTotal() <= 0))
129 {
130 // If no upload directory or space, uploads are disabled.
131 response.sendError(HttpServletResponse.SC_FORBIDDEN, "Uploads are disabled for this system, sorry.");
132 return;
133 }
134 final DataSourceBean dataSource = getDataSource(getServletConfig(), request);
135 if(!Boolean.FALSE.equals(dataSource.isSlave()))
136 {
137 // If not the master then we can't do an upload.
138 response.sendError(HttpServletResponse.SC_FORBIDDEN, "You are not connected to the master system, so you cannot do an upload, sorry.");
139 return;
140 }
141
142 // Check the user credentials (supplied in the headers).
143 //
144 // We insist on:
145 // 1) A syntactically-valid author name.
146 // 2) The author being already extant in the Gallery.
147 // 3) A valid-length password being supplied.
148 // 4) The password actually being correct!
149 final String userID = request.getHeader("X-" + UploaderConsts.USERID_FIELD_NAME);
150 final String userPass = request.getHeader("X-" + UploaderConsts.PASSWD_FIELD_NAME);
151
152 /* if(IsDebug.isDebug) */ { System.out.println("[***userID = "+userID+" uploading with batch client.]"); }
153
154 final AllExhibitProperties aep = dataSource.getAllExhibitProperties(-1);
155 if(!ExhibitName.validAuthorSyntax(userID) || // Valid author syntax.
156 (aep.getAuthorExhibitCounts().get(userID) == null) || // Extant.
157 (userPass == null) || // Must be a password and a suitable length.
158 (userPass.length() < CoreConsts.MIN_PASSWORD_LEN) ||
159 (userPass.length() > CoreConsts.MAX_PASSWORD_LEN) ||
160 !SimplepassProps.isAuthorUploadPasswordCorrect(userID, userPass))
161 {
162 // Bad credentials...
163 // Wait a while before responding
164 // so as to make brute-force attacks hard(er),
165 // but not so long as to absorb excess resources.
166 try { Thread.sleep(5000 + Rnd.fastRnd.nextInt(15000)); }
167 catch(final InterruptedException e) { }
168 response.sendError(HttpServletResponse.SC_FORBIDDEN, "Bad credentials.");
169 return;
170 }
171
172 final GenProps gp = dataSource.getGenProps(-1);
173 final WebUtils.ServletLogger logger = new WebUtils.ServletLogger(getServletContext());
174
175 // Get access to the input and output streams.
176 final ServletInputStream rawIS = request.getInputStream();
177 final ServletOutputStream rawOS = response.getOutputStream();
178
179 // Do the batch upload protocol..
180 HTTPUploaderUtils.runBatchUploadProtocolServerSide(logger, aep, gp, rawIS, rawOS, uploadDirS, userID, userPass);
181 }
182
183
184 /**Unique Serialisation class ID generated by http://random.hd.org/. */
185 private static final long serialVersionUID = 4321077856636574117L;
186 }