Home | History | Annotate | Download | only in obex
      1 /*
      2  * Copyright (c) 2008-2009, Motorola, Inc.
      3  *
      4  * All rights reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions are met:
      8  *
      9  * - Redistributions of source code must retain the above copyright notice,
     10  * this list of conditions and the following disclaimer.
     11  *
     12  * - Redistributions in binary form must reproduce the above copyright notice,
     13  * this list of conditions and the following disclaimer in the documentation
     14  * and/or other materials provided with the distribution.
     15  *
     16  * - Neither the name of the Motorola, Inc. nor the names of its contributors
     17  * may be used to endorse or promote products derived from this software
     18  * without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
     24  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     30  * POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package javax.obex;
     34 
     35 import java.io.IOException;
     36 import java.io.InputStream;
     37 import java.io.DataInputStream;
     38 import java.io.OutputStream;
     39 import java.io.DataOutputStream;
     40 import java.io.ByteArrayOutputStream;
     41 
     42 /**
     43  * This class implements the Operation interface for server side connections.
     44  * <P>
     45  * <STRONG>Request Codes</STRONG> There are four different request codes that
     46  * are in this class. 0x02 is a PUT request that signals that the request is not
     47  * complete and requires an additional OBEX packet. 0x82 is a PUT request that
     48  * says that request is complete. In this case, the server can begin sending the
     49  * response. The 0x03 is a GET request that signals that the request is not
     50  * finished. When the server receives a 0x83, the client is signaling the server
     51  * that it is done with its request. TODO: Extend the ClientOperation and reuse
     52  * the methods defined TODO: in that class.
     53  * @hide
     54  */
     55 public final class ServerOperation implements Operation, BaseStream {
     56 
     57     public boolean isAborted;
     58 
     59     public HeaderSet requestHeader;
     60 
     61     public HeaderSet replyHeader;
     62 
     63     public boolean finalBitSet;
     64 
     65     private InputStream mInput;
     66 
     67     private ServerSession mParent;
     68 
     69     private int mMaxPacketLength;
     70 
     71     private int mResponseSize;
     72 
     73     private boolean mClosed;
     74 
     75     private boolean mGetOperation;
     76 
     77     private PrivateInputStream mPrivateInput;
     78 
     79     private PrivateOutputStream mPrivateOutput;
     80 
     81     private boolean mPrivateOutputOpen;
     82 
     83     private String mExceptionString;
     84 
     85     private ServerRequestHandler mListener;
     86 
     87     private boolean mRequestFinished;
     88 
     89     private boolean mHasBody;
     90 
     91     private boolean mSendBodyHeader = true;
     92 
     93     /**
     94      * Creates new ServerOperation
     95      * @param p the parent that created this object
     96      * @param in the input stream to read from
     97      * @param out the output stream to write to
     98      * @param request the initial request that was received from the client
     99      * @param maxSize the max packet size that the client will accept
    100      * @param listen the listener that is responding to the request
    101      * @throws IOException if an IO error occurs
    102      */
    103     public ServerOperation(ServerSession p, InputStream in, int request, int maxSize,
    104             ServerRequestHandler listen) throws IOException {
    105 
    106         isAborted = false;
    107         mParent = p;
    108         mInput = in;
    109         mMaxPacketLength = maxSize;
    110         mClosed = false;
    111         requestHeader = new HeaderSet();
    112         replyHeader = new HeaderSet();
    113         mPrivateInput = new PrivateInputStream(this);
    114         mResponseSize = 3;
    115         mListener = listen;
    116         mRequestFinished = false;
    117         mPrivateOutputOpen = false;
    118         mHasBody = false;
    119         int bytesReceived;
    120 
    121         /*
    122          * Determine if this is a PUT request
    123          */
    124         if ((request == 0x02) || (request == 0x82)) {
    125             /*
    126              * It is a PUT request.
    127              */
    128             mGetOperation = false;
    129 
    130             /*
    131              * Determine if the final bit is set
    132              */
    133             if ((request & 0x80) == 0) {
    134                 finalBitSet = false;
    135             } else {
    136                 finalBitSet = true;
    137                 mRequestFinished = true;
    138             }
    139         } else if ((request == 0x03) || (request == 0x83)) {
    140             /*
    141              * It is a GET request.
    142              */
    143             mGetOperation = true;
    144 
    145             // For Get request, final bit set is decided by server side logic
    146             finalBitSet = false;
    147 
    148             if (request == 0x83) {
    149                 mRequestFinished = true;
    150             }
    151         } else {
    152             throw new IOException("ServerOperation can not handle such request");
    153         }
    154 
    155         int length = in.read();
    156         length = (length << 8) + in.read();
    157 
    158         /*
    159          * Determine if the packet length is larger than this device can receive
    160          */
    161         if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
    162             mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
    163             throw new IOException("Packet received was too large");
    164         }
    165 
    166         /*
    167          * Determine if any headers were sent in the initial request
    168          */
    169         if (length > 3) {
    170             byte[] data = new byte[length - 3];
    171             bytesReceived = in.read(data);
    172 
    173             while (bytesReceived != data.length) {
    174                 bytesReceived += in.read(data, bytesReceived, data.length - bytesReceived);
    175             }
    176 
    177             byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
    178 
    179             if (body != null) {
    180                 mHasBody = true;
    181             }
    182 
    183             if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
    184                 mListener.setConnectionId(ObexHelper.convertToLong(requestHeader.mConnectionID));
    185             } else {
    186                 mListener.setConnectionId(1);
    187             }
    188 
    189             if (requestHeader.mAuthResp != null) {
    190                 if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
    191                     mExceptionString = "Authentication Failed";
    192                     mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
    193                     mClosed = true;
    194                     requestHeader.mAuthResp = null;
    195                     return;
    196                 }
    197             }
    198 
    199             if (requestHeader.mAuthChall != null) {
    200                 mParent.handleAuthChall(requestHeader);
    201                 // send the  authResp to the client
    202                 replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
    203                 System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
    204                         replyHeader.mAuthResp.length);
    205                 requestHeader.mAuthResp = null;
    206                 requestHeader.mAuthChall = null;
    207 
    208             }
    209 
    210             if (body != null) {
    211                 mPrivateInput.writeBytes(body, 1);
    212             } else {
    213                 while ((!mGetOperation) && (!finalBitSet)) {
    214                     sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
    215                     if (mPrivateInput.available() > 0) {
    216                         break;
    217                     }
    218                 }
    219             }
    220         }
    221 
    222         while ((!mGetOperation) && (!finalBitSet) && (mPrivateInput.available() == 0)) {
    223             sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
    224             if (mPrivateInput.available() > 0) {
    225                 break;
    226             }
    227         }
    228 
    229         // wait for get request finished !!!!
    230         while (mGetOperation && !mRequestFinished) {
    231             sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
    232         }
    233     }
    234 
    235     public boolean isValidBody() {
    236         return mHasBody;
    237     }
    238 
    239     /**
    240      * Determines if the operation should continue or should wait. If it should
    241      * continue, this method will continue the operation.
    242      * @param sendEmpty if <code>true</code> then this will continue the
    243      *        operation even if no headers will be sent; if <code>false</code>
    244      *        then this method will only continue the operation if there are
    245      *        headers to send
    246      * @param inStream if<code>true</code> the stream is input stream, otherwise
    247      *        output stream
    248      * @return <code>true</code> if the operation was completed;
    249      *         <code>false</code> if no operation took place
    250      */
    251     public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream)
    252             throws IOException {
    253         if (!mGetOperation) {
    254             if (!finalBitSet) {
    255                 if (sendEmpty) {
    256                     sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
    257                     return true;
    258                 } else {
    259                     if ((mResponseSize > 3) || (mPrivateOutput.size() > 0)) {
    260                         sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
    261                         return true;
    262                     } else {
    263                         return false;
    264                     }
    265                 }
    266             } else {
    267                 return false;
    268             }
    269         } else {
    270             sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
    271             return true;
    272         }
    273     }
    274 
    275     /**
    276      * Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it
    277      * will wait for a response from the client before ending.
    278      * @param type the response code to send back to the client
    279      * @return <code>true</code> if the final bit was not set on the reply;
    280      *         <code>false</code> if no reply was received because the operation
    281      *         ended, an abort was received, or the final bit was set in the
    282      *         reply
    283      * @throws IOException if an IO error occurs
    284      */
    285     public synchronized boolean sendReply(int type) throws IOException {
    286         ByteArrayOutputStream out = new ByteArrayOutputStream();
    287         int bytesReceived;
    288 
    289         long id = mListener.getConnectionId();
    290         if (id == -1) {
    291             replyHeader.mConnectionID = null;
    292         } else {
    293             replyHeader.mConnectionID = ObexHelper.convertToByteArray(id);
    294         }
    295 
    296         byte[] headerArray = ObexHelper.createHeader(replyHeader, true);
    297         int bodyLength = -1;
    298         int orginalBodyLength = -1;
    299 
    300         if (mPrivateOutput != null) {
    301             bodyLength = mPrivateOutput.size();
    302             orginalBodyLength = bodyLength;
    303         }
    304 
    305         if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length) > mMaxPacketLength) {
    306 
    307             int end = 0;
    308             int start = 0;
    309 
    310             while (end != headerArray.length) {
    311                 end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketLength
    312                         - ObexHelper.BASE_PACKET_LENGTH);
    313                 if (end == -1) {
    314 
    315                     mClosed = true;
    316 
    317                     if (mPrivateInput != null) {
    318                         mPrivateInput.close();
    319                     }
    320 
    321                     if (mPrivateOutput != null) {
    322                         mPrivateOutput.close();
    323                     }
    324                     mParent.sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
    325                     throw new IOException("OBEX Packet exceeds max packet size");
    326                 }
    327                 byte[] sendHeader = new byte[end - start];
    328                 System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length);
    329 
    330                 mParent.sendResponse(type, sendHeader);
    331                 start = end;
    332             }
    333 
    334             if (bodyLength > 0) {
    335                 return true;
    336             } else {
    337                 return false;
    338             }
    339 
    340         } else {
    341             out.write(headerArray);
    342         }
    343 
    344         // For Get operation: if response code is OBEX_HTTP_OK, then this is the
    345         // last packet; so set finalBitSet to true.
    346         if (mGetOperation && type == ResponseCodes.OBEX_HTTP_OK) {
    347             finalBitSet = true;
    348         }
    349 
    350         if ((finalBitSet) || (headerArray.length < (mMaxPacketLength - 20))) {
    351             if (bodyLength > 0) {
    352                 /*
    353                  * Determine if I can send the whole body or just part of
    354                  * the body.  Remember that there is the 3 bytes for the
    355                  * response message and 3 bytes for the header ID and length
    356                  */
    357                 if (bodyLength > (mMaxPacketLength - headerArray.length - 6)) {
    358                     bodyLength = mMaxPacketLength - headerArray.length - 6;
    359                 }
    360 
    361                 byte[] body = mPrivateOutput.readBytes(bodyLength);
    362 
    363                 /*
    364                  * Since this is a put request if the final bit is set or
    365                  * the output stream is closed we need to send the 0x49
    366                  * (End of Body) otherwise, we need to send 0x48 (Body)
    367                  */
    368                 if ((finalBitSet) || (mPrivateOutput.isClosed())) {
    369                     if(mSendBodyHeader == true) {
    370                         out.write(0x49);
    371                         bodyLength += 3;
    372                         out.write((byte)(bodyLength >> 8));
    373                         out.write((byte)bodyLength);
    374                         out.write(body);
    375                     }
    376                 } else {
    377                     if(mSendBodyHeader == true) {
    378                     out.write(0x48);
    379                     bodyLength += 3;
    380                     out.write((byte)(bodyLength >> 8));
    381                     out.write((byte)bodyLength);
    382                     out.write(body);
    383                     }
    384                 }
    385 
    386             }
    387         }
    388 
    389         if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) {
    390             if(mSendBodyHeader == true) {
    391                 out.write(0x49);
    392                 orginalBodyLength = 3;
    393                 out.write((byte)(orginalBodyLength >> 8));
    394                 out.write((byte)orginalBodyLength);
    395             }
    396         }
    397 
    398         mResponseSize = 3;
    399         mParent.sendResponse(type, out.toByteArray());
    400 
    401         if (type == ResponseCodes.OBEX_HTTP_CONTINUE) {
    402             int headerID = mInput.read();
    403             int length = mInput.read();
    404             length = (length << 8) + mInput.read();
    405             if ((headerID != ObexHelper.OBEX_OPCODE_PUT)
    406                     && (headerID != ObexHelper.OBEX_OPCODE_PUT_FINAL)
    407                     && (headerID != ObexHelper.OBEX_OPCODE_GET)
    408                     && (headerID != ObexHelper.OBEX_OPCODE_GET_FINAL)) {
    409 
    410                 if (length > 3) {
    411                     byte[] temp = new byte[length - 3];
    412                     // First three bytes already read, compensating for this
    413                     bytesReceived = mInput.read(temp);
    414 
    415                     while (bytesReceived != temp.length) {
    416                         bytesReceived += mInput.read(temp, bytesReceived,
    417                                 temp.length - bytesReceived);
    418                     }
    419                 }
    420 
    421                 /*
    422                  * Determine if an ABORT was sent as the reply
    423                  */
    424                 if (headerID == ObexHelper.OBEX_OPCODE_ABORT) {
    425                     mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null);
    426                     mClosed = true;
    427                     isAborted = true;
    428                     mExceptionString = "Abort Received";
    429                     throw new IOException("Abort Received");
    430                 } else {
    431                     mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null);
    432                     mClosed = true;
    433                     mExceptionString = "Bad Request Received";
    434                     throw new IOException("Bad Request Received");
    435                 }
    436             } else {
    437 
    438                 if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
    439                     finalBitSet = true;
    440                 } else if (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL) {
    441                     mRequestFinished = true;
    442                 }
    443 
    444                 /*
    445                  * Determine if the packet length is larger then this device can receive
    446                  */
    447                 if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
    448                     mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
    449                     throw new IOException("Packet received was too large");
    450                 }
    451 
    452                 /*
    453                  * Determine if any headers were sent in the initial request
    454                  */
    455                 if (length > 3) {
    456                     byte[] data = new byte[length - 3];
    457                     bytesReceived = mInput.read(data);
    458 
    459                     while (bytesReceived != data.length) {
    460                         bytesReceived += mInput.read(data, bytesReceived, data.length
    461                                 - bytesReceived);
    462                     }
    463                     byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
    464                     if (body != null) {
    465                         mHasBody = true;
    466                     }
    467                     if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
    468                         mListener.setConnectionId(ObexHelper
    469                                 .convertToLong(requestHeader.mConnectionID));
    470                     } else {
    471                         mListener.setConnectionId(1);
    472                     }
    473 
    474                     if (requestHeader.mAuthResp != null) {
    475                         if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
    476                             mExceptionString = "Authentication Failed";
    477                             mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
    478                             mClosed = true;
    479                             requestHeader.mAuthResp = null;
    480                             return false;
    481                         }
    482                         requestHeader.mAuthResp = null;
    483                     }
    484 
    485                     if (requestHeader.mAuthChall != null) {
    486                         mParent.handleAuthChall(requestHeader);
    487                         // send the auhtResp to the client
    488                         replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
    489                         System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
    490                                 replyHeader.mAuthResp.length);
    491                         requestHeader.mAuthResp = null;
    492                         requestHeader.mAuthChall = null;
    493                     }
    494 
    495                     if (body != null) {
    496                         mPrivateInput.writeBytes(body, 1);
    497                     }
    498                 }
    499             }
    500             return true;
    501         } else {
    502             return false;
    503         }
    504     }
    505 
    506     /**
    507      * Sends an ABORT message to the server. By calling this method, the
    508      * corresponding input and output streams will be closed along with this
    509      * object.
    510      * @throws IOException if the transaction has already ended or if an OBEX
    511      *         server called this method
    512      */
    513     public void abort() throws IOException {
    514         throw new IOException("Called from a server");
    515     }
    516 
    517     /**
    518      * Returns the headers that have been received during the operation.
    519      * Modifying the object returned has no effect on the headers that are sent
    520      * or retrieved.
    521      * @return the headers received during this <code>Operation</code>
    522      * @throws IOException if this <code>Operation</code> has been closed
    523      */
    524     public HeaderSet getReceivedHeader() throws IOException {
    525         ensureOpen();
    526         return requestHeader;
    527     }
    528 
    529     /**
    530      * Specifies the headers that should be sent in the next OBEX message that
    531      * is sent.
    532      * @param headers the headers to send in the next message
    533      * @throws IOException if this <code>Operation</code> has been closed or the
    534      *         transaction has ended and no further messages will be exchanged
    535      * @throws IllegalArgumentException if <code>headers</code> was not created
    536      *         by a call to <code>ServerRequestHandler.createHeaderSet()</code>
    537      */
    538     public void sendHeaders(HeaderSet headers) throws IOException {
    539         ensureOpen();
    540 
    541         if (headers == null) {
    542             throw new IOException("Headers may not be null");
    543         }
    544 
    545         int[] headerList = headers.getHeaderList();
    546         if (headerList != null) {
    547             for (int i = 0; i < headerList.length; i++) {
    548                 replyHeader.setHeader(headerList[i], headers.getHeader(headerList[i]));
    549             }
    550 
    551         }
    552     }
    553 
    554     /**
    555      * Retrieves the response code retrieved from the server. Response codes are
    556      * defined in the <code>ResponseCodes</code> interface.
    557      * @return the response code retrieved from the server
    558      * @throws IOException if an error occurred in the transport layer during
    559      *         the transaction; if this method is called on a
    560      *         <code>HeaderSet</code> object created by calling
    561      *         <code>createHeaderSet</code> in a <code>ClientSession</code>
    562      *         object; if this is called from a server
    563      */
    564     public int getResponseCode() throws IOException {
    565         throw new IOException("Called from a server");
    566     }
    567 
    568     /**
    569      * Always returns <code>null</code>
    570      * @return <code>null</code>
    571      */
    572     public String getEncoding() {
    573         return null;
    574     }
    575 
    576     /**
    577      * Returns the type of content that the resource connected to is providing.
    578      * E.g. if the connection is via HTTP, then the value of the content-type
    579      * header field is returned.
    580      * @return the content type of the resource that the URL references, or
    581      *         <code>null</code> if not known
    582      */
    583     public String getType() {
    584         try {
    585             return (String)requestHeader.getHeader(HeaderSet.TYPE);
    586         } catch (IOException e) {
    587             return null;
    588         }
    589     }
    590 
    591     /**
    592      * Returns the length of the content which is being provided. E.g. if the
    593      * connection is via HTTP, then the value of the content-length header field
    594      * is returned.
    595      * @return the content length of the resource that this connection's URL
    596      *         references, or -1 if the content length is not known
    597      */
    598     public long getLength() {
    599         try {
    600             Long temp = (Long)requestHeader.getHeader(HeaderSet.LENGTH);
    601 
    602             if (temp == null) {
    603                 return -1;
    604             } else {
    605                 return temp.longValue();
    606             }
    607         } catch (IOException e) {
    608             return -1;
    609         }
    610     }
    611 
    612     public int getMaxPacketSize() {
    613         return mMaxPacketLength - 6 - getHeaderLength();
    614     }
    615 
    616     public int getHeaderLength() {
    617         long id = mListener.getConnectionId();
    618         if (id == -1) {
    619             replyHeader.mConnectionID = null;
    620         } else {
    621             replyHeader.mConnectionID = ObexHelper.convertToByteArray(id);
    622         }
    623 
    624         byte[] headerArray = ObexHelper.createHeader(replyHeader, false);
    625 
    626         return headerArray.length;
    627     }
    628 
    629     /**
    630      * Open and return an input stream for a connection.
    631      * @return an input stream
    632      * @throws IOException if an I/O error occurs
    633      */
    634     public InputStream openInputStream() throws IOException {
    635         ensureOpen();
    636         return mPrivateInput;
    637     }
    638 
    639     /**
    640      * Open and return a data input stream for a connection.
    641      * @return an input stream
    642      * @throws IOException if an I/O error occurs
    643      */
    644     public DataInputStream openDataInputStream() throws IOException {
    645         return new DataInputStream(openInputStream());
    646     }
    647 
    648     /**
    649      * Open and return an output stream for a connection.
    650      * @return an output stream
    651      * @throws IOException if an I/O error occurs
    652      */
    653     public OutputStream openOutputStream() throws IOException {
    654         ensureOpen();
    655 
    656         if (mPrivateOutputOpen) {
    657             throw new IOException("no more input streams available, stream already opened");
    658         }
    659 
    660         if (!mRequestFinished) {
    661             throw new IOException("no  output streams available ,request not finished");
    662         }
    663 
    664         if (mPrivateOutput == null) {
    665             mPrivateOutput = new PrivateOutputStream(this, getMaxPacketSize());
    666         }
    667         mPrivateOutputOpen = true;
    668         return mPrivateOutput;
    669     }
    670 
    671     /**
    672      * Open and return a data output stream for a connection.
    673      * @return an output stream
    674      * @throws IOException if an I/O error occurs
    675      */
    676     public DataOutputStream openDataOutputStream() throws IOException {
    677         return new DataOutputStream(openOutputStream());
    678     }
    679 
    680     /**
    681      * Closes the connection and ends the transaction
    682      * @throws IOException if the operation has already ended or is closed
    683      */
    684     public void close() throws IOException {
    685         ensureOpen();
    686         mClosed = true;
    687     }
    688 
    689     /**
    690      * Verifies that the connection is open and no exceptions should be thrown.
    691      * @throws IOException if an exception needs to be thrown
    692      */
    693     public void ensureOpen() throws IOException {
    694         if (mExceptionString != null) {
    695             throw new IOException(mExceptionString);
    696         }
    697         if (mClosed) {
    698             throw new IOException("Operation has already ended");
    699         }
    700     }
    701 
    702     /**
    703      * Verifies that additional information may be sent. In other words, the
    704      * operation is not done.
    705      * <P>
    706      * Included to implement the BaseStream interface only. It does not do
    707      * anything on the server side since the operation of the Operation object
    708      * is not done until after the handler returns from its method.
    709      * @throws IOException if the operation is completed
    710      */
    711     public void ensureNotDone() throws IOException {
    712     }
    713 
    714     /**
    715      * Called when the output or input stream is closed. It does not do anything
    716      * on the server side since the operation of the Operation object is not
    717      * done until after the handler returns from its method.
    718      * @param inStream <code>true</code> if the input stream is closed;
    719      *        <code>false</code> if the output stream is closed
    720      * @throws IOException if an IO error occurs
    721      */
    722     public void streamClosed(boolean inStream) throws IOException {
    723 
    724     }
    725 
    726     public void noBodyHeader(){
    727         mSendBodyHeader = false;
    728     }
    729 }
    730