/*++ Copyright (c) 1996 Microsoft Corporation Module Name: parseini.c Abstract: Process model-specific printer INI file, if any Environment: Windows NT printer driver Revision History: 01/22/97 -davidx- Allow ANSI-format INI file as well. 01/21/97 -davidx- Created it. --*/ #include "lib.h" #define INI_FILENAME_EXT TEXT(".INI") #define INI_COMMENT_CHAR '#' #define OEMFILES_SECTION "[OEMFiles]" #define INI_COMMENT_CHAR_UNICODE L'#' #define OEMFILES_SECTION_UNICODE L"[OEMFiles]" DWORD DwCopyAnsiCharsToUnicode( PSTR pstr, DWORD dwLength, PWSTR pwstr) /*++ Routine Description: Convert the specified ANSI source string into a Unicode string. It doesn't assume the ANSI source string is NULL terminated. Arguments: pstr - Points to the ANSI source string dwLength - Specifies number of bytes in the ANSI source string pwstr - Points to the buffer where the resulting Unicode string is returned Return Value: Number of wide characters written to the buffer pointed to by pwstr --*/ { ULONG ulBytesWritten; #if !defined(KERNEL_MODE) || defined(USERMODE_DRIVER) return MultiByteToWideChar(CP_ACP, 0, pstr, dwLength, pwstr, dwLength); #else // NT4 kernel mode render module EngMultiByteToUnicodeN(pwstr, dwLength*sizeof(WCHAR), &ulBytesWritten, (PCHAR)pstr, dwLength); return (DWORD)(ulBytesWritten / sizeof(WCHAR)); #endif } PWSTR PwstrParsePrinterIniFileW( PWSTR pwstrFileData, DWORD dwCharCount, PDWORD pdwReturnSize ) /*++ Routine Description: Parse a model-specific printer INI file (Unicode text) and assemble all key=value entries into MultiSZ string pairs Arguments: pwstrFileData - Points to printer INI file data (Unicode text file) dwCharCount - Size of printer INI file (in characters) pdwReturnSize - Return size of the parsed MultiSZ data (in bytes) Return Value: Pointer to parsed MultiSZ data, NULL if there is an error --*/ { PWSTR pwstrCurLine, pwstrNextLine; PWSTR pwstrLineEnd, pwstrFileEnd; PWSTR pwstrEqual, pwstrResult, pwstr; DWORD dwLength; BOOL bOEMFilesSection = FALSE; // // Allocate a buffer to hold the parsed data. // We ask for a size equaling to that of the original file. // This may be a little redundant but it's better than // having to go through the data twice. // *pdwReturnSize = 0; if (! (pwstrResult = MemAlloc(sizeof(WCHAR) * (dwCharCount + 2)))) { ERR(("Memory allocation failed\n")); return NULL; } pwstr = pwstrResult; pwstrFileEnd = pwstrFileData + dwCharCount; for (pwstrCurLine = pwstrFileData; pwstrCurLine < pwstrFileEnd; pwstrCurLine = pwstrNextLine) { // // Find the end of current line and // the beginning of next line // pwstrLineEnd = pwstrCurLine; while (pwstrLineEnd < pwstrFileEnd && *pwstrLineEnd != L'\r' && *pwstrLineEnd != L'\n') { pwstrLineEnd++; } pwstrNextLine = pwstrLineEnd; while ((pwstrNextLine < pwstrFileEnd) && (*pwstrNextLine == L'\r' || *pwstrNextLine == L'\n')) { pwstrNextLine++; } // // Throw away leading and trailing spaces // and ignore empty and comment lines // while (pwstrCurLine < pwstrLineEnd && iswspace(*pwstrCurLine)) pwstrCurLine++; while (pwstrLineEnd > pwstrCurLine && iswspace(pwstrLineEnd[-1])) pwstrLineEnd--; if (pwstrCurLine >= pwstrLineEnd || *pwstrCurLine == INI_COMMENT_CHAR_UNICODE) continue; // // Handle [section] entries // if (*pwstrCurLine == L'[') { dwLength = (DWORD)(pwstrLineEnd - pwstrCurLine); bOEMFilesSection = dwLength == wcslen(OEMFILES_SECTION_UNICODE) && _wcsnicmp(pwstrCurLine, OEMFILES_SECTION_UNICODE, dwLength) == EQUAL_STRING; if (! bOEMFilesSection) TERSE(("[Section] entry ignored\n")); continue; } // // Ignore all entries outside of [OEMFiles] section // if (! bOEMFilesSection) { TERSE(("Entries outside of [OEMFiles] section ignored\n")); continue; } // // Find the first occurrence of = character // pwstrEqual = pwstrCurLine; while (pwstrEqual < pwstrLineEnd && *pwstrEqual != L'=') pwstrEqual++; if (pwstrEqual >= pwstrLineEnd || pwstrEqual == pwstrCurLine) { WARNING(("Entry not in the form of key=value\n")); continue; } // // Add the key/value pair to the result buffer // if ((dwLength = (DWORD)(pwstrEqual - pwstrCurLine)) != 0) { CopyMemory(pwstr, pwstrCurLine, dwLength*sizeof(WCHAR)); pwstr += dwLength; } *pwstr++ = NUL; pwstrEqual++; if ((dwLength = (DWORD)(pwstrLineEnd - pwstrEqual)) > 0) { CopyMemory(pwstr, pwstrEqual, dwLength*sizeof(WCHAR)); pwstr += dwLength; } *pwstr++ = NUL; } *pwstr++ = NUL; *pdwReturnSize =(DWORD)((pwstr - pwstrResult) * sizeof(WCHAR)); return pwstrResult; } PWSTR PwstrParsePrinterIniFileA( PSTR pstrFileData, DWORD dwCharCount, PDWORD pdwReturnSize ) /*++ Routine Description: Parse a model-specific printer INI file (ANSI text) and assemble all key=value entries into MultiSZ string pairs Arguments: pstrFileData - Points to printer INI file data (ANSI text file) dwCharCount - Size of printer INI file (in characters) pdwReturnSize - Return size of the parsed MultiSZ data (in bytes) Return Value: Pointer to parsed MultiSZ data, NULL if there is an error --*/ { PSTR pstrCurLine, pstrNextLine; PSTR pstrLineEnd, pstrFileEnd, pstrEqual; PWSTR pwstrResult, pwstr; DWORD dwLength; BOOL bOEMFilesSection = FALSE; // // Allocate a buffer to hold the parsed data. // We ask for a size equaling to that of the original file. // This may be a little redundant but it's better than // having to go through the data twice. // *pdwReturnSize = 0; if (! (pwstrResult = MemAlloc(sizeof(WCHAR) * (dwCharCount + 2)))) { ERR(("Memory allocation failed\n")); return NULL; } pwstr = pwstrResult; pstrFileEnd = pstrFileData + dwCharCount; for (pstrCurLine = pstrFileData; pstrCurLine < pstrFileEnd; pstrCurLine = pstrNextLine) { // // Find the end of current line and // the beginning of next line // pstrLineEnd = pstrCurLine; while (pstrLineEnd < pstrFileEnd && *pstrLineEnd != '\r' && *pstrLineEnd != '\n') { pstrLineEnd++; } pstrNextLine = pstrLineEnd; while ((pstrNextLine < pstrFileEnd) && (*pstrNextLine == '\r' || *pstrNextLine == '\n')) { pstrNextLine++; } // // Throw away leading and trailing spaces // and ignore empty and comment lines // while (pstrCurLine < pstrLineEnd && isspace(*pstrCurLine)) pstrCurLine++; while (pstrLineEnd > pstrCurLine && isspace(pstrLineEnd[-1])) pstrLineEnd--; if (pstrCurLine >= pstrLineEnd || *pstrCurLine == INI_COMMENT_CHAR) continue; // // Handle [section] entries // if (*pstrCurLine == '[') { dwLength = (DWORD)(pstrLineEnd - pstrCurLine); bOEMFilesSection = dwLength == strlen(OEMFILES_SECTION) && _strnicmp(pstrCurLine, OEMFILES_SECTION, dwLength) == EQUAL_STRING; if (! bOEMFilesSection) TERSE(("[Section] entry ignored\n")); continue; } // // Ignore all entries outside of [OEMFiles] section // if (! bOEMFilesSection) { TERSE(("Entries outside of [OEMFiles] section ignored\n")); continue; } // // Find the first occurrence of = character // pstrEqual = pstrCurLine; while (pstrEqual < pstrLineEnd && *pstrEqual != '=') pstrEqual++; if (pstrEqual >= pstrLineEnd || pstrEqual == pstrCurLine) { WARNING(("Entry not in the form of key=value\n")); continue; } // // Add the key/value pair to the result buffer // Convert ANSI chars to Unicode chars using system default code page // if ((dwLength = (DWORD)(pstrEqual - pstrCurLine)) != 0) pwstr += DwCopyAnsiCharsToUnicode(pstrCurLine, dwLength, pwstr); *pwstr++ = NUL; pstrEqual++; if ((dwLength = (DWORD)(pstrLineEnd - pstrEqual)) != 0) pwstr += DwCopyAnsiCharsToUnicode(pstrEqual, dwLength, pwstr); *pwstr++ = NUL; } *pwstr++ = NUL; *pdwReturnSize = (DWORD)((pwstr - pwstrResult) * sizeof(WCHAR)); return pwstrResult; } BOOL BProcessPrinterIniFile( HANDLE hPrinter, PDRIVER_INFO_3 pDriverInfo3, PTSTR *ppParsedData, DWORD dwFlags ) /*++ Routine Description: Process model-specific printer INI file, if any Arguments: hPrinter - Handle to a local printer, with admin access pDriverInfo3 - Printer driver info level 3 ppParsedData - output buffer to return ini file content dwFlags - FLAG_INIPROCESS_UPGRADE is set if the printer is being upgraded Return Value: TRUE if successful, FALSE if there is an error --*/ { PCWSTR pwstrIniFilename; // the .INI file with latest LastWrite time PCWSTR pwstrCurFilename; // the current .INI file we see in the list PWSTR pwstrExtension; PWSTR pwstrParsedData; DWORD dwParsedDataSize; BOOL bResult = TRUE; // // Find INI filename associated with the printer driver // #if !defined(WINNT_40) || !defined(KERNEL_MODE) pwstrIniFilename = PtstrSearchDependentFileWithExtension(pDriverInfo3->pDependentFiles, INI_FILENAME_EXT); // // We only need to do .INI FileTime comparison if there are // more than one .INI file in the dependent file list. // if (pwstrIniFilename && PtstrSearchDependentFileWithExtension(pwstrIniFilename + wcslen(pwstrIniFilename) + 1, INI_FILENAME_EXT)) { FILETIME ftLatest = {0, 0}; pwstrIniFilename = NULL; pwstrCurFilename = pDriverInfo3->pDependentFiles; while (pwstrCurFilename = PtstrSearchDependentFileWithExtension(pwstrCurFilename, INI_FILENAME_EXT)) { HANDLE hFile; hFile = CreateFile(pwstrCurFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS, NULL); if (hFile != INVALID_HANDLE_VALUE) { FILETIME ftCurrent = {0, 0}; if (GetFileTime(hFile, NULL, NULL, &ftCurrent)) { // // If it's the first .INI file we encountered, we just // neeed to remember its file name and time as the latest. // // Otherwise, we need to compare its file time with the // latest .INI file time we've seen before and choose the // newer file as the latest. // if ((pwstrIniFilename == NULL) || (CompareFileTime(&ftCurrent, &ftLatest) == 1)) { ftLatest = ftCurrent; pwstrIniFilename = pwstrCurFilename; } } else { ERR(("GetFileTime failed: %d\n", GetLastError())); } CloseHandle(hFile); } else { ERR(("CreateFile failed: %d\n", GetLastError())); } pwstrCurFilename += wcslen(pwstrCurFilename) + 1; } } #else // NT4 kernel mode only // // In point-and-print case, the client may not have admin // priviledge to write to server's registry, and in NT4 // kernel mode we can't get the printer's DriverInfo3 for // the dependent file list, so in order to allow us get // the plugin info, we require following: // // If the printer's data file is named XYZ.PPD/XYZ.GPD, and // the printer has plugins, then it must use file named XYZ.INI // to specify its plugin info. // if ((pwstrIniFilename = DuplicateString(pDriverInfo3->pDataFile)) == NULL || (pwstrExtension = wcsrchr(pwstrIniFilename, TEXT('.'))) == NULL || wcslen(pwstrExtension) != _tcslen(INI_FILENAME_EXT)) { ERR(("Can't compose the .ini file name from PPD/GPD name.")); MemFree((PWSTR)pwstrIniFilename); return FALSE; } StringCchCopyW(pwstrExtension, wcslen(pwstrExtension) + 1, INI_FILENAME_EXT); #endif // !defined(WINNT_40) || !defined(KERNEL_MODE) // // We only have work to do if there is a model-specific printer INI file // if (pwstrIniFilename != NULL) { HFILEMAP hFileMap; PBYTE pubIniFileData; DWORD dwIniFileSize; hFileMap = MapFileIntoMemory(pwstrIniFilename, (PVOID *) &pubIniFileData, &dwIniFileSize); if (hFileMap != NULL) { // // If the first two bytes are FF FE, then we assume // the text file is in Unicode format. Otherwise, // assume the text is in ANSI format. // if (dwIniFileSize >= sizeof(WCHAR) && pubIniFileData[0] == 0xFF && pubIniFileData[1] == 0xFE) { ASSERT((dwIniFileSize % sizeof(WCHAR)) == 0); pwstrParsedData = PwstrParsePrinterIniFileW( (PWSTR) pubIniFileData + 1, dwIniFileSize / sizeof(WCHAR) - 1, &dwParsedDataSize); } else { pwstrParsedData = PwstrParsePrinterIniFileA( (PSTR) pubIniFileData, dwIniFileSize, &dwParsedDataSize); } bResult = (pwstrParsedData != NULL); #ifndef KERNEL_MODE // // If not in kernel mode (where we can't write to registry), // we will try to save the parsed data into registry. // This may not succeed if user doesn't have proper right. // // // Fixing RC1 bug #423567 // #if 0 if (bResult && hPrinter) { BSetPrinterDataMultiSZPair( hPrinter, REGVAL_INIDATA, pwstrParsedData, dwParsedDataSize); } #endif #endif // // If caller ask for the parsed data directly, // don't free it and save the pointer for caller. // Caller is responsible for freeing the memory // if (ppParsedData) { *ppParsedData = pwstrParsedData; } else { MemFree(pwstrParsedData); } UnmapFileFromMemory(hFileMap); } else bResult = FALSE; #if defined(WINNT_40) && defined(KERNEL_MODE) // // Need to free memory allocated by DuplicateString // MemFree((PWSTR)pwstrIniFilename); #endif } else { #ifndef KERNEL_MODE if (dwFlags & FLAG_INIPROCESS_UPGRADE) { DWORD dwType, dwSize, dwStatus; // // We know there is no .ini file in the dependent list. So // we will check if there is an old INI registry value there, // if so delete it. // ASSERT(hPrinter != NULL); dwStatus = GetPrinterData(hPrinter, REGVAL_INIDATA, &dwType, NULL, 0, &dwSize); if ((dwStatus == ERROR_MORE_DATA || dwStatus == ERROR_SUCCESS) && (dwSize > 0) && (dwType == REG_MULTI_SZ)) { dwStatus = DeletePrinterData(hPrinter, REGVAL_INIDATA); if (dwStatus != ERROR_SUCCESS) { ERR(("Couldn't delete '%ws' during upgrade: %d\n", REGVAL_INIDATA, dwStatus)); } } } #endif // !KERNEL_MODE bResult = FALSE; } return bResult; }