[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
This commit is contained in:
Colin Finck 2015-07-15 18:15:33 +00:00
parent 1f52401541
commit afb3ba35d8
5 changed files with 165 additions and 60 deletions

View file

@ -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;

View file

@ -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 pwszNonspooledPortName;
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;
// 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_<PortName>" 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)
{

View file

@ -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);

View file

@ -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;
}

View file

@ -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();