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&#46;hd&#46;org/. */
185        private static final long serialVersionUID = 4321077856636574117L;
186        }