1using System; 2using System.IO; 3using System.Net; 4using System.Net.Sockets; 5using System.Text; 6using System.Threading; 7 8namespace WindowCapture.Helpers 9{ 10 /// <summary> 11 /// Localhost bridge for the native TSF TIP (Tip/WCTip.cpp). The TIP, loaded into other apps, reads 12 /// the just-typed word + its left context via ITfRange and POSTs them here; we run the SAME brain 13 /// as live typing (TextProcessor.CorrectWordForTip) and return the corrected word, which the TIP 14 /// writes back IN PLACE. Uses a plain TcpListener on 127.0.0.1 (no HttpListener urlacl/admin needed) 15 /// and a tiny hand-rolled HTTP/1.1 reply. Off unless Settings.TsfBridge is enabled. Never throws out. 16 /// 17 /// Request (POST /correctword): body = "word\nleftContext" (UTF-8). 18 /// Response (200): body = corrected word (UTF-8); equals the input word when no correction applies. 19 /// </summary> 20 public static class TipBridge 21 { 22 public const int Port = 8766; // MUST match BRAIN_PORT in Tip/WCTip.cpp 23 24 private static readonly object gate = new object(); 25 private static TcpListener listener; 26 private static Thread thread; 27 private static volatile bool running; 28 29 public static bool IsRunning { get { return running; } } 30 31 public static void Start() 32 { 33 lock (gate) 34 { 35 if (running) return; 36 try 37 { 38 listener = new TcpListener(IPAddress.Loopback, Port); 39 listener.Start(); 40 running = true; 41 thread = new Thread(Loop) { IsBackground = true }; 42 thread.Start(); 43 } 44 catch { running = false; try { if (listener != null) listener.Stop(); } catch { } listener = null; } 45 } 46 } 47 48 public static void Stop() 49 { 50 lock (gate) 51 { 52 running = false; 53 try { if (listener != null) listener.Stop(); } catch { } 54 listener = null; 55 } 56 } 57 58 private static void Loop() 59 { 60 while (running) 61 { 62 TcpClient client = null; 63 try { client = listener.AcceptTcpClient(); } 64 catch { if (!running) break; else continue; } 65 try { Handle(client); } 66 catch { } 67 finally { try { if (client != null) client.Close(); } catch { } } 68 } 69 } 70 71 private static void Handle(TcpClient client) 72 { 73 client.ReceiveTimeout = 2000; 74 client.SendTimeout = 2000; 75 using (var ns = client.GetStream()) 76 { 77 var buf = new byte[8192]; 78 var ms = new MemoryStream(); 79 int headerEnd = -1; 80 int contentLen = -1; 81 while (true) 82 { 83 int n = ns.Read(buf, 0, buf.Length); 84 if (n <= 0) break; 85 ms.Write(buf, 0, n); 86 if (headerEnd < 0) 87 { 88 byte[] cur = ms.ToArray(); 89 headerEnd = IndexOfDoubleCrlf(cur); 90 if (headerEnd >= 0) 91 contentLen = ParseContentLength(Encoding.ASCII.GetString(cur, 0, headerEnd)); 92 } 93 if (headerEnd >= 0) 94 { 95 long bodyHave = ms.Length - (headerEnd + 4); 96 if (contentLen < 0 || bodyHave >= contentLen) break; 97 } 98 if (ms.Length > 65536) break; // sanity cap 99 } 100 101 byte[] all = ms.ToArray(); 102 string body = ""; 103 if (headerEnd >= 0) 104 { 105 int start = headerEnd + 4; 106 int len = (contentLen >= 0) ? Math.Min(contentLen, all.Length - start) : all.Length - start; 107 if (len > 0) body = Encoding.UTF8.GetString(all, start, len); 108 } 109 110 string wordPart = body, ctxPart = ""; 111 int nl = body.IndexOf('\n'); 112 if (nl >= 0) { wordPart = body.Substring(0, nl); ctxPart = body.Substring(nl + 1); } 113 wordPart = wordPart.Trim('\r', '\n', ' '); 114 115 string corrected = wordPart; 116 try { corrected = TextProcessor.CorrectWordForTip(wordPart, ctxPart); } 117 catch { corrected = wordPart; } 118 if (corrected == null) corrected = wordPart; 119 120 byte[] respBody = Encoding.UTF8.GetBytes(corrected); 121 var sb = new StringBuilder(); 122 sb.Append("HTTP/1.1 200 OK\r\n"); 123 sb.Append("Content-Type: text/plain; charset=utf-8\r\n"); 124 sb.Append("Content-Length: ").Append(respBody.Length).Append("\r\n"); 125 sb.Append("Connection: close\r\n\r\n"); 126 byte[] head = Encoding.ASCII.GetBytes(sb.ToString()); 127 ns.Write(head, 0, head.Length); 128 ns.Write(respBody, 0, respBody.Length); 129 ns.Flush(); 130 } 131 } 132 133 private static int IndexOfDoubleCrlf(byte[] b) 134 { 135 for (int i = 0; i + 3 < b.Length; i++) 136 if (b[i] == 13 && b[i + 1] == 10 && b[i + 2] == 13 && b[i + 3] == 10) return i; 137 return -1; 138 } 139 140 private static int ParseContentLength(string headers) 141 { 142 foreach (var line in headers.Split('\n')) 143 { 144 string t = line.Trim(); 145 if (t.ToLower().StartsWith("content-length:")) 146 { 147 int v; 148 if (int.TryParse(t.Substring("content-length:".Length).Trim(), out v)) return v; 149 } 150 } 151 return -1; 152 } 153 } 154}