OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / frameworks / base / obex / javax / obex / ServerOperation.java
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     /**
92      * Creates new ServerOperation
93      * @param p the parent that created this object
94      * @param in the input stream to read from
95      * @param out the output stream to write to
96      * @param request the initial request that was received from the client
97      * @param maxSize the max packet size that the client will accept
98      * @param listen the listener that is responding to the request
99      * @throws IOException if an IO error occurs
100      */
101     public ServerOperation(ServerSession p, InputStream in, int request, int maxSize,
102             ServerRequestHandler listen) throws IOException {
103
104         isAborted = false;
105         mParent = p;
106         mInput = in;
107         mMaxPacketLength = maxSize;
108         mClosed = false;
109         requestHeader = new HeaderSet();
110         replyHeader = new HeaderSet();
111         mPrivateInput = new PrivateInputStream(this);
112         mResponseSize = 3;
113         mListener = listen;
114         mRequestFinished = false;
115         mPrivateOutputOpen = false;
116         mHasBody = false;
117         int bytesReceived;
118
119         /*
120          * Determine if this is a PUT request
121          */
122         if ((request == 0x02) || (request == 0x82)) {
123             /*
124              * It is a PUT request.
125              */
126             mGetOperation = false;
127
128             /*
129              * Determine if the final bit is set
130              */
131             if ((request & 0x80) == 0) {
132                 finalBitSet = false;
133             } else {
134                 finalBitSet = true;
135                 mRequestFinished = true;
136             }
137         } else if ((request == 0x03) || (request == 0x83)) {
138             /*
139              * It is a GET request.
140              */
141             mGetOperation = true;
142
143             // For Get request, final bit set is decided by server side logic
144             finalBitSet = false;
145
146             if (request == 0x83) {
147                 mRequestFinished = true;
148             }
149         } else {
150             throw new IOException("ServerOperation can not handle such request");
151         }
152
153         int length = in.read();
154         length = (length << 8) + in.read();
155
156         /*
157          * Determine if the packet length is larger than this device can receive
158          */
159         if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
160             mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
161             throw new IOException("Packet received was too large");
162         }
163
164         /*
165          * Determine if any headers were sent in the initial request
166          */
167         if (length > 3) {
168             byte[] data = new byte[length - 3];
169             bytesReceived = in.read(data);
170
171             while (bytesReceived != data.length) {
172                 bytesReceived += in.read(data, bytesReceived, data.length - bytesReceived);
173             }
174
175             byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
176
177             if (body != null) {
178                 mHasBody = true;
179             }
180
181             if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
182                 mListener.setConnectionId(ObexHelper.convertToLong(requestHeader.mConnectionID));
183             } else {
184                 mListener.setConnectionId(1);
185             }
186
187             if (requestHeader.mAuthResp != null) {
188                 if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
189                     mExceptionString = "Authentication Failed";
190                     mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
191                     mClosed = true;
192                     requestHeader.mAuthResp = null;
193                     return;
194                 }
195             }
196
197             if (requestHeader.mAuthChall != null) {
198                 mParent.handleAuthChall(requestHeader);
199                 // send the  authResp to the client
200                 replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
201                 System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
202                         replyHeader.mAuthResp.length);
203                 requestHeader.mAuthResp = null;
204                 requestHeader.mAuthChall = null;
205
206             }
207
208             if (body != null) {
209                 mPrivateInput.writeBytes(body, 1);
210             } else {
211                 while ((!mGetOperation) && (!finalBitSet)) {
212                     sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
213                     if (mPrivateInput.available() > 0) {
214                         break;
215                     }
216                 }
217             }
218         }
219
220         while ((!mGetOperation) && (!finalBitSet) && (mPrivateInput.available() == 0)) {
221             sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
222             if (mPrivateInput.available() > 0) {
223                 break;
224             }
225         }
226
227         // wait for get request finished !!!!
228         while (mGetOperation && !mRequestFinished) {
229             sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
230         }
231     }
232
233     public boolean isValidBody() {
234         return mHasBody;
235     }
236
237     /**
238      * Determines if the operation should continue or should wait. If it should
239      * continue, this method will continue the operation.
240      * @param sendEmpty if <code>true</code> then this will continue the
241      *        operation even if no headers will be sent; if <code>false</code>
242      *        then this method will only continue the operation if there are
243      *        headers to send
244      * @param inStream if<code>true</code> the stream is input stream, otherwise
245      *        output stream
246      * @return <code>true</code> if the operation was completed;
247      *         <code>false</code> if no operation took place
248      */
249     public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream)
250             throws IOException {
251         if (!mGetOperation) {
252             if (!finalBitSet) {
253                 if (sendEmpty) {
254                     sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
255                     return true;
256                 } else {
257                     if ((mResponseSize > 3) || (mPrivateOutput.size() > 0)) {
258                         sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
259                         return true;
260                     } else {
261                         return false;
262                     }
263                 }
264             } else {
265                 return false;
266             }
267         } else {
268             sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
269             return true;
270         }
271     }
272
273     /**
274      * Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it
275      * will wait for a response from the client before ending.
276      * @param type the response code to send back to the client
277      * @return <code>true</code> if the final bit was not set on the reply;
278      *         <code>false</code> if no reply was received because the operation
279      *         ended, an abort was received, or the final bit was set in the
280      *         reply
281      * @throws IOException if an IO error occurs
282      */
283     public synchronized boolean sendReply(int type) throws IOException {
284         ByteArrayOutputStream out = new ByteArrayOutputStream();
285         int bytesReceived;
286
287         long id = mListener.getConnectionId();
288         if (id == -1) {
289             replyHeader.mConnectionID = null;
290         } else {
291             replyHeader.mConnectionID = ObexHelper.convertToByteArray(id);
292         }
293
294         byte[] headerArray = ObexHelper.createHeader(replyHeader, true);
295         int bodyLength = -1;
296         int orginalBodyLength = -1;
297
298         if (mPrivateOutput != null) {
299             bodyLength = mPrivateOutput.size();
300             orginalBodyLength = bodyLength;
301         }
302
303         if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length) > mMaxPacketLength) {
304
305             int end = 0;
306             int start = 0;
307
308             while (end != headerArray.length) {
309                 end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketLength
310                         - ObexHelper.BASE_PACKET_LENGTH);
311                 if (end == -1) {
312
313                     mClosed = true;
314
315                     if (mPrivateInput != null) {
316                         mPrivateInput.close();
317                     }
318
319                     if (mPrivateOutput != null) {
320                         mPrivateOutput.close();
321                     }
322                     mParent.sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
323                     throw new IOException("OBEX Packet exceeds max packet size");
324                 }
325                 byte[] sendHeader = new byte[end - start];
326                 System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length);
327
328                 mParent.sendResponse(type, sendHeader);
329                 start = end;
330             }
331
332             if (bodyLength > 0) {
333                 return true;
334             } else {
335                 return false;
336             }
337
338         } else {
339             out.write(headerArray);
340         }
341
342         // For Get operation: if response code is OBEX_HTTP_OK, then this is the
343         // last packet; so set finalBitSet to true.
344         if (mGetOperation && type == ResponseCodes.OBEX_HTTP_OK) {
345             finalBitSet = true;
346         }
347
348         if ((finalBitSet) || (headerArray.length < (mMaxPacketLength - 20))) {
349             if (bodyLength > 0) {
350                 /*
351                  * Determine if I can send the whole body or just part of
352                  * the body.  Remember that there is the 3 bytes for the
353                  * response message and 3 bytes for the header ID and length
354                  */
355                 if (bodyLength > (mMaxPacketLength - headerArray.length - 6)) {
356                     bodyLength = mMaxPacketLength - headerArray.length - 6;
357                 }
358
359                 byte[] body = mPrivateOutput.readBytes(bodyLength);
360
361                 /*
362                  * Since this is a put request if the final bit is set or
363                  * the output stream is closed we need to send the 0x49
364                  * (End of Body) otherwise, we need to send 0x48 (Body)
365                  */
366                 if ((finalBitSet) || (mPrivateOutput.isClosed())) {
367                     out.write(0x49);
368                 } else {
369                     out.write(0x48);
370                 }
371
372                 bodyLength += 3;
373                 out.write((byte)(bodyLength >> 8));
374                 out.write((byte)bodyLength);
375                 out.write(body);
376             }
377         }
378
379         if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) {
380             out.write(0x49);
381             orginalBodyLength = 3;
382             out.write((byte)(orginalBodyLength >> 8));
383             out.write((byte)orginalBodyLength);
384
385         }
386
387         mResponseSize = 3;
388         mParent.sendResponse(type, out.toByteArray());
389
390         if (type == ResponseCodes.OBEX_HTTP_CONTINUE) {
391             int headerID = mInput.read();
392             int length = mInput.read();
393             length = (length << 8) + mInput.read();
394             if ((headerID != ObexHelper.OBEX_OPCODE_PUT)
395                     && (headerID != ObexHelper.OBEX_OPCODE_PUT_FINAL)
396                     && (headerID != ObexHelper.OBEX_OPCODE_GET)
397                     && (headerID != ObexHelper.OBEX_OPCODE_GET_FINAL)) {
398
399                 if (length > 3) {
400                     byte[] temp = new byte[length];
401                     bytesReceived = mInput.read(temp);
402
403                     while (bytesReceived != length) {
404                         bytesReceived += mInput.read(temp, bytesReceived, length - bytesReceived);
405                     }
406                 }
407
408                 /*
409                  * Determine if an ABORT was sent as the reply
410                  */
411                 if (headerID == ObexHelper.OBEX_OPCODE_ABORT) {
412                     mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null);
413                     mClosed = true;
414                     isAborted = true;
415                     mExceptionString = "Abort Received";
416                     throw new IOException("Abort Received");
417                 } else {
418                     mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null);
419                     mClosed = true;
420                     mExceptionString = "Bad Request Received";
421                     throw new IOException("Bad Request Received");
422                 }
423             } else {
424
425                 if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
426                     finalBitSet = true;
427                 } else if (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL) {
428                     mRequestFinished = true;
429                 }
430
431                 /*
432                  * Determine if the packet length is larger then this device can receive
433                  */
434                 if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
435                     mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
436                     throw new IOException("Packet received was too large");
437                 }
438
439                 /*
440                  * Determine if any headers were sent in the initial request
441                  */
442                 if (length > 3) {
443                     byte[] data = new byte[length - 3];
444                     bytesReceived = mInput.read(data);
445
446                     while (bytesReceived != data.length) {
447                         bytesReceived += mInput.read(data, bytesReceived, data.length
448                                 - bytesReceived);
449                     }
450                     byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
451                     if (body != null) {
452                         mHasBody = true;
453                     }
454                     if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
455                         mListener.setConnectionId(ObexHelper
456                                 .convertToLong(requestHeader.mConnectionID));
457                     } else {
458                         mListener.setConnectionId(1);
459                     }
460
461                     if (requestHeader.mAuthResp != null) {
462                         if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
463                             mExceptionString = "Authentication Failed";
464                             mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
465                             mClosed = true;
466                             requestHeader.mAuthResp = null;
467                             return false;
468                         }
469                         requestHeader.mAuthResp = null;
470                     }
471
472                     if (requestHeader.mAuthChall != null) {
473                         mParent.handleAuthChall(requestHeader);
474                         // send the auhtResp to the client
475                         replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
476                         System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
477                                 replyHeader.mAuthResp.length);
478                         requestHeader.mAuthResp = null;
479                         requestHeader.mAuthChall = null;
480                     }
481
482                     if (body != null) {
483                         mPrivateInput.writeBytes(body, 1);
484                     }
485                 }
486             }
487             return true;
488         } else {
489             return false;
490         }
491     }
492
493     /**
494      * Sends an ABORT message to the server. By calling this method, the
495      * corresponding input and output streams will be closed along with this
496      * object.
497      * @throws IOException if the transaction has already ended or if an OBEX
498      *         server called this method
499      */
500     public void abort() throws IOException {
501         throw new IOException("Called from a server");
502     }
503
504     /**
505      * Returns the headers that have been received during the operation.
506      * Modifying the object returned has no effect on the headers that are sent
507      * or retrieved.
508      * @return the headers received during this <code>Operation</code>
509      * @throws IOException if this <code>Operation</code> has been closed
510      */
511     public HeaderSet getReceivedHeader() throws IOException {
512         ensureOpen();
513         return requestHeader;
514     }
515
516     /**
517      * Specifies the headers that should be sent in the next OBEX message that
518      * is sent.
519      * @param headers the headers to send in the next message
520      * @throws IOException if this <code>Operation</code> has been closed or the
521      *         transaction has ended and no further messages will be exchanged
522      * @throws IllegalArgumentException if <code>headers</code> was not created
523      *         by a call to <code>ServerRequestHandler.createHeaderSet()</code>
524      */
525     public void sendHeaders(HeaderSet headers) throws IOException {
526         ensureOpen();
527
528         if (headers == null) {
529             throw new IOException("Headers may not be null");
530         }
531
532         int[] headerList = headers.getHeaderList();
533         if (headerList != null) {
534             for (int i = 0; i < headerList.length; i++) {
535                 replyHeader.setHeader(headerList[i], headers.getHeader(headerList[i]));
536             }
537
538         }
539     }
540
541     /**
542      * Retrieves the response code retrieved from the server. Response codes are
543      * defined in the <code>ResponseCodes</code> interface.
544      * @return the response code retrieved from the server
545      * @throws IOException if an error occurred in the transport layer during
546      *         the transaction; if this method is called on a
547      *         <code>HeaderSet</code> object created by calling
548      *         <code>createHeaderSet</code> in a <code>ClientSession</code>
549      *         object; if this is called from a server
550      */
551     public int getResponseCode() throws IOException {
552         throw new IOException("Called from a server");
553     }
554
555     /**
556      * Always returns <code>null</code>
557      * @return <code>null</code>
558      */
559     public String getEncoding() {
560         return null;
561     }
562
563     /**
564      * Returns the type of content that the resource connected to is providing.
565      * E.g. if the connection is via HTTP, then the value of the content-type
566      * header field is returned.
567      * @return the content type of the resource that the URL references, or
568      *         <code>null</code> if not known
569      */
570     public String getType() {
571         try {
572             return (String)requestHeader.getHeader(HeaderSet.TYPE);
573         } catch (IOException e) {
574             return null;
575         }
576     }
577
578     /**
579      * Returns the length of the content which is being provided. E.g. if the
580      * connection is via HTTP, then the value of the content-length header field
581      * is returned.
582      * @return the content length of the resource that this connection's URL
583      *         references, or -1 if the content length is not known
584      */
585     public long getLength() {
586         try {
587             Long temp = (Long)requestHeader.getHeader(HeaderSet.LENGTH);
588
589             if (temp == null) {
590                 return -1;
591             } else {
592                 return temp.longValue();
593             }
594         } catch (IOException e) {
595             return -1;
596         }
597     }
598
599     public int getMaxPacketSize() {
600         return mMaxPacketLength - 6 - getHeaderLength();
601     }
602
603     public int getHeaderLength() {
604         long id = mListener.getConnectionId();
605         if (id == -1) {
606             replyHeader.mConnectionID = null;
607         } else {
608             replyHeader.mConnectionID = ObexHelper.convertToByteArray(id);
609         }
610
611         byte[] headerArray = ObexHelper.createHeader(replyHeader, false);
612
613         return headerArray.length;
614     }
615
616     /**
617      * Open and return an input stream for a connection.
618      * @return an input stream
619      * @throws IOException if an I/O error occurs
620      */
621     public InputStream openInputStream() throws IOException {
622         ensureOpen();
623         return mPrivateInput;
624     }
625
626     /**
627      * Open and return a data input stream for a connection.
628      * @return an input stream
629      * @throws IOException if an I/O error occurs
630      */
631     public DataInputStream openDataInputStream() throws IOException {
632         return new DataInputStream(openInputStream());
633     }
634
635     /**
636      * Open and return an output stream for a connection.
637      * @return an output stream
638      * @throws IOException if an I/O error occurs
639      */
640     public OutputStream openOutputStream() throws IOException {
641         ensureOpen();
642
643         if (mPrivateOutputOpen) {
644             throw new IOException("no more input streams available, stream already opened");
645         }
646
647         if (!mRequestFinished) {
648             throw new IOException("no  output streams available ,request not finished");
649         }
650
651         if (mPrivateOutput == null) {
652             mPrivateOutput = new PrivateOutputStream(this, getMaxPacketSize());
653         }
654         mPrivateOutputOpen = true;
655         return mPrivateOutput;
656     }
657
658     /**
659      * Open and return a data output stream for a connection.
660      * @return an output stream
661      * @throws IOException if an I/O error occurs
662      */
663     public DataOutputStream openDataOutputStream() throws IOException {
664         return new DataOutputStream(openOutputStream());
665     }
666
667     /**
668      * Closes the connection and ends the transaction
669      * @throws IOException if the operation has already ended or is closed
670      */
671     public void close() throws IOException {
672         ensureOpen();
673         mClosed = true;
674     }
675
676     /**
677      * Verifies that the connection is open and no exceptions should be thrown.
678      * @throws IOException if an exception needs to be thrown
679      */
680     public void ensureOpen() throws IOException {
681         if (mExceptionString != null) {
682             throw new IOException(mExceptionString);
683         }
684         if (mClosed) {
685             throw new IOException("Operation has already ended");
686         }
687     }
688
689     /**
690      * Verifies that additional information may be sent. In other words, the
691      * operation is not done.
692      * <P>
693      * Included to implement the BaseStream interface only. It does not do
694      * anything on the server side since the operation of the Operation object
695      * is not done until after the handler returns from its method.
696      * @throws IOException if the operation is completed
697      */
698     public void ensureNotDone() throws IOException {
699     }
700
701     /**
702      * Called when the output or input stream is closed. It does not do anything
703      * on the server side since the operation of the Operation object is not
704      * done until after the handler returns from its method.
705      * @param inStream <code>true</code> if the input stream is closed;
706      *        <code>false</code> if the output stream is closed
707      * @throws IOException if an IO error occurs
708      */
709     public void streamClosed(boolean inStream) throws IOException {
710
711     }
712 }