1 //
2 // $Id: FileUploadThreadHTTP.java 488 2008-07-06 20:21:43Z etienne_sf $
3 //
4 // jupload - A file upload applet.
5 // Copyright 2007 The JUpload Team
6 //
7 // Created: 2007-03-07
8 // Creator: etienne_sf
9 // Last modified: $Date: 2008-07-06 22:21:43 +0200 (dim., 06 juil. 2008) $
10 //
11 // This program is free software; you can redistribute it and/or modify it under
12 // the terms of the GNU General Public License as published by the Free Software
13 // Foundation; either version 2 of the License, or (at your option) any later
14 // version. This program is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 // details. You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation, Inc.,
19 // 675 Mass Ave, Cambridge, MA 02139, USA.
20
21 package wjhk.jupload2.upload.helper;
22
23 import java.io.BufferedOutputStream;
24 import java.io.DataOutputStream;
25 import java.io.IOException;
26 import java.io.OutputStream;
27 import java.io.PushbackInputStream;
28 import java.net.Proxy;
29 import java.net.ProxySelector;
30 import java.net.Socket;
31 import java.net.URISyntaxException;
32 import java.net.URL;
33 import java.security.KeyManagementException;
34 import java.security.KeyStoreException;
35 import java.security.NoSuchAlgorithmException;
36 import java.security.UnrecoverableKeyException;
37 import java.security.cert.CertificateException;
38
39 import wjhk.jupload2.exception.JUploadException;
40 import wjhk.jupload2.exception.JUploadIOException;
41 import wjhk.jupload2.policies.UploadPolicy;
42
43 /**
44 * This class contains utilities to delegate network manipulation. It hides the management for the current upload policy
45 * connection parameters.<BR>
46 * This class goes through the following states, stored in the private connectionStatus attribute: <DIR> <LI>
47 * STATUS_NOT_INITIALIZED: default status when the instance in created. Only available action:
48 * {@link #initRequest(URL, String, boolean, boolean)} <LI>STATUS_BEFORE_SERVER_CONNECTION: the instance is initialized,
49 * and the caller may begin writing the request to this HttpConnectionHelper. All data written to it, will be stored in
50 * a {@link ByteArrayEncoderHTTP}. The connection switches to this status when the
51 * {@link #initRequest(URL, String, boolean, boolean)} is called. <LI>STATUS_WRITING_REQUEST: The network connection to
52 * the server is now opened. The content of the ByteArrayEncoderHTTP has been sent to the server. All subsequent calls
53 * to write methods will directly write on the socket to the server. The {@link #sendRequest()} method changes the
54 * connection to this status. <LI>STATUS_READING_RESPONSE: The request to the server has been totally written. No more
55 * calls to the write methods are allowed. The {@link #readHttpResponse()} is responsible to put the
56 * HttpConnectionHelper to this status. <LI>STATUS_CONNECTION_CLOSED: The response has been read. All getters can be
57 * called, to get information about the server response. The only other method allowed is the
58 * {@link #initRequest(URL, String, boolean, boolean)}, to start a new request to the server. Using the same
59 * HttpConnectionHelper allows to use the same network connection, when the allowHttpPersistent applet parameter is
60 * used. </DIR>
61 *
62 * @author etienne_sf
63 */
64 public class HTTPConnectionHelper extends OutputStream {
65
66 // ////////////////////////////////////////////////////////////////////////////////////
67 // /////////////////// PRIVATE CONSTANTS
68 // ////////////////////////////////////////////////////////////////////////////////////
69 /**
70 * Indicates that the connection has not been initialized. The only authorized action in this state is a call to
71 * {@link #initRequest(URL, String, boolean, boolean)}.
72 */
73 final static private int STATUS_NOT_INITIALIZED = 0;
74
75 /**
76 * Indicates that the network connection to the server has not been opened. All data sent to the
77 * HttpConnectionHelper with write methods are sent to the current ByteArrayEncoder.
78 */
79 final static private int STATUS_BEFORE_SERVER_CONNECTION = 1;
80
81 /**
82 * Indicates that the network connection to the server is opened, but the request has not been totally sent to the
83 * server. All data sent to the HttpConnectionHelper with write methods is sent to the network connection, that is:
84 * to the current OutputStream. <BR>
85 * That is: the ByteArrayEncoder is now read only (closed).
86 */
87 final static private int STATUS_WRITING_REQUEST = 2;
88
89 /**
90 * Indicates that the network connection to the server is opened, but the request has not been totally sent to the
91 * server. All data sent to the HttpConnectionHelper with write methods is sent to the network connection, that is:
92 * to the current OutputStream. <BR>
93 * That is: the ByteArrayEncoder is now read only (closed).
94 */
95 final static private int STATUS_READING_RESPONSE = 3;
96
97 /**
98 * Indicates that the network connection to the server is now closed, that is: we've written the request, read the
99 * response, and free the server connection. If the keepAlive parameter is used, the connection may remain opened
100 * for the next request. <BR>
101 * No more action may be done on this connection helper, out of reading data, until the application do a call to
102 * {@link #initRequest(URL, String, boolean, boolean)}.
103 */
104 final static private int STATUS_CONNECTION_CLOSED = 4;
105
106 // ////////////////////////////////////////////////////////////////////////////////////
107 // /////////////////// ATTRIBUTE USED TO CONTROL THE OUTPUT TO THE SERVER
108 // ////////////////////////////////////////////////////////////////////////////////////
109 /**
110 * http boundary, for the posting multipart post.
111 */
112 private String boundary = calculateRandomBoundary();
113
114 /**
115 * Is chunk upload on for this request ?
116 */
117 private boolean bChunkEnabled;
118
119 /**
120 * Is it the last chunk ? If yes, we'll try to keep the connection open, according to the current applet
121 * configuration.
122 */
123 private boolean bLastChunk;
124
125 /**
126 * The encoder that will contain the HTTP request.
127 */
128 private ByteArrayEncoder byteArrayEncoder = null;
129
130 /**
131 * Indicates where data sent to appendXxx method should be added. It must be one of the SENDING_XXX private strings.
132 */
133 private int connectionStatus = STATUS_NOT_INITIALIZED;
134
135 /**
136 * Contains the HTTP reader. All data coming from the server response are read from it. If this attribute is null,
137 * it means that the server response has not been read.
138 */
139 private HTTPInputStreamReader httpInputStreamReader = null;
140
141 /**
142 * This stream allows the applet to get the server response. It is opened and closed as the {@link #outputStream}.
143 */
144 private PushbackInputStream inputStream = null;
145
146 /**
147 * The HTTP method: POST, GET, HEAD...
148 */
149 private String method = null;
150
151 /**
152 * This stream is open by {@link #sendRequest()}. It is closed by the {@link #readHttpResponse()} method.
153 *
154 * @see #sendRequest()
155 * @see #readHttpResponse()
156 * @see #getOutputStream()
157 */
158 private DataOutputStream outputStream = null;
159
160 /**
161 * The current proxy, if any
162 */
163 private Proxy proxy = null;
164
165 /**
166 * The network socket where the bytes should be written.
167 */
168 private Socket socket = null;
169
170 /**
171 * The current upload policy
172 */
173 private UploadPolicy uploadPolicy = null;
174
175 /**
176 * The current URL
177 */
178 private URL url = null;
179
180 /**
181 * Should we use a proxy
182 */
183 private boolean useProxy;
184
185 /**
186 * Is SSL mode on ?
187 */
188 private boolean useSSL;
189
190 // ////////////////////////////////////////////////////////////////////////////////////
191 // /////////////////////// PUBLIC METHODS
192 // ////////////////////////////////////////////////////////////////////////////////////
193
194 /**
195 * The standard constructor for this class.
196 *
197 * @param uploadPolicy The current upload policy.
198 */
199 public HTTPConnectionHelper(UploadPolicy uploadPolicy) {
200 this.uploadPolicy = uploadPolicy;
201 }
202
203 /**
204 * The standard constructor for this class.
205 *
206 * @param url The target URL
207 * @param method The HTTP method (POST, GET, HEAD...)
208 * @param bChunkEnabled Indicates if chunkUpload is enabled for this query. Put false, if non chunked request or if
209 * it is not relevant.
210 * @param bLastChunk Indicates whether this chunk is the last one. Put true, if non chunked request or if it is not
211 * relevant.
212 * @param uploadPolicy The current upload policy.
213 * @throws JUploadIOException
214 */
215 public HTTPConnectionHelper(URL url, String method, boolean bChunkEnabled, boolean bLastChunk,
216 UploadPolicy uploadPolicy) throws JUploadIOException {
217 this.uploadPolicy = uploadPolicy;
218 initRequest(url, method, bChunkEnabled, bLastChunk);
219 }
220
221 /**
222 * The standard constructor for this class.
223 *
224 * @param url The target URL
225 * @param bChunkEnabled Indicates if chunkUpload is enabled for this query. Put false, if non chunked request or if
226 * it is not relevant.
227 * @param method The HTTP method (POST, GET, HEAD...)
228 * @param bLastChunk Indicates whether this chunk is the last one. Put true, if non chunked request or if it is not
229 * relevant.
230 * @throws JUploadIOException
231 */
232 public synchronized void initRequest(URL url, String method, boolean bChunkEnabled, boolean bLastChunk)
233 throws JUploadIOException {
234 // This method expects that the connection has not been initialized yet,
235 // or that the previous request is finished.
236 if (this.connectionStatus != STATUS_NOT_INITIALIZED && this.connectionStatus != STATUS_CONNECTION_CLOSED) {
237 throw new JUploadIOException("Bad status of the HttpConnectionHelper in initRequest: " + getStatusLabel());
238 }
239
240 // Clean any current request.
241 if (isKeepAlive()) {
242 dispose();
243 }
244
245 // Load the new parameters.
246 this.url = url;
247 this.method = method;
248 this.bChunkEnabled = bChunkEnabled;
249 this.bLastChunk = bLastChunk;
250 // We will write to the local ByteArrayEncoder, until a connection to
251 // the server is opened.
252 initByteArrayEncoder();
253
254 // Ok, the HttpConnectionHelper is now ready to get write commands.
255 this.connectionStatus = STATUS_BEFORE_SERVER_CONNECTION;
256 }
257
258 /**
259 * Return the current {@link ByteArrayEncoder}. If it was not created, it is initialized.
260 *
261 * @return The current {@link ByteArrayEncoder}, null if called before the first initialization.
262 * @throws JUploadIOException
263 * @see #initRequest(URL, String, boolean, boolean)
264 */
265 public ByteArrayEncoder getByteArrayEncoder() throws JUploadIOException {
266 return this.byteArrayEncoder;
267 }
268
269 /**
270 * @return Returns the boundary for this HTTP request.
271 */
272 public String getBoundary() {
273 return this.boundary;
274 }
275
276 /**
277 * Closes the byteArrayEncoder, create the socket (or not, depending on the current uploadPolicy, and upload
278 * history), send the request, and create the InputStream to read the server response.
279 *
280 * @throws JUploadIOException
281 */
282 public synchronized void sendRequest() throws JUploadIOException {
283 // This method expects that the connection is writing data to the
284 // server.
285 if (this.connectionStatus != STATUS_BEFORE_SERVER_CONNECTION) {
286 throw new JUploadIOException("Bad status of the HttpConnectionHelper in sendRequest: " + getStatusLabel());
287 }
288
289 try {
290 // We've finished with the current encoder.
291 if (!this.byteArrayEncoder.isClosed()) {
292 this.byteArrayEncoder.close();
293 }
294
295 // Let's clear any field that could have been read in a previous
296 // step:
297 this.httpInputStreamReader = null;
298
299 // Only connect, if sock is null!!
300 // ... or if we don't persist HTTP connections (patch for IIS, based
301 // on Marc Reidy's patch)
302 if (this.socket == null || !this.uploadPolicy.getAllowHttpPersistent()) {
303 this.socket = new HttpConnect(this.uploadPolicy).connect(this.url, this.proxy);
304 this.outputStream = new DataOutputStream(new BufferedOutputStream(this.socket.getOutputStream()));
305 this.inputStream = new PushbackInputStream(this.socket.getInputStream(), 1);
306 }
307
308 // Send http request to server
309 this.outputStream.write(this.byteArrayEncoder.getEncodedByteArray());
310
311 // The request has been sent. The current ByteArrayEncoder is now
312 // useless. A new one is to be created for the next request.
313 this.connectionStatus = STATUS_WRITING_REQUEST;
314
315 } catch (IOException e) {
316 throw new JUploadIOException("Unable to open socket", e);
317 } catch (KeyManagementException e) {
318 throw new JUploadIOException("Unable to open socket", e);
319 } catch (UnrecoverableKeyException e) {
320 throw new JUploadIOException("Unable to open socket", e);
321 } catch (NoSuchAlgorithmException e) {
322 throw new JUploadIOException("Unable to open socket", e);
323 } catch (KeyStoreException e) {
324 throw new JUploadIOException("Unable to open socket", e);
325 } catch (CertificateException e) {
326 throw new JUploadIOException("Unable to open socket", e);
327 } catch (IllegalArgumentException e) {
328 throw new JUploadIOException("Unable to open socket", e);
329 }
330
331 }
332
333 /**
334 * Releases all reserved resources.
335 *
336 * @throws JUploadIOException
337 */
338 public synchronized void dispose() throws JUploadIOException {
339 // A try to properly clean the connection to the server.
340 try {
341 // Let's shutdown the output, to free the server connection. Only
342 // valid for non https connections.
343 if (this.socket != null && !this.useSSL && !this.socket.isOutputShutdown()) {
344 this.socket.shutdownOutput();
345 }
346 } catch (IOException e) {
347 throw new JUploadIOException(e);
348 }
349 try {
350 if (this.socket != null && !this.useSSL && !this.socket.isInputShutdown()) {
351 this.socket.shutdownInput();
352 }
353 } catch (IOException e) {
354 throw new JUploadIOException(e);
355 }
356
357 try {
358 if (this.outputStream != null) {
359 this.outputStream.close();
360 }
361 } catch (IOException e) {
362 throw new JUploadIOException(e);
363 } finally {
364 this.outputStream = null;
365 }
366
367 try {
368 if (this.inputStream != null) {
369 this.inputStream.close();
370 }
371 } catch (IOException e) {
372 throw new JUploadIOException(e);
373 } finally {
374 this.inputStream = null;
375 }
376
377 try {
378 if (this.socket != null) {
379 if (!this.socket.isClosed()) {
380 this.socket.close();
381 }
382 }
383 } catch (IOException e) {
384 throw new JUploadIOException(e);
385 } finally {
386 this.socket = null;
387 }
388 }
389
390 /**
391 * Return the current socket. If the byteArrayEncoder is not closed: close it, and send the request to the server.
392 *
393 * @return public Socket getSocket() { }
394 */
395
396 /**
397 * get the output stream, where HTTP data can be written.
398 *
399 * @return The current output stream to the server, where things can be written, event after the socket is open, if
400 * the byteArrayEncoder did not contain the full request.
401 */
402 public OutputStream getOutputStream() {
403 return this;
404 }
405
406 /**
407 * get the input stream, where HTTP server response can be read.
408 *
409 * @return The current input stream of the socket.
410 */
411 public PushbackInputStream getInputStream() {
412 return this.inputStream;
413 }
414
415 /**
416 * Get the HTTP method (HEAD, POST, GET...)
417 *
418 * @return The HTTP method
419 */
420 public String getMethod() {
421 return this.method;
422 }
423
424 /**
425 * Get the last response body.
426 *
427 * @return The full response body, that is: the HTTP body of the server response.
428 */
429 public String getResponseBody() {
430 return this.httpInputStreamReader.getResponseBody();
431 }
432
433 /**
434 * Get the headers of the HTTP response.
435 *
436 * @return The HTTP headers.
437 */
438 public String getResponseHeaders() {
439 return this.httpInputStreamReader.getResponseHeaders();
440 }
441
442 /**
443 * Get the last response message.
444 *
445 * @return the response message, like "200 OK"
446 */
447 public String getResponseMsg() {
448 return this.httpInputStreamReader.getResponseMsg();
449 }
450
451 /**
452 * Get the label describing the current state of this connection helper.
453 *
454 * @return A text describing briefly the current connection status.
455 */
456 public synchronized String getStatusLabel() {
457 switch (this.connectionStatus) {
458 case STATUS_NOT_INITIALIZED:
459 return "Not initialized";
460 case STATUS_BEFORE_SERVER_CONNECTION:
461 return "Before server connection";
462 case STATUS_WRITING_REQUEST:
463 return "Writing request to the network";
464 case STATUS_READING_RESPONSE:
465 return "Reading server response";
466 case STATUS_CONNECTION_CLOSED:
467 return "Connection closed";
468 }
469 return "Unknown status in HTTPConnectionHelper.getStatusLabel()";
470 }
471
472 /**
473 * Get the current socket.
474 *
475 * @return return the current Socket, opened toward the server.
476 */
477 Socket getSocket() {
478 return this.socket;
479 }
480
481 /**
482 * Append bytes to the current query. The bytes will be written to the current ByteArrayEncoder if the the
483 * connection to the server is not open, or directly to the server if the connection is opened.
484 *
485 * @param b The byte to send to the server.
486 * @return Returns the current HttpConnectionHelper, to allow coding like StringBuffers: a.append(b).append(c);
487 * @throws JUploadIOException
488 */
489 public synchronized HTTPConnectionHelper append(int b) throws JUploadIOException {
490 if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
491 this.byteArrayEncoder.append(b);
492 } else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
493 try {
494 this.outputStream.write(b);
495 } catch (IOException e) {
496 throw new JUploadIOException(e.getClass().getName() + " while writing to httpDataOut", e);
497 }
498 } else {
499 throw new JUploadIOException("Wrong status in HTTPConnectionHelper.write() [" + getStatusLabel() + "]");
500 }
501 return this;
502 }
503
504 /**
505 * Append bytes to the current query. The bytes will be written to the current ByteArrayEncoder if the the
506 * connection to the server is not open, or directly to the server if the connection is opened.
507 *
508 * @param bytes The bytes to send to the server.
509 * @return Returns the current HttpConnectionHelper, to allow coding like StringBuffers: a.append(b).append(c);
510 * @throws JUploadIOException
511 */
512 public synchronized HTTPConnectionHelper append(byte[] bytes) throws JUploadIOException {
513 return this.append(bytes, 0, bytes.length);
514 }
515
516 /**
517 * Append bytes to the current query. The bytes will be written to the current ByteArrayEncoder if the the
518 * connection to the server is not open, or directly to the server if the connection is opened.
519 *
520 * @param bytes The bytes to send to the server.
521 * @param off The first byte to send
522 * @param len Number of bytes to send.
523 * @return Returns the current HttpConnectionHelper, to allow coding like StringBuffers: a.append(b).append(c);
524 * @throws JUploadIOException
525 */
526 public synchronized HTTPConnectionHelper append(byte[] bytes, int off, int len) throws JUploadIOException {
527
528 if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
529 this.byteArrayEncoder.append(bytes);
530 } else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
531 try {
532 this.outputStream.write(bytes, off, len);
533 } catch (IOException e) {
534 throw new JUploadIOException(e.getClass().getName() + " while writing to httpDataOut", e);
535 }
536 } else {
537 throw new JUploadIOException("Wrong status in HTTPConnectionHelper.write() [" + getStatusLabel() + "]");
538 }
539
540 if (this.uploadPolicy.getDebugLevel() > 100) {
541 this.uploadPolicy.displayDebug("[HTTPConnectionHelper append(byte[],int,int)] ("
542 + len
543 + " bytes appended to "
544 + (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION ? " current ByteArrayEncoder"
545 : " socket") + ")", 101);
546 }
547
548 return this;
549 }
550
551 /**
552 * write a string to the current HTTP request.
553 *
554 * @param str The string to write
555 * @return The current HTTPConnectionHelper
556 * @throws JUploadIOException If any problem occurs during the writing operation.
557 * @see #append(byte[])
558 */
559 public synchronized HTTPConnectionHelper append(String str) throws JUploadIOException {
560 this.uploadPolicy.displayDebug("[HTTPConnectionHelper append] " + str, 70);
561 if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
562 this.byteArrayEncoder.append(str);
563 } else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
564 ByteArrayEncoder bae = new ByteArrayEncoderHTTP(this.uploadPolicy, this.byteArrayEncoder.getBoundary(),
565 this.byteArrayEncoder.getEncoding());
566 bae.append(str);
567 bae.close();
568 this.append(bae);
569 }
570 return this;
571 }
572
573 /**
574 * Appends a string to the current HTTP request.
575 *
576 * @param bae The ByteArrayEncoder to write. It is expected to be correctly encoded. That is: it is up to the caller
577 * to check that its encoding is the same as the current HTTP request encoding.
578 * @return The current HTTPConnectionHelper
579 * @throws JUploadIOException If any problem occurs during the writing operation.
580 * @see #append(byte[])
581 */
582 public synchronized HTTPConnectionHelper append(ByteArrayEncoder bae) throws JUploadIOException {
583 this.uploadPolicy.displayDebug("[HTTPConnectionHelper append] " + bae.getString(), 70);
584 return this.append(bae.getEncodedByteArray());
585 }
586
587 /**
588 * Read the response of the server. This method delegates the work to the HTTPInputStreamReader. handles the chunk
589 * HTTP response.
590 *
591 * @return The HTTP status. Should be 200, when everything is right.
592 * @throws JUploadException
593 */
594 public synchronized int readHttpResponse() throws JUploadException {
595 // This method expects that the connection is writing data to the
596 // server.
597 if (this.connectionStatus != STATUS_WRITING_REQUEST) {
598 throw new JUploadIOException("Bad status of the HttpConnectionHelper in readHttpResponse: "
599 + getStatusLabel());
600 }
601 this.connectionStatus = STATUS_READING_RESPONSE;
602
603 // Let's connect in InputStream to read this server response.
604 if (this.httpInputStreamReader == null) {
605 this.httpInputStreamReader = new HTTPInputStreamReader(this, this.uploadPolicy);
606 }
607
608 // Let's do the job
609 try {
610 this.outputStream.flush();
611 } catch (IOException ioe) {
612 throw new JUploadIOException("flushing outputStream, in " + getClass().getName() + ".readHttpResponse()");
613 }
614 this.httpInputStreamReader.readHttpResponse();
615
616 if (this.httpInputStreamReader.gotClose) {
617 // RFC 2868, section 8.1.2.1
618 dispose();
619 }
620
621 // We got the response
622 this.connectionStatus = STATUS_CONNECTION_CLOSED;
623
624 // FIXME Should close the connection, from time to time...
625
626 //
627 return this.httpInputStreamReader.gethttpStatusCode();
628 }
629
630 // ////////////////////////////////////////////////////////////////////////////////////
631 // /////////////////////// PRIVATE METHODS
632 // ////////////////////////////////////////////////////////////////////////////////////
633
634 /**
635 * creating of a new {@link ByteArrayEncoderHTTP}, and initializing of the following header items: First line (POST
636 * currentProtocol URI), Host, Connection, Keep-Alive, Proxy-Connection.
637 *
638 * @throws JUploadIOException
639 */
640 private synchronized void initByteArrayEncoder() throws JUploadIOException {
641 if (this.byteArrayEncoder != null && !this.byteArrayEncoder.isClosed()) {
642 this.byteArrayEncoder.close();
643 this.byteArrayEncoder = null;
644 }
645 this.byteArrayEncoder = new ByteArrayEncoderHTTP(this.uploadPolicy, this.boundary);
646 this.connectionStatus = STATUS_BEFORE_SERVER_CONNECTION;
647 this.proxy = null;
648 try {
649 this.proxy = ProxySelector.getDefault().select(this.url.toURI()).get(0);
650 } catch (URISyntaxException e) {
651 throw new JUploadIOException("Error while managing url " + this.url.toExternalForm(), e);
652 }
653 this.useProxy = ((this.proxy != null) && (this.proxy.type() != Proxy.Type.DIRECT));
654 this.useSSL = this.url.getProtocol().equals("https");
655
656 // Header: Request line
657 // Let's clear it. Useful only for chunked uploads.
658 this.byteArrayEncoder.append(this.method);
659 this.byteArrayEncoder.append(" ");
660 this.uploadPolicy.displayDebug("[initByteArrayEncoder] proxy=" + proxy + ", proxy.type=" + this.proxy.type()
661 + ", useProxy=" + useProxy + ", url.host=" + this.url.getHost() + ", url.port=" + this.url.getPort(),
662 80);
663 if (this.useProxy && (!this.useSSL)) {
664 // with a proxy we need the absolute URL, but only if not
665 // using SSL. (with SSL, we first use the proxy CONNECT method,
666 // and then a plain request.)
667 this.byteArrayEncoder.append(this.url.getProtocol()).append("://").append(this.url.getHost());
668 // TODO port in proxy mode: to be tested !
669 if (this.url.getPort() > 0) {
670 this.byteArrayEncoder.append(":").append(Integer.toString(this.url.getPort()));
671 }
672 }
673 this.byteArrayEncoder.append(this.url.getPath());
674
675 // Append the query params.
676 // TODO: This probably can be removed as we now have everything in POST
677 // data. However in order to be
678 // backwards-compatible, it stays here for now. So we now provide
679 // *both* GET and POST params.
680 if (null != this.url.getQuery() && !"".equals(this.url.getQuery()))
681 this.byteArrayEncoder.append("?").append(this.url.getQuery());
682
683 this.byteArrayEncoder.append(" ").append(this.uploadPolicy.getServerProtocol()).append("\r\n");
684
685 // Header: General
686 this.byteArrayEncoder.append("Host: ").append(this.url.getHost());
687 if (this.url.getPort() > 0) {
688 this.byteArrayEncoder.append(":").append(Integer.toString(this.url.getPort()));
689 }
690 this.byteArrayEncoder.append("\r\nAccept: */*\r\n");
691
692 // We do not want gzipped or compressed responses, so we must
693 // specify that here (RFC 2616, Section 14.3)
694 this.byteArrayEncoder.append("Accept-Encoding: identity\r\n");
695
696 // Seems like the Keep-alive doesn't work properly, at least on my
697 // local dev (Etienne).
698 if (!this.uploadPolicy.getAllowHttpPersistent()) {
699 this.byteArrayEncoder.append("Connection: close\r\n");
700 } else {
701 if (!this.bChunkEnabled || this.bLastChunk || this.useProxy
702 || !this.uploadPolicy.getServerProtocol().equals("HTTP/1.1")) { // RFC 2086, section 19.7.1
703 this.byteArrayEncoder.append("Connection: close\r\n");
704 } else {
705 this.byteArrayEncoder.append("Keep-Alive: 300\r\n");
706 if (this.useProxy)
707 this.byteArrayEncoder.append("Proxy-Connection: keep-alive\r\n");
708 else
709 this.byteArrayEncoder.append("Connection: keep-alive\r\n");
710 }
711 }
712
713 // Get specific headers for this upload.
714 this.uploadPolicy.onAppendHeader(this.byteArrayEncoder);
715 }
716
717 /**
718 * Indicates whether the current socket should be reused ... if any.
719 */
720 private boolean isKeepAlive() {
721 if (this.socket == null) {
722 return false;
723 } else if (!this.uploadPolicy.getAllowHttpPersistent()) {
724 return false;
725 } else {
726 if (!this.bChunkEnabled || this.bLastChunk || this.useProxy
727 || !this.uploadPolicy.getServerProtocol().equals("HTTP/1.1")) { // RFC 2086, section 19.7.1
728 return false;
729 } else {
730 return true;
731 }
732 }
733 }
734
735 /**
736 * Construction of a random boundary, to separate the uploaded files, in the HTTP upload request.
737 *
738 * @return The calculated boundary.
739 */
740 private final String calculateRandomBoundary() {
741 StringBuffer sbRan = new StringBuffer(11);
742 sbRan.append("-----------------------------");
743 String alphaNum = "1234567890abcdefghijklmnopqrstuvwxyz";
744 int num;
745 for (int i = 0; i < 11; i++) {
746 num = (int) (Math.random() * (alphaNum.length() - 1));
747 sbRan.append(alphaNum.charAt(num));
748 }
749 return sbRan.toString();
750 }
751
752 // ////////////////////////////////////////////////////////////////////////////////////
753 // /////////////////// OVERRIDE OF OutputStream METHODS
754 // ////////////////////////////////////////////////////////////////////////////////////
755 /** {@inheritDoc} */
756 @Override
757 public void write(int b) throws IOException {
758 try {
759 append(b);
760 } catch (JUploadIOException e) {
761 // Hum, HTTPConnectionHelper catch IOException, and throws a
762 // JUploadIOException. Now we get the cause, that is the original
763 // IOException. Not optimized.
764 if (e.getCause() == null) {
765 // This should not happen
766 throw new IOException();
767 } else if (e.getCause() instanceof IOException) {
768 throw (IOException) e.getCause();
769 } else {
770 // Hum, can something like an OutOfMemory. We must throw it.
771 throw new IOException(e.getCause().getClass().getName() + ": " + e.getCause().getMessage());
772 }
773 }
774 }
775
776 /** {@inheritDoc} */
777 @Override
778 public void write(byte[] b, int off, int len) throws IOException {
779 try {
780 append(b, off, len);
781 } catch (JUploadIOException e) {
782 // Hum, HTTPConnectionHelper catch IOException, and throws a
783 // JUploadIOException. Now we get the cause, that is the original
784 // IOException. Not optimized.
785 if (e.getCause() == null) {
786 // This should not happen
787 throw new IOException();
788 } else if (e.getCause() instanceof IOException) {
789 throw (IOException) e.getCause();
790 } else {
791 // Hum, can something like an OutOfMemory. We must throw it.
792 throw new IOException(e.getCause().getClass().getName() + ": " + e.getCause().getMessage());
793 }
794 }
795 }
796
797 /** {@inheritDoc} */
798 @Override
799 public void write(byte[] b) throws IOException {
800 write(b, 0, b.length);
801 }
802
803 /**
804 * This method is the override of {@link OutputStream#close()} one. It may not been called. You must use the
805 * {@link #sendRequest()} or {@link #readHttpResponse()} methods instead.
806 *
807 * @see java.io.OutputStream#close()
808 */
809 @Override
810 public void close() throws IOException {
811 this.uploadPolicy
812 .displayWarn("This method should not be called (HTTPConnectionHelper.close()). Please use the "
813 + getClass().getName() + ".sendRequest() method");
814 super.close();
815 }
816
817 /**
818 * Flushes the output stream. Useful only when the HTTPConnectionHelper is writing to the socket toward the server,
819 * that is when the status is: STATUS_WRITING_REQUEST.
820 *
821 * @see java.io.OutputStream#flush()
822 */
823 @Override
824 public void flush() throws IOException {
825 if (this.connectionStatus == STATUS_WRITING_REQUEST) {
826 this.outputStream.flush();
827 } else {
828 throw new IOException("Wrong status in " + getClass().getName() + ".flush method: " + getStatusLabel());
829 }
830 }
831 }