diff --git a/nmf-view/Properties/AssemblyInfo.cs b/nmf-view/Properties/AssemblyInfo.cs index beee439..b023147 100644 --- a/nmf-view/Properties/AssemblyInfo.cs +++ b/nmf-view/Properties/AssemblyInfo.cs @@ -10,7 +10,16 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] -[assembly: AssemblyVersion("1.0.7.0")] +[assembly: AssemblyVersion("1.1.0.1")] + +// v1.1.0.1 +// Allow 0-byte messages + +// v1.1 (underway) +// Add support for watching std_err + +// v1.0.8 +// More info about filetype of handles // v1.0.7 // Display the invoking process' name and PID diff --git a/nmf-view/Utilities.cs b/nmf-view/Utilities.cs index 09820f1..7d95857 100644 --- a/nmf-view/Utilities.cs +++ b/nmf-view/Utilities.cs @@ -14,6 +14,39 @@ class Utilities [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsUserAnAdmin(); + [StructLayout(LayoutKind.Sequential)] + public struct STARTUPINFO + { + public uint cb; + public string lpReserved; + public string lpDesktop; + public string lpTitle; + public Int32 dwX; + public Int32 dwY; + public Int32 dwXSize; + public Int32 dwYSize; + public Int32 dwXCountChars; + public Int32 dwYCountChars; + public Int32 dwFillAttribute; + public Int32 dwFlags; + public Int16 wShowWindow; + public Int16 cbReserved2; + public IntPtr lpReserved2; + public IntPtr hStdInput; + public IntPtr hStdOutput; + public IntPtr hStdError; + } + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern void GetStartupInfo(out STARTUPINFO lpStartupInfo); + + public static string DescribeStartupHandles() + { + STARTUPINFO si; + GetStartupInfo(out si); // dwFlags == 0x400 is STARTF_HASSHELLDATA, meaning stdout is actually a monitor handle, see GetMonitorInfoA. + return $"GetStartupInfo() says dwFlags=0x{si.dwFlags:x}, {((si.dwFlags & 0x100)==0x100 ? "in" : "ex")}cludes STARTF_USESTDHANDLES; stdin=0x{si.hStdInput.ToInt64():x}; stdout=0x{si.hStdOutput.ToInt64():x}; stderr=0x{si.hStdError.ToInt64():x}."; + } + public static bool DenyProcessTermination() { try diff --git a/nmf-view/frmMain.Designer.cs b/nmf-view/frmMain.Designer.cs index dea1763..9baf67e 100644 --- a/nmf-view/frmMain.Designer.cs +++ b/nmf-view/frmMain.Designer.cs @@ -56,6 +56,7 @@ private void InitializeComponent() this.scInjector = new System.Windows.Forms.SplitContainer(); this.btnSendToApp = new System.Windows.Forms.Button(); this.txtSendToApp = new System.Windows.Forms.TextBox(); + this.btnPokeStdErr = new System.Windows.Forms.Button(); this.txtSendToExtension = new System.Windows.Forms.TextBox(); this.btnSendToExtension = new System.Windows.Forms.Button(); this.pageTroubleshooter = new System.Windows.Forms.TabPage(); @@ -297,7 +298,7 @@ private void InitializeComponent() this.label2.AutoSize = true; this.label2.Location = new System.Drawing.Point(5, 32); this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(1218, 32); + this.label2.Size = new System.Drawing.Size(1217, 32); this.label2.TabIndex = 0; this.label2.Text = "NOT YET IMPLEMENTED (Eventually, select which NativeMessagingHosts this applicati" + "on should intercept/proxy.)"; @@ -327,6 +328,7 @@ private void InitializeComponent() // // scInjector.Panel2 // + this.scInjector.Panel2.Controls.Add(this.btnPokeStdErr); this.scInjector.Panel2.Controls.Add(this.txtSendToExtension); this.scInjector.Panel2.Controls.Add(this.btnSendToExtension); this.scInjector.Size = new System.Drawing.Size(1463, 812); @@ -359,6 +361,17 @@ private void InitializeComponent() this.txtSendToApp.Text = "{\"example_1\": \"to nmh.exe\", \"field_2\": 2, \"field_3\":false}"; this.txtSendToApp.TextChanged += new System.EventHandler(this.txtSendToApp_TextChanged); // + // btnPokeStdErr + // + this.btnPokeStdErr.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.btnPokeStdErr.Location = new System.Drawing.Point(8, 345); + this.btnPokeStdErr.Name = "btnPokeStdErr"; + this.btnPokeStdErr.Size = new System.Drawing.Size(324, 41); + this.btnPokeStdErr.TabIndex = 5; + this.btnPokeStdErr.Text = "Poke StdErr"; + this.btnPokeStdErr.UseVisualStyleBackColor = true; + this.btnPokeStdErr.Click += new System.EventHandler(this.btnPokeStdErr_Click); + // // txtSendToExtension // this.txtSendToExtension.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) @@ -473,7 +486,7 @@ private void InitializeComponent() this.lblVersion.AutoSize = true; this.lblVersion.Location = new System.Drawing.Point(39, 113); this.lblVersion.Name = "lblVersion"; - this.lblVersion.Size = new System.Drawing.Size(94, 32); + this.lblVersion.Size = new System.Drawing.Size(93, 32); this.lblVersion.TabIndex = 2; this.lblVersion.Text = "v0.0.0.0"; // @@ -577,5 +590,6 @@ private void InitializeComponent() private System.Windows.Forms.TextBox txtSendToApp; private System.Windows.Forms.TabPage pageTroubleshooter; private System.Windows.Forms.RichTextBox rtbTroubleshoot; + private System.Windows.Forms.Button btnPokeStdErr; } } \ No newline at end of file diff --git a/nmf-view/frmMain.cs b/nmf-view/frmMain.cs index 3683ce3..16b6e18 100644 --- a/nmf-view/frmMain.cs +++ b/nmf-view/frmMain.cs @@ -30,20 +30,36 @@ enum FileType : uint FileTypePipe = 0x0003, FileTypeRemote = 0x8000, FileTypeUnknown = 0x0000, + FileTypeUnknownError = 0xFFFE, + FileTypeUnknownHandleInvalid = 0xFFFF, } const int STD_INPUT_HANDLE = -10; const int STD_OUTPUT_HANDLE = -11; const int STD_ERROR_HANDLE = -12; -// [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] -// static extern bool FreeConsole(); + [DllImport("kernel32.dll", SetLastError = true)] + static extern FileType GetFileType(IntPtr hFile); + FileType GetFileType2(IntPtr hFile) + { + FileType ftResult = GetFileType(hFile); + + if (ftResult != FileType.FileTypeUnknown) return ftResult; + int iError = Marshal.GetLastWin32Error(); + if (0 == iError /* S_OK */) return FileType.FileTypeUnknown; + if (6 == iError /* ERROR_INVALID_HANDLE */) + { + return FileType.FileTypeUnknownHandleInvalid; + } + return FileType.FileTypeUnknownError; + } + + // [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] + // static extern bool FreeConsole(); private const int SW_SHOW = 5; [DllImport("User32")] private static extern int ShowWindow(int hwnd, int nCmdShow); - [DllImport("kernel32.dll")] - static extern FileType GetFileType(IntPtr hFile); [DllImport("Kernel32.dll", SetLastError = true)] static extern IntPtr GetStdHandle(int nStdHandle); @@ -79,6 +95,8 @@ struct app_state public Stream strmToApp; public Stream strmFromExt; public Stream strmToExt; + //public Stream strmErrToExt; + public Stream strmErrFromApp; public Process procParent; } @@ -91,6 +109,7 @@ private static async Task WriteToApp(string sMessage) { try { + if (null == oSettings.strmToApp) return; byte[] arrPayload = Encoding.UTF8.GetBytes(sMessage); byte[] arrSize = BitConverter.GetBytes((UInt32)arrPayload.Length); await oSettings.strmToApp.WriteAsync(arrSize, 0, 4); @@ -257,6 +276,7 @@ private void detachApp() log("Detaching NativeHost App pipes."); if (null != oSettings.strmToApp) { oSettings.strmToApp.Close(); oSettings.strmToApp = null; } if (null != oSettings.strmFromApp) { oSettings.strmFromApp.Close(); oSettings.strmFromApp = null; } + if (null != oSettings.strmErrFromApp) { oSettings.strmErrFromApp.Close(); oSettings.strmErrFromApp = null; } log("NativeHost App pipes detached."); markAppDetached(); if (oSettings.bPropagateClosures) detachExtension(); @@ -296,7 +316,7 @@ private async Task MessageShufflerForExtension() if (cbBodyPromised >= Int32.MaxValue) { - log("Was promised a message >=2gb. Technically this is legal but this app only allows 2GB due to .NET Framework size limits."); + log("Was promised a message >=2gb. Technically this is legal, but this debugger only allows 2GB due to .NET Framework size limits."); detachExtension(); return; } @@ -321,6 +341,7 @@ private async Task MessageShufflerForExtension() if (oSettings.bSendToFiddler) { + log("Forwarding message to Fiddler..."); try { HttpContent entity = new ByteArrayContent(buffer); @@ -350,16 +371,39 @@ private async Task MessageShufflerForExtension() log($"!!! ERROR: JSON Parsing failed at offset {oErrors.iErrorIndex} {oErrors.sWarningText}. Note:Strings must be double-quoted."); } - if (oSettings.bReflectToExtension && - (sMessage.Length < (1024 * 1024))) // Don't reflect messages over 1mb. They're illegal! + if (oSettings.bReflectToExtension) { - await WriteToExtension(sMessage); + // Don't reflect messages over 1mb. They're illegal!) + if (sMessage.Length < (1024 * 1024)) + { + log("Reflecting message to extension..."); + await WriteToExtension(sMessage); + } + else log("!! Message was over 1mb and must not be reflected !!"); } - if (null != oSettings.strmToApp) + await WriteToApp(sMessage); + } + } + + /// + /// This function sits around waiting for messages from the browser extension. + /// + private async Task WatchStdErrFromApp() + { + //if (null == oSettings.strmErrFromApp) return; + byte[] arrErrString = new byte[1024]; + + while (true) + { + int cbThisRead = await oSettings.strmErrFromApp.ReadAsync(arrErrString, 0, arrErrString.Length, ctsApp.Token); + if (cbThisRead < 1) { - await WriteToApp(sMessage); + return; } + //MaybeWriteBytesToLogfile("-RawRead: ", arrLenBytes, cbSizeRead, cbThisRead); + string sMessage = Encoding.UTF8.GetString(arrErrString, 0, (int)cbThisRead); + log("App wrote Std_Err: " + sMessage, false); } } @@ -534,6 +578,8 @@ private void frmMain_Load(object sender, EventArgs e) log($"I am{sExtraInfo}, launched by [{((null != oSettings.procParent) ? (oSettings.procParent.ProcessName + ':' + oSettings.procParent.Id) : "unknown")}]."); lblVersion.Text = $"v{Application.ProductVersion} [{((8 == IntPtr.Size) ? "64" : "32")}-bit]"; Text += sExtraInfo; // Append extra info to form caption. + log(Utilities.DescribeStartupHandles()); + log(DescribeStandardHandles()); var arrArgs = Environment.GetCommandLineArgs(); if (arrArgs.Length > 1) oSettings.sExtensionID = arrArgs[1]; @@ -584,17 +630,9 @@ private void WaitForMessages() { try { - var hIn = GetStdHandle(STD_INPUT_HANDLE); - var hInType = GetFileType(hIn); - var hOut = GetStdHandle(STD_OUTPUT_HANDLE); - var hOutType = GetFileType(hOut); - var hErr = GetStdHandle(STD_ERROR_HANDLE); - var hErrType = GetFileType(hErr); + log(DescribeStandardHandles()); oSettings.strmFromExt = Console.OpenStandardInput(); oSettings.strmToExt = Console.OpenStandardOutput(); - log($"Attached stdin (0x{hIn.ToInt64():x}, {hInType}) and " + - $"stdout (0x{hOut.ToInt64():x}, {hOutType}) streams."); - log($"Not using stderr (0x{hErr.ToInt64():x}, {hErrType})."); pbExt.BackColor = Color.FromArgb(159, 255, 159); Task.Run(async () => await MessageShufflerForExtension()); } @@ -604,6 +642,19 @@ private void WaitForMessages() } } + private string DescribeStandardHandles() + { + var hIn = GetStdHandle(STD_INPUT_HANDLE); + var hInType = GetFileType2(hIn); + var hOut = GetStdHandle(STD_OUTPUT_HANDLE); + var hOutType = GetFileType2(hOut); + var hErr = GetStdHandle(STD_ERROR_HANDLE); + var hErrType = GetFileType2(hErr); + return ($"GetStdHandle() says stdin=(0x{hIn.ToInt64():x}, {hInType}); " + + $"stdout=(0x{hOut.ToInt64():x}, {hOutType}); " + + $"stderr=(0x{hErr.ToInt64():x}, {hErrType})."); + } + private void clbOptions_ItemCheck(object sender, ItemCheckEventArgs e) { if (e.Index == 0) { oSettings.bReflectToExtension = (e.NewValue == CheckState.Checked); return; } @@ -714,14 +765,15 @@ private bool ConnectApp(string sFilename) myProcess.StartInfo.UseShellExecute = false; myProcess.StartInfo.WorkingDirectory = Path.GetDirectoryName(myProcess.StartInfo.FileName); - // Hide by default - // TODO: allow showing https://source.chromium.org/chromium/chromium/src/+/main:base/process/launch_win.cc;l=298;drc=1ad438dde6b39e1c0d04b8f8cb27c1a14ba6f90e + // TODO: If the compat hack lands for Chrome, then we should use the same logic here to show GUI if the app targets SUBSYSTEM_WINDOWS + // https://weblogs.asp.net/whaggard/223020 + // https://source.chromium.org/chromium/chromium/src/+/main:base/process/launch_win.cc;l=298;drc=1ad438dde6b39e1c0d04b8f8cb27c1a14ba6f90e myProcess.StartInfo.CreateNoWindow = true; myProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; // Does this do anything? myProcess.StartInfo.RedirectStandardInput = true; myProcess.StartInfo.RedirectStandardOutput = true; - // TODO: STDERR? + myProcess.StartInfo.RedirectStandardError = true; try { @@ -741,8 +793,10 @@ private bool ConnectApp(string sFilename) // https://docs.microsoft.com/en-us/dotnet/api/system.console?view=net-5.0#Streams oSettings.strmToApp = myProcess.StandardInput.BaseStream; oSettings.strmFromApp = myProcess.StandardOutput.BaseStream; + oSettings.strmErrFromApp = myProcess.StandardError.BaseStream; log($"Started {oSettings.sExeName} as the proxied NativeMessagingHost."); Task.Run(async () => await MessageShufflerForApp()); + Task.Run(async () => await WatchStdErrFromApp()); return true; } } @@ -952,12 +1006,12 @@ private void frmMain_FormClosed(object sender, FormClosedEventArgs e) private void txtSendToApp_TextChanged(object sender, EventArgs e) { - btnSendToApp.Enabled = ((txtSendToApp.TextLength > 0) && IsAppAttached()); + btnSendToApp.Enabled = (/*(txtSendToApp.TextLength > 0) && */ IsAppAttached()); } private void txtSendToExtension_TextChanged(object sender, EventArgs e) { - btnSendToExtension.Enabled = ((txtSendToExtension.TextLength > 0) && IsExtensionAttached()); + btnSendToExtension.Enabled = (/*(txtSendToExtension.TextLength > 0) && */ IsExtensionAttached()); } private async void btnSendToApp_Click(object sender, EventArgs e) @@ -1001,5 +1055,10 @@ private void frmMain_KeyDown(object sender, KeyEventArgs e) } } } + + private void btnPokeStdErr_Click(object sender, EventArgs e) + { + Console.Error.WriteLine("Poking StdErr @" + DateTime.Now.ToString()); + } } }