1 /*
   2  * Copyright (c) 1997, 2013, 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 ibmVmVendor = "http://www.ibm.com/";

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