1 package com.android.hotspot2.osu.service; 2 3 import android.util.Log; 4 5 import com.android.hotspot2.osu.OSUManager; 6 import com.android.hotspot2.osu.OSUOperationStatus; 7 8 import java.io.BufferedReader; 9 import java.io.BufferedWriter; 10 import java.io.IOException; 11 import java.io.InputStreamReader; 12 import java.io.OutputStreamWriter; 13 import java.net.InetAddress; 14 import java.net.ServerSocket; 15 import java.net.Socket; 16 import java.net.URL; 17 import java.nio.charset.StandardCharsets; 18 import java.util.Random; 19 20 public class RedirectListener extends Thread { 21 private static final long ThreadTimeout = 3000L; 22 private static final long UserTimeout = 3600000L; 23 private static final int MaxRetry = 5; 24 private static final String TAG = "OSULSN"; 25 26 private static final String HTTPResponseHeader = 27 "HTTP/1.1 304 Not Modified\r\n" + 28 "Server: dummy\r\n" + 29 "Keep-Alive: timeout=500, max=5\r\n\r\n"; 30 31 private static final String GoodBye = 32 "<html>" + 33 "<head><title>Goodbye</title></head>" + 34 "<body>" + 35 "<h3>Killing browser...</h3>" + 36 "</body>" + 37 "</html>\r\n"; 38 39 private final OSUManager mOSUManager; 40 private final String mSpName; 41 private final ServerSocket mServerSocket; 42 private final String mPath; 43 private final URL mURL; 44 private final Object mLock = new Object(); 45 46 private boolean mListening; 47 private OSUOperationStatus mUserStatus; 48 private volatile boolean mAborted; 49 RedirectListener(OSUManager osuManager, String spName)50 public RedirectListener(OSUManager osuManager, String spName) throws IOException { 51 mOSUManager = osuManager; 52 mSpName = spName; 53 mServerSocket = new ServerSocket(0, 5, InetAddress.getLocalHost()); 54 Random rnd = new Random(System.currentTimeMillis()); 55 mPath = "rnd" + Integer.toString(Math.abs(rnd.nextInt()), Character.MAX_RADIX); 56 mURL = new URL("http", mServerSocket.getInetAddress().getHostAddress(), 57 mServerSocket.getLocalPort(), mPath); 58 59 Log.d(TAG, "Redirect URL: " + mURL); 60 setName("HS20-Redirect-Listener"); 61 setDaemon(true); 62 } 63 startService()64 public void startService() throws IOException { 65 start(); 66 synchronized (mLock) { 67 long bail = System.currentTimeMillis() + ThreadTimeout; 68 long remainder = ThreadTimeout; 69 while (remainder > 0 && !mListening) { 70 try { 71 mLock.wait(remainder); 72 } catch (InterruptedException ie) { 73 /**/ 74 } 75 if (mListening) { 76 break; 77 } 78 remainder = bail - System.currentTimeMillis(); 79 } 80 if (!mListening) { 81 throw new IOException("Failed to start listener"); 82 } else { 83 Log.d(TAG, "OSU Redirect listener running"); 84 } 85 } 86 } 87 waitForUser()88 public boolean waitForUser() { 89 boolean success; 90 synchronized (mLock) { 91 long bail = System.currentTimeMillis() + UserTimeout; 92 long remainder = UserTimeout; 93 while (remainder > 0 && mUserStatus == null) { 94 try { 95 mLock.wait(remainder); 96 } catch (InterruptedException ie) { 97 /**/ 98 } 99 if (mUserStatus != null) { 100 break; 101 } 102 remainder = bail - System.currentTimeMillis(); 103 } 104 success = mUserStatus == OSUOperationStatus.UserInputComplete; 105 } 106 abort(); 107 return success; 108 } 109 abort()110 public void abort() { 111 try { 112 mAborted = true; 113 mServerSocket.close(); 114 } catch (IOException ioe) { 115 /**/ 116 } 117 } 118 getURL()119 public URL getURL() { 120 return mURL; 121 } 122 123 @Override run()124 public void run() { 125 int count = 0; 126 synchronized (mLock) { 127 mListening = true; 128 mLock.notifyAll(); 129 } 130 131 boolean terminate = false; 132 133 for (; ; ) { 134 count++; 135 try (Socket instance = mServerSocket.accept()) { 136 try (BufferedReader in = new BufferedReader( 137 new InputStreamReader(instance.getInputStream(), StandardCharsets.UTF_8))) { 138 boolean detected = false; 139 StringBuilder sb = new StringBuilder(); 140 String s; 141 while ((s = in.readLine()) != null) { 142 sb.append(s).append('\n'); 143 if (!detected && s.startsWith("GET")) { 144 String[] segments = s.split(" "); 145 if (segments.length == 3 && 146 segments[2].startsWith("HTTP/") && 147 segments[1].regionMatches(1, mPath, 0, mPath.length())) { 148 detected = true; 149 } 150 } 151 if (s.length() == 0) { 152 break; 153 } 154 } 155 Log.d(TAG, "Redirect receive: " + sb); 156 String response = null; 157 if (detected) { 158 response = status(OSUOperationStatus.UserInputComplete); 159 if (response == null) { 160 response = GoodBye; 161 terminate = true; 162 } 163 } 164 try (BufferedWriter out = new BufferedWriter( 165 new OutputStreamWriter(instance.getOutputStream(), 166 StandardCharsets.UTF_8))) { 167 168 out.write(HTTPResponseHeader); 169 if (response != null) { 170 out.write(response); 171 } 172 } 173 if (terminate) { 174 break; 175 } else if (count > MaxRetry) { 176 status(OSUOperationStatus.UserInputAborted); 177 break; 178 } 179 } 180 } catch (IOException ioe) { 181 if (mAborted) { 182 return; 183 } else if (count > MaxRetry) { 184 status(OSUOperationStatus.UserInputAborted); 185 break; 186 } 187 } 188 } 189 } 190 status(OSUOperationStatus status)191 private String status(OSUOperationStatus status) { 192 Log.d(TAG, "User input status: " + status); 193 synchronized (mLock) { 194 mUserStatus = status; 195 mLock.notifyAll(); 196 } 197 String message = (status == OSUOperationStatus.UserInputAborted) ? 198 "Browser closed" : null; 199 200 return mOSUManager.notifyUser(status, message, mSpName); 201 } 202 } 203