//
// $Id: FileUploadThreadHTTP.java 488 2008-07-06 20:21:43Z etienne_sf $
//
// jupload - A file upload applet.
// Copyright 2007 The JUpload Team
//
// Created: 2007-03-07
// Creator: etienne_sf
// Last modified: $Date: 2008-07-06 22:21:43 +0200 (dim., 06 juil. 2008) $
//
// This program is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation; either version 2 of the License, or (at your option) any later
// version. This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details. You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation, Inc.,
// 675 Mass Ave, Cambridge, MA 02139, USA.
package wjhk.jupload2.upload.helper;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.Socket;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import wjhk.jupload2.exception.JUploadException;
import wjhk.jupload2.exception.JUploadIOException;
import wjhk.jupload2.policies.UploadPolicy;
/**
* This class contains utilities to delegate network manipulation. It hides the
* management for the current upload policy connection parameters.
* This class goes through the following states, stored in the private
* connectionStatus attribute:
STATUS_NOT_INITIALIZED: default status
* when the instance in created. Only available action:
* {@link #initRequest(URL, String, boolean, boolean)}
* STATUS_BEFORE_SERVER_CONNECTION: the instance is initialized, and the caller
* may begin writing the request to this HttpConnectionHelper. All data written
* to it, will be stored in a {@link ByteArrayEncoderHTTP}. The connection
* switches to this status when the
* {@link #initRequest(URL, String, boolean, boolean)} is called.
* STATUS_WRITING_REQUEST: The network connection to the server is now opened.
* The content of the ByteArrayEncoderHTTP has been sent to the server. All
* subsequent calls to write methods will directly write on the socket to the
* server. The {@link #sendRequest()} method changes the connection to this
* status. STATUS_READING_RESPONSE: The request to the server has been
* totally written. No more calls to the write methods are allowed. The
* {@link #readHttpResponse()} is responsible to put the HttpConnectionHelper to
* this status. STATUS_CONNECTION_CLOSED: The response has been read. All
* getters can be called, to get information about the server response. The only
* other method allowed is the
* {@link #initRequest(URL, String, boolean, boolean)}, to start a new request
* to the server. Using the same HttpConnectionHelper allows to use the same
* network connection, when the allowHttpPersistent applet parameter is used.
*
*
* @author etienne_sf
*/
public class HTTPConnectionHelper extends OutputStream {
// FIXME Does HTTPConnectionHelper really need to be an OutputStream? (Find
// Bug generate not closed exception)
// ////////////////////////////////////////////////////////////////////////////////////
// /////////////////// PRIVATE CONSTANTS
// ////////////////////////////////////////////////////////////////////////////////////
/**
* Indicates that the connection has not been initialized. The only
* authorized action in this state is a call to
* {@link #initRequest(URL, String, boolean, boolean)}.
*/
final static private int STATUS_NOT_INITIALIZED = 0;
/**
* Indicates that the network connection to the server has not been opened.
* All data sent to the HttpConnectionHelper with write methods are sent to
* the current ByteArrayEncoder.
*/
final static private int STATUS_BEFORE_SERVER_CONNECTION = 1;
/**
* Indicates that the network connection to the server is opened, but the
* request has not been totally sent to the server. All data sent to the
* HttpConnectionHelper with write methods is sent to the network
* connection, that is: to the current OutputStream.
* That is: the ByteArrayEncoder is now read only (closed).
*/
final static private int STATUS_WRITING_REQUEST = 2;
/**
* Indicates that the network connection to the server is opened, but the
* request has not been totally sent to the server. All data sent to the
* HttpConnectionHelper with write methods is sent to the network
* connection, that is: to the current OutputStream.
* That is: the ByteArrayEncoder is now read only (closed).
*/
final static private int STATUS_READING_RESPONSE = 3;
/**
* Indicates that the network connection to the server is now closed, that
* is: we've written the request, read the response, and free the server
* connection. If the keepAlive parameter is used, the connection may remain
* opened for the next request.
* No more action may be done on this connection helper, out of reading
* data, until the application do a call to
* {@link #initRequest(URL, String, boolean, boolean)}.
*/
final static private int STATUS_CONNECTION_CLOSED = 4;
// ////////////////////////////////////////////////////////////////////////////////////
// /////////////////// ATTRIBUTE USED TO CONTROL THE OUTPUT TO THE SERVER
// ////////////////////////////////////////////////////////////////////////////////////
/**
* http boundary, for the posting multipart post.
*/
private String boundary = calculateRandomBoundary();
/**
* Is chunk upload on for this request ?
*/
private boolean bChunkEnabled;
/**
* Is it the last chunk ? If yes, we'll try to keep the connection open,
* according to the current applet configuration.
*/
private boolean bLastChunk;
/**
* The encoder that will contain the HTTP request.
*/
private ByteArrayEncoder byteArrayEncoder = null;
/**
* Indicates where data sent to appendXxx method should be added. It must be
* one of the SENDING_XXX private strings.
*/
private int connectionStatus = STATUS_NOT_INITIALIZED;
/**
* Contains the HTTP reader. All data coming from the server response are
* read from it. If this attribute is null, it means that the server
* response has not been read.
*/
private HTTPInputStreamReader httpInputStreamReader = null;
/**
* This stream allows the applet to get the server response. It is opened
* and closed as the {@link #outputStream}.
*/
private PushbackInputStream inputStream = null;
/**
* The HTTP method: POST, GET, HEAD...
*/
private String method = null;
/**
* This stream is open by {@link #sendRequest()}. It is closed by the
* {@link #readHttpResponse()} method.
*
* @see #sendRequest()
* @see #readHttpResponse()
* @see #getOutputStream()
*/
private DataOutputStream outputStream = null;
/**
* The current proxy, if any
*/
private Proxy proxy = null;
/**
* The network socket where the bytes should be written.
*/
private Socket socket = null;
/**
* The current upload policy
*/
private UploadPolicy uploadPolicy = null;
/**
* The current URL
*/
private URL url = null;
/**
* Should we use a proxy
*/
private boolean useProxy;
/**
* Is SSL mode on ?
*/
private boolean useSSL;
// ////////////////////////////////////////////////////////////////////////////////////
// /////////////////////// PUBLIC METHODS
// ////////////////////////////////////////////////////////////////////////////////////
/**
* The standard constructor for this class.
*
* @param uploadPolicy
* The current upload policy.
*/
public HTTPConnectionHelper(UploadPolicy uploadPolicy) {
this.uploadPolicy = uploadPolicy;
}
/**
* The standard constructor for this class.
*
* @param url
* The target URL
* @param method
* The HTTP method (POST, GET, HEAD...)
* @param bChunkEnabled
* Indicates if chunkUpload is enabled for this query. Put false,
* if non chunked request or if it is not relevant.
* @param bLastChunk
* Indicates whether this chunk is the last one. Put true, if non
* chunked request or if it is not relevant.
* @param uploadPolicy
* The current upload policy.
* @throws JUploadIOException
*/
public HTTPConnectionHelper(URL url, String method, boolean bChunkEnabled,
boolean bLastChunk, UploadPolicy uploadPolicy)
throws JUploadIOException {
this.uploadPolicy = uploadPolicy;
initRequest(url, method, bChunkEnabled, bLastChunk);
}
/**
* The standard constructor for this class.
*
* @param url
* The target URL
* @param bChunkEnabled
* Indicates if chunkUpload is enabled for this query. Put false,
* if non chunked request or if it is not relevant.
* @param method
* The HTTP method (POST, GET, HEAD...)
* @param bLastChunk
* Indicates whether this chunk is the last one. Put true, if non
* chunked request or if it is not relevant.
* @throws JUploadIOException
*/
public synchronized void initRequest(URL url, String method,
boolean bChunkEnabled, boolean bLastChunk)
throws JUploadIOException {
// This method expects that the connection has not been initialized yet,
// or that the previous request is finished.
if (this.connectionStatus != STATUS_NOT_INITIALIZED
&& this.connectionStatus != STATUS_CONNECTION_CLOSED) {
throw new JUploadIOException(
"Bad status of the HttpConnectionHelper in initRequest: "
+ getStatusLabel());
}
// Clean any current request.
if (isKeepAlive()) {
dispose();
}
// Load the new parameters.
this.url = url;
this.method = method;
this.bChunkEnabled = bChunkEnabled;
this.bLastChunk = bLastChunk;
// We will write to the local ByteArrayEncoder, until a connection to
// the server is opened.
initByteArrayEncoder();
// Ok, the HttpConnectionHelper is now ready to get write commands.
this.connectionStatus = STATUS_BEFORE_SERVER_CONNECTION;
}
/**
* Return the current {@link ByteArrayEncoder}. If it was not created, it is
* initialized.
*
* @return The current {@link ByteArrayEncoder}, null if called before the
* first initialization.
* @throws JUploadIOException
* @see #initRequest(URL, String, boolean, boolean)
*/
public ByteArrayEncoder getByteArrayEncoder() throws JUploadIOException {
return this.byteArrayEncoder;
}
/**
* @return Returns the boundary for this HTTP request.
*/
public String getBoundary() {
return this.boundary;
}
/**
* Closes the byteArrayEncoder, create the socket (or not, depending on the
* current uploadPolicy, and upload history), send the request, and create
* the InputStream to read the server response.
*
* @throws JUploadIOException
*/
public synchronized void sendRequest() throws JUploadIOException {
// This method expects that the connection is writing data to the
// server.
if (this.connectionStatus != STATUS_BEFORE_SERVER_CONNECTION) {
throw new JUploadIOException(
"Bad status of the HttpConnectionHelper in sendRequest: "
+ getStatusLabel());
}
try {
// We've finished with the current encoder.
if (!this.byteArrayEncoder.isClosed()) {
this.byteArrayEncoder.close();
}
// Let's clear any field that could have been read in a previous
// step:
this.httpInputStreamReader = null;
// Only connect, if sock is null!!
// ... or if we don't persist HTTP connections (patch for IIS, based
// on Marc Reidy's patch)
if (this.socket == null
|| !this.uploadPolicy.getAllowHttpPersistent()) {
this.socket = new HttpConnect(this.uploadPolicy).connect(
this.url, this.proxy);
this.outputStream = new DataOutputStream(
new BufferedOutputStream(this.socket.getOutputStream()));
this.inputStream = new PushbackInputStream(this.socket
.getInputStream(), 1);
}
// Send http request to server
this.outputStream
.write(this.byteArrayEncoder.getEncodedByteArray());
// The request has been sent. The current ByteArrayEncoder is now
// useless. A new one is to be created for the next request.
this.connectionStatus = STATUS_WRITING_REQUEST;
} catch (IOException e) {
throw new JUploadIOException("Unable to open socket", e);
} catch (KeyManagementException e) {
throw new JUploadIOException("Unable to open socket", e);
} catch (UnrecoverableKeyException e) {
throw new JUploadIOException("Unable to open socket", e);
} catch (NoSuchAlgorithmException e) {
throw new JUploadIOException("Unable to open socket", e);
} catch (KeyStoreException e) {
throw new JUploadIOException("Unable to open socket", e);
} catch (CertificateException e) {
throw new JUploadIOException("Unable to open socket", e);
} catch (IllegalArgumentException e) {
throw new JUploadIOException("Unable to open socket", e);
}
}
/**
* Releases all reserved resources.
*
* @throws JUploadIOException
*/
public synchronized void dispose() throws JUploadIOException {
// A try to properly clean the connection to the server.
try {
// Let's shutdown the output, to free the server connection. Only
// valid for non https connections.
if (this.socket != null && !this.useSSL
&& !this.socket.isOutputShutdown()) {
this.socket.shutdownOutput();
}
} catch (IOException e) {
throw new JUploadIOException(e);
}
try {
if (this.socket != null && !this.useSSL
&& !this.socket.isInputShutdown()) {
this.socket.shutdownInput();
}
} catch (IOException e) {
throw new JUploadIOException(e);
}
try {
if (this.outputStream != null) {
this.outputStream.close();
}
} catch (IOException e) {
throw new JUploadIOException(e);
} finally {
this.outputStream = null;
}
try {
if (this.inputStream != null) {
this.inputStream.close();
}
} catch (IOException e) {
throw new JUploadIOException(e);
} finally {
this.inputStream = null;
}
try {
if (this.socket != null) {
if (!this.socket.isClosed()) {
this.socket.close();
}
}
} catch (IOException e) {
throw new JUploadIOException(e);
} finally {
this.socket = null;
}
}
/**
* Return the current socket. If the byteArrayEncoder is not closed: close
* it, and send the request to the server.
*
* @return public Socket getSocket() { }
*/
/**
* get the output stream, where HTTP data can be written.
*
* @return The current output stream to the server, where things can be
* written, event after the socket is open, if the byteArrayEncoder
* did not contain the full request.
*/
public OutputStream getOutputStream() {
return this;
}
/**
* get the input stream, where HTTP server response can be read.
*
* @return The current input stream of the socket.
*/
public PushbackInputStream getInputStream() {
return this.inputStream;
}
/**
* Get the HTTP method (HEAD, POST, GET...)
*
* @return The HTTP method
*/
public String getMethod() {
return this.method;
}
/**
* Get the last response body.
*
* @return The full response body, that is: the HTTP body of the server
* response.
*/
public String getResponseBody() {
return this.httpInputStreamReader.getResponseBody();
}
/**
* Get the headers of the HTTP response.
*
* @return The HTTP headers.
*/
public String getResponseHeaders() {
return this.httpInputStreamReader.getResponseHeaders();
}
/**
* Get the last response message.
*
* @return the response message, like "200 OK"
*/
public String getResponseMsg() {
return this.httpInputStreamReader.getResponseMsg();
}
/**
* Get the label describing the current state of this connection helper.
*
* @return A text describing briefly the current connection status.
*/
public synchronized String getStatusLabel() {
switch (this.connectionStatus) {
case STATUS_NOT_INITIALIZED:
return "Not initialized";
case STATUS_BEFORE_SERVER_CONNECTION:
return "Before server connection";
case STATUS_WRITING_REQUEST:
return "Writing request to the network";
case STATUS_READING_RESPONSE:
return "Reading server response";
case STATUS_CONNECTION_CLOSED:
return "Connection closed";
}
return "Unknown status in HTTPConnectionHelper.getStatusLabel()";
}
/**
* Get the current socket.
*
* @return return the current Socket, opened toward the server.
*/
Socket getSocket() {
return this.socket;
}
/**
* Append bytes to the current query. The bytes will be written to the
* current ByteArrayEncoder if the the connection to the server is not open,
* or directly to the server if the connection is opened.
*
* @param b
* The byte to send to the server.
* @return Returns the current HttpConnectionHelper, to allow coding like
* StringBuffers: a.append(b).append(c);
* @throws JUploadIOException
*/
public synchronized HTTPConnectionHelper append(int b)
throws JUploadIOException {
if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
this.byteArrayEncoder.append(b);
} else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
try {
this.outputStream.write(b);
} catch (IOException e) {
throw new JUploadIOException(e.getClass().getName()
+ " while writing to httpDataOut", e);
}
} else {
throw new JUploadIOException(
"Wrong status in HTTPConnectionHelper.write() ["
+ getStatusLabel() + "]");
}
return this;
}
/**
* Append bytes to the current query. The bytes will be written to the
* current ByteArrayEncoder if the the connection to the server is not open,
* or directly to the server if the connection is opened.
*
* @param bytes
* The bytes to send to the server.
* @return Returns the current HttpConnectionHelper, to allow coding like
* StringBuffers: a.append(b).append(c);
* @throws JUploadIOException
*/
public synchronized HTTPConnectionHelper append(byte[] bytes)
throws JUploadIOException {
return this.append(bytes, 0, bytes.length);
}
/**
* Append bytes to the current query. The bytes will be written to the
* current ByteArrayEncoder if the the connection to the server is not open,
* or directly to the server if the connection is opened.
*
* @param bytes
* The bytes to send to the server.
* @param off
* The first byte to send
* @param len
* Number of bytes to send.
* @return Returns the current HttpConnectionHelper, to allow coding like
* StringBuffers: a.append(b).append(c);
* @throws JUploadIOException
*/
public synchronized HTTPConnectionHelper append(byte[] bytes, int off,
int len) throws JUploadIOException {
if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
this.byteArrayEncoder.append(bytes);
} else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
try {
this.outputStream.write(bytes, off, len);
} catch (IOException e) {
throw new JUploadIOException(e.getClass().getName()
+ " while writing to httpDataOut", e);
}
} else {
throw new JUploadIOException(
"Wrong status in HTTPConnectionHelper.write() ["
+ getStatusLabel() + "]");
}
if (this.uploadPolicy.getDebugLevel() > 100) {
this.uploadPolicy
.displayDebug(
"[HTTPConnectionHelper append(byte[],int,int)] ("
+ len
+ " bytes appended to "
+ (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION ? " current ByteArrayEncoder"
: " socket") + ")", 101);
}
return this;
}
/**
* write a string to the current HTTP request.
*
* @param str
* The string to write
* @return The current HTTPConnectionHelper
* @throws JUploadIOException
* If any problem occurs during the writing operation.
* @see #append(byte[])
*/
public synchronized HTTPConnectionHelper append(String str)
throws JUploadIOException {
this.uploadPolicy.displayDebug("[HTTPConnectionHelper append] " + str,
70);
if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
this.byteArrayEncoder.append(str);
} else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
ByteArrayEncoder bae = new ByteArrayEncoderHTTP(this.uploadPolicy,
this.byteArrayEncoder.getBoundary(), this.byteArrayEncoder
.getEncoding());
bae.append(str);
bae.close();
this.append(bae);
}
return this;
}
/**
* Appends a string to the current HTTP request.
*
* @param bae
* The ByteArrayEncoder to write. It is expected to be correctly
* encoded. That is: it is up to the caller to check that its
* encoding is the same as the current HTTP request encoding.
* @return The current HTTPConnectionHelper
* @throws JUploadIOException
* If any problem occurs during the writing operation.
* @see #append(byte[])
*/
public synchronized HTTPConnectionHelper append(ByteArrayEncoder bae)
throws JUploadIOException {
this.uploadPolicy.displayDebug("[HTTPConnectionHelper append] "
+ bae.getString(), 70);
return this.append(bae.getEncodedByteArray());
}
/**
* Read the response of the server. This method delegates the work to the
* HTTPInputStreamReader. handles the chunk HTTP response.
*
* @return The HTTP status. Should be 200, when everything is right.
* @throws JUploadException
*/
public synchronized int readHttpResponse() throws JUploadException {
// This method expects that the connection is writing data to the
// server.
if (this.connectionStatus != STATUS_WRITING_REQUEST) {
throw new JUploadIOException(
"Bad status of the HttpConnectionHelper in readHttpResponse: "
+ getStatusLabel());
}
this.connectionStatus = STATUS_READING_RESPONSE;
// Let's connect in InputStream to read this server response.
if (this.httpInputStreamReader == null) {
this.httpInputStreamReader = new HTTPInputStreamReader(this,
this.uploadPolicy);
}
// Let's do the job
try {
this.outputStream.flush();
} catch (IOException ioe) {
throw new JUploadIOException("flushing outputStream, in "
+ getClass().getName() + ".readHttpResponse()");
}
this.httpInputStreamReader.readHttpResponse();
if (this.httpInputStreamReader.gotClose) {
// RFC 2868, section 8.1.2.1
dispose();
}
// We got the response
this.connectionStatus = STATUS_CONNECTION_CLOSED;
// FIXME Should close the connection, from time to time...
//
return this.httpInputStreamReader.gethttpStatusCode();
}
// ////////////////////////////////////////////////////////////////////////////////////
// /////////////////////// PRIVATE METHODS
// ////////////////////////////////////////////////////////////////////////////////////
/**
* creating of a new {@link ByteArrayEncoderHTTP}, and initializing of the
* following header items: First line (POST currentProtocol URI), Host,
* Connection, Keep-Alive, Proxy-Connection.
*
* @throws JUploadIOException
*/
private synchronized void initByteArrayEncoder() throws JUploadIOException {
if (this.byteArrayEncoder != null && !this.byteArrayEncoder.isClosed()) {
this.byteArrayEncoder.close();
this.byteArrayEncoder = null;
}
this.byteArrayEncoder = new ByteArrayEncoderHTTP(this.uploadPolicy,
this.boundary);
this.connectionStatus = STATUS_BEFORE_SERVER_CONNECTION;
this.proxy = null;
try {
this.proxy = ProxySelector.getDefault().select(this.url.toURI())
.get(0);
} catch (URISyntaxException e) {
throw new JUploadIOException("Error while managing url "
+ this.url.toExternalForm(), e);
}
this.useProxy = ((this.proxy != null) && (this.proxy.type() != Proxy.Type.DIRECT));
this.useSSL = this.url.getProtocol().equals("https");
// Header: Request line
// Let's clear it. Useful only for chunked uploads.
this.byteArrayEncoder.append(this.method);
this.byteArrayEncoder.append(" ");
this.uploadPolicy.displayDebug("[initByteArrayEncoder] proxy=" + proxy
+ ", proxy.type=" + this.proxy.type() + ", useProxy="
+ useProxy + ", url.host=" + this.url.getHost() + ", url.port="
+ this.url.getPort(), 80);
if (this.useProxy && (!this.useSSL)) {
// with a proxy we need the absolute URL, but only if not
// using SSL. (with SSL, we first use the proxy CONNECT method,
// and then a plain request.)
this.byteArrayEncoder.append(this.url.getProtocol()).append("://")
.append(this.url.getHost());
// TODO port in proxy mode: to be tested !
if (this.url.getPort() > 0) {
this.byteArrayEncoder.append(":").append(
Integer.toString(this.url.getPort()));
}
}
this.byteArrayEncoder.append(this.url.getPath());
// Append the query params.
// TODO: This probably can be removed as we now have everything in POST
// data. However in order to be
// backwards-compatible, it stays here for now. So we now provide
// *both* GET and POST params.
if (null != this.url.getQuery() && !"".equals(this.url.getQuery()))
this.byteArrayEncoder.append("?").append(this.url.getQuery());
this.byteArrayEncoder.append(" ").append(
this.uploadPolicy.getServerProtocol()).append("\r\n");
// Header: General
this.byteArrayEncoder.append("Host: ").append(this.url.getHost())
.append("\r\nAccept: */*\r\n");
// We do not want gzipped or compressed responses, so we must
// specify that here (RFC 2616, Section 14.3)
this.byteArrayEncoder.append("Accept-Encoding: identity\r\n");
// Seems like the Keep-alive doesn't work properly, at least on my
// local dev (Etienne).
if (!this.uploadPolicy.getAllowHttpPersistent()) {
this.byteArrayEncoder.append("Connection: close\r\n");
} else {
if (!this.bChunkEnabled
|| this.bLastChunk
|| this.useProxy
|| !this.uploadPolicy.getServerProtocol()
.equals("HTTP/1.1")) { // RFC 2086, section 19.7.1
this.byteArrayEncoder.append("Connection: close\r\n");
} else {
this.byteArrayEncoder.append("Keep-Alive: 300\r\n");
if (this.useProxy)
this.byteArrayEncoder
.append("Proxy-Connection: keep-alive\r\n");
else
this.byteArrayEncoder.append("Connection: keep-alive\r\n");
}
}
// Get specific headers for this upload.
this.uploadPolicy.onAppendHeader(this.byteArrayEncoder);
}
/**
* Indicates whether the current socket should be reused ... if any.
*/
private boolean isKeepAlive() {
if (this.socket == null) {
return false;
} else if (!this.uploadPolicy.getAllowHttpPersistent()) {
return false;
} else {
if (!this.bChunkEnabled
|| this.bLastChunk
|| this.useProxy
|| !this.uploadPolicy.getServerProtocol()
.equals("HTTP/1.1")) { // RFC 2086, section 19.7.1
return false;
} else {
return true;
}
}
}
/**
* Construction of a random boundary, to separate the uploaded files, in the
* HTTP upload request.
*
* @return The calculated boundary.
*/
private final String calculateRandomBoundary() {
StringBuffer sbRan = new StringBuffer(11);
sbRan.append("-----------------------------");
String alphaNum = "1234567890abcdefghijklmnopqrstuvwxyz";
int num;
for (int i = 0; i < 11; i++) {
num = (int) (Math.random() * (alphaNum.length() - 1));
sbRan.append(alphaNum.charAt(num));
}
return sbRan.toString();
}
// ////////////////////////////////////////////////////////////////////////////////////
// /////////////////// OVERRIDE OF OutputStream METHODS
// ////////////////////////////////////////////////////////////////////////////////////
/** {@inheritDoc} */
@Override
public void write(int b) throws IOException {
try {
append(b);
} catch (JUploadIOException e) {
// Hum, HTTPConnectionHelper catch IOException, and throws a
// JUploadIOException. Now we get the cause, that is the original
// IOException. Not optimized.
if (e.getCause() == null) {
// This should not happen
throw new IOException();
} else if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
} else {
// Hum, can something like an OutOfMemory. We must throw it.
throw new IOException(e.getCause().getClass().getName() + ": "
+ e.getCause().getMessage());
}
}
}
/** {@inheritDoc} */
@Override
public void write(byte[] b, int off, int len) throws IOException {
try {
append(b, off, len);
} catch (JUploadIOException e) {
// Hum, HTTPConnectionHelper catch IOException, and throws a
// JUploadIOException. Now we get the cause, that is the original
// IOException. Not optimized.
if (e.getCause() == null) {
// This should not happen
throw new IOException();
} else if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
} else {
// Hum, can something like an OutOfMemory. We must throw it.
throw new IOException(e.getCause().getClass().getName() + ": "
+ e.getCause().getMessage());
}
}
}
/** {@inheritDoc} */
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
/**
* This method is the override of {@link OutputStream#close()} one. It may
* not been called. You must use the {@link #sendRequest()} or
* {@link #readHttpResponse()} methods instead.
*
* @see java.io.OutputStream#close()
*/
@Override
public void close() throws IOException {
throw new IOException(
"Forbidden action (HTTPConnectionHelper.close()). Please use the "
+ getClass().getName() + ".sendRequest() method");
}
/**
* Flushes the output stream. Useful only when the HTTPConnectionHelper is
* writing to the socket toward the server, that is when the status is:
* STATUS_WRITING_REQUEST.
*
* @see java.io.OutputStream#flush()
*/
@Override
public void flush() throws IOException {
if (this.connectionStatus == STATUS_WRITING_REQUEST) {
this.outputStream.flush();
} else {
throw new IOException("Wrong status in " + getClass().getName()
+ ".flush method: " + getStatusLabel());
}
}
}