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 } 713