@joebigelow / wix-1 / commits / fd513889

Handle unexpected BOM when reading from stdin in wixnative.exe

In certain cases, the encoding of stdin/stdout can be changed in Windows to cause a BOM to be added on the first read of stdin. To handle the unexpected BOM, the first line read from stdin is now a "preamble" where the last character is expected to be a ":". We use the last character because a multibyte BOM value may appear before it. Also updated SAL annotations to resolve IDE warnings.

Rob Mensching committed Dec 2, 2021 at 19:57 UTC fd5138896d596a6f3226a5c9438e456d4c8c2068
7 files changed +57 -29
src/wix/WixToolset.Core.Native/Cabinet.cs
-13
@@ -57,19 +57,6 @@ namespace WixToolset.Core.Native
57 }
58
59 wixnative.Run();
60 -
61 -#if TOOD_ERROR_HANDLING
62 - catch (COMException ce)
63 - {
64 - // If we get a "the file exists" error, we must have a full temp directory - so report the issue
65 - if (0x80070050 == unchecked((uint)ce.ErrorCode))
66 - {
67 - throw new WixException(WixErrors.FullTempDirectory("WSC", Path.GetTempPath()));
68 - }
69 -
70 - throw;
71 - }
72 -#endif
60 }
61
62 /// <summary>
src/wix/WixToolset.Core.Native/WixNativeExe.cs
+3 -1
@@ -4,7 +4,6 @@ namespace WixToolset.Core.Native
4 {
5 using System;
6 using System.Collections.Generic;
7 - using System.ComponentModel;
7 using System.Diagnostics;
8 using System.IO;
9
@@ -54,6 +53,9 @@ namespace WixToolset.Core.Native
53 process.BeginOutputReadLine();
54 process.BeginErrorReadLine();
55
56 + // Send the stdin preamble.
57 + process.StandardInput.WriteLine(":");
58 +
59 if (this.stdinLines.Count > 0)
60 {
61 foreach (var line in this.stdinLines)
src/wix/wixnative/extractcab.cpp
+4 -4
@@ -2,12 +2,12 @@
2
3 #include "precomp.h"
4
5 -static HRESULT ProgressCallback(BOOL fBeginFile, LPCWSTR wzFileId, LPVOID pvContext);
5 +static HRESULT ProgressCallback(__in BOOL fBeginFile, __in_z LPCWSTR wzFileId, __in_opt LPVOID pvContext);
6
7
8 HRESULT ExtractCabCommand(
9 __in int argc,
10 - __in LPWSTR argv[]
10 + __in_ecount(argc) LPWSTR argv[]
11 )
12 {
13 HRESULT hr = E_INVALIDARG;
@@ -37,8 +37,8 @@ LExit:
37
38 static HRESULT ProgressCallback(
39 __in BOOL fBeginFile,
40 - __in LPCWSTR wzFileId,
41 - __in LPVOID /*pvContext*/
40 + __in_z LPCWSTR wzFileId,
41 + __in_opt LPVOID /*pvContext*/
42 )
43 {
44 if (fBeginFile)
src/wix/wixnative/precomp.h
+6 -4
@@ -4,6 +4,7 @@
4 #include <windows.h>
5 #include <aclapi.h>
6 #include <mergemod.h>
7 +#include <strsafe.h>
8
9 #include "dutil.h"
10 #include "conutil.h"
@@ -13,7 +14,8 @@
14 #include "cabcutil.h"
15 #include "cabutil.h"
16
16 -HRESULT SmartCabCommand(int argc, LPWSTR argv[]);
17 -HRESULT ResetAclsCommand(int argc, LPWSTR argv[]);
18 -HRESULT EnumCabCommand(int argc, LPWSTR argv[]);
19 -HRESULT ExtractCabCommand(int argc, LPWSTR argv[]);
17 +HRESULT WixNativeReadStdinPreamble();
18 +HRESULT SmartCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
19 +HRESULT ResetAclsCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
20 +HRESULT EnumCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
21 +HRESULT ExtractCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
src/wix/wixnative/resetacls.cpp
+6 -1
@@ -2,7 +2,9 @@
2
3 #include "precomp.h"
4
5 -HRESULT ResetAclsCommand(int argc, LPWSTR argv[])
5 +HRESULT ResetAclsCommand(
6 + __in int argc,
7 + __in_ecount(argc) LPWSTR argv[])
8 {
9 Unused(argc);
10 Unused(argv);
@@ -24,6 +26,9 @@ HRESULT ResetAclsCommand(int argc, LPWSTR argv[])
26 ConsoleExitOnLastError(hr, CONSOLE_COLOR_RED, "failed to initialize ACL");
27 }
28
29 + hr = WixNativeReadStdinPreamble();
30 + ExitOnFailure(hr, "failed to read stdin preamble before resetting ACLs");
31 +
32 // Reset the existing security permissions on each provided file.
33 for (;;)
34 {
src/wix/wixnative/smartcab.cpp
+10 -6
@@ -1,14 +1,15 @@
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 #include "precomp.h"
4 +#include "fileutil.h"
5
5 -static HRESULT CompressFiles(HANDLE hCab);
6 -static void __stdcall CabNamesCallback(LPWSTR wzFirstCabName, LPWSTR wzNewCabName, LPWSTR wzFileToken);
6 +static HRESULT CompressFiles(__in HANDLE hCab);
7 +static void __stdcall CabNamesCallback(__in_z LPWSTR wzFirstCabName, __in_z LPWSTR wzNewCabName, __in_z LPWSTR wzFileToken);
8
9
10 HRESULT SmartCabCommand(
11 __in int argc,
11 - __in LPWSTR argv[]
12 + __in_ecount(argc) LPWSTR argv[]
13 )
14 {
15 HRESULT hr = E_INVALIDARG;
@@ -66,6 +67,9 @@ HRESULT SmartCabCommand(
67
68 if (uiFileCount > 0)
69 {
70 + hr = WixNativeReadStdinPreamble();
71 + ExitOnFailure(hr, "failed to read stdin preamble before smartcabbing");
72 +
73 hr = CompressFiles(hCab);
74 ExitOnFailure(hr, "failed to compress files into cabinet: %ls", wzCabPath);
75 }
@@ -151,9 +155,9 @@ LExit:
155 // Second argument is name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"
156 // Third argument is the file token of the first file present in the splitting cabinet
157 static void __stdcall CabNamesCallback(
154 - __in LPWSTR wzFirstCabName,
155 - __in LPWSTR wzNewCabName,
156 - __in LPWSTR wzFileToken
158 + __in_z LPWSTR wzFirstCabName,
159 + __in_z LPWSTR wzNewCabName,
160 + __in_z LPWSTR wzFileToken
161 )
162 {
163 ConsoleWriteLine(CONSOLE_COLOR_NORMAL, "%ls\t%ls\t%ls", wzFirstCabName, wzNewCabName, wzFileToken);
src/wix/wixnative/wixnative.cpp
+28
@@ -36,3 +36,31 @@ int __cdecl wmain(int argc, LPWSTR argv[])
36 ConsoleUninitialize();
37 return HRESULT_CODE(hr);
38 }
39 +
40 +HRESULT WixNativeReadStdinPreamble()
41 +{
42 + HRESULT hr = S_OK;
43 + LPWSTR sczLine = NULL;
44 + size_t cchPreamble = 0;
45 +
46 + // Read the first line to determine if a byte-order-mark was prepended to stdin.
47 + // A byte-order-mark is not normally expected but has been seen in some CI/CD systems.
48 + // The preable is a single line with ":".
49 + hr = ConsoleReadW(&sczLine);
50 + ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to read preamble from stdin");
51 +
52 + hr = ::StringCchLengthW(sczLine, STRSAFE_MAX_CCH, &cchPreamble);
53 + ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to get length of stdin preamble");
54 +
55 + // Ensure the preamble ends with ":" and ignore anything before that (since it may be a BOM).
56 + if (!cchPreamble || sczLine[cchPreamble - 1] != L':')
57 + {
58 + hr = E_INVALIDDATA;
59 + ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "expected ':' as preamble on first line of stdin");
60 + }
61 +
62 +LExit:
63 + ReleaseStr(sczLine);
64 +
65 + return hr;
66 +}