X-Git-Url: http://xvm.mit.edu/gitweb/invirt/packages/invirt-vnc-client.git/blobdiff_plain/0d88c0c9bd61f710cba0a29af49397fbc1c4902c..bcb39a3295741f6dddd5d7e3a1db23b62baa38b4:/debian/patches/invirt-ssl-proxy.patch?ds=sidebyside diff --git a/debian/patches/invirt-ssl-proxy.patch b/debian/patches/invirt-ssl-proxy.patch new file mode 100644 index 0000000..f777283 --- /dev/null +++ b/debian/patches/invirt-ssl-proxy.patch @@ -0,0 +1,685 @@ +Index: invirt-vnc-client/InvirtTrustManager.java +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ invirt-vnc-client/InvirtTrustManager.java 2008-10-31 06:09:10.000000000 -0400 +@@ -0,0 +1,122 @@ ++/* ++ * Copyright 2006 Perry Nguyen ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++import java.io.IOException; ++import java.io.InputStream; ++import java.security.KeyStore; ++import java.security.KeyStoreException; ++import java.security.NoSuchAlgorithmException; ++import java.security.cert.CertificateException; ++import java.security.cert.X509Certificate; ++import java.util.Enumeration; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++import javax.net.ssl.TrustManager; ++import javax.net.ssl.TrustManagerFactory; ++import javax.net.ssl.X509TrustManager; ++ ++public class InvirtTrustManager implements X509TrustManager { ++ private X509TrustManager trustManager; ++ private final static char[] KEY_STORE_PASSWORD = ++ { 'f', 'o', 'o', 'b', 'a', 'r' }; ++ private final static String KEY_STORE_RESOURCE = ++ "trust.store"; ++ ++ private KeyStore loadKeyStore() throws Exception { ++ InputStream in = getClass().getClassLoader().getResourceAsStream( ++ KEY_STORE_RESOURCE); ++ KeyStore ks = null; ++ try { ++ if (in == null) { ++ //log.severe("Unable to open KeyStore"); ++ throw new NullPointerException(); ++ } ++ ks = KeyStore.getInstance(KeyStore.getDefaultType()); ++ ks.load(in, KEY_STORE_PASSWORD); ++ /*if (log.isLoggable(Level.FINEST)) { ++ for (Enumeration aliases = ks.aliases(); ++ aliases.hasMoreElements();) { ++ String alias = aliases.nextElement(); ++ log.finest("ALIAS: " + alias); ++ } ++ }*/ ++ } catch (NoSuchAlgorithmException e) { ++ throwError(e); ++ } catch (CertificateException e) { ++ throwError(e); ++ } catch (IOException e) { ++ throwError(e); ++ } catch (KeyStoreException e) { ++ throwError(e); ++ } finally { ++ try { ++ if (in != null) ++ in.close(); ++ } ++ catch (IOException e) { } // ignore ++ } ++ return ks; ++ } ++ private void createTrustManager() { ++ try { ++ try { ++ KeyStore keystore = loadKeyStore(); ++ TrustManagerFactory factory = TrustManagerFactory.getInstance( ++ TrustManagerFactory.getDefaultAlgorithm()); ++ factory.init(keystore); ++ TrustManager[] trustManagers = factory.getTrustManagers(); ++ if (trustManagers.length == 0) ++ throw new IllegalStateException("No trust manager found"); ++ setTrustManager((X509TrustManager) trustManagers[0]); ++ } catch (NoSuchAlgorithmException e) { ++ throwError(e); ++ } catch (KeyStoreException e) { ++ throwError(e); ++ } ++ } catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ private void throwError(Exception e) throws Exception { ++ //HttpClientError error = new HttpClientError(e.getMessage()); ++ //error.initCause(e); ++ throw e; ++ } ++ public X509TrustManager getTrustManager() { ++ if (trustManager == null) ++ createTrustManager(); ++ return trustManager; ++ } ++ ++ public void setTrustManager(X509TrustManager trustManager) { ++ this.trustManager = trustManager; ++ } ++ ++ public void checkClientTrusted(X509Certificate[] chain, String authType) ++ throws CertificateException { ++ getTrustManager().checkClientTrusted(chain, authType); ++ } ++ ++ public void checkServerTrusted(X509Certificate[] chain, String authType) ++ throws CertificateException { ++ getTrustManager().checkServerTrusted(chain, authType); ++ ++ } ++ ++ public X509Certificate[] getAcceptedIssuers() { ++ return getTrustManager().getAcceptedIssuers(); ++ } ++ ++} +\ No newline at end of file +Index: invirt-vnc-client/Makefile +=================================================================== +--- invirt-vnc-client.orig/Makefile 2008-10-31 06:09:10.000000000 -0400 ++++ invirt-vnc-client/Makefile 2008-10-31 06:09:10.000000000 -0400 +@@ -17,8 +17,10 @@ + DesCipher.class CapabilityInfo.class CapsContainer.class \ + RecordingFrame.class SessionRecorder.class \ + SocketFactory.class HTTPConnectSocketFactory.class \ ++ VNCProxyConnectSocketFactory.class VNCProxyConnectSocket.class \ + HTTPConnectSocket.class ReloginPanel.class \ +- InStream.class MemInStream.class ZlibInStream.class ++ InStream.class MemInStream.class ZlibInStream.class \ ++ VNCProxyConnectSocketWrapper.class SocketWrapper.class SocketWrapper\$$WrappingSocketImpl.class InvirtTrustManager.class + + SOURCES = VncViewer.java RfbProto.java AuthPanel.java VncCanvas.java \ + VncCanvas2.java \ +@@ -26,8 +28,10 @@ + DesCipher.java CapabilityInfo.java CapsContainer.java \ + RecordingFrame.java SessionRecorder.java \ + SocketFactory.java HTTPConnectSocketFactory.java \ ++ VNCProxyConnectSocketFactory.java VNCProxyConnectSocket.java \ + HTTPConnectSocket.java ReloginPanel.java \ +- InStream.java MemInStream.java ZlibInStream.java ++ InStream.java MemInStream.java ZlibInStream.java \ ++ VNCProxyConnectSocketWrapper.java SocketWrapper.java InvirtTrustManager.java + + all: $(CLASSES) $(ARCHIVE) + +Index: invirt-vnc-client/RfbProto.java +=================================================================== +--- invirt-vnc-client.orig/RfbProto.java 2007-04-26 22:36:00.000000000 -0400 ++++ invirt-vnc-client/RfbProto.java 2008-10-31 06:09:10.000000000 -0400 +@@ -208,11 +208,13 @@ + port = p; + + if (viewer.socketFactory == null) { ++ System.out.println("Null socketFactory"); + sock = new Socket(host, port); + } else { + try { + Class factoryClass = Class.forName(viewer.socketFactory); + SocketFactory factory = (SocketFactory)factoryClass.newInstance(); ++ System.out.println("Using socketFactory " + factory); + if (viewer.inAnApplet) + sock = factory.createSocket(host, port, viewer); + else +@@ -236,7 +238,7 @@ + try { + sock.close(); + closed = true; +- System.out.println("RFB socket closed"); ++ System.out.println("RFB socket closed " + sock); + if (rec != null) { + rec.close(); + rec = null; +Index: invirt-vnc-client/SocketWrapper.java +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ invirt-vnc-client/SocketWrapper.java 2008-10-31 06:09:10.000000000 -0400 +@@ -0,0 +1,262 @@ ++/* ++ * Written by Dawid Kurzyniec and released to the public domain, as explained ++ * at http://creativecommons.org/licenses/publicdomain ++ */ ++ ++//package edu.emory.mathcs.util.net; ++ ++import java.io.*; ++import java.net.*; ++import java.nio.channels.*; ++ ++/** ++ * Wrapper for sockets which enables to add functionality in subclasses ++ * on top of existing, connected sockets. It is useful when direct subclassing ++ * of delegate socket class is not possible, e.g. if the delegate socket is ++ * created by a library. Possible usage example is socket factory chaining. ++ * This class delegates all socket-related requests to the wrapped delegate, ++ * as of JDK 1.4. ++ * ++ * @author Dawid Kurzyniec ++ * @version 1.4 ++ */ ++public abstract class SocketWrapper extends Socket { ++ ++ /** ++ * the wrapped delegate socket. ++ */ ++ protected final Socket delegate; ++ ++ /** ++ * Creates new socket wrapper for a given socket. The delegate ++ * must be connected and bound and it must not be closed. ++ * @param delegate the delegate socket to wrap ++ * @throws SocketException if the delegate socket is closed, not bound, ++ * or not connected ++ */ ++ protected SocketWrapper(Socket delegate) throws SocketException { ++ super(new WrappingSocketImpl(delegate)); ++ this.delegate = delegate; ++ System.out.println("Creating SocketWrapper $Rev$"); ++ } ++ ++ public SocketChannel getChannel() { ++ return delegate.getChannel(); ++ } ++ ++ /** ++ * Returns true, indicating that the socket is bound. ++ * ++ * @return true ++ */ ++ public boolean isBound() { ++ return true; ++ } ++ ++ public boolean isClosed() { ++ return super.isClosed() || delegate.isClosed(); ++ } ++ ++ /** ++ * Returns true, indicating that the socket is connected. ++ * ++ * @return true ++ */ ++ public boolean isConnected() { ++ return true; ++ } ++ ++ public boolean isInputShutdown() { ++ return super.isInputShutdown() || delegate.isInputShutdown(); ++ } ++ ++ public boolean isOutputShutdown() { ++ return super.isInputShutdown() || delegate.isOutputShutdown(); ++ } ++ ++ private static class WrappingSocketImpl extends SocketImpl { ++ private final Socket delegate; ++ WrappingSocketImpl(Socket delegate) throws SocketException { ++ if (delegate == null) { ++ throw new NullPointerException(); ++ } ++ if (delegate.isClosed()) { ++ throw new SocketException("Delegate server socket is closed"); ++ } ++ if (!(delegate.isBound())) { ++ throw new SocketException("Delegate server socket is not bound"); ++ } ++ if (!(delegate.isConnected())) { ++ throw new SocketException("Delegate server socket is not connected"); ++ } ++ this.delegate = delegate; ++ } ++ ++ protected void create(boolean stream) {} ++ ++ protected void connect(String host, int port) { ++ // delegate is always connected, thus this method is never called ++ throw new UnsupportedOperationException(); ++ } ++ ++ protected void connect(InetAddress address, int port) { ++ // delegate is always connected, thus this method is never called ++ throw new UnsupportedOperationException(); ++ } ++ ++ protected void connect(SocketAddress address, int timeout) { ++ // delegate is always connected, thus this method is never called ++ throw new UnsupportedOperationException(); ++ } ++ ++ protected void bind(InetAddress host, int port) { ++ // delegate is always bound, thus this method is never called ++ throw new UnsupportedOperationException(); ++ } ++ ++ protected void listen(int backlog) { ++ // this wrapper is never used by a ServerSocket ++ throw new UnsupportedOperationException(); ++ } ++ ++ protected void accept(SocketImpl s) { ++ // this wrapper is never used by a ServerSocket ++ throw new UnsupportedOperationException(); ++ } ++ ++ protected InputStream getInputStream() throws IOException { ++ return delegate.getInputStream(); ++ } ++ ++ protected OutputStream getOutputStream() throws IOException { ++ return delegate.getOutputStream(); ++ } ++ ++ protected int available() throws IOException { ++ return getInputStream().available(); ++ } ++ ++ protected void close() throws IOException { ++ System.out.println("Calling delegate.close"); ++ delegate.close(); ++ } ++ ++ protected void shutdownInput() throws IOException { ++ delegate.shutdownInput(); ++ } ++ ++ protected void shutdownOutput() throws IOException { ++ delegate.shutdownOutput(); ++ } ++ ++ protected FileDescriptor getFileDescriptor() { ++ // this wrapper is never used by a ServerSocket ++ throw new UnsupportedOperationException(); ++ } ++ ++ protected InetAddress getInetAddress() { ++ return delegate.getInetAddress(); ++ } ++ ++ protected int getPort() { ++ return delegate.getPort(); ++ } ++ ++ protected boolean supportsUrgentData() { ++ return false; // must be overridden in sub-class ++ } ++ ++ protected void sendUrgentData (int data) throws IOException { ++ delegate.sendUrgentData(data); ++ } ++ ++ protected int getLocalPort() { ++ return delegate.getLocalPort(); ++ } ++ ++ public Object getOption(int optID) throws SocketException { ++ switch (optID) { ++ case SocketOptions.IP_TOS: ++ return new Integer(delegate.getTrafficClass()); ++ case SocketOptions.SO_BINDADDR: ++ return delegate.getLocalAddress(); ++ case SocketOptions.SO_KEEPALIVE: ++ return Boolean.valueOf(delegate.getKeepAlive()); ++ case SocketOptions.SO_LINGER: ++ return new Integer(delegate.getSoLinger()); ++ case SocketOptions.SO_OOBINLINE: ++ return Boolean.valueOf(delegate.getOOBInline()); ++ case SocketOptions.SO_RCVBUF: ++ return new Integer(delegate.getReceiveBufferSize()); ++ case SocketOptions.SO_REUSEADDR: ++ return Boolean.valueOf(delegate.getReuseAddress()); ++ case SocketOptions.SO_SNDBUF: ++ return new Integer(delegate.getSendBufferSize()); ++ case SocketOptions.SO_TIMEOUT: ++ return new Integer(delegate.getSoTimeout()); ++ case SocketOptions.TCP_NODELAY: ++ return Boolean.valueOf(delegate.getTcpNoDelay()); ++ case SocketOptions.SO_BROADCAST: ++ default: ++ throw new IllegalArgumentException("Unsupported option type"); ++ } ++ } ++ ++ public void setOption(int optID, Object value) throws SocketException { ++ switch (optID) { ++ case SocketOptions.SO_BINDADDR: ++ throw new IllegalArgumentException("Socket is bound"); ++ case SocketOptions.SO_KEEPALIVE: ++ delegate.setKeepAlive(((Boolean)value).booleanValue()); ++ break; ++ case SocketOptions.SO_LINGER: ++ if (value instanceof Boolean) { ++ delegate.setSoLinger(((Boolean)value).booleanValue(), 0); ++ } ++ else { ++ delegate.setSoLinger(true, ((Integer)value).intValue()); ++ } ++ break; ++ case SocketOptions.SO_OOBINLINE: ++ delegate.setOOBInline(((Boolean)value).booleanValue()); ++ break; ++ case SocketOptions.SO_RCVBUF: ++ delegate.setReceiveBufferSize(((Integer)value).intValue()); ++ break; ++ case SocketOptions.SO_REUSEADDR: ++ delegate.setReuseAddress(((Boolean)value).booleanValue()); ++ break; ++ case SocketOptions.SO_SNDBUF: ++ delegate.setSendBufferSize(((Integer)value).intValue()); ++ break; ++ case SocketOptions.SO_TIMEOUT: ++ delegate.setSoTimeout(((Integer)value).intValue()); ++ break; ++ case SocketOptions.TCP_NODELAY: ++ delegate.setTcpNoDelay(((Boolean)value).booleanValue()); ++ break; ++ case SocketOptions.SO_BROADCAST: ++ default: ++ throw new IllegalArgumentException("Unsupported option type"); ++ } ++ } ++ } ++ ++ public void close() throws IOException { ++ System.out.println("Calling SocketWrapper.delegate.close"); ++ delegate.close(); ++ } ++ ++ public boolean equals(Object obj) { ++ if (!(obj instanceof SocketWrapper)) return false; ++ SocketWrapper that = (SocketWrapper)obj; ++ return this.delegate.equals(that.delegate); ++ } ++ ++ public int hashCode() { ++ return delegate.hashCode() ^ 0x01010101; ++ } ++ public String toString() { ++ return ""; ++ } ++} +\ No newline at end of file +Index: invirt-vnc-client/VNCProxyConnectSocket.java +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ invirt-vnc-client/VNCProxyConnectSocket.java 2008-10-31 06:09:10.000000000 -0400 +@@ -0,0 +1,61 @@ ++// ++// Copyright (C) 2002 Constantin Kaplinsky, Inc. All Rights Reserved. ++// Copyright 2007 MIT Student Information Processing Board ++// ++// This is free software; you can redistribute it and/or modify ++// it under the terms of the GNU General Public License as published by ++// the Free Software Foundation; either version 2 of the License, or ++// (at your option) any later version. ++// ++// This software is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with this software; if not, write to the Free Software ++// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, ++// USA. ++// ++ ++// ++// VNCProxySocket.java together with VNCProxySocketFactory.java ++// implement an alternate way to connect to VNC servers via one or two ++// VNCProxy proxies supporting the VNCProxy VNCCONNECT method. ++// ++ ++import java.net.*; ++import java.io.*; ++ ++class VNCProxyConnectSocket extends Socket { ++ ++ public VNCProxyConnectSocket(String host, int port, ++ String vmname, String authtoken) ++ throws IOException { ++ ++ // Connect to the specified HTTP proxy ++ super(host, port); ++ ++ // Send the CONNECT request ++ getOutputStream().write(("CONNECTVNC " + vmname + ++ " VNCProxy/1.0\r\nAuth-token: " + authtoken + ++ "\r\n\r\n").getBytes()); ++ ++ // Read the first line of the response ++ DataInputStream is = new DataInputStream(getInputStream()); ++ String str = is.readLine(); ++ ++ // Check the HTTP error code -- it should be "200" on success ++ if (!str.startsWith("VNCProxy/1.0 200 ")) { ++ if (str.startsWith("VNCProxy/1.0 ")) ++ str = str.substring(13); ++ throw new IOException("Proxy reports \"" + str + "\""); ++ } ++ ++ // Success -- skip remaining HTTP headers ++ do { ++ str = is.readLine(); ++ } while (str.length() != 0); ++ } ++} ++ +Index: invirt-vnc-client/VNCProxyConnectSocketFactory.java +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ invirt-vnc-client/VNCProxyConnectSocketFactory.java 2008-10-31 06:09:10.000000000 -0400 +@@ -0,0 +1,98 @@ ++// ++// Copyright (C) 2002 Constantin Kaplinsky, Inc. All Rights Reserved. ++// Copyright 2007 MIT Student Information Processing Board ++// ++// This is free software; you can redistribute it and/or modify ++// it under the terms of the GNU General Public License as published by ++// the Free Software Foundation; either version 2 of the License, or ++// (at your option) any later version. ++// ++// This software is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with this software; if not, write to the Free Software ++// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, ++// USA. ++// ++ ++// ++// VNCProxyConnectSocketFactory.java together with VNCProxyConnectSocket.java ++// implement an alternate way to connect to VNC servers via one or two ++// VNCProxy proxies supporting the VNCProxy CONNECT method. ++// ++ ++import java.applet.*; ++import java.net.*; ++import javax.net.ssl.*; ++import java.io.*; ++ ++class VNCProxyConnectSocketFactory implements SocketFactory { ++ ++ SSLSocketFactory factory; ++ ++ public VNCProxyConnectSocketFactory() { ++ try { ++ SSLContext c = SSLContext.getInstance("SSL"); ++ c.init(null, ++ new TrustManager[] { new InvirtTrustManager() }, ++ null); ++ factory = ++ (SSLSocketFactory)c.getSocketFactory(); ++ } catch (Exception e) { ++ e.printStackTrace(); ++ } ++ } ++ ++ public Socket createSocket(String host, int port, Applet applet) ++ throws IOException { ++ ++ return createSocket(host, port, ++ applet.getParameter("VMNAME"), ++ applet.getParameter("AUTHTOKEN")); ++ } ++ ++ public Socket createSocket(String host, int port, String[] args) ++ throws IOException { ++ ++ return createSocket(host, port, ++ readArg(args, "VMNAME"), ++ readArg(args, "AUTHTOKEN")); ++ } ++ ++ public Socket createSocket(String host, int port, ++ String vmname, String authtoken) ++ throws IOException { ++ ++ if (vmname == null || authtoken == null) { ++ System.out.println("Incomplete parameter list for VNCProxyConnectSocket"); ++ return new Socket(host, port); ++ } ++ ++ System.out.println("VNCProxy CONNECT via proxy " + host + ++ " port " + port + " to vm " + vmname); ++ SSLSocket ssls = (SSLSocket)factory.createSocket(host, port); ++ ssls.startHandshake(); ++ VNCProxyConnectSocketWrapper s = ++ new VNCProxyConnectSocketWrapper(ssls, vmname, authtoken); ++ ++ return (Socket)s; ++ } ++ ++ private String readArg(String[] args, String name) { ++ ++ for (int i = 0; i < args.length; i += 2) { ++ if (args[i].equalsIgnoreCase(name)) { ++ try { ++ return args[i+1]; ++ } catch (Exception e) { ++ return null; ++ } ++ } ++ } ++ return null; ++ } ++} ++ +Index: invirt-vnc-client/VNCProxyConnectSocketWrapper.java +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ invirt-vnc-client/VNCProxyConnectSocketWrapper.java 2008-10-31 06:09:10.000000000 -0400 +@@ -0,0 +1,60 @@ ++// ++// Copyright (C) 2002 Constantin Kaplinsky, Inc. All Rights Reserved. ++// Copyright 2007 MIT Student Information Processing Board ++// ++// This is free software; you can redistribute it and/or modify ++// it under the terms of the GNU General Public License as published by ++// the Free Software Foundation; either version 2 of the License, or ++// (at your option) any later version. ++// ++// This software is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with this software; if not, write to the Free Software ++// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, ++// USA. ++// ++ ++// ++// VNCProxySocket.java together with VNCProxySocketFactory.java ++// implement an alternate way to connect to VNC servers via one or two ++// VNCProxy proxies supporting the VNCProxy VNCCONNECT method. ++// ++ ++import java.net.*; ++import java.io.*; ++ ++class VNCProxyConnectSocketWrapper extends SocketWrapper { ++ ++ public VNCProxyConnectSocketWrapper(Socket sock, ++ String vmname, String authtoken) ++ throws IOException { ++ ++ super(sock); ++ ++ // Send the CONNECT request ++ getOutputStream().write(("CONNECTVNC " + vmname + ++ " VNCProxy/1.0\r\nAuth-token: " + authtoken + ++ "\r\n\r\n").getBytes()); ++ ++ // Read the first line of the response ++ DataInputStream is = new DataInputStream(getInputStream()); ++ String str = is.readLine(); ++ ++ // Check the HTTP error code -- it should be "200" on success ++ if (!str.startsWith("VNCProxy/1.0 200 ")) { ++ if (str.startsWith("VNCProxy/1.0 ")) ++ str = str.substring(13); ++ throw new IOException("Proxy reports \"" + str + "\""); ++ } ++ ++ // Success -- skip remaining HTTP headers ++ do { ++ str = is.readLine(); ++ } while (str.length() != 0); ++ } ++} ++