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