1 /* 2 * Copyright (c) 2015, 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 sun.security.ssl; 27 28 import java.io.IOException; 29 import java.nio.charset.*; 30 import java.util.*; 31 32 import javax.net.ssl.*; 33 34 /* 35 * [RFC 7301] 36 * This TLS extension facilitates the negotiation of application-layer protocols 37 * within the TLS handshake. Clients MAY include an extension of type 38 * "application_layer_protocol_negotiation" in the (extended) ClientHello 39 * message. The "extension_data" field of this extension SHALL contain a 40 * "ProtocolNameList" value: 41 * 42 * enum { 43 * application_layer_protocol_negotiation(16), (65535) 44 * } ExtensionType; 45 * 46 * opaque ProtocolName<1..2^8-1>; 47 * 48 * struct { 49 * ProtocolName protocol_name_list<2..2^16-1> 50 * } ProtocolNameList; 51 */ 52 final class ALPNExtension extends HelloExtension { 53 54 final static int ALPN_HEADER_LENGTH = 1; 55 final static int MAX_APPLICATION_PROTOCOL_LENGTH = 255; 56 final static int MAX_APPLICATION_PROTOCOL_LIST_LENGTH = 65535; 57 private int listLength = 0; // ProtocolNameList length 58 private List<String> protocolNames = null; 59 60 // constructor for ServerHello 61 ALPNExtension(String protocolName) throws SSLException { 62 this(new String[]{ protocolName }); 63 } 64 65 // constructor for ClientHello 66 ALPNExtension(String[] protocolNames) throws SSLException { 67 super(ExtensionType.EXT_ALPN); 68 if (protocolNames.length == 0) { // never null, never empty 69 throw new IllegalArgumentException( 70 "The list of application protocols cannot be empty"); 71 } 72 this.protocolNames = Arrays.asList(protocolNames); 73 for (String p : protocolNames) { 74 int length = p.getBytes(StandardCharsets.UTF_8).length; 75 if (length == 0) { 76 throw new SSLProtocolException( 77 "Application protocol name is empty"); 78 } 79 if (length <= MAX_APPLICATION_PROTOCOL_LENGTH) { 80 listLength += length + ALPN_HEADER_LENGTH; 81 } else { 82 throw new SSLProtocolException( 83 "Application protocol name is too long: " + p); 84 } 85 if (listLength > MAX_APPLICATION_PROTOCOL_LIST_LENGTH) { 86 throw new SSLProtocolException( 87 "Application protocol name list is too long"); 88 } 89 } 90 } 91 92 // constructor for ServerHello for parsing ALPN extension 93 ALPNExtension(HandshakeInStream s, int len) throws IOException { 94 super(ExtensionType.EXT_ALPN); 95 96 if (len >= 2) { 97 listLength = s.getInt16(); // list length 98 if (listLength < 2 || listLength + 2 != len) { 99 throw new SSLProtocolException( 100 "Invalid " + type + " extension: incorrect list length " + 101 "(length=" + listLength + ")"); 102 } 103 } else { 104 throw new SSLProtocolException( 105 "Invalid " + type + " extension: insufficient data " + 106 "(length=" + len + ")"); 107 } 108 109 int remaining = listLength; 110 this.protocolNames = new ArrayList<>(); 111 while (remaining > 0) { 112 // opaque ProtocolName<1..2^8-1>; // RFC 7301 113 byte[] bytes = s.getBytes8(); 114 if (bytes.length == 0) { 115 throw new SSLProtocolException("Invalid " + type + 116 " extension: empty application protocol name"); 117 } 118 String p = 119 new String(bytes, StandardCharsets.UTF_8); // app protocol 120 protocolNames.add(p); 121 remaining -= bytes.length + ALPN_HEADER_LENGTH; 122 } 123 124 if (remaining != 0) { 125 throw new SSLProtocolException( 126 "Invalid " + type + " extension: extra data " + 127 "(length=" + remaining + ")"); 128 } 129 } 130 131 List<String> getPeerAPs() { 132 return protocolNames; 133 } 134 135 /* 136 * Return the length in bytes, including extension type and length fields. 137 */ 138 @Override 139 int length() { 140 return 6 + listLength; 141 } 142 143 @Override 144 void send(HandshakeOutStream s) throws IOException { 145 s.putInt16(type.id); 146 s.putInt16(listLength + 2); // length of extension_data 147 s.putInt16(listLength); // length of ProtocolNameList 148 149 for (String p : protocolNames) { 150 s.putBytes8(p.getBytes(StandardCharsets.UTF_8)); 151 } 152 } 153 154 @Override 155 public String toString() { 156 StringBuilder sb = new StringBuilder(); 157 if (protocolNames == null || protocolNames.isEmpty()) { 158 sb.append("<empty>"); 159 } else { 160 for (String protocolName : protocolNames) { 161 sb.append("[" + protocolName + "]"); 162 } 163 } 164 165 return "Extension " + type + 166 ", protocol names: " + sb; 167 } 168 } | 1 /* 2 * Copyright (c) 2015, 2018, 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 sun.security.ssl; 27 28 import java.io.IOException; 29 import java.nio.ByteBuffer; 30 import java.nio.charset.StandardCharsets; 31 import java.util.Arrays; 32 import java.util.Collections; 33 import java.util.LinkedList; 34 import java.util.List; 35 import javax.net.ssl.SSLEngine; 36 import javax.net.ssl.SSLProtocolException; 37 import javax.net.ssl.SSLSocket; 38 import sun.security.ssl.SSLExtension.ExtensionConsumer; 39 import sun.security.ssl.SSLExtension.SSLExtensionSpec; 40 import sun.security.ssl.SSLHandshake.HandshakeMessage; 41 42 /** 43 * Pack of the "application_layer_protocol_negotiation" extensions [RFC 7301]. 44 */ 45 final class AlpnExtension { 46 static final HandshakeProducer chNetworkProducer = new CHAlpnProducer(); 47 static final ExtensionConsumer chOnLoadConcumer = new CHAlpnConsumer(); 48 static final HandshakeAbsence chOnLoadAbsence = new CHAlpnAbsence(); 49 50 static final HandshakeProducer shNetworkProducer = new SHAlpnProducer(); 51 static final ExtensionConsumer shOnLoadConcumer = new SHAlpnConsumer(); 52 static final HandshakeAbsence shOnLoadAbsence = new SHAlpnAbsence(); 53 54 // Note: we reuse ServerHello operations for EncryptedExtensions for now. 55 // Please be careful about any code or specification changes in the future. 56 static final HandshakeProducer eeNetworkProducer = new SHAlpnProducer(); 57 static final ExtensionConsumer eeOnLoadConcumer = new SHAlpnConsumer(); 58 static final HandshakeAbsence eeOnLoadAbsence = new SHAlpnAbsence(); 59 60 static final SSLStringize alpnStringize = new AlpnStringize(); 61 62 /** 63 * The "application_layer_protocol_negotiation" extension. 64 * 65 * See RFC 7301 for the specification of this extension. 66 */ 67 static final class AlpnSpec implements SSLExtensionSpec { 68 final List<String> applicationProtocols; 69 70 private AlpnSpec(String[] applicationProtocols) { 71 this.applicationProtocols = Collections.unmodifiableList( 72 Arrays.asList(applicationProtocols)); 73 } 74 75 private AlpnSpec(ByteBuffer buffer) throws IOException { 76 // ProtocolName protocol_name_list<2..2^16-1>, RFC 7301. 77 if (buffer.remaining() < 2) { 78 throw new SSLProtocolException( 79 "Invalid application_layer_protocol_negotiation: " + 80 "insufficient data (length=" + buffer.remaining() + ")"); 81 } 82 83 int listLen = Record.getInt16(buffer); 84 if (listLen < 2 || listLen != buffer.remaining()) { 85 throw new SSLProtocolException( 86 "Invalid application_layer_protocol_negotiation: " + 87 "incorrect list length (length=" + listLen + ")"); 88 } 89 90 List<String> protocolNames = new LinkedList<>(); 91 while (buffer.hasRemaining()) { 92 // opaque ProtocolName<1..2^8-1>, RFC 7301. 93 byte[] bytes = Record.getBytes8(buffer); 94 if (bytes.length == 0) { 95 throw new SSLProtocolException( 96 "Invalid application_layer_protocol_negotiation " + 97 "extension: empty application protocol name"); 98 } 99 100 String appProtocol = new String(bytes, StandardCharsets.UTF_8); 101 protocolNames.add(appProtocol); 102 } 103 104 this.applicationProtocols = 105 Collections.unmodifiableList(protocolNames); 106 } 107 108 @Override 109 public String toString() { 110 return applicationProtocols.toString(); 111 } 112 } 113 114 private static final class AlpnStringize implements SSLStringize { 115 @Override 116 public String toString(ByteBuffer buffer) { 117 try { 118 return (new AlpnSpec(buffer)).toString(); 119 } catch (IOException ioe) { 120 // For debug logging only, so please swallow exceptions. 121 return ioe.getMessage(); 122 } 123 } 124 } 125 126 /** 127 * Network data producer of the extension in a ClientHello 128 * handshake message. 129 */ 130 private static final class CHAlpnProducer implements HandshakeProducer { 131 static final int MAX_AP_LENGTH = 255; 132 static final int MAX_AP_LIST_LENGTH = 65535; 133 134 // Prevent instantiation of this class. 135 private CHAlpnProducer() { 136 // blank 137 } 138 139 @Override 140 public byte[] produce(ConnectionContext context, 141 HandshakeMessage message) throws IOException { 142 // The producing happens in client side only. 143 ClientHandshakeContext chc = (ClientHandshakeContext)context; 144 145 // Is it a supported and enabled extension? 146 if (!chc.sslConfig.isAvailable(SSLExtension.CH_ALPN)) { 147 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 148 SSLLogger.info( 149 "Ignore client unavailable extension: " + 150 SSLExtension.CH_ALPN.name); 151 } 152 return null; 153 } 154 155 String[] laps = chc.sslConfig.applicationProtocols; 156 if ((laps == null) || (laps.length == 0)) { 157 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 158 SSLLogger.info( 159 "No available application protocols"); 160 } 161 return null; 162 } 163 164 // Produce the extension. 165 int listLength = 0; // ProtocolNameList length 166 for (String ap : laps) { 167 int length = ap.getBytes(StandardCharsets.UTF_8).length; 168 if (length == 0) { 169 // log the configuration problem 170 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 171 SSLLogger.severe( 172 "Application protocol name cannot be empty"); 173 } 174 chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, 175 "Application protocol name cannot be empty"); 176 } 177 178 if (length <= MAX_AP_LENGTH) { 179 // opaque ProtocolName<1..2^8-1>, RFC 7301. 180 listLength += (length + 1); 181 } else { 182 // log the configuration problem 183 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 184 SSLLogger.severe( 185 "Application protocol name (" + ap + 186 ") exceeds the size limit (" + 187 MAX_AP_LENGTH + " bytes)"); 188 } 189 chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, 190 "Application protocol name (" + ap + 191 ") exceeds the size limit (" + 192 MAX_AP_LENGTH + " bytes)"); 193 } 194 195 if (listLength > MAX_AP_LIST_LENGTH) { 196 // log the configuration problem 197 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 198 SSLLogger.severe( 199 "The configured application protocols (" + 200 Arrays.toString(laps) + 201 ") exceed the size limit (" + 202 MAX_AP_LIST_LENGTH + " bytes)"); 203 } 204 chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, 205 "The configured application protocols (" + 206 Arrays.toString(laps) + 207 ") exceed the size limit (" + 208 MAX_AP_LIST_LENGTH + " bytes)"); 209 } 210 } 211 212 // ProtocolName protocol_name_list<2..2^16-1>, RFC 7301. 213 byte[] extData = new byte[listLength + 2]; 214 ByteBuffer m = ByteBuffer.wrap(extData); 215 Record.putInt16(m, listLength); 216 for (String ap : laps) { 217 Record.putBytes8(m, ap.getBytes(StandardCharsets.UTF_8)); 218 } 219 220 // Update the context. 221 chc.handshakeExtensions.put(SSLExtension.CH_ALPN, 222 new AlpnSpec(chc.sslConfig.applicationProtocols)); 223 224 return extData; 225 } 226 } 227 228 /** 229 * Network data consumer of the extension in a ClientHello 230 * handshake message. 231 */ 232 private static final class CHAlpnConsumer implements ExtensionConsumer { 233 // Prevent instantiation of this class. 234 private CHAlpnConsumer() { 235 // blank 236 } 237 238 @Override 239 public void consume(ConnectionContext context, 240 HandshakeMessage message, ByteBuffer buffer) throws IOException { 241 // The comsuming happens in server side only. 242 ServerHandshakeContext shc = (ServerHandshakeContext)context; 243 244 // Is it a supported and enabled extension? 245 if (!shc.sslConfig.isAvailable(SSLExtension.CH_ALPN)) { 246 shc.applicationProtocol = ""; 247 shc.conContext.applicationProtocol = ""; 248 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 249 SSLLogger.info( 250 "Ignore server unavailable extension: " + 251 SSLExtension.CH_ALPN.name); 252 } 253 return; // ignore the extension 254 } 255 256 // Is the extension enabled? 257 boolean noAPSelector; 258 if (shc.conContext.transport instanceof SSLEngine) { 259 noAPSelector = (shc.sslConfig.engineAPSelector == null); 260 } else { 261 noAPSelector = (shc.sslConfig.socketAPSelector == null); 262 } 263 264 boolean noAlpnProtocols = 265 shc.sslConfig.applicationProtocols == null || 266 shc.sslConfig.applicationProtocols.length == 0; 267 if (noAPSelector && noAlpnProtocols) { 268 shc.applicationProtocol = ""; 269 shc.conContext.applicationProtocol = ""; 270 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 271 SSLLogger.fine( 272 "Ignore server unenabled extension: " + 273 SSLExtension.CH_ALPN.name); 274 } 275 return; // ignore the extension 276 } 277 278 // Parse the extension. 279 AlpnSpec spec; 280 try { 281 spec = new AlpnSpec(buffer); 282 } catch (IOException ioe) { 283 shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); 284 return; // fatal() always throws, make the compiler happy. 285 } 286 287 // Update the context. 288 if (noAPSelector) { // noAlpnProtocols is false 289 List<String> protocolNames = spec.applicationProtocols; 290 boolean matched = false; 291 // Use server application protocol preference order. 292 for (String ap : shc.sslConfig.applicationProtocols) { 293 if (protocolNames.contains(ap)) { 294 shc.applicationProtocol = ap; 295 shc.conContext.applicationProtocol = ap; 296 matched = true; 297 break; 298 } 299 } 300 301 if (!matched) { 302 shc.conContext.fatal(Alert.NO_APPLICATION_PROTOCOL, 303 "No matching application layer protocol values"); 304 } 305 } // Otherwise, applicationProtocol will be set by the 306 // application selector callback later. 307 308 shc.handshakeExtensions.put(SSLExtension.CH_ALPN, spec); 309 310 // No impact on session resumption. 311 // 312 // [RFC 7301] Unlike many other TLS extensions, this extension 313 // does not establish properties of the session, only of the 314 // connection. When session resumption or session tickets are 315 // used, the previous contents of this extension are irrelevant, 316 // and only the values in the new handshake messages are 317 // considered. 318 } 319 } 320 321 /** 322 * The absence processing if the extension is not present in 323 * a ClientHello handshake message. 324 */ 325 private static final class CHAlpnAbsence implements HandshakeAbsence { 326 @Override 327 public void absent(ConnectionContext context, 328 HandshakeMessage message) throws IOException { 329 // The producing happens in server side only. 330 ServerHandshakeContext shc = (ServerHandshakeContext)context; 331 332 // Please don't use the previous negotiated application protocol. 333 shc.applicationProtocol = ""; 334 shc.conContext.applicationProtocol = ""; 335 } 336 } 337 338 /** 339 * Network data producer of the extension in the ServerHello 340 * handshake message. 341 */ 342 private static final class SHAlpnProducer implements HandshakeProducer { 343 // Prevent instantiation of this class. 344 private SHAlpnProducer() { 345 // blank 346 } 347 348 @Override 349 public byte[] produce(ConnectionContext context, 350 HandshakeMessage message) throws IOException { 351 // The producing happens in client side only. 352 ServerHandshakeContext shc = (ServerHandshakeContext)context; 353 354 // In response to ALPN request only 355 AlpnSpec requestedAlps = 356 (AlpnSpec)shc.handshakeExtensions.get(SSLExtension.CH_ALPN); 357 if (requestedAlps == null) { 358 // Ignore, this extension was not requested and accepted. 359 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 360 SSLLogger.fine( 361 "Ignore unavailable extension: " + 362 SSLExtension.SH_ALPN.name); 363 } 364 return null; 365 } 366 367 List<String> alps = requestedAlps.applicationProtocols; 368 if (shc.conContext.transport instanceof SSLEngine) { 369 if (shc.sslConfig.engineAPSelector != null) { 370 SSLEngine engine = (SSLEngine)shc.conContext.transport; 371 shc.applicationProtocol = 372 shc.sslConfig.engineAPSelector.apply(engine, alps); 373 if ((shc.applicationProtocol == null) || 374 (!shc.applicationProtocol.isEmpty() && 375 !alps.contains(shc.applicationProtocol))) { 376 shc.conContext.fatal(Alert.NO_APPLICATION_PROTOCOL, 377 "No matching application layer protocol values"); 378 } 379 } 380 } else { 381 if (shc.sslConfig.socketAPSelector != null) { 382 SSLSocket socket = (SSLSocket)shc.conContext.transport; 383 shc.applicationProtocol = 384 shc.sslConfig.socketAPSelector.apply(socket, alps); 385 if ((shc.applicationProtocol == null) || 386 (!shc.applicationProtocol.isEmpty() && 387 !alps.contains(shc.applicationProtocol))) { 388 shc.conContext.fatal(Alert.NO_APPLICATION_PROTOCOL, 389 "No matching application layer protocol values"); 390 } 391 } 392 } 393 394 if ((shc.applicationProtocol == null) || 395 (shc.applicationProtocol.isEmpty())) { 396 // Ignore, no negotiated application layer protocol. 397 shc.applicationProtocol = ""; 398 shc.conContext.applicationProtocol = ""; 399 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 400 SSLLogger.warning( 401 "Ignore, no negotiated application layer protocol"); 402 } 403 404 return null; 405 } 406 407 // opaque ProtocolName<1..2^8-1>, RFC 7301. 408 int listLen = shc.applicationProtocol.length() + 1; 409 // 1: length byte 410 // ProtocolName protocol_name_list<2..2^16-1>, RFC 7301. 411 byte[] extData = new byte[listLen + 2]; // 2: list length 412 ByteBuffer m = ByteBuffer.wrap(extData); 413 Record.putInt16(m, listLen); 414 Record.putBytes8(m, 415 shc.applicationProtocol.getBytes(StandardCharsets.UTF_8)); 416 417 // Update the context. 418 shc.conContext.applicationProtocol = shc.applicationProtocol; 419 420 // Clean or register the extension 421 // 422 // No further use of the request and respond extension any more. 423 shc.handshakeExtensions.remove(SSLExtension.CH_ALPN); 424 425 return extData; 426 } 427 } 428 429 /** 430 * Network data consumer of the extension in the ServerHello 431 * handshake message. 432 */ 433 private static final class SHAlpnConsumer implements ExtensionConsumer { 434 // Prevent instantiation of this class. 435 private SHAlpnConsumer() { 436 // blank 437 } 438 439 @Override 440 public void consume(ConnectionContext context, 441 HandshakeMessage message, ByteBuffer buffer) throws IOException { 442 // The producing happens in client side only. 443 ClientHandshakeContext chc = (ClientHandshakeContext)context; 444 445 // In response to ALPN request only 446 AlpnSpec requestedAlps = 447 (AlpnSpec)chc.handshakeExtensions.get(SSLExtension.CH_ALPN); 448 if (requestedAlps == null) { 449 chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, 450 "Unexpected " + SSLExtension.CH_ALPN.name + " extension"); 451 } 452 453 // Parse the extension. 454 AlpnSpec spec; 455 try { 456 spec = new AlpnSpec(buffer); 457 } catch (IOException ioe) { 458 chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); 459 return; // fatal() always throws, make the compiler happy. 460 } 461 462 // Only one application protocol is allowed. 463 if (spec.applicationProtocols.size() != 1) { 464 chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, 465 "Invalid " + SSLExtension.CH_ALPN.name + " extension: " + 466 "Only one protocol name is allowed in ServerHello message"); 467 } 468 469 // Update the context. 470 chc.applicationProtocol = spec.applicationProtocols.get(0); 471 chc.conContext.applicationProtocol = chc.applicationProtocol; 472 473 // Clean or register the extension 474 // 475 // No further use of the request and respond extension any more. 476 chc.handshakeExtensions.remove(SSLExtension.CH_ALPN); 477 } 478 } 479 480 /** 481 * The absence processing if the extension is not present in 482 * the ServerHello handshake message. 483 */ 484 private static final class SHAlpnAbsence implements HandshakeAbsence { 485 @Override 486 public void absent(ConnectionContext context, 487 HandshakeMessage message) throws IOException { 488 // The producing happens in client side only. 489 ClientHandshakeContext chc = (ClientHandshakeContext)context; 490 491 // Please don't use the previous negotiated application protocol. 492 chc.applicationProtocol = ""; 493 chc.conContext.applicationProtocol = ""; 494 } 495 } 496 } |