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
33package javax.obex;
34
35import java.io.IOException;
36import java.io.InputStream;
37import java.io.DataInputStream;
38import java.io.OutputStream;
39import java.io.DataOutputStream;
40import 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 */
55public 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