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                 *    
248             */
249                public ThisPage (HttpServletRequest value1, HttpServletResponse value2, ServletConfig value3)
250                throws IOException
251                {
252                        super(value1, value2, value3);
253                        context = config.getServletContext();
254                        boundary = extractBoundary(getRequest());
255                        input = getRequest().getInputStream();
256
257                }
258                /**
259                 * Return an enumeration containing the names of the elements.
260                 * @return Enumeration containing list of names.
261                 */
262                public Enumeration<String> getPartNames()
263                {
264                        return items.keys();
265                }
266                /**
267                 * Address to be used for redirecting servlet upon completion.
268                 * @see HttpServletResponse#sendRedirect(String)
269                 * @see #getRedirectAddress()
270                 * @see #setRedirectAddress(String)
271                 */
272                protected String redirectAddress = null;
273                /**
274                 * Set the URL for redirecting the servlet upon completion.
275                 * @param value URL to be used for redirecting servlet
276                 * @see #redirectAddress
277                 */
278                public void setRedirectAddress(String value)
279                {
280                        redirectAddress = value;
281                }
282                /**
283                 * Get the URL to be used for redirecting the servlet upon completion.
284                 * @return URL to be used
285                 * @see #redirectAddress
286                 */
287                public String getRedirectAddress()
288                {
289                        return redirectAddress;
290                }
291                /**
292                 * Retrieve the object associated with an element based on the
293                 * element name.
294                 * @param key Name of the element in the form
295                 * @return Object containing the contents of the element, including
296                 *         name, MIME type, and contents
297                 */
298                public Contents getElement(String key)
299                {
300                        return items.get(key);
301                }
302                /**
303                 * Return the contents of one of the elements as
304                 * a String object.
305                 * 
306                 * @param key Key value for element
307                 * @return String object containing contents
308                 */
309                public String getString(String key)
310                {
311                        Contents element = getElement(key);
312                        if (element == null)
313                        {
314                                return null;
315                        }
316                        byte contents[] = element.getContents();
317                        if (contents == null)
318                        {
319                                return null;
320                        }
321                        else if (contents.length == 0)
322                        {
323                                return null;
324                        }
325                        String working = new String(contents);
326                        return working.trim();
327                }
328                /**
329                 * Return the contents of one of the elements as a byte
330                 * array.
331                 * 
332                 * @param key Key value for the element
333                 * @return Byte array containing contents of element
334                 */
335                public byte[] getByteArray(String key)
336                {
337                        Contents element = getElement(key);
338                        if (element == null)
339                        {
340                                return null;
341                        }
342                        byte contents[] = element.getContents();
343                        if (contents == null)
344                        {
345                                return null;
346                        }
347                        else if (contents.length == 0)
348                        {
349                                return null;
350                        }
351                        return contents;
352                }
353        }
354        /**
355         * Contains information on the contents of a single part of a 
356         * multipart form.
357         * 
358         * <p>See <a href="http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2" target="_blank">
359         *    http://www.w3.org/TR/html401/interact/forms.html#h-17.13.42</a> for a discussion
360         *    of <i>multipart/form-data</i>.</p>
361         * <p>All lines must end with \r\n.  However, binary data may have \r\n in the middle
362         *    of the material.</p>
363         * 
364         * @author Bradley Ross
365         *
366         */
367        public class Contents
368        {
369                /**
370                 * Name of the part.
371                 */
372                protected String name = null;
373                /**
374                 * Filename associated with the part.
375                 */
376                protected String filename = null;
377                /**
378                 * MIME code for the data in the part.
379                 */
380                protected String mime = null;
381                /**
382                 * Content transfer encoding method for the
383                 * element of the transaction.
384                 * 
385                 * <p>According to the documentation, there
386                 *    are various encoding methods that can be
387                 *    used such as base64 and quoted-printable.
388                 *    These have not been seen in the browsers
389                 *    I have used, but I may have to allow for
390                 *    these encoding methods.</p>
391                 */
392                protected String transferEncoding = null;
393                /**
394                 * The byte array containing the data for this
395                 * part.
396                 */
397                protected byte value[] = null;
398                /**
399                 * Obtain the name of the part.
400                 * @return Name of part
401                 */
402                public String getName()
403                {
404                        return name;
405                }
406                /**
407                 * Obtain the filename associated with the part.
408                 * 
409                 * <p>If the data was not taken from a file, a null
410                 *    value is returned.</p>
411                 * @return Filename associated with part
412                 */
413                public String getFilename()
414                {
415                        return filename;
416                }
417                /**
418                 * Obtain mime type for part.
419                 * 
420                 * <p>MIME types would only be associated with data taken from
421                 *    files.</p>
422                 *    
423                 * @return MIME code
424                 */
425                public String getMime()
426                {
427                        return mime;
428                }
429                /**
430                 * Obtain the value for content-transfer-encoding.
431                 * 
432                 * @return Type of encoding
433                 */
434                public String getTransferEncoding()
435                {
436                        return transferEncoding;
437                }
438                /**
439                 * <p>Obtains the byte array that was associated with this
440                 * part of the form.</p>
441                 * @return byte array
442                 */
443                public byte[] getContents()
444                {
445                        return value;
446                }
447                /**
448                 * Obtains the size of the byte array that was associated with
449                 * this part of the form.
450                 * @return Size of byte array
451                 */
452                public int getContentsSize()
453                {
454                        if (value == null)
455                        { return 0; }
456                        return value.length;
457                }
458                /**
459                 * Parses header lines in an individual part of a multipart form.
460                 * 
461                 * <p>This algorithm works if the values for the name and filename
462                 *    are surrounded by double quotes, there are no escaped 
463                 *    characters with the values, and there are no spaces between the
464                 *    start of the word name or filename and the corresponding closing
465                 *    quote.  It also assumes that the name parameter comes before
466                 *    the filename parameter.  I'm not sure if these assumptions are valid.</p>
467                 * @param lineValue Line to be parsed
468                 */
469                public void parseLine(String lineValue)
470                {
471                        String line = lineValue;
472                        if (line.endsWith("\r\n"))
473                        {
474                                int length = line.length();
475                                line = line.substring(0, length - 2);
476                        }
477                        if (line.toLowerCase().startsWith("content-disposition:")) 
478                        {
479                                int start = -1;
480                                int end = -1;
481                                start = line.toLowerCase().indexOf("name=\"");
482                                if (start >= 0)
483                                {
484                                        start = start + 6;
485                                        end = line.indexOf("\"", start);
486                                        name = line.substring(start, end);
487                                }
488                                start = line.toLowerCase().indexOf("filename=\"") ;
489                                if (start >= 0)
490                                {
491                                        start = start + 10;
492                                        end = line.indexOf("\"", start);
493                                        filename = line.substring(start, end);
494                                }               
495                        }
496                        else if (line.toLowerCase().startsWith("content-type:"))
497                        {
498                                int loc = line.indexOf(":");
499                                mime = line.substring(loc + 1).trim();
500                        }       
501                        else if (line.toLowerCase().startsWith("content-transfer-encoding:"))
502                        {
503                                int loc = line.indexOf(":");
504                                transferEncoding = line.substring(loc + 1).trim();
505                        }
506                }
507                /**
508                 * Connect the byte array to the object.
509                 * @param input Byte array to be attached to object
510                 */
511                public void setContents(byte[] input)
512                {
513                        value = input;
514                }
515                public String toString()
516                {
517                        StringBuffer working = new StringBuffer();
518                        working.append( "Name: " + name + "; Filename: " + filename + "; MIME: " + mime +
519                        "\r\n\r\n");
520                        if (value == null)
521                        {
522                                return new String(working);
523                        }
524                        else if (value.length == 0)
525                        {
526                                return new String(working);
527                        }
528                        try 
529                        {
530                                String addition = new String(value, "ISO8859_1");
531                                working.append(addition);
532                        } 
533                        catch (UnsupportedEncodingException e) 
534                        {       
535                                working.append("Unable to represent byte stream");
536                        }
537                        return new String(working);
538                }
539        }
540        /**
541         * Dummy id to satisfy serializable interface
542         */
543        private static final long serialVersionUID = 1L;
544        /**
545         * Used to control amount of debugging output.
546         */
547        protected static int debugLevel = 0;
548        /**
549         * ServletConfig object for servlet.
550         */
551        protected ServletConfig config = null;
552        /**
553         * Called when object for handling HTTP request is created.
554         */
555        public void init(ServletConfig configIn) throws ServletException
556        {
557                config= configIn;
558        }
559        /**
560         * Process the call to the servlet.
561         * 
562         * <p>With regard to reading data from the request, a
563         *    {@link javax.servlet.ServletInputStream} object for binary data
564         *    can be obtained by {@link HttpServletRequest#getInputStream()}
565         *    or a {@link java.io.BufferedReader} can be obtained for character data
566         *    by using
567         *    {@link HttpServletRequest#getReader}.</p>
568         * <p>With regard to writing data to the response a
569         *    {@link javax.servlet.ServletOutputStream} object for binary
570         *    data can be obtained using {@link HttpServletResponse#getOutputStream()}
571         *    while a {@link java.io.PrintWriter} object can be obtained using
572         *    {@link HttpServletResponse#getWriter()}.</p>
573         * <p>The {@link java.io.ByteArrayOutputStream} class can be used as a means of collecting
574         *    the bytes contained in the attached file.</p>
575         * <p>It would be desirable to have tests for enctype and method.</p>
576         *    
577         * @param req Request object
578         * @param res Response object
579         * @throws IOException
580         */
581        public void service (HttpServletRequest req,
582                        HttpServletResponse res)
583        throws IOException
584        {
585                ThisPage thisPage = new ThisPage(req, res, config);
586                GenericPrinter output = thisPage.getPrinter();
587                if (!req.getMethod().equalsIgnoreCase("post"))
588                {
589                        output.println("<html><head>");
590                        output.println("<title>Must use POST method</title>");
591                        output.println("</head><body>");
592                        output.println("<h1>Must use POST method</h1>");
593                        output.println("<p>Request used " + req.getMethod() + " method</p>");
594                        output.println("<p>Must use POST method</p>");
595                        output.println("</body></html>");
596                        thisPage.sendContents();
597                        return;
598                }
599                else if (req.getHeader("content-type") == null)
600                {
601                        output.println("<html><head>");
602                        output.println("<title>Missing content-type header</title>");
603                        output.println("</head><body>");
604                        output.println("<h1>Missing content-type header</h1>");
605                        output.println("<p>Must use content-type header " 
606                                        + "to specify multipart/form-data encoding</p>");
607                        output.println("</body></html>");
608                        thisPage.sendContents();
609                        return;
610                }
611                else if (!req.getHeader("content-type").toLowerCase().startsWith("multipart/form-data"))
612                {
613                        output.println("<html><head>");
614                        output.println("<title>Must use multipart/form-data encoding</title>");
615                        output.println("</head><body>");
616                        output.println("<h1>Must use multipart/form-data encoding</h1>");
617                        output.println("<p>content-type is " + req.getHeader("content-type") + " </p>");
618                        output.println("<p>Must use multipart/form-data encoding</p>");
619                        output.println("</body></html>");
620                        thisPage.sendContents();
621                        return;
622                }
623                String boundary = extractBoundary(req);
624                if (boundary == null)
625                {
626                        thisPage.addMessage("Unable to extract boundary value");
627                        thisPage.addMessage(req.getHeader("content-type"));
628                        thisPage.errorMessage();
629                        return;
630                }
631                int counter = 0;
632                byte buffer[] = new byte[4096];
633                byte extract[];
634                int bytesRead = -1;
635                ServletInputStream input = req.getInputStream();
636                bytesRead = input.readLine(buffer, 0, buffer.length);
637                if (!new String(buffer, 0, bytesRead, "ISO8859_1").startsWith("--" + boundary))
638                {
639                        thisPage.addMessage("Should be separator " + "--" + boundary + " : "
640                                        +" found " + new String(buffer, 0, bytesRead));
641                        thisPage.errorMessage();
642                        return;
643                }
644                while (true)
645                {
646                        counter++;
647                        Contents part = new Contents();
648                        thisPage.addMessage("Starting part " + Integer.toString(counter) + " of form");
649                        while (true)
650                        {
651                                
652                                bytesRead = input.readLine(buffer, 0, buffer.length);
653                                if (bytesRead < 0)
654                                {
655                                        thisPage.addMessage("Unexpected end of packet");
656                                        thisPage.errorMessage();
657                                        return;
658                                }
659                                else
660                                {
661                                        String value = new String(buffer, 0, bytesRead, "ISO8859_1");
662                                        if (value.endsWith("\r\n"))
663                                        {
664                                                value = stripEOL(value);
665                                        }
666                                        if (value.length() == 0)
667                                        {
668                                                extract = readPart(input, thisPage, part.getTransferEncoding());
669                                                if (thisPage.getTerminateRequest())
670                                                {
671                                                        return;
672                                                }
673                                                part.setContents(extract);
674                                                thisPage.addElement(part);
675                                                break;
676                                        }
677                                        else
678                                        {
679                                                thisPage.addMessage("service method - Parsing line: " + value);
680                                                part.parseLine(value);
681                                        }
682                                }
683                        }
684                        if (thisPage.getEndOfPacket())
685                        {
686                                break;
687                        }
688                }
689                if (thisPage.getTerminateRequest())
690                {
691                        return;
692                }
693                starter(thisPage);
694                if (thisPage.getTerminateRequest())
695                {
696                        return;
697                }
698                processor(thisPage);
699                if (thisPage.getTerminateRequest())
700                {
701                        return;
702                }
703                ender(thisPage);
704                if (thisPage.getTerminateRequest())
705                {
706                        return;
707                }
708                if (thisPage.getRedirectAddress() != null)
709                {
710                        thisPage.getResponse().sendRedirect(thisPage.getResponse().encodeRedirectURL(thisPage.getRedirectAddress()));
711                }
712        }
713        /**
714         * Obtain the byte array for this part of the request.
715         * 
716         * <p>It may be necessary to modify this code to handle additional encoding
717         *    types such as Base64.</p>
718         *    
719         * @param input Object from which data is read
720         * @param thisPage Object containing information for this request
721         * @return Byte array for this part of request
722         * @throws IOException
723         */
724        protected byte[] readPart(ServletInputStream input, ThisPage thisPage, String encoding)
725        throws IOException
726        {
727                HttpServletRequest req = thisPage.getRequest();
728                String boundary = extractBoundary(req);
729                byte buffer[] = new byte[4096];
730                int bytesRead = -1;
731                ByteArrayOutputStream working = null;
732                boolean pendingRN = false;
733                working = new ByteArrayOutputStream();
734                /*
735                 * Read body of part
736                 */
737                while (true)
738                {
739                        bytesRead = input.readLine(buffer, 0, buffer.length);
740                        if (bytesRead < 0)
741                        {
742                                thisPage.addMessage("readPart method - Section of form not properly ended");
743                                thisPage.errorMessage();
744                                return null;
745                        }
746                        else if (bytesRead == 0)
747                        {
748                                thisPage.addMessage("readPart method - Read yielded 0 characters");
749                                thisPage.errorMessage();
750                                return null;
751                        }
752                        else if (bytesRead == 1 && buffer[0] == '\n')
753                        {
754                                if (pendingRN)
755                                {
756                                        working.write('\r');
757                                        working.write('\n');
758                                        pendingRN = false;
759                                }
760                                working.write(buffer[0]);
761                        }
762                        else if (new String(buffer, 0, bytesRead).equals("--" + boundary + "\r\n"))
763                        {
764                                if (!pendingRN)
765                                {
766                                        thisPage.addMessage("readPart method - Boundary reached without preceding end of line");
767                                        thisPage.errorMessage();
768                                        return null;
769                                }
770                                if (working.size() == 0)
771                                {
772                                        return null;
773                                }
774                                return working.toByteArray();
775                        }
776                        else if (new String(buffer, 0, bytesRead).equals("--" + boundary  + "--\r\n"))
777                        {
778                                thisPage.setEndOfPacket(true);
779                                if (!pendingRN)
780                                {
781                                        thisPage.addMessage("readPart method - Final boundary reached with preceding end of line");
782                                        thisPage.errorMessage();
783                                        return null;
784                                }
785                                if (working.size() == 0)
786                                {
787                                        return null;
788                                }
789                                return working.toByteArray();
790                        }
791                        else if (buffer[bytesRead - 2]  == '\r' && buffer[bytesRead - 1] == '\n')
792                        {
793                                if (pendingRN)
794                                {
795                                        working.write('\r');
796                                        working.write('\n');
797                                }
798                                if (bytesRead > 2)
799                                {
800                                        working.write(buffer, 0, bytesRead - 2);
801                                }
802                                pendingRN = true;
803                        }
804                        else if (buffer[bytesRead - 1] == '\r')
805                        {
806                                if (pendingRN)
807                                {
808                                        working.write('\r');
809                                        working.write('\n');
810                                        pendingRN = false;
811                                }
812                                if (bytesRead > 1)
813                                {
814                                        working.write(buffer,  0, bytesRead - 1);
815                                }
816                                int nextChar = input.read();
817                                if (nextChar == '\n')
818                                {
819                                        pendingRN = true;
820                                }
821                                else
822                                {
823                                        working.write('\r');
824                                        working.write(nextChar);
825                                }
826                        }
827                        else
828                        {
829                                if (pendingRN)
830                                {
831                                        working.write('\r');
832                                        working.write('\n');
833                                        pendingRN=false;
834                                }
835                                working.write(buffer, 0, bytesRead);
836                        }
837                }
838        }
839        /**
840         * Strips end of line characters from a character string.
841         * 
842         * @param input String to be processed
843         * @return String with EOL characters stripped off
844         */
845        protected String stripEOL(String input)
846        {
847                if (input == null)
848                {
849                        return (String) null;
850                }
851                String working = input;
852                if (working.endsWith("\r\n") || working.endsWith("\n\r"))
853                {
854                        working = working.substring(0, working.length() - 2);
855                }
856                else if (working.endsWith("\r") || working.endsWith("\n"))
857                {
858                        working = working.substring(0, working.length() - 1);
859                }
860                return working;
861        }
862        /**
863         * Extracts the value of the boundary string from the header.
864         * @param req Request object
865         * @return Boundary string
866         */
867        public String extractBoundary(HttpServletRequest req)
868        {
869                String working = null;
870                working = req.getHeader("content-type");
871                if (working == null)
872                {
873                        return null;
874                }
875                int loc = working.indexOf("boundary=");
876                if ( loc < 0)
877                {
878                        return null;
879                }
880                else
881                {
882                        working = working.substring(loc + 9);
883                }
884                loc = working.indexOf(";");
885                if (loc == 0)
886                {
887                        return null;
888                }
889                else if (loc > 0)
890                {
891                        working = working.substring(0, loc);
892                }
893                return working;
894        }
895        /**
896         * Utility method for moving the contents of one of the parts into
897         * a prepared statement as a string object.
898         * @param data Data connection object.
899         * @param stmt Prepared statement
900         * @param thisPage Object representing this request
901         * @param position Position of the value in the prepared statement's argument list
902         * @param element Name of the part in the multi-part form
903         * @param convertFlag True means that value should be converted to upper case
904         * @param SQLType Type of SQL column (Types.CHAR or Types.VARCHAR)
905         * @throws SQLException
906         */
907        public void loadByteArray(DatabaseProperties data, PreparedStatement stmt, ThisPage thisPage,
908                        int position, String element, boolean convertFlag, int SQLType) throws SQLException
909        {
910                byte contents[] = null;
911                Contents portion = thisPage.getElement(element);
912                if (portion == null)
913                {
914                        stmt.setNull(position, SQLType);
915                        return;
916                }
917                contents = thisPage.getElement(element).getContents();
918                if (contents == null)
919                {
920                        stmt.setNull(position, SQLType); 
921                }
922                else if (contents.length == 0)
923                {
924                        stmt.setNull(position, SQLType);
925                }
926                else 
927                {
928                        String working = new String(contents).trim();
929                        if (convertFlag)
930                        {
931                                working = working.toUpperCase();
932                        }
933                        if (working.length() == 0)
934                        {
935                                stmt.setNull(position, SQLType);
936                        }
937                        else
938                        {
939                                stmt.setString(position, working);      
940                        }
941                }
942        }
943        /**
944         * Utility method for moving the contents of one of the parts into
945         * a prepared statement as a string object.
946         * @param data Data connection object.
947         * @param stmt Prepared statement
948         * @param thisPage Object representing this request
949         * @param position Position of the value in the prepared statement's argument list
950         * @param element Name of the part in the multi-part form
951         * @param convertFlag True means that value should be converted to upper case
952         * @throws SQLException
953         */
954        public void loadByteArray(DatabaseProperties data, PreparedStatement stmt, ThisPage thisPage,
955                        int position, String element, boolean convertFlag) throws SQLException
956        {
957                loadByteArray(data, stmt, thisPage, position, element, convertFlag, Types.VARCHAR);
958        }
959        /**
960         * Utility method for moving the contents of one of the parts into
961         * a prepared statement as a string object.
962         * @param data Data connection object.
963         * @param stmt Prepared statement
964         * @param thisPage Object representing this request
965         * @param position Position of the value in the prepared statement's argument list
966         * @param element Name of the part in the multi-part form
967         * @throws SQLException
968         */
969        public void loadByteArray(DatabaseProperties data, PreparedStatement stmt, ThisPage thisPage,
970                        int position, String element) throws SQLException
971        {
972                loadByteArray(data, stmt, thisPage, position, element, false, Types.VARCHAR);
973        }
974        /**
975         * Perform common operations on the contents of the
976         * request packet before running the main program.
977         * 
978         * <p>The intent is to override this method in a subclass
979         *    that will then be subclassed to handle the servlets
980         *    for the various web pages.</p>
981         *    
982         * @param thisPage Information on this HTTP request
983         */
984        protected void starter(ThisPage thisPage) throws IOException
985        {
986                ;
987        }
988        /**
989         * Perform common operations on the contents of the request packet
990         * to be carried out after running the main program.
991         * 
992         * @param thisPage Information on this HTTP transaction
993         */
994        protected void ender(ThisPage thisPage) throws IOException
995        { 
996                ;
997        }
998        /**
999         * This method generates the web page based on the contents
1000         * of the request packet.
1001         * 
1002         * <p>This method will be overwritten to enable various actions
1003         *    to take place as the information is uploaded.</p>
1004         * <p>For the result of File requests, write the contents to a file.</p>
1005         * <p>I am having a problem with the loading of binary files.</p>
1006         * @param thisPage Information on this HTTP request
1007         */
1008        protected void processor(ThisPage thisPage) throws IOException
1009        {
1010                Enumeration<String> keys = thisPage.getPartNames();
1011                HttpServletRequest req = thisPage.getRequest();
1012                ServletConfig config = thisPage.getConfig();
1013                String targetDirectory = config.getInitParameter("directory");
1014                GenericPrinter output = thisPage.getPrinter();
1015                output.println("<html><head>");
1016                output.println("<title>Dummy Upload Program</title>");
1017                output.println("</head><body>");
1018                output.println("<p>It is assumed that the processor method of the ");
1019                output.println("bradleyross.library.servlets.UploadServlet class will ");
1020                output.println("be overridden to provide the desired function.  This ");
1021                output.println("sample version is designed for testing the applications ");
1022                output.println("and to allow demonstration of the capabilities.</p>");
1023                output.println("<p>Target directory for upload tests is " + targetDirectory + "</p>");
1024                output.println("<h2>Headers</h2>");
1025                output.println("<table border=\"1\">");
1026                Enumeration<?> list1 = req.getHeaderNames();
1027                while (list1.hasMoreElements())
1028                {
1029                        String name = (String) list1.nextElement();
1030                        Enumeration<?> list2 = req.getHeaders(name);
1031                        while (list2.hasMoreElements())
1032                        {
1033                                String value = (String) list2.nextElement();
1034                                output.println("<tr><td>" + name + "</td><td>" + value + "</td></tr>");
1035                        }
1036                }
1037                output.println("</table>");
1038                output.println("<h2>Parts of form</h2>");
1039                output.println("<p>The following are the parts of the multipart form</p>");         
1040                output.println("<ul>");
1041                while (keys.hasMoreElements())
1042                {
1043                        String name = keys.nextElement();
1044                        String mime = thisPage.getElement(name).getMime();
1045                        String fileName = thisPage.getElement(name).getFilename();
1046                        String encoding = thisPage.getElement(name).getTransferEncoding();
1047                        output.println("<li><p>" + name + "</p>");
1048                        if (fileName == null)
1049                        {
1050                                output.println("<p>Filename not specified</p>");
1051                        }
1052                        else
1053                        {
1054                                output.println("<p>Filename is " + fileName + "</p>");
1055                        }
1056                        if (mime == null)
1057                        {
1058                                output.println("<p>Content type not specified</p>");
1059                                mime = new String();
1060                        }
1061                        else
1062                        {
1063                                output.println("<p>Content-type: " + mime + "</p>");
1064                        }
1065                        if (encoding == null)
1066                        {
1067                                output.println("<p>Transfer encoding not specified</p>");
1068                        }
1069                        else
1070                        {
1071                        output.println("<p>Content-transfer-encoding: " + encoding + "</p>");
1072                        }
1073                        if (mime.toUpperCase().startsWith("TEXT"))
1074                        {
1075                                output.println("<p>" + StringHelpers.escapeHTML(thisPage.getElement(name).toString())
1076                                        + "</p></li>");
1077                        }
1078                        output.println("<p>Size of contents: " + Integer.toString(thisPage.getElement(name).getContentsSize()));
1079                        if (fileName != null && targetDirectory != null)
1080                        {
1081                                try
1082                                {
1083                                        boolean validEntry = true;
1084                                        File outputFile = null;
1085                                        if (targetDirectory.length() == 0 || fileName.length() == 0)
1086                                        {
1087                                                validEntry = false;
1088                                        }
1089                                        if (validEntry)
1090                                        {
1091                                                outputFile = new File (targetDirectory, fileName);
1092                                                output.println("<p>Name of file is " + StringHelpers.escapeHTML(outputFile.getCanonicalPath()) + "</p>");
1093                                        }
1094                                        if (!validEntry)
1095                                        { ; }
1096                                        else if (outputFile == null)
1097                                        {
1098                                                output.println("<p>Unable to open output file</p>");
1099                                        }
1100                                        else
1101                                        {
1102                                                FileOutputStream outputStream = new FileOutputStream(outputFile);
1103                                                byte transfer[] = thisPage.getElement(name).getContents();
1104                                                if (transfer == null)
1105                                                {
1106                                                        output.println("<p>Unable to get file contents</p>");
1107                                                }
1108                                                else 
1109                                                {
1110                                                        outputStream.write(thisPage.getElement(name).getContents());
1111                                                }
1112                                                outputStream.close();   
1113                                        }
1114                                }
1115                                catch (IOException e)
1116                                {
1117                                        output.println("<p>Error while writing file</p>");
1118                                        output.println(StringHelpers.escapeHTML("<p>" + e.getClass().getName() + " " +
1119                                                        e.getMessage() + "</p>"));
1120                                }
1121                        }
1122                        else
1123                        { ;     }       
1124                }
1125                output.println("</ul>");
1126                output.println("<h2>Messages</h2>");
1127                output.println("<p>These messages are normally printed only if an error occurs during the processing ");
1128                output.println("of the HTTP transaction.  They are included here to test the behavior of the servlet.</p><ol>");
1129                Vector<String> messages = thisPage.getMessageList();
1130                messages.trimToSize();
1131                for (int i = 0; i < messages.size(); i++)
1132                {
1133                        output.println("<li><p>" + StringHelpers.escapeHTML(messages.elementAt(i)) + "</p></li>");
1134                }
1135                output.println("</ol>");
1136                output.println("</body></html>");
1137                thisPage.sendContents();
1138        }
1139}