1 /*
   2  * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.xml.internal.messaging.saaj.client.p2p;
  27 
  28 import java.io.*;
  29 import java.lang.reflect.Method;
  30 import java.net.*;
  31 import java.security.*;
  32 import java.util.Iterator;
  33 import java.util.StringTokenizer;
  34 import java.util.logging.Level;
  35 import java.util.logging.Logger;
  36 
  37 import javax.xml.soap.*;
  38 
  39 import com.sun.xml.internal.messaging.saaj.SOAPExceptionImpl;
  40 import com.sun.xml.internal.messaging.saaj.util.*;
  41 
  42 /**
  43  * This represents a "connection" to the simple HTTP-based provider.
  44  *
  45  * @author Anil Vijendran (akv@eng.sun.com)
  46  * @author Rajiv Mordani (rajiv.mordani@sun.com)
  47  * @author Manveen Kaur (manveen.kaur@sun.com)
  48  *
  49  */
  50 class HttpSOAPConnection extends SOAPConnection {
  51 
  52     public static final String vmVendor = SAAJUtil.getSystemProperty("java.vendor.url");
  53     private static final String sunVmVendor = "http://java.sun.com/";
  54     private static final String ibmVmVendor = "http://www.ibm.com/";
  55     private static final boolean isSunVM = sunVmVendor.equals(vmVendor) ? true: false;
  56     private static final boolean isIBMVM = ibmVmVendor.equals(vmVendor) ? true : false;
  57     private static final String JAXM_URLENDPOINT="javax.xml.messaging.URLEndpoint";
  58 
  59     protected static final Logger log =
  60         Logger.getLogger(LogDomainConstants.HTTP_CONN_DOMAIN,
  61                          "com.sun.xml.internal.messaging.saaj.client.p2p.LocalStrings");
  62 
  63 
  64     MessageFactory messageFactory = null;
  65 
  66     boolean closed = false;
  67 
  68     public HttpSOAPConnection() throws SOAPException {
  69 
  70         try {
  71             messageFactory = MessageFactory.newInstance(SOAPConstants.DYNAMIC_SOAP_PROTOCOL);
  72         } catch (NoSuchMethodError ex) {
  73             //fallback to default SOAP 1.1 in this case for backward compatibility
  74             messageFactory = MessageFactory.newInstance();
  75         } catch (Exception ex) {
  76             log.log(Level.SEVERE, "SAAJ0001.p2p.cannot.create.msg.factory", ex);
  77             throw new SOAPExceptionImpl("Unable to create message factory", ex);
  78         }
  79     }
  80 
  81     public void close() throws SOAPException {
  82         if (closed) {
  83             log.severe("SAAJ0002.p2p.close.already.closed.conn");
  84             throw new SOAPExceptionImpl("Connection already closed");
  85         }
  86 
  87         messageFactory = null;
  88         closed = true;
  89     }
  90 
  91    public SOAPMessage call(SOAPMessage message, Object endPoint)
  92         throws SOAPException {
  93         if (closed) {
  94             log.severe("SAAJ0003.p2p.call.already.closed.conn");
  95             throw new SOAPExceptionImpl("Connection is closed");
  96         }
  97 
  98         Class urlEndpointClass = null;
  99         ClassLoader loader = Thread.currentThread().getContextClassLoader();
 100         try {
 101             if (loader != null) {
 102                 urlEndpointClass = loader.loadClass(JAXM_URLENDPOINT);
 103             } else {
 104                 urlEndpointClass = Class.forName(JAXM_URLENDPOINT);
 105             }
 106         } catch (ClassNotFoundException ex) {
 107             //Do nothing. URLEndpoint is available only when JAXM is there.
 108             log.finest("SAAJ0090.p2p.endpoint.available.only.for.JAXM");
 109         }
 110 
 111         if (urlEndpointClass != null) {
 112             if (urlEndpointClass.isInstance(endPoint)) {
 113                 String url = null;
 114 
 115                 try {
 116                     Method m = urlEndpointClass.getMethod("getURL", (Class[])null);
 117                     url = (String) m.invoke(endPoint, (Object[])null);
 118                 } catch (Exception ex) {
 119                     // TBD -- exception chaining
 120                     log.log(Level.SEVERE,"SAAJ0004.p2p.internal.err",ex);
 121                     throw new SOAPExceptionImpl(
 122                         "Internal error: " + ex.getMessage());
 123                 }
 124                 try {
 125                     endPoint = new URL(url);
 126                 } catch (MalformedURLException mex) {
 127                     log.log(Level.SEVERE,"SAAJ0005.p2p.", mex);
 128                     throw new SOAPExceptionImpl("Bad URL: " + mex.getMessage());
 129                 }
 130             }
 131         }
 132 
 133         if (endPoint instanceof java.lang.String) {
 134             try {
 135                 endPoint = new URL((String) endPoint);
 136             } catch (MalformedURLException mex) {
 137                 log.log(Level.SEVERE, "SAAJ0006.p2p.bad.URL", mex);
 138                 throw new SOAPExceptionImpl("Bad URL: " + mex.getMessage());
 139             }
 140         }
 141 
 142         if (endPoint instanceof URL)
 143             try {
 144                 SOAPMessage response = post(message, (URL)endPoint);
 145                 return response;
 146             } catch (Exception ex) {
 147                 // TBD -- chaining?
 148                 throw new SOAPExceptionImpl(ex);
 149             } else {
 150             log.severe("SAAJ0007.p2p.bad.endPoint.type");
 151             throw new SOAPExceptionImpl("Bad endPoint type " + endPoint);
 152         }
 153     }
 154 
 155     SOAPMessage post(SOAPMessage message, URL endPoint) throws SOAPException {
 156         boolean isFailure = false;
 157 
 158         URL url = null;
 159         HttpURLConnection httpConnection = null;
 160 
 161         int responseCode = 0;
 162         try {
 163             if (endPoint.getProtocol().equals("https"))
 164                 //if(!setHttps)
 165                 initHttps();
 166             // Process the URL
 167             JaxmURI uri = new JaxmURI(endPoint.toString());
 168             String userInfo = uri.getUserinfo();
 169 
 170             url = endPoint;
 171 
 172             if (dL > 0)
 173                 d("uri: " + userInfo + " " + url + " " + uri);
 174 
 175             // TBD
 176             //    Will deal with https later.
 177             if (!url.getProtocol().equalsIgnoreCase("http")
 178                 && !url.getProtocol().equalsIgnoreCase("https")) {
 179                 log.severe("SAAJ0052.p2p.protocol.mustbe.http.or.https");
 180                 throw new IllegalArgumentException(
 181                     "Protocol "
 182                         + url.getProtocol()
 183                         + " not supported in URL "
 184                         + url);
 185             }
 186             httpConnection = (HttpURLConnection) createConnection(url);
 187 
 188             httpConnection.setRequestMethod("POST");
 189 
 190             httpConnection.setDoOutput(true);
 191             httpConnection.setDoInput(true);
 192             httpConnection.setUseCaches(false);
 193             httpConnection.setInstanceFollowRedirects(true);
 194 
 195             if (message.saveRequired())
 196                 message.saveChanges();
 197 
 198             MimeHeaders headers = message.getMimeHeaders();
 199 
 200             Iterator it = headers.getAllHeaders();
 201             boolean hasAuth = false; // true if we find explicit Auth header
 202             while (it.hasNext()) {
 203                 MimeHeader header = (MimeHeader) it.next();
 204 
 205                 String[] values = headers.getHeader(header.getName());
 206                 if (values.length == 1)
 207                     httpConnection.setRequestProperty(
 208                         header.getName(),
 209                         header.getValue());
 210                 else {
 211                     StringBuffer concat = new StringBuffer();
 212                     int i = 0;
 213                     while (i < values.length) {
 214                         if (i != 0)
 215                             concat.append(',');
 216                         concat.append(values[i]);
 217                         i++;
 218                     }
 219 
 220                     httpConnection.setRequestProperty(
 221                         header.getName(),
 222                         concat.toString());
 223                 }
 224 
 225                 if ("Authorization".equals(header.getName())) {
 226                     hasAuth = true;
 227                     log.fine("SAAJ0091.p2p.https.auth.in.POST.true");
 228                 }
 229             }
 230 
 231             if (!hasAuth && userInfo != null) {
 232                 initAuthUserInfo(httpConnection, userInfo);
 233             }
 234 
 235             OutputStream out = httpConnection.getOutputStream();
 236             message.writeTo(out);
 237 
 238             out.flush();
 239             out.close();
 240 
 241             httpConnection.connect();
 242 
 243             try {
 244 
 245                 responseCode = httpConnection.getResponseCode();
 246 
 247                 // let HTTP_INTERNAL_ERROR (500) through because it is used for SOAP faults
 248                 if (responseCode == HttpURLConnection.HTTP_INTERNAL_ERROR) {
 249                     isFailure = true;
 250                 }
 251                 //else if (responseCode != HttpURLConnection.HTTP_OK)
 252                 //else if (!(responseCode >= HttpURLConnection.HTTP_OK && responseCode < 207))
 253                 else if ((responseCode / 100) != 2) {
 254                     log.log(Level.SEVERE,
 255                             "SAAJ0008.p2p.bad.response",
 256                             new String[] {httpConnection.getResponseMessage()});
 257                     throw new SOAPExceptionImpl(
 258                         "Bad response: ("
 259                             + responseCode
 260                             + httpConnection.getResponseMessage());
 261 
 262                 }
 263             } catch (IOException e) {
 264                 // on JDK1.3.1_01, we end up here, but then getResponseCode() succeeds!
 265                 responseCode = httpConnection.getResponseCode();
 266                 if (responseCode == HttpURLConnection.HTTP_INTERNAL_ERROR) {
 267                     isFailure = true;
 268                 } else {
 269                     throw e;
 270                 }
 271 
 272             }
 273 
 274         } catch (SOAPException ex) {
 275             throw ex;
 276         } catch (Exception ex) {
 277             log.severe("SAAJ0009.p2p.msg.send.failed");
 278             throw new SOAPExceptionImpl("Message send failed", ex);
 279         }
 280 
 281         SOAPMessage response = null;
 282         if (responseCode == HttpURLConnection.HTTP_OK || isFailure) {
 283             try {
 284                 MimeHeaders headers = new MimeHeaders();
 285 
 286                 String key, value;
 287 
 288                 // Header field 0 is the status line so we skip it.
 289 
 290                 int i = 1;
 291 
 292                 while (true) {
 293                     key = httpConnection.getHeaderFieldKey(i);
 294                     value = httpConnection.getHeaderField(i);
 295 
 296                     if (key == null && value == null)
 297                         break;
 298 
 299                     if (key != null) {
 300                         StringTokenizer values =
 301                             new StringTokenizer(value, ",");
 302                         while (values.hasMoreTokens())
 303                             headers.addHeader(key, values.nextToken().trim());
 304                     }
 305                     i++;
 306                 }
 307 
 308                 InputStream httpIn =
 309                     (isFailure
 310                         ? httpConnection.getErrorStream()
 311                         : httpConnection.getInputStream());
 312 
 313                 byte[] bytes = readFully(httpIn);
 314 
 315                 int length =
 316                     httpConnection.getContentLength() == -1
 317                         ? bytes.length
 318                         : httpConnection.getContentLength();
 319 
 320                 // If no reply message is returned,
 321                 // content-Length header field value is expected to be zero.
 322                 if (length == 0) {
 323                     response = null;
 324                     log.warning("SAAJ0014.p2p.content.zero");
 325                 } else {
 326                     ByteInputStream in = new ByteInputStream(bytes, length);
 327                     response = messageFactory.createMessage(headers, in);
 328                 }
 329 
 330                 httpIn.close();
 331                 httpConnection.disconnect();
 332 
 333             } catch (SOAPException ex) {
 334                 throw ex;
 335             } catch (Exception ex) {
 336                 log.log(Level.SEVERE,"SAAJ0010.p2p.cannot.read.resp", ex);
 337                 throw new SOAPExceptionImpl(
 338                     "Unable to read response: " + ex.getMessage());
 339             }
 340         }
 341         return response;
 342     }
 343 
 344     // Object identifies where the request should be sent.
 345     // It is required to support objects of type String and java.net.URL.
 346 
 347     public SOAPMessage get(Object endPoint) throws SOAPException {
 348         if (closed) {
 349             log.severe("SAAJ0011.p2p.get.already.closed.conn");
 350             throw new SOAPExceptionImpl("Connection is closed");
 351         }
 352         Class urlEndpointClass = null;
 353 
 354         try {
 355             urlEndpointClass = Class.forName("javax.xml.messaging.URLEndpoint");
 356         } catch (Exception ex) {
 357             //Do nothing. URLEndpoint is available only when JAXM is there.
 358         }
 359 
 360         if (urlEndpointClass != null) {
 361             if (urlEndpointClass.isInstance(endPoint)) {
 362                 String url = null;
 363 
 364                 try {
 365                     Method m = urlEndpointClass.getMethod("getURL", (Class[])null);
 366                     url = (String) m.invoke(endPoint, (Object[])null);
 367                 } catch (Exception ex) {
 368                     log.severe("SAAJ0004.p2p.internal.err");
 369                     throw new SOAPExceptionImpl(
 370                         "Internal error: " + ex.getMessage());
 371                 }
 372                 try {
 373                     endPoint = new URL(url);
 374                 } catch (MalformedURLException mex) {
 375                     log.severe("SAAJ0005.p2p.");
 376                     throw new SOAPExceptionImpl("Bad URL: " + mex.getMessage());
 377                 }
 378             }
 379         }
 380 
 381         if (endPoint instanceof java.lang.String) {
 382             try {
 383                 endPoint = new URL((String) endPoint);
 384             } catch (MalformedURLException mex) {
 385                 log.severe("SAAJ0006.p2p.bad.URL");
 386                 throw new SOAPExceptionImpl("Bad URL: " + mex.getMessage());
 387             }
 388         }
 389 
 390         if (endPoint instanceof URL)
 391             try {
 392                 SOAPMessage response = doGet((URL)endPoint);
 393                 return response;
 394             } catch (Exception ex) {
 395                 throw new SOAPExceptionImpl(ex);
 396             } else
 397             throw new SOAPExceptionImpl("Bad endPoint type " + endPoint);
 398     }
 399 
 400     SOAPMessage doGet(URL endPoint) throws SOAPException {
 401         boolean isFailure = false;
 402 
 403         URL url = null;
 404         HttpURLConnection httpConnection = null;
 405 
 406         int responseCode = 0;
 407         try {
 408             /// Is https GET allowed??
 409             if (endPoint.getProtocol().equals("https"))
 410                 initHttps();
 411             // Process the URL
 412             JaxmURI uri = new JaxmURI(endPoint.toString());
 413             String userInfo = uri.getUserinfo();
 414 
 415             url = endPoint;
 416 
 417             if (dL > 0)
 418                 d("uri: " + userInfo + " " + url + " " + uri);
 419 
 420             // TBD
 421             //    Will deal with https later.
 422             if (!url.getProtocol().equalsIgnoreCase("http")
 423                 && !url.getProtocol().equalsIgnoreCase("https")) {
 424                 log.severe("SAAJ0052.p2p.protocol.mustbe.http.or.https");
 425                 throw new IllegalArgumentException(
 426                     "Protocol "
 427                         + url.getProtocol()
 428                         + " not supported in URL "
 429                         + url);
 430             }
 431             httpConnection = (HttpURLConnection) createConnection(url);
 432 
 433             httpConnection.setRequestMethod("GET");
 434 
 435             httpConnection.setDoOutput(true);
 436             httpConnection.setDoInput(true);
 437             httpConnection.setUseCaches(false);
 438             httpConnection.setFollowRedirects(true);
 439 
 440             httpConnection.connect();
 441 
 442             try {
 443 
 444                 responseCode = httpConnection.getResponseCode();
 445 
 446                 // let HTTP_INTERNAL_ERROR (500) through because it is used for SOAP faults
 447                 if (responseCode == HttpURLConnection.HTTP_INTERNAL_ERROR) {
 448                     isFailure = true;
 449                 } else if ((responseCode / 100) != 2) {
 450                     log.log(Level.SEVERE,
 451                             "SAAJ0008.p2p.bad.response",
 452                             new String[] { httpConnection.getResponseMessage()});
 453                     throw new SOAPExceptionImpl(
 454                         "Bad response: ("
 455                             + responseCode
 456                             + httpConnection.getResponseMessage());
 457 
 458                 }
 459             } catch (IOException e) {
 460                 // on JDK1.3.1_01, we end up here, but then getResponseCode() succeeds!
 461                 responseCode = httpConnection.getResponseCode();
 462                 if (responseCode == HttpURLConnection.HTTP_INTERNAL_ERROR) {
 463                     isFailure = true;
 464                 } else {
 465                     throw e;
 466                 }
 467 
 468             }
 469 
 470         } catch (SOAPException ex) {
 471             throw ex;
 472         } catch (Exception ex) {
 473             log.severe("SAAJ0012.p2p.get.failed");
 474             throw new SOAPExceptionImpl("Get failed", ex);
 475         }
 476 
 477         SOAPMessage response = null;
 478         if (responseCode == HttpURLConnection.HTTP_OK || isFailure) {
 479             try {
 480                 MimeHeaders headers = new MimeHeaders();
 481 
 482                 String key, value;
 483 
 484                 // Header field 0 is the status line so we skip it.
 485 
 486                 int i = 1;
 487 
 488                 while (true) {
 489                     key = httpConnection.getHeaderFieldKey(i);
 490                     value = httpConnection.getHeaderField(i);
 491 
 492                     if (key == null && value == null)
 493                         break;
 494 
 495                     if (key != null) {
 496                         StringTokenizer values =
 497                             new StringTokenizer(value, ",");
 498                         while (values.hasMoreTokens())
 499                             headers.addHeader(key, values.nextToken().trim());
 500                     }
 501                     i++;
 502                 }
 503 
 504                 InputStream httpIn =
 505                         (isFailure
 506                         ? httpConnection.getErrorStream()
 507                         : httpConnection.getInputStream());
 508                 // If no reply message is returned,
 509                 // content-Length header field value is expected to be zero.
 510                 // java SE 6 documentation says :
 511                 // available() : an estimate of the number of bytes that can be read
 512                 //(or skipped over) from this input stream without blocking
 513                 //or 0 when it reaches the end of the input stream.
 514                 if ((httpIn == null )
 515                         || (httpConnection.getContentLength() == 0)
 516                         || (httpIn.available() == 0)) {
 517                     response = null;
 518                     log.warning("SAAJ0014.p2p.content.zero");
 519                 } else {
 520                     response = messageFactory.createMessage(headers, httpIn);
 521                 }
 522 
 523                 httpIn.close();
 524                 httpConnection.disconnect();
 525 
 526             } catch (SOAPException ex) {
 527                 throw ex;
 528             } catch (Exception ex) {
 529                 log.log(Level.SEVERE,
 530                         "SAAJ0010.p2p.cannot.read.resp",
 531                         ex);
 532                 throw new SOAPExceptionImpl(
 533                     "Unable to read response: " + ex.getMessage());
 534             }
 535         }
 536         return response;
 537     }
 538 
 539     private byte[] readFully(InputStream istream) throws IOException {
 540         ByteArrayOutputStream bout = new ByteArrayOutputStream();
 541         byte[] buf = new byte[1024];
 542         int num = 0;
 543 
 544         while ((num = istream.read(buf)) != -1) {
 545             bout.write(buf, 0, num);
 546         }
 547 
 548         byte[] ret = bout.toByteArray();
 549 
 550         return ret;
 551     }
 552 
 553     //private static String SSL_PKG = "com.sun.net.ssl.internal.www.protocol";
 554     //private static String SSL_PROVIDER =
 555       //  "com.sun.net.ssl.internal.ssl.Provider";
 556     private static final String SSL_PKG;
 557     private static final String SSL_PROVIDER;
 558 
 559     static {
 560         if (isIBMVM) {
 561             SSL_PKG ="com.ibm.net.ssl.internal.www.protocol";
 562             SSL_PROVIDER ="com.ibm.net.ssl.internal.ssl.Provider";
 563         } else {
 564             //if not IBM VM default to Sun.
 565             SSL_PKG = "com.sun.net.ssl.internal.www.protocol";
 566             SSL_PROVIDER ="com.sun.net.ssl.internal.ssl.Provider";
 567         }
 568     }
 569 
 570     private void initHttps() {
 571         //if(!setHttps) {
 572         String pkgs = SAAJUtil.getSystemProperty("java.protocol.handler.pkgs");
 573         log.log(Level.FINE,
 574                 "SAAJ0053.p2p.providers",
 575                 new String[] { pkgs });
 576 
 577         if (pkgs == null || pkgs.indexOf(SSL_PKG) < 0) {
 578             if (pkgs == null)
 579                 pkgs = SSL_PKG;
 580             else
 581                 pkgs = pkgs + "|" + SSL_PKG;
 582             System.setProperty("java.protocol.handler.pkgs", pkgs);
 583             log.log(Level.FINE,
 584                     "SAAJ0054.p2p.set.providers",
 585                     new String[] { pkgs });
 586             try {
 587                 Class c = Class.forName(SSL_PROVIDER);
 588                 Provider p = (Provider) c.newInstance();
 589                 Security.addProvider(p);
 590                 log.log(Level.FINE,
 591                         "SAAJ0055.p2p.added.ssl.provider",
 592                         new String[] { SSL_PROVIDER });
 593                 //System.out.println("Added SSL_PROVIDER " + SSL_PROVIDER);
 594                 //setHttps = true;
 595             } catch (Exception ex) {
 596             }
 597         }
 598         //}
 599     }
 600 
 601     private void initAuthUserInfo(HttpURLConnection conn, String userInfo) {
 602         String user;
 603         String password;
 604         if (userInfo != null) { // get the user and password
 605             //System.out.println("UserInfo= " + userInfo );
 606             int delimiter = userInfo.indexOf(':');
 607             if (delimiter == -1) {
 608                 user = ParseUtil.decode(userInfo);
 609                 password = null;
 610             } else {
 611                 user = ParseUtil.decode(userInfo.substring(0, delimiter++));
 612                 password = ParseUtil.decode(userInfo.substring(delimiter));
 613             }
 614 
 615             String plain = user + ":";
 616             byte[] nameBytes = plain.getBytes();
 617             byte[] passwdBytes = password.getBytes();
 618 
 619             // concatenate user name and password bytes and encode them
 620             byte[] concat = new byte[nameBytes.length + passwdBytes.length];
 621 
 622             System.arraycopy(nameBytes, 0, concat, 0, nameBytes.length);
 623             System.arraycopy(
 624                 passwdBytes,
 625                 0,
 626                 concat,
 627                 nameBytes.length,
 628                 passwdBytes.length);
 629             String auth = "Basic " + new String(Base64.encode(concat));
 630             conn.setRequestProperty("Authorization", auth);
 631             if (dL > 0)
 632                 d("Adding auth " + auth);
 633         }
 634     }
 635 
 636     private static final int dL = 0;
 637     private void d(String s) {
 638         log.log(Level.SEVERE,
 639                 "SAAJ0013.p2p.HttpSOAPConnection",
 640                 new String[] { s });
 641         System.err.println("HttpSOAPConnection: " + s);
 642     }
 643 
 644     private java.net.HttpURLConnection createConnection(URL endpoint)
 645         throws IOException {
 646         return (HttpURLConnection) endpoint.openConnection();
 647     }
 648 
 649 }