001package bradleyross.j2ee.servlets;
002import javax.servlet.http.HttpServlet;
003import javax.servlet.http.HttpServletRequest;
004import javax.servlet.http.HttpServletResponse;
005import javax.servlet.ServletConfig;
006import javax.servlet.ServletException;
007import javax.servlet.ServletInputStream;
008import java.io.IOException;
009import java.io.ByteArrayOutputStream;
010import java.io.File;
011import java.io.FileOutputStream;
012import java.io.UnsupportedEncodingException;
013import java.util.Enumeration;
014import java.util.Hashtable;
015import java.util.Vector;
016import java.sql.PreparedStatement;
017import java.sql.SQLException;
018import java.sql.Types;
019import java.text.NumberFormat;
020import bradleyross.library.helpers.StringHelpers;
021import bradleyross.library.database.DatabaseProperties;
022import bradleyross.library.helpers.GenericPrinter;
023// import bradleyross.library.helpers.StringHelpers;
024/**
025 * Start of servlet for testing techniques for uploads.
026 * <ul>
027 * <li><a href="http://www.faqs.org/rfcs/rfc2616.html" target="_blank">
028 *     RFC 2616</a>  See section 19.5.1</li>
029 * <li><a href="http://www.faqs.org/rfcs/rfc2388.html" target="_blank">
030 *     RFC 2388 - Returning Values From Forms: multipart/form-data</a></li>
031 * <li><a href="http://www.faqs.org/rfcs/rfc1867.html" target="_blank">
032 *     RFC 1867 - Form-based File Upload in HTML</a></li>
033 * </ul>
034 * <p>The specifications seem to say that the values on the headers may
035 *    be tokens or quoted strings.</p>
036 *    
037 * <p>It may be necessary to expand this algorithm to handle additional 
038 *    transfer encoding types.  The two mentioned on the web are base64 and
039 *    quoted-printable.</p>
040 * <p>MSDN lists the following types:  7bit, 8bit, binary, base64,
041 *    quoted-printable, and X-token.</p>
042 * @author Bradley Ross
043 *
044 */
045public class UploadServlet extends HttpServlet
046{
047        /**
048         * Methods for formatting integers.
049         * 
050         * @author Bradley Ross
051         *
052         */
053        public class Formatters
054        {
055                /**
056                 * Format as a two digit number with leading zeroes.
057                 */
058                protected NumberFormat nf2 = null;
059                /**
060                 * Return the two digit formatter.
061                 * @return Formatter
062                 */
063                public NumberFormat getNf2()
064                {
065                        return nf2;
066                }
067                /**
068                 * Format as a four digit number with leading zeroes.
069                 */
070                protected NumberFormat nf4 = null;
071                /**
072                 * Return the four digit formatter.
073                 * @return Formatter
074                 */
075                public NumberFormat getNf4()
076                {
077                        return nf4;
078                }
079                /**
080                 * Format as a five digit number with leading zeroes.
081                 */
082                protected NumberFormat nf5 = null;
083                /**
084                 * Return the five digit formatter.
085                 * @return Formatter
086                 */
087                public NumberFormat getNf5()
088                {
089                        return nf5;
090                }
091                /**
092                 * Format as a six digit number with leading zeroes.
093                 */
094                protected NumberFormat nf6 = null;
095                /**
096                 * Return the six digit formatter.
097                 * @return Formatter
098                 */
099                public NumberFormat getNf6()
100                {
101                        return nf6;
102                }
103                /**
104                 * Format as a eight digit number with leading zeroes.
105                 */
106                protected NumberFormat nf8 = null;
107                /**
108                 * Return the eight digit formatter.
109                 * @return Formatter
110                 */
111                public NumberFormat getNf8()
112                {
113                        return nf8;
114                }
115                public Formatters()
116                {
117                        /*
118                         * 2 digit formatter
119                         */
120                        nf2 = NumberFormat.getIntegerInstance();
121                        nf2.setMinimumIntegerDigits(2);
122                        nf2.setMaximumIntegerDigits(2);
123                        nf2.setGroupingUsed(false);
124                        nf2.setParseIntegerOnly(true);
125                        nf2.setMaximumFractionDigits(0);
126                        /*
127                         * 4 digit formatter
128                         */
129                        nf4 = NumberFormat.getIntegerInstance();
130                        nf4.setMinimumIntegerDigits(4);
131                        nf4.setMaximumIntegerDigits(4);
132                        nf4.setGroupingUsed(false);
133                        nf4.setParseIntegerOnly(true);
134                        nf4.setMaximumFractionDigits(0);
135                        /*
136                         * 5 digit formatter
137                         */
138                        nf5 = NumberFormat.getIntegerInstance();
139                        nf5.setMinimumIntegerDigits(5);
140                        nf5.setMaximumIntegerDigits(5);
141                        nf5.setGroupingUsed(false);
142                        nf5.setParseIntegerOnly(true);
143                        nf5.setMaximumFractionDigits(0);
144                        /*
145                         * 6 digit formatter
146                         */
147                        nf6 = NumberFormat.getIntegerInstance();
148                        nf6.setMinimumIntegerDigits(6);
149                        nf6.setMaximumIntegerDigits(6); 
150                        nf6.setGroupingUsed(false);
151                        nf6.setParseIntegerOnly(true);
152                        nf6.setMaximumFractionDigits(0);
153                        /*
154                         * 8 digit formatter
155                         */
156                        nf8 = NumberFormat.getIntegerInstance();
157                        nf8.setMinimumIntegerDigits(6);
158                        nf8.setMaximumIntegerDigits(6); 
159                        nf8.setGroupingUsed(false);
160                        nf8.setParseIntegerOnly(true);
161                        nf8.setMaximumFractionDigits(0);
162                }
163        }
164        /**
165         * Contains information relating to a single HTTP request.
166         * 
167         * <p>Since a single object can be used for multiple 
168         *    simultaneous HTTP requests, information about a
169         *    single request can't be placed in the fields relating
170         *    to the instance.</p>
171         * <p>By placing the contents of all of the data fields in this object, the
172         *    methods can have access to all of the fields without interfering with each other.</p>
173         * <p>Each of the input fields is identified by its name in the HTML tag.</p>
174         *    
175         * @author Bradley Ross
176         *
177         */
178        public class ThisPage extends bradleyross.j2ee.servlets.ThisPage
179        {
180                /**
181                 * Contains the various elements from the requesting form.
182                 */
183                protected Hashtable<String, Contents> items = new Hashtable<String, Contents>();
184                /** 
185                 * True if final part of request has been
186                 * processed.
187                 * 
188                 * @see #getEndOfPacket()
189                 * @see #setEndOfPacket(boolean)
190                 */
191                protected boolean endOfPacket = false;
192                /**
193                 * Obtain value of ServletInputStream object.
194                 * @return ServletInputStream object
195                 */
196                /* public ServletInputStream getInputStream()
197                {
198                        return input;
199                } */
200
201                /**
202                 * Sets value of endOfPacket.
203                 * 
204                 * @param value Value for endOfPacket
205                 * @see #endOfPacket
206                 */
207                public void setEndOfPacket(boolean value)
208                {
209                        endOfPacket = value;
210                }
211                /**
212                 * Gets value of endOfPacket.
213                 * 
214                 * @return value of endOfPacket
215                 * @see #endOfPacket
216                 */
217                public boolean getEndOfPacket()
218                {
219                        return endOfPacket;
220                }
221                /**
222                 * Boundary marker for multipart form
223                 */
224                protected String boundary = null;
225                /**
226                 * Obtains value of boundary marker
227                 * @return Value of boundary marker
228                 */
229                public String getBoundary()
230                {
231                        return boundary;
232                }
233                /**
234                 * Adds an element to the list associated with the HTTP request.
235                 * @param value Element
236                 */
237                public void addElement(Contents value)
238                {
239                        items.put(value.getName(), value);
240                }
241                /**
242                 * Constructor for object, setting the input and output streams so that the 
243                 *    element can be processed.
244                 *    @param value1 Request object
245                 *    @param value2 Response object
246                 *    @param value3 Servlet configuration object
247                 *    @throws IOException if io errors
248                 *    
249             */
250                public ThisPage (HttpServletRequest value1, HttpServletResponse value2, ServletConfig value3)
251                throws IOException
252                {
253                        super(value1, value2, value3);
254                        context = config.getServletContext();
255                        boundary = extractBoundary(getRequest());
256                        input = getRequest().getInputStream();
257
258                }
259                /**
260                 * Return an enumeration containing the names of the elements.
261                 * @return Enumeration containing list of names.
262                 */
263                public Enumeration<String> getPartNames()
264                {
265                        return items.keys();
266                }
267                /**
268                 * Address to be used for redirecting servlet upon completion.
269                 * @see HttpServletResponse#sendRedirect(String)
270                 * @see #getRedirectAddress()
271                 * @see #setRedirectAddress(String)
272                 */
273                protected String redirectAddress = null;
274                /**
275                 * Set the URL for redirecting the servlet upon completion.
276                 * @param value URL to be used for redirecting servlet
277                 * @see #redirectAddress
278                 */
279                public void setRedirectAddress(String value)
280                {
281                        redirectAddress = value;
282                }
283                /**
284                 * Get the URL to be used for redirecting the servlet upon completion.
285                 * @return URL to be used
286                 * @see #redirectAddress
287                 */
288                public String getRedirectAddress()
289                {
290                        return redirectAddress;
291                }
292                /**
293                 * Retrieve the object associated with an element based on the
294                 * element name.
295                 * @param key Name of the element in the form
296                 * @return Object containing the contents of the element, including
297                 *         name, MIME type, and contents
298                 */
299                public Contents getElement(String key)
300                {
301                        return items.get(key);
302                }
303                /**
304                 * Return the contents of one of the elements as
305                 * a String object.
306                 * 
307                 * @param key Key value for element
308                 * @return String object containing contents
309                 */
310                public String getString(String key)
311                {
312                        Contents element = getElement(key);
313                        if (element == null)
314                        {
315                                return null;
316                        }
317                        byte contents[] = element.getContents();
318                        if (contents == null)
319                        {
320                                return null;
321                        }
322                        else if (contents.length == 0)
323                        {
324                                return null;
325                        }
326                        String working = new String(contents);
327                        return working.trim();
328                }
329                /**
330                 * Return the contents of one of the elements as a byte
331                 * array.
332                 * 
333                 * @param key Key value for the element
334                 * @return Byte array containing contents of element
335                 */
336                public byte[] getByteArray(String key)
337                {
338                        Contents element = getElement(key);
339                        if (element == null)
340                        {
341                                return null;
342                        }
343                        byte contents[] = element.getContents();
344                        if (contents == null)
345                        {
346                                return null;
347                        }
348                        else if (contents.length == 0)
349                        {
350                                return null;
351                        }
352                        return contents;
353                }
354        }
355        /**
356         * Contains information on the contents of a single part of a 
357         * multipart form.
358         * 
359         * <p>See <a href="http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2" target="_blank">
360         *    http://www.w3.org/TR/html401/interact/forms.html#h-17.13.42</a> for a discussion
361         *    of <i>multipart/form-data</i>.</p>
362         * <p>All lines must end with \r\n.  However, binary data may have \r\n in the middle
363         *    of the material.</p>
364         * 
365         * @author Bradley Ross
366         *
367         */
368        public class Contents
369        {
370                /**
371                 * Name of the part.
372                 */
373                protected String name = null;
374                /**
375                 * Filename associated with the part.
376                 */
377                protected String filename = null;
378                /**
379                 * MIME code for the data in the part.
380                 */
381                protected String mime = null;
382                /**
383                 * Content transfer encoding method for the
384                 * element of the transaction.
385                 * 
386                 * <p>According to the documentation, there
387                 *    are various encoding methods that can be
388                 *    used such as base64 and quoted-printable.
389                 *    These have not been seen in the browsers
390                 *    I have used, but I may have to allow for
391                 *    these encoding methods.</p>
392                 */
393                protected String transferEncoding = null;
394                /**
395                 * The byte array containing the data for this
396                 * part.
397                 */
398                protected byte value[] = null;
399                /**
400                 * Obtain the name of the part.
401                 * @return Name of part
402                 */
403                public String getName()
404                {
405                        return name;
406                }
407                /**
408                 * Obtain the filename associated with the part.
409                 * 
410                 * <p>If the data was not taken from a file, a null
411                 *    value is returned.</p>
412                 * @return Filename associated with part
413                 */
414                public String getFilename()
415                {
416                        return filename;
417                }
418                /**
419                 * Obtain mime type for part.
420                 * 
421                 * <p>MIME types would only be associated with data taken from
422                 *    files.</p>
423                 *    
424                 * @return MIME code
425                 */
426                public String getMime()
427                {
428                        return mime;
429                }
430                /**
431                 * Obtain the value for content-transfer-encoding.
432                 * 
433                 * @return Type of encoding
434                 */
435                public String getTransferEncoding()
436                {
437                        return transferEncoding;
438                }
439                /**
440                 * <p>Obtains the byte array that was associated with this
441                 * part of the form.</p>
442                 * @return byte array
443                 */
444                public byte[] getContents()
445                {
446                        return value;
447                }
448                /**
449                 * Obtains the size of the byte array that was associated with
450                 * this part of the form.
451                 * @return Size of byte array
452                 */
453                public int getContentsSize()
454                {
455                        if (value == null)
456                        { return 0; }
457                        return value.length;
458                }
459                /**
460                 * Parses header lines in an individual part of a multipart form.
461                 * 
462                 * <p>This algorithm works if the values for the name and filename
463                 *    are surrounded by double quotes, there are no escaped 
464                 *    characters with the values, and there are no spaces between the
465                 *    start of the word name or filename and the corresponding closing
466                 *    quote.  It also assumes that the name parameter comes before
467                 *    the filename parameter.  I'm not sure if these assumptions are valid.</p>
468                 * @param lineValue Line to be parsed
469                 */
470                public void parseLine(String lineValue)
471                {
472                        String line = lineValue;
473                        if (line.endsWith("\r\n"))
474                        {
475                                int length = line.length();
476                                line = line.substring(0, length - 2);
477                        }
478                        if (line.toLowerCase().startsWith("content-disposition:")) 
479                        {
480                                int start = -1;
481                                int end = -1;
482                                start = line.toLowerCase().indexOf("name=\"");
483                                if (start >= 0)
484                                {
485                                        start = start + 6;
486                                        end = line.indexOf("\"", start);
487                                        name = line.substring(start, end);
488                                }
489                                start = line.toLowerCase().indexOf("filename=\"") ;
490                                if (start >= 0)
491                                {
492                                        start = start + 10;
493                                        end = line.indexOf("\"", start);
494                                        filename = line.substring(start, end);
495                                }               
496                        }
497                        else if (line.toLowerCase().startsWith("content-type:"))
498                        {
499                                int loc = line.indexOf(":");
500                                mime = line.substring(loc + 1).trim();
501                        }       
502                        else if (line.toLowerCase().startsWith("content-transfer-encoding:"))
503                        {
504                                int loc = line.indexOf(":");
505                                transferEncoding = line.substring(loc + 1).trim();
506                        }
507                }
508                /**
509                 * Connect the byte array to the object.
510                 * @param input Byte array to be attached to object
511                 */
512                public void setContents(byte[] input)
513                {
514                        value = input;
515                }
516                public String toString()
517                {
518                        StringBuffer working = new StringBuffer();
519                        working.append( "Name: " + name + "; Filename: " + filename + "; MIME: " + mime +
520                        "\r\n\r\n");
521                        if (value == null)
522                        {
523                                return new String(working);
524                        }
525                        else if (value.length == 0)
526                        {
527                                return new String(working);
528                        }
529                        try 
530                        {
531                                String addition = new String(value, "ISO8859_1");
532                                working.append(addition);
533                        } 
534                        catch (UnsupportedEncodingException e) 
535                        {       
536                                working.append("Unable to represent byte stream");
537                        }
538                        return new String(working);
539                }
540        }
541        /**
542         * Dummy id to satisfy serializable interface
543         */
544        private static final long serialVersionUID = 1L;
545        /**
546         * Used to control amount of debugging output.
547         */
548        protected static int debugLevel = 0;
549        /**
550         * ServletConfig object for servlet.
551         */
552        protected ServletConfig config = null;
553        /**
554         * Called when object for handling HTTP request is created.
555         */
556        public void init(ServletConfig configIn) throws ServletException
557        {
558                config= configIn;
559        }
560        /**
561         * Process the call to the servlet.
562         * 
563         * <p>With regard to reading data from the request, a
564         *    {@link javax.servlet.ServletInputStream} object for binary data
565         *    can be obtained by {@link HttpServletRequest#getInputStream()}
566         *    or a {@link java.io.BufferedReader} can be obtained for character data
567         *    by using
568         *    {@link HttpServletRequest#getReader}.</p>
569         * <p>With regard to writing data to the response a
570         *    {@link javax.servlet.ServletOutputStream} object for binary
571         *    data can be obtained using {@link HttpServletResponse#getOutputStream()}
572         *    while a {@link java.io.PrintWriter} object can be obtained using
573         *    {@link HttpServletResponse#getWriter()}.</p>
574         * <p>The {@link java.io.ByteArrayOutputStream} class can be used as a means of collecting
575         *    the bytes contained in the attached file.</p>
576         * <p>It would be desirable to have tests for enctype and method.</p>
577         *    
578         * @param req Request object
579         * @param res Response object
580         * @throws IOException if io problems
581         */
582        public void service (HttpServletRequest req,
583                        HttpServletResponse res)
584        throws IOException
585        {
586                ThisPage thisPage = new ThisPage(req, res, config);
587                GenericPrinter output = thisPage.getPrinter();
588                if (!req.getMethod().equalsIgnoreCase("post"))
589                {
590                        output.println("<html><head>");
591                        output.println("<title>Must use POST method</title>");
592                        output.println("</head><body>");
593                        output.println("<h1>Must use POST method</h1>");
594                        output.println("<p>Request used " + req.getMethod() + " method</p>");
595                        output.println("<p>Must use POST method</p>");
596                        output.println("</body></html>");
597                        thisPage.sendContents();
598                        return;
599                }
600                else if (req.getHeader("content-type") == null)
601                {
602                        output.println("<html><head>");
603                        output.println("<title>Missing content-type header</title>");
604                        output.println("</head><body>");
605                        output.println("<h1>Missing content-type header</h1>");
606                        output.println("<p>Must use content-type header " 
607                                        + "to specify multipart/form-data encoding</p>");
608                        output.println("</body></html>");
609                        thisPage.sendContents();
610                        return;
611                }
612                else if (!req.getHeader("content-type").toLowerCase().startsWith("multipart/form-data"))
613                {
614                        output.println("<html><head>");
615                        output.println("<title>Must use multipart/form-data encoding</title>");
616                        output.println("</head><body>");
617                        output.println("<h1>Must use multipart/form-data encoding</h1>");
618                        output.println("<p>content-type is " + req.getHeader("content-type") + " </p>");
619                        output.println("<p>Must use multipart/form-data encoding</p>");
620                        output.println("</body></html>");
621                        thisPage.sendContents();
622                        return;
623                }
624                String boundary = extractBoundary(req);
625                if (boundary == null)
626                {
627                        thisPage.addMessage("Unable to extract boundary value");
628                        thisPage.addMessage(req.getHeader("content-type"));
629                        thisPage.errorMessage();
630                        return;
631                }
632                int counter = 0;
633                byte buffer[] = new byte[4096];
634                byte extract[];
635                int bytesRead = -1;
636                ServletInputStream input = req.getInputStream();
637                bytesRead = input.readLine(buffer, 0, buffer.length);
638                if (!new String(buffer, 0, bytesRead, "ISO8859_1").startsWith("--" + boundary))
639                {
640                        thisPage.addMessage("Should be separator " + "--" + boundary + " : "
641                                        +" found " + new String(buffer, 0, bytesRead));
642                        thisPage.errorMessage();
643                        return;
644                }
645                while (true)
646                {
647                        counter++;
648                        Contents part = new Contents();
649                        thisPage.addMessage("Starting part " + Integer.toString(counter) + " of form");
650                        while (true)
651                        {
652                                
653                                bytesRead = input.readLine(buffer, 0, buffer.length);
654                                if (bytesRead < 0)
655                                {
656                                        thisPage.addMessage("Unexpected end of packet");
657                                        thisPage.errorMessage();
658                                        return;
659                                }
660                                else
661                                {
662                                        String value = new String(buffer, 0, bytesRead, "ISO8859_1");
663                                        if (value.endsWith("\r\n"))
664                                        {
665                                                value = stripEOL(value);
666                                        }
667                                        if (value.length() == 0)
668                                        {
669                                                extract = readPart(input, thisPage, part.getTransferEncoding());
670                                                if (thisPage.getTerminateRequest())
671                                                {
672                                                        return;
673                                                }
674                                                part.setContents(extract);
675                                                thisPage.addElement(part);
676                                                break;
677                                        }
678                                        else
679                                        {
680                                                thisPage.addMessage("service method - Parsing line: " + value);
681                                                part.parseLine(value);
682                                        }
683                                }
684                        }
685                        if (thisPage.getEndOfPacket())
686                        {
687                                break;
688                        }
689                }
690                if (thisPage.getTerminateRequest())
691                {
692                        return;
693                }
694                starter(thisPage);
695                if (thisPage.getTerminateRequest())
696                {
697                        return;
698                }
699                processor(thisPage);
700                if (thisPage.getTerminateRequest())
701                {
702                        return;
703                }
704                ender(thisPage);
705                if (thisPage.getTerminateRequest())
706                {
707                        return;
708                }
709                if (thisPage.getRedirectAddress() != null)
710                {
711                        thisPage.getResponse().sendRedirect(thisPage.getResponse().encodeRedirectURL(thisPage.getRedirectAddress()));
712                }
713        }
714        /**
715         * Obtain the byte array for this part of the request.
716         * 
717         * <p>It may be necessary to modify this code to handle additional encoding
718         *    types such as Base64.</p>
719         *    
720         * @param input Object from which data is read
721         * @param thisPage Object containing information for this request
722         * @param encoding type of character encoding to be used
723         * @return Byte array for this part of request
724         * @throws IOException if io errors
725         */
726        protected byte[] readPart(ServletInputStream input, ThisPage thisPage, String encoding)
727        throws IOException
728        {
729                HttpServletRequest req = thisPage.getRequest();
730                String boundary = extractBoundary(req);
731                byte buffer[] = new byte[4096];
732                int bytesRead = -1;
733                ByteArrayOutputStream working = null;
734                boolean pendingRN = false;
735                working = new ByteArrayOutputStream();
736                /*
737                 * Read body of part
738                 */
739                while (true)
740                {
741                        bytesRead = input.readLine(buffer, 0, buffer.length);
742                        if (bytesRead < 0)
743                        {
744                                thisPage.addMessage("readPart method - Section of form not properly ended");
745                                thisPage.errorMessage();
746                                return null;
747                        }
748                        else if (bytesRead == 0)
749                        {
750                                thisPage.addMessage("readPart method - Read yielded 0 characters");
751                                thisPage.errorMessage();
752                                return null;
753                        }
754                        else if (bytesRead == 1 && buffer[0] == '\n')
755                        {
756                                if (pendingRN)
757                                {
758                                        working.write('\r');
759                                        working.write('\n');
760                                        pendingRN = false;
761                                }
762                                working.write(buffer[0]);
763                        }
764                        else if (new String(buffer, 0, bytesRead).equals("--" + boundary + "\r\n"))
765                        {
766                                if (!pendingRN)
767                                {
768                                        thisPage.addMessage("readPart method - Boundary reached without preceding end of line");
769                                        thisPage.errorMessage();
770                                        return null;
771                                }
772                                if (working.size() == 0)
773                                {
774                                        return null;
775                                }
776                                return working.toByteArray();
777                        }
778                        else if (new String(buffer, 0, bytesRead).equals("--" + boundary  + "--\r\n"))
779                        {
780                                thisPage.setEndOfPacket(true);
781                                if (!pendingRN)
782                                {
783                                        thisPage.addMessage("readPart method - Final boundary reached with preceding end of line");
784                                        thisPage.errorMessage();
785                                        return null;
786                                }
787                                if (working.size() == 0)
788                                {
789                                        return null;
790                                }
791                                return working.toByteArray();
792                        }
793                        else if (buffer[bytesRead - 2]  == '\r' && buffer[bytesRead - 1] == '\n')
794                        {
795                                if (pendingRN)
796                                {
797                                        working.write('\r');
798                                        working.write('\n');
799                                }
800                                if (bytesRead > 2)
801                                {
802                                        working.write(buffer, 0, bytesRead - 2);
803                                }
804                                pendingRN = true;
805                        }
806                        else if (buffer[bytesRead - 1] == '\r')
807                        {
808                                if (pendingRN)
809                                {
810                                        working.write('\r');
811                                        working.write('\n');
812                                        pendingRN = false;
813                                }
814                                if (bytesRead > 1)
815                                {
816                                        working.write(buffer,  0, bytesRead - 1);
817                                }
818                                int nextChar = input.read();
819                                if (nextChar == '\n')
820                                {
821                                        pendingRN = true;
822                                }
823                                else
824                                {
825                                        working.write('\r');
826                                        working.write(nextChar);
827                                }
828                        }
829                        else
830                        {
831                                if (pendingRN)
832                                {
833                                        working.write('\r');
834                                        working.write('\n');
835                                        pendingRN=false;
836                                }
837                                working.write(buffer, 0, bytesRead);
838                        }
839                }
840        }
841        /**
842         * Strips end of line characters from a character string.
843         * 
844         * @param input String to be processed
845         * @return String with EOL characters stripped off
846         */
847        protected String stripEOL(String input)
848        {
849                if (input == null)
850                {
851                        return (String) null;
852                }
853                String working = input;
854                if (working.endsWith("\r\n") || working.endsWith("\n\r"))
855                {
856                        working = working.substring(0, working.length() - 2);
857                }
858                else if (working.endsWith("\r") || working.endsWith("\n"))
859                {
860                        working = working.substring(0, working.length() - 1);
861                }
862                return working;
863        }
864        /**
865         * Extracts the value of the boundary string from the header.
866         * @param req Request object
867         * @return Boundary string
868         */
869        public String extractBoundary(HttpServletRequest req)
870        {
871                String working = null;
872                working = req.getHeader("content-type");
873                if (working == null)
874                {
875                        return null;
876                }
877                int loc = working.indexOf("boundary=");
878                if ( loc < 0)
879                {
880                        return null;
881                }
882                else
883                {
884                        working = working.substring(loc + 9);
885                }
886                loc = working.indexOf(";");
887                if (loc == 0)
888                {
889                        return null;
890                }
891                else if (loc > 0)
892                {
893                        working = working.substring(0, loc);
894                }
895                return working;
896        }
897        /**
898         * Utility method for moving the contents of one of the parts into
899         * a prepared statement as a string object.
900         * @param data Data connection object.
901         * @param stmt Prepared statement
902         * @param thisPage Object representing this request
903         * @param position Position of the value in the prepared statement's argument list
904         * @param element Name of the part in the multi-part form
905         * @param convertFlag True means that value should be converted to upper case
906         * @param SQLType Type of SQL column (Types.CHAR or Types.VARCHAR)
907         * @throws SQLException if database errors
908         */
909        public void loadByteArray(DatabaseProperties data, PreparedStatement stmt, ThisPage thisPage,
910                        int position, String element, boolean convertFlag, int SQLType) throws SQLException
911        {
912                byte contents[] = null;
913                Contents portion = thisPage.getElement(element);
914                if (portion == null)
915                {
916                        stmt.setNull(position, SQLType);
917                        return;
918                }
919                contents = thisPage.getElement(element).getContents();
920                if (contents == null)
921                {
922                        stmt.setNull(position, SQLType); 
923                }
924                else if (contents.length == 0)
925                {
926                        stmt.setNull(position, SQLType);
927                }
928                else 
929                {
930                        String working = new String(contents).trim();
931                        if (convertFlag)
932                        {
933                                working = working.toUpperCase();
934                        }
935                        if (working.length() == 0)
936                        {
937                                stmt.setNull(position, SQLType);
938                        }
939                        else
940                        {
941                                stmt.setString(position, working);      
942                        }
943                }
944        }
945        /**
946         * Utility method for moving the contents of one of the parts into
947         * a prepared statement as a string object.
948         * @param data Data connection object.
949         * @param stmt Prepared statement
950         * @param thisPage Object representing this request
951         * @param position Position of the value in the prepared statement's argument list
952         * @param element Name of the part in the multi-part form
953         * @param convertFlag True means that value should be converted to upper case
954         * @throws SQLException if database errors
955         */
956        public void loadByteArray(DatabaseProperties data, PreparedStatement stmt, ThisPage thisPage,
957                        int position, String element, boolean convertFlag) throws SQLException
958        {
959                loadByteArray(data, stmt, thisPage, position, element, convertFlag, Types.VARCHAR);
960        }
961        /**
962         * Utility method for moving the contents of one of the parts into
963         * a prepared statement as a string object.
964         * @param data Data connection object.
965         * @param stmt Prepared statement
966         * @param thisPage Object representing this request
967         * @param position Position of the value in the prepared statement's argument list
968         * @param element Name of the part in the multi-part form
969         * @throws SQLException if database errors
970         */
971        public void loadByteArray(DatabaseProperties data, PreparedStatement stmt, ThisPage thisPage,
972                        int position, String element) throws SQLException
973        {
974                loadByteArray(data, stmt, thisPage, position, element, false, Types.VARCHAR);
975        }
976        /**
977         * Perform common operations on the contents of the
978         * request packet before running the main program.
979         * 
980         * <p>The intent is to override this method in a subclass
981         *    that will then be subclassed to handle the servlets
982         *    for the various web pages.</p>
983         *    
984         * @param thisPage Information on this HTTP request
985         * @throws IOException if io errors
986         */
987        protected void starter(ThisPage thisPage) throws IOException
988        {
989                ;
990        }
991        /**
992         * Perform common operations on the contents of the request packet
993         * to be carried out after running the main program.
994         * 
995         * @param thisPage Information on this HTTP transaction
996         * 
997         */
998        protected void ender(ThisPage thisPage) 
999        { 
1000                ;
1001        }
1002        /**
1003         * This method generates the web page based on the contents
1004         * of the request packet.
1005         * 
1006         * <p>This method will be overwritten to enable various actions
1007         *    to take place as the information is uploaded.</p>
1008         * <p>For the result of File requests, write the contents to a file.</p>
1009         * <p>I am having a problem with the loading of binary files.</p>
1010         * @param thisPage Information on this HTTP request
1011         * @throws IOException if io errors
1012         */
1013        protected void processor(ThisPage thisPage) throws IOException
1014        {
1015                Enumeration<String> keys = thisPage.getPartNames();
1016                HttpServletRequest req = thisPage.getRequest();
1017                ServletConfig config = thisPage.getConfig();
1018                String targetDirectory = config.getInitParameter("directory");
1019                GenericPrinter output = thisPage.getPrinter();
1020                output.println("<html><head>");
1021                output.println("<title>Dummy Upload Program</title>");
1022                output.println("</head><body>");
1023                output.println("<p>It is assumed that the processor method of the ");
1024                output.println("bradleyross.library.servlets.UploadServlet class will ");
1025                output.println("be overridden to provide the desired function.  This ");
1026                output.println("sample version is designed for testing the applications ");
1027                output.println("and to allow demonstration of the capabilities.</p>");
1028                output.println("<p>Target directory for upload tests is " + targetDirectory + "</p>");
1029                output.println("<h2>Headers</h2>");
1030                output.println("<table border=\"1\">");
1031                Enumeration<?> list1 = req.getHeaderNames();
1032                while (list1.hasMoreElements())
1033                {
1034                        String name = (String) list1.nextElement();
1035                        Enumeration<?> list2 = req.getHeaders(name);
1036                        while (list2.hasMoreElements())
1037                        {
1038                                String value = (String) list2.nextElement();
1039                                output.println("<tr><td>" + name + "</td><td>" + value + "</td></tr>");
1040                        }
1041                }
1042                output.println("</table>");
1043                output.println("<h2>Parts of form</h2>");
1044                output.println("<p>The following are the parts of the multipart form</p>");             
1045                output.println("<ul>");
1046                while (keys.hasMoreElements())
1047                {
1048                        String name = keys.nextElement();
1049                        String mime = thisPage.getElement(name).getMime();
1050                        String fileName = thisPage.getElement(name).getFilename();
1051                        String encoding = thisPage.getElement(name).getTransferEncoding();
1052                        output.println("<li><p>" + name + "</p>");
1053                        if (fileName == null)
1054                        {
1055                                output.println("<p>Filename not specified</p>");
1056                        }
1057                        else
1058                        {
1059                                output.println("<p>Filename is " + fileName + "</p>");
1060                        }
1061                        if (mime == null)
1062                        {
1063                                output.println("<p>Content type not specified</p>");
1064                                mime = new String();
1065                        }
1066                        else
1067                        {
1068                                output.println("<p>Content-type: " + mime + "</p>");
1069                        }
1070                        if (encoding == null)
1071                        {
1072                                output.println("<p>Transfer encoding not specified</p>");
1073                        }
1074                        else
1075                        {
1076                        output.println("<p>Content-transfer-encoding: " + encoding + "</p>");
1077                        }
1078                        if (mime.toUpperCase().startsWith("TEXT"))
1079                        {
1080                                output.println("<p>" + StringHelpers.escapeHTML(thisPage.getElement(name).toString())
1081                                        + "</p></li>");
1082                        }
1083                        output.println("<p>Size of contents: " + Integer.toString(thisPage.getElement(name).getContentsSize()));
1084                        if (fileName != null && targetDirectory != null)
1085                        {
1086                                try
1087                                {
1088                                        boolean validEntry = true;
1089                                        File outputFile = null;
1090                                        if (targetDirectory.length() == 0 || fileName.length() == 0)
1091                                        {
1092                                                validEntry = false;
1093                                        }
1094                                        if (validEntry)
1095                                        {
1096                                                outputFile = new File (targetDirectory, fileName);
1097                                                output.println("<p>Name of file is " + StringHelpers.escapeHTML(outputFile.getCanonicalPath()) + "</p>");
1098                                        }
1099                                        if (!validEntry)
1100                                        { ; }
1101                                        else if (outputFile == null)
1102                                        {
1103                                                output.println("<p>Unable to open output file</p>");
1104                                        }
1105                                        else
1106                                        {
1107                                                FileOutputStream outputStream = new FileOutputStream(outputFile);
1108                                                byte transfer[] = thisPage.getElement(name).getContents();
1109                                                if (transfer == null)
1110                                                {
1111                                                        output.println("<p>Unable to get file contents</p>");
1112                                                }
1113                                                else 
1114                                                {
1115                                                        outputStream.write(thisPage.getElement(name).getContents());
1116                                                }
1117                                                outputStream.close();   
1118                                        }
1119                                }
1120                                catch (IOException e)
1121                                {
1122                                        output.println("<p>Error while writing file</p>");
1123                                        output.println(StringHelpers.escapeHTML("<p>" + e.getClass().getName() + " " +
1124                                                        e.getMessage() + "</p>"));
1125                                }
1126                        }
1127                        else
1128                        { ;     }       
1129                }
1130                output.println("</ul>");
1131                output.println("<h2>Messages</h2>");
1132                output.println("<p>These messages are normally printed only if an error occurs during the processing ");
1133                output.println("of the HTTP transaction.  They are included here to test the behavior of the servlet.</p><ol>");
1134                Vector<String> messages = thisPage.getMessageList();
1135                messages.trimToSize();
1136                for (int i = 0; i < messages.size(); i++)
1137                {
1138                        output.println("<li><p>" + StringHelpers.escapeHTML(messages.elementAt(i)) + "</p></li>");
1139                }
1140                output.println("</ol>");
1141                output.println("</body></html>");
1142                thisPage.sendContents();
1143        }
1144}