From: Colin Finck Date: Wed, 15 Jul 2015 18:15:33 +0000 (+0000) Subject: [LOCALMON] X-Git-Tag: backups/colins-printing-for-freedom@73041~28 X-Git-Url: https://git.reactos.org/?p=reactos.git;a=commitdiff_plain;h=afb3ba35d86b02fbccd5e2f73852d8e41de82599 [LOCALMON] My idea to just care about COM, FILE: and LPT ports was too short-sighted. Apart from selecting a FILE: port that prompts for the output filename at printing, you can also add a port "C:\bla.txt" to always output into that particular file. Even shared network printers can be added as a local port "\\COMPUTERNAME\PrinterName" (and Windows even does that when auto-adding printers found on the network). Note that this is the exception though, shared network printers are normally handled by a different component. Our localmon now handles all valid ports found in the registry. Port name checks were modified to be extra-picky and not let any false positives happen (e.g. trying to print into a file starting with "LPT" shouldn't be treated as printing to an LPT port) svn path=/branches/colins-printing-for-freedom/; revision=68402 --- diff --git a/reactos/win32ss/printing/monitors/localmon/main.c b/reactos/win32ss/printing/monitors/localmon/main.c index e07bf6d239f..336a03a1740 100644 --- a/reactos/win32ss/printing/monitors/localmon/main.c +++ b/reactos/win32ss/printing/monitors/localmon/main.c @@ -38,6 +38,52 @@ static MONITOR2 _MonitorFunctions = { }; +/** + * @name _IsNEPort + * + * Checks if the given port name is a virtual Ne port. + * A virtual Ne port may appear in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Ports and can have the formats + * Ne00:, Ne01:, Ne-02:, Ne456: + * This check is extra picky to not cause false positives (like file name ports starting with "Ne"). + * + * @param pwszPortName + * The port name to check. + * + * @return + * TRUE if this is definitely a virtual Ne port, FALSE if not. + */ +static __inline BOOL +_IsNEPort(PCWSTR pwszPortName) +{ + PCWSTR p = pwszPortName; + + // First character needs to be 'N' (uppercase or lowercase) + if (*p != L'N' && *p != L'n') + return FALSE; + + // Next character needs to be 'E' (uppercase or lowercase) + p++; + if (*p != L'E' && *p != L'e') + return FALSE; + + // An optional hyphen may follow now. + p++; + if (*p == L'-') + p++; + + // Now an arbitrary number of digits may follow. + while (*p >= L'0' && *p <= L'9') + p++; + + // Finally, the virtual Ne port must be terminated by a colon. + if (*p != ':') + return FALSE; + + // If this is the end of the string, we have a virtual Ne port. + p++; + return (*p == L'\0'); +} + static void _LoadResources(HINSTANCE hinstDLL) { @@ -165,8 +211,16 @@ InitializePrintMonitor2(PMONITORINIT pMonitorInit, PHANDLE phMonitor) goto Cleanup; } - // This Port Monitor supports COM, FILE and LPT ports. Skip all others. - if (_wcsnicmp(pPort->pwszPortName, L"COM", 3) != 0 && _wcsicmp(pPort->pwszPortName, L"FILE:") != 0 && _wcsnicmp(pPort->pwszPortName, L"LPT", 3) != 0) + // pwszPortName can be one of the following to be valid for this Port Monitor: + // COMx: - Physical COM port + // LPTx: - Physical LPT port (or redirected one using "net use LPT1 ...") + // FILE: - Opens a prompt that asks for an output filename + // C:\bla.txt - Redirection into the file "C:\bla.txt" + // \\COMPUTERNAME\PrinterName - Redirection to a shared network printer installed as a local port + // + // We can't detect valid and invalid ones by the name, so we can only exclude empty ports and the virtual "Ne00:", "Ne01:", ... ports. + // Skip the invalid ones here. + if (!cchPortName || _IsNEPort(pPort->pwszPortName)) { DllFreeSplMem(pPort); continue; diff --git a/reactos/win32ss/printing/monitors/localmon/ports.c b/reactos/win32ss/printing/monitors/localmon/ports.c index 6ab896b2981..fda6a79af7d 100644 --- a/reactos/win32ss/printing/monitors/localmon/ports.c +++ b/reactos/win32ss/printing/monitors/localmon/ports.c @@ -17,33 +17,76 @@ static const DWORD cchNonspooledPrefix = _countof(wszNonspooledPrefix) - 1; * @name _GetNonspooledPortName * * Prepends "NONSPOOLED_" to a port name without colon. - * You have to free the returned buffer using DllFreeSplMem. * * @param pwszPortNameWithoutColon * Result of a previous GetPortNameWithoutColon call. * + * @param ppwszNonspooledPortName + * Pointer to a buffer that will contain the NONSPOOLED port name. + * You have to free this buffer using DllFreeSplMem. + * * @return - * Buffer containing the NONSPOOLED port name or NULL in case of failure. + * ERROR_SUCCESS if the NONSPOOLED port name was successfully copied into the buffer. + * ERROR_NOT_ENOUGH_MEMORY if memory allocation failed. */ -static __inline PWSTR -_GetNonspooledPortName(PCWSTR pwszPortNameWithoutColon) +static __inline DWORD +_GetNonspooledPortName(PCWSTR pwszPortNameWithoutColon, PWSTR* ppwszNonspooledPortName) { DWORD cchPortNameWithoutColon; - PWSTR pwszNonspooledPortName; cchPortNameWithoutColon = wcslen(pwszPortNameWithoutColon); - pwszNonspooledPortName = DllAllocSplMem((cchNonspooledPrefix + cchPortNameWithoutColon + 1) * sizeof(WCHAR)); - if (!pwszNonspooledPortName) + *ppwszNonspooledPortName = DllAllocSplMem((cchNonspooledPrefix + cchPortNameWithoutColon + 1) * sizeof(WCHAR)); + if (!*ppwszNonspooledPortName) { ERR("DllAllocSplMem failed with error %lu!\n", GetLastError()); - return NULL; + return ERROR_NOT_ENOUGH_MEMORY; } - CopyMemory(pwszNonspooledPortName, wszNonspooledPrefix, cchNonspooledPrefix * sizeof(WCHAR)); - CopyMemory(&pwszNonspooledPortName[cchNonspooledPrefix], pwszPortNameWithoutColon, (cchPortNameWithoutColon + 1) * sizeof(WCHAR)); + CopyMemory(*ppwszNonspooledPortName, wszNonspooledPrefix, cchNonspooledPrefix * sizeof(WCHAR)); + CopyMemory(&(*ppwszNonspooledPortName)[cchNonspooledPrefix], pwszPortNameWithoutColon, (cchPortNameWithoutColon + 1) * sizeof(WCHAR)); + + return ERROR_SUCCESS; +} + +/** + * @name _IsLegacyPort + * + * Checks if the given port name is a legacy port (COM or LPT). + * This check is extra picky to not cause false positives (like file name ports starting with "COM" or "LPT"). + * + * @param pwszPortName + * The port name to check. + * + * @param pwszPortType + * L"COM" or L"LPT" + * + * @return + * TRUE if this is definitely the asked legacy port, FALSE if not. + */ +static __inline BOOL +_IsLegacyPort(PCWSTR pwszPortName, PCWSTR pwszPortType) +{ + const DWORD cchPortType = 3; + PCWSTR p = pwszPortName; + + // The port name must begin with pwszPortType. + if (_wcsnicmp(p, pwszPortType, cchPortType) != 0) + return FALSE; + + p += cchPortType; + + // Now an arbitrary number of digits may follow. + while (*p >= L'0' && *p <= L'9') + p++; + + // Finally, the legacy port must be terminated by a colon. + if (*p != ':') + return FALSE; - return pwszNonspooledPortName; + // If this is the end of the string, we have a legacy port. + p++; + return (*p == L'\0'); } /** @@ -78,11 +121,9 @@ _ClosePortHandles(PLOCALMON_PORT pPort) pPort->pwszMapping = NULL; // Finally get the required strings and remove the DOS device definition for the NONSPOOLED port. - pwszPortNameWithoutColon = GetPortNameWithoutColon(pPort->pwszPortName); - if (pwszPortNameWithoutColon) + if (GetPortNameWithoutColon(pPort->pwszPortName, &pwszPortNameWithoutColon) == ERROR_SUCCESS) { - pwszNonspooledPortName = _GetNonspooledPortName(pwszPortNameWithoutColon); - if (pwszNonspooledPortName) + if (_GetNonspooledPortName(pwszPortNameWithoutColon, &pwszNonspooledPortName) == ERROR_SUCCESS) { DefineDosDeviceW(DDD_REMOVE_DEFINITION, pwszNonspooledPortName, NULL); DllFreeSplMem(pwszNonspooledPortName); @@ -105,8 +146,7 @@ _ClosePortHandles(PLOCALMON_PORT pPort) * @return * TRUE if a NONSPOOLED port was successfully created, FALSE otherwise. * A more specific error code can be obtained through GetLastError. - * In particular, if the return value is FALSE and GetLastError returns ERROR_SUCCESS, no NONSPOOLED port is needed, - * because no system-wide device definition is available. + * In particular, if the return value is FALSE and GetLastError returns ERROR_SUCCESS, no NONSPOOLED port is needed for this port. */ static BOOL _CreateNonspooledPort(PLOCALMON_PORT pPort) @@ -129,10 +169,16 @@ _CreateNonspooledPort(PLOCALMON_PORT pPort) PWSTR pwszPortNameWithoutColon = NULL; // We need the port name without the trailing colon. - pwszPortNameWithoutColon = GetPortNameWithoutColon(pPort->pwszPortName); - if (!pwszPortNameWithoutColon) + dwErrorCode = GetPortNameWithoutColon(pPort->pwszPortName, &pwszPortNameWithoutColon); + if (dwErrorCode == ERROR_INVALID_PARAMETER) { - dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; + // This port has no trailing colon, so we also need no NONSPOOLED mapping for it. + dwErrorCode = ERROR_SUCCESS; + goto Cleanup; + } + else if (dwErrorCode != ERROR_SUCCESS) + { + // Another unexpected failure. goto Cleanup; } @@ -222,12 +268,9 @@ _CreateNonspooledPort(PLOCALMON_PORT pPort) } // We now want to create a DOS device "NONSPOOLED_" to this mapping, so that we're able to open it through CreateFileW. - pwszNonspooledPortName = _GetNonspooledPortName(pwszPortNameWithoutColon); - if (!pwszNonspooledPortName) - { - dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; + dwErrorCode = _GetNonspooledPortName(pwszPortNameWithoutColon, &pwszNonspooledPortName); + if (dwErrorCode != ERROR_SUCCESS) goto Cleanup; - } // Delete a possibly existing NONSPOOLED device before creating the new one, so we don't stack up device definitions. DefineDosDeviceW(DDD_REMOVE_DEFINITION, pwszNonspooledPortName, NULL); @@ -493,7 +536,7 @@ LocalmonClosePort(HANDLE hPort) pPort->hPrinter = NULL; } - // Free virtual FILE ports which were created in LocalmonOpenPort. + // Free virtual FILE: ports which were created in LocalmonOpenPort. if (pPort->PortType == PortType_FILE) { EnterCriticalSection(&pPort->pLocalmon->Section); @@ -712,11 +755,8 @@ LocalmonOpenPort(HANDLE hMonitor, PWSTR pName, PHANDLE pHandle) // Even if this API is called OpenPort, port file handles aren't always opened here :-P // Windows only does this for physical LPT ports here to enable bidirectional communication with the printer outside of jobs (using ReadPort and WritePort). // The others are only opened per job in StartDocPort. - if (_wcsnicmp(pName, L"LPT", 3) == 0) + if (_IsLegacyPort(pName, L"LPT")) { - // Treat all ports as other LPT ports until we can definitely say that it's a physical one. - pPort->PortType = PortType_OtherLPT; - // Try to create a NONSPOOLED port and open it. if (_CreateNonspooledPort(pPort)) { @@ -740,9 +780,9 @@ LocalmonOpenPort(HANDLE hMonitor, PWSTR pName, PHANDLE pHandle) goto Cleanup; } } - else + else if (_IsLegacyPort(pName, L"COM")) { - // This can only be a COM port. + // COM ports can't be redirected over the network, so this is a physical one. pPort->PortType = PortType_PhysicalCOM; } } @@ -948,13 +988,17 @@ LocalmonStartDocPort(HANDLE hPort, PWSTR pPrinterName, DWORD JobId, DWORD Level, } else { - // This is a COM port or a non-physical LPT port. We open NONSPOOLED ports for these per job. + // This can be: + // - a physical COM port + // - a non-physical LPT port (e.g. with "net use LPT1 ...") + // - any other port (e.g. a file or a shared printer installed as a local port) + // + // For all these cases, we try to create a NONSPOOLED port per job. + // If _CreateNonspooledPort reports that no NONSPOOLED port is necessary, we can just open the port name. if (!_CreateNonspooledPort(pPort)) { if (GetLastError() == ERROR_SUCCESS) { - // This is a user-local instead of a system-wide port. - // Such local ports haven't been remapped by the spooler, so we can just open them. pPort->hFile = CreateFileW(pPort->pwszPortName, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, NULL); if (pPort->hFile == INVALID_HANDLE_VALUE) { diff --git a/reactos/win32ss/printing/monitors/localmon/precomp.h b/reactos/win32ss/printing/monitors/localmon/precomp.h index 2eed28fc750..6e5388de06b 100644 --- a/reactos/win32ss/printing/monitors/localmon/precomp.h +++ b/reactos/win32ss/printing/monitors/localmon/precomp.h @@ -35,8 +35,8 @@ WINE_DEFAULT_DEBUG_CHANNEL(localmon); typedef struct _LOCALMON_HANDLE { CRITICAL_SECTION Section; /** Critical Section for modifying or reading the ports. */ - LIST_ENTRY FilePorts; /** Virtual ports created for every document that's redirected to an output file. */ - LIST_ENTRY RegistryPorts; /** COM, FILE: and LPT ports loaded from the local registry. */ + LIST_ENTRY FilePorts; /** Ports created when a document is printed on FILE: and the user entered a file name. */ + LIST_ENTRY RegistryPorts; /** Valid ports loaded from the local registry. */ LIST_ENTRY XcvHandles; /** Xcv handles created with LocalmonXcvOpenPort. */ } LOCALMON_HANDLE, *PLOCALMON_HANDLE; @@ -49,10 +49,10 @@ typedef struct _LOCALMON_PORT { LIST_ENTRY Entry; enum { - PortType_FILE, /** A virtual port for redirecting the document into a file. */ + PortType_Other = 0, /** Any port that doesn't belong into the other categories (default). */ + PortType_FILE, /** A port created when a document is printed on FILE: and the user entered a file name. */ PortType_PhysicalCOM, /** A physical serial port (COM) */ - PortType_PhysicalLPT, /** A physical parallel port (LPT) */ - PortType_OtherLPT /** A non-physical parallel port (e.g. a redirected one over network using "net use LPT1 ...") */ + PortType_PhysicalLPT /** A physical parallel port (LPT) */ } PortType; BOOL bStartedDoc; /** Whether a document has been started with StartDocPort. */ @@ -99,7 +99,7 @@ BOOL WINAPI LocalmonWritePort(HANDLE hPort, PBYTE pBuffer, DWORD cbBuf, PDWORD p // tools.c BOOL DoesPortExist(PCWSTR pwszPortName); DWORD GetLPTTransmissionRetryTimeout(); -PWSTR GetPortNameWithoutColon(PCWSTR pwszPortName); +DWORD GetPortNameWithoutColon(PCWSTR pwszPortName, PWSTR* ppwszPortNameWithoutColon); // xcv.c BOOL WINAPI LocalmonXcvClosePort(HANDLE hXcv); diff --git a/reactos/win32ss/printing/monitors/localmon/tools.c b/reactos/win32ss/printing/monitors/localmon/tools.c index 4e0d36c9950..3101b898d27 100644 --- a/reactos/win32ss/printing/monitors/localmon/tools.c +++ b/reactos/win32ss/printing/monitors/localmon/tools.c @@ -124,32 +124,42 @@ Cleanup: * @name GetPortNameWithoutColon * * Most of the time, we operate on port names with a trailing colon. But some functions require the name without the trailing colon. - * This function returns the port name without the colon. You have to free the returned buffer using DllFreeSplMem. + * This function checks if the port has a trailing colon and if so, it returns the port name without the colon. * * @param pwszPortName * The port name with colon * + * @param ppwszPortNameWithoutColon + * Pointer to a PWSTR that will contain the port name without colon. + * You have to free this buffer using DllFreeSplMem. + * * @return - * Buffer containing the port name without a colon or NULL in case of failure. + * ERROR_SUCCESS if the port name without colon was successfully copied into the buffer. + * ERROR_INVALID_PARAMETER if this port name has no trailing colon. + * ERROR_NOT_ENOUGH_MEMORY if memory allocation failed. */ -PWSTR -GetPortNameWithoutColon(PCWSTR pwszPortName) +DWORD +GetPortNameWithoutColon(PCWSTR pwszPortName, PWSTR* ppwszPortNameWithoutColon) { DWORD cchPortName; - PWSTR pwszPortNameWithoutColon; - // Every port in our port list has a trailing colon, so we just need to remove the last character of the string. + // Compute the string length of pwszPortNameWithoutColon. cchPortName = wcslen(pwszPortName) - 1; - pwszPortNameWithoutColon = DllAllocSplMem((cchPortName + 1) * sizeof(WCHAR)); - if (!pwszPortNameWithoutColon) + // Check if pwszPortName really has a colon as the last character. + if (pwszPortName[cchPortName] != L':') + return ERROR_INVALID_PARAMETER; + + // It has, so allocate a buffer and copy the port name without colon into it. + *ppwszPortNameWithoutColon = DllAllocSplMem((cchPortName + 1) * sizeof(WCHAR)); + if (!*ppwszPortNameWithoutColon) { ERR("DllAllocSplMem failed with error %lu!\n", GetLastError()); - return NULL; + return ERROR_NOT_ENOUGH_MEMORY; } - CopyMemory(pwszPortNameWithoutColon, pwszPortName, cchPortName * sizeof(WCHAR)); - pwszPortNameWithoutColon[cchPortName] = 0; + CopyMemory(*ppwszPortNameWithoutColon, pwszPortName, cchPortName * sizeof(WCHAR)); + *ppwszPortNameWithoutColon[cchPortName] = 0; - return pwszPortNameWithoutColon; + return ERROR_SUCCESS; } diff --git a/reactos/win32ss/printing/monitors/localmon/xcv.c b/reactos/win32ss/printing/monitors/localmon/xcv.c index 3bb41cfeb0c..c436278f35f 100644 --- a/reactos/win32ss/printing/monitors/localmon/xcv.c +++ b/reactos/win32ss/printing/monitors/localmon/xcv.c @@ -305,12 +305,9 @@ _HandleSetDefaultCommConfig(PLOCALMON_XCV pXcv, PBYTE pInputData, PDWORD pcbOutp } // SetDefaultCommConfigW needs the port name without colon. - pwszPortNameWithoutColon = GetPortNameWithoutColon(pXcv->pwszObject); - if (!pwszPortNameWithoutColon) - { - dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; + dwErrorCode = GetPortNameWithoutColon(pXcv->pwszObject, &pwszPortNameWithoutColon); + if (dwErrorCode != ERROR_SUCCESS) goto Cleanup; - } // Switch to the SYSTEM context for setting the port configuration. hToken = RevertToPrinterSelf();