1 /*
2  * PROJECT:     ReactOS Spooler API
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Functions giving information about DEVMODE structures
5  * COPYRIGHT:   Copyright 2016-2017 Colin Finck (colin@reactos.org)
6  */
7 
8 #include "precomp.h"
9 
10 typedef struct _MINIMUM_SIZE_TABLE
11 {
12     DWORD dwField;
13     WORD wSize;
14 }
15 MINIMUM_SIZE_TABLE, *PMINIMUM_SIZE_TABLE;
16 
17 /**
18  * Minimum required DEVMODEA structure size based on the used fields. Must be in descending order!
19  */
20 static MINIMUM_SIZE_TABLE MinimumSizeA[] = {
21     { DM_PANNINGHEIGHT, FIELD_OFFSET(DEVMODEA, dmPanningHeight) + RTL_FIELD_SIZE(DEVMODEA, dmPanningHeight) },
22     { DM_PANNINGWIDTH, FIELD_OFFSET(DEVMODEA, dmPanningWidth) + RTL_FIELD_SIZE(DEVMODEA, dmPanningWidth) },
23     { DM_DITHERTYPE, FIELD_OFFSET(DEVMODEA, dmDitherType) + RTL_FIELD_SIZE(DEVMODEA, dmDitherType) },
24     { DM_MEDIATYPE, FIELD_OFFSET(DEVMODEA, dmMediaType) + RTL_FIELD_SIZE(DEVMODEA, dmMediaType) },
25     { DM_ICMINTENT, FIELD_OFFSET(DEVMODEA, dmICMIntent) + RTL_FIELD_SIZE(DEVMODEA, dmICMIntent) },
26     { DM_ICMMETHOD, FIELD_OFFSET(DEVMODEA, dmICMMethod) + RTL_FIELD_SIZE(DEVMODEA, dmICMMethod) },
27     { DM_DISPLAYFREQUENCY, FIELD_OFFSET(DEVMODEA, dmDisplayFrequency) + RTL_FIELD_SIZE(DEVMODEA, dmDisplayFrequency) },
28     { DM_NUP, FIELD_OFFSET(DEVMODEA, dmNup) + RTL_FIELD_SIZE(DEVMODEA, dmNup) },
29     { DM_DISPLAYFLAGS, FIELD_OFFSET(DEVMODEA, dmDisplayFlags) + RTL_FIELD_SIZE(DEVMODEA, dmDisplayFlags) },
30     { DM_PELSHEIGHT, FIELD_OFFSET(DEVMODEA, dmPelsHeight) + RTL_FIELD_SIZE(DEVMODEA, dmPelsHeight) },
31     { DM_PELSWIDTH, FIELD_OFFSET(DEVMODEA, dmPelsWidth) + RTL_FIELD_SIZE(DEVMODEA, dmPelsWidth) },
32     { DM_BITSPERPEL, FIELD_OFFSET(DEVMODEA, dmBitsPerPel) + RTL_FIELD_SIZE(DEVMODEA, dmBitsPerPel) },
33     { DM_LOGPIXELS, FIELD_OFFSET(DEVMODEA, dmLogPixels) + RTL_FIELD_SIZE(DEVMODEA, dmLogPixels) },
34     { DM_FORMNAME, FIELD_OFFSET(DEVMODEA, dmFormName) + RTL_FIELD_SIZE(DEVMODEA, dmFormName) },
35     { DM_COLLATE, FIELD_OFFSET(DEVMODEA, dmCollate) + RTL_FIELD_SIZE(DEVMODEA, dmCollate) },
36     { DM_TTOPTION, FIELD_OFFSET(DEVMODEA, dmTTOption) + RTL_FIELD_SIZE(DEVMODEA, dmTTOption) },
37     { DM_YRESOLUTION, FIELD_OFFSET(DEVMODEA, dmYResolution) + RTL_FIELD_SIZE(DEVMODEA, dmYResolution) },
38     { DM_DUPLEX, FIELD_OFFSET(DEVMODEA, dmDuplex) + RTL_FIELD_SIZE(DEVMODEA, dmDuplex) },
39     { DM_COLOR, FIELD_OFFSET(DEVMODEA, dmColor) + RTL_FIELD_SIZE(DEVMODEA, dmColor) },
40     { DM_DISPLAYFIXEDOUTPUT, FIELD_OFFSET(DEVMODEA, dmDisplayFixedOutput) + RTL_FIELD_SIZE(DEVMODEA, dmDisplayFixedOutput) },
41     { DM_DISPLAYORIENTATION, FIELD_OFFSET(DEVMODEA, dmDisplayOrientation) + RTL_FIELD_SIZE(DEVMODEA, dmDisplayOrientation) },
42     { DM_POSITION, FIELD_OFFSET(DEVMODEA, dmPosition) + RTL_FIELD_SIZE(DEVMODEA, dmPosition) },
43     { DM_PRINTQUALITY, FIELD_OFFSET(DEVMODEA, dmPrintQuality) + RTL_FIELD_SIZE(DEVMODEA, dmPrintQuality) },
44     { DM_DEFAULTSOURCE, FIELD_OFFSET(DEVMODEA, dmDefaultSource) + RTL_FIELD_SIZE(DEVMODEA, dmDefaultSource) },
45     { DM_COPIES, FIELD_OFFSET(DEVMODEA, dmCopies) + RTL_FIELD_SIZE(DEVMODEA, dmCopies) },
46     { DM_SCALE, FIELD_OFFSET(DEVMODEA, dmScale) + RTL_FIELD_SIZE(DEVMODEA, dmScale) },
47     { DM_PAPERWIDTH, FIELD_OFFSET(DEVMODEA, dmPaperWidth) + RTL_FIELD_SIZE(DEVMODEA, dmPaperWidth) },
48     { DM_PAPERLENGTH, FIELD_OFFSET(DEVMODEA, dmPaperLength) + RTL_FIELD_SIZE(DEVMODEA, dmPaperLength) },
49     { DM_PAPERSIZE, FIELD_OFFSET(DEVMODEA, dmPaperSize) + RTL_FIELD_SIZE(DEVMODEA, dmPaperSize) },
50     { DM_ORIENTATION, FIELD_OFFSET(DEVMODEA, dmOrientation) + RTL_FIELD_SIZE(DEVMODEA, dmOrientation) },
51     { 0, 0 }
52 };
53 
54 /**
55  * Minimum required DEVMODEW structure size based on the used fields. Must be in descending order!
56  */
57 static MINIMUM_SIZE_TABLE MinimumSizeW[] = {
58     { DM_PANNINGHEIGHT, FIELD_OFFSET(DEVMODEW, dmPanningHeight) + RTL_FIELD_SIZE(DEVMODEW, dmPanningHeight) },
59     { DM_PANNINGWIDTH, FIELD_OFFSET(DEVMODEW, dmPanningWidth) + RTL_FIELD_SIZE(DEVMODEW, dmPanningWidth) },
60     { DM_DITHERTYPE, FIELD_OFFSET(DEVMODEW, dmDitherType) + RTL_FIELD_SIZE(DEVMODEW, dmDitherType) },
61     { DM_MEDIATYPE, FIELD_OFFSET(DEVMODEW, dmMediaType) + RTL_FIELD_SIZE(DEVMODEW, dmMediaType) },
62     { DM_ICMINTENT, FIELD_OFFSET(DEVMODEW, dmICMIntent) + RTL_FIELD_SIZE(DEVMODEW, dmICMIntent) },
63     { DM_ICMMETHOD, FIELD_OFFSET(DEVMODEW, dmICMMethod) + RTL_FIELD_SIZE(DEVMODEW, dmICMMethod) },
64     { DM_DISPLAYFREQUENCY, FIELD_OFFSET(DEVMODEW, dmDisplayFrequency) + RTL_FIELD_SIZE(DEVMODEW, dmDisplayFrequency) },
65     { DM_NUP, FIELD_OFFSET(DEVMODEW, dmNup) + RTL_FIELD_SIZE(DEVMODEW, dmNup) },
66     { DM_DISPLAYFLAGS, FIELD_OFFSET(DEVMODEW, dmDisplayFlags) + RTL_FIELD_SIZE(DEVMODEW, dmDisplayFlags) },
67     { DM_PELSHEIGHT, FIELD_OFFSET(DEVMODEW, dmPelsHeight) + RTL_FIELD_SIZE(DEVMODEW, dmPelsHeight) },
68     { DM_PELSWIDTH, FIELD_OFFSET(DEVMODEW, dmPelsWidth) + RTL_FIELD_SIZE(DEVMODEW, dmPelsWidth) },
69     { DM_BITSPERPEL, FIELD_OFFSET(DEVMODEW, dmBitsPerPel) + RTL_FIELD_SIZE(DEVMODEW, dmBitsPerPel) },
70     { DM_LOGPIXELS, FIELD_OFFSET(DEVMODEW, dmLogPixels) + RTL_FIELD_SIZE(DEVMODEW, dmLogPixels) },
71     { DM_FORMNAME, FIELD_OFFSET(DEVMODEW, dmFormName) + RTL_FIELD_SIZE(DEVMODEW, dmFormName) },
72     { DM_COLLATE, FIELD_OFFSET(DEVMODEW, dmCollate) + RTL_FIELD_SIZE(DEVMODEW, dmCollate) },
73     { DM_TTOPTION, FIELD_OFFSET(DEVMODEW, dmTTOption) + RTL_FIELD_SIZE(DEVMODEW, dmTTOption) },
74     { DM_YRESOLUTION, FIELD_OFFSET(DEVMODEW, dmYResolution) + RTL_FIELD_SIZE(DEVMODEW, dmYResolution) },
75     { DM_DUPLEX, FIELD_OFFSET(DEVMODEW, dmDuplex) + RTL_FIELD_SIZE(DEVMODEW, dmDuplex) },
76     { DM_COLOR, FIELD_OFFSET(DEVMODEW, dmColor) + RTL_FIELD_SIZE(DEVMODEW, dmColor) },
77     { DM_DISPLAYFIXEDOUTPUT, FIELD_OFFSET(DEVMODEW, dmDisplayFixedOutput) + RTL_FIELD_SIZE(DEVMODEW, dmDisplayFixedOutput) },
78     { DM_DISPLAYORIENTATION, FIELD_OFFSET(DEVMODEW, dmDisplayOrientation) + RTL_FIELD_SIZE(DEVMODEW, dmDisplayOrientation) },
79     { DM_POSITION, FIELD_OFFSET(DEVMODEW, dmPosition) + RTL_FIELD_SIZE(DEVMODEW, dmPosition) },
80     { DM_PRINTQUALITY, FIELD_OFFSET(DEVMODEW, dmPrintQuality) + RTL_FIELD_SIZE(DEVMODEW, dmPrintQuality) },
81     { DM_DEFAULTSOURCE, FIELD_OFFSET(DEVMODEW, dmDefaultSource) + RTL_FIELD_SIZE(DEVMODEW, dmDefaultSource) },
82     { DM_COPIES, FIELD_OFFSET(DEVMODEW, dmCopies) + RTL_FIELD_SIZE(DEVMODEW, dmCopies) },
83     { DM_SCALE, FIELD_OFFSET(DEVMODEW, dmScale) + RTL_FIELD_SIZE(DEVMODEW, dmScale) },
84     { DM_PAPERWIDTH, FIELD_OFFSET(DEVMODEW, dmPaperWidth) + RTL_FIELD_SIZE(DEVMODEW, dmPaperWidth) },
85     { DM_PAPERLENGTH, FIELD_OFFSET(DEVMODEW, dmPaperLength) + RTL_FIELD_SIZE(DEVMODEW, dmPaperLength) },
86     { DM_PAPERSIZE, FIELD_OFFSET(DEVMODEW, dmPaperSize) + RTL_FIELD_SIZE(DEVMODEW, dmPaperSize) },
87     { DM_ORIENTATION, FIELD_OFFSET(DEVMODEW, dmOrientation) + RTL_FIELD_SIZE(DEVMODEW, dmOrientation) },
88     { 0, 0 }
89 };
90 
91 /**
92  * Replace the last character by a null terminator if the given ANSI string is not null-terminated.
93  */
94 static __inline void
95 _FixStringA(PBYTE String, DWORD cbString)
96 {
97     const PBYTE pLastCharacter = &String[cbString / sizeof(BYTE) - 1];
98     PBYTE p = String;
99 
100     while (*p)
101     {
102         if (p == pLastCharacter)
103         {
104             *p = 0;
105             break;
106         }
107 
108         p++;
109     }
110 }
111 
112 /**
113  * Replace the last character by a null terminator if the given Unicode string is not null-terminated.
114  */
115 static __inline void
116 _FixStringW(PWSTR String, DWORD cbString)
117 {
118     const PWSTR pLastCharacter = &String[cbString / sizeof(WCHAR) - 1];
119     PWSTR p = String;
120 
121     while (*p)
122     {
123         if (p == pLastCharacter)
124         {
125             *p = 0;
126             break;
127         }
128 
129         p++;
130     }
131 }
132 
133 BOOL WINAPI
134 IsValidDevmodeA(PDEVMODEA pDevmode, size_t DevmodeSize)
135 {
136     PMINIMUM_SIZE_TABLE pTable = MinimumSizeA;
137     WORD wRequiredSize;
138 
139     TRACE("IsValidDevmodeA(%p, %lu)\n", pDevmode, DevmodeSize);
140 
141     // Check if a Devmode was given at all.
142     if (!pDevmode)
143         goto Failure;
144 
145     // Verify that DevmodeSize is large enough to hold the public and private members of the structure.
146     if (DevmodeSize < pDevmode->dmSize + pDevmode->dmDriverExtra)
147         goto Failure;
148 
149     // If the structure has private members, the public structure must be 32-bit packed.
150     if (pDevmode->dmDriverExtra && pDevmode->dmSize % 4)
151         goto Failure;
152 
153     // Now determine the minimum possible dmSize based on the given fields in dmFields.
154     wRequiredSize = FIELD_OFFSET(DEVMODEA, dmFields) + RTL_FIELD_SIZE(DEVMODEA, dmFields);
155 
156     while (pTable->dwField)
157     {
158         if (pDevmode->dmFields & pTable->dwField)
159         {
160             wRequiredSize = pTable->wSize;
161             break;
162         }
163 
164         pTable++;
165     }
166 
167     // Verify that the value in dmSize is big enough for the used fields.
168     if (pDevmode->dmSize < wRequiredSize)
169         goto Failure;
170 
171     // Check if dmDeviceName and (if used) dmFormName are null-terminated.
172     // Fix this if they aren't.
173     _FixStringA(pDevmode->dmDeviceName, sizeof(pDevmode->dmDeviceName));
174     if (pDevmode->dmFields & DM_FORMNAME)
175         _FixStringA(pDevmode->dmFormName, sizeof(pDevmode->dmFormName));
176 
177     // Return success without setting the error code.
178     return TRUE;
179 
180 Failure:
181     SetLastError(ERROR_INVALID_DATA);
182     return FALSE;
183 }
184 
185 BOOL WINAPI
186 IsValidDevmodeW(PDEVMODEW pDevmode, size_t DevmodeSize)
187 {
188     PMINIMUM_SIZE_TABLE pTable = MinimumSizeW;
189     WORD wRequiredSize;
190 
191     TRACE("IsValidDevmodeW(%p, %lu)\n", pDevmode, DevmodeSize);
192 
193     // Check if a Devmode was given at all.
194     if (!pDevmode)
195         goto Failure;
196 
197     // Verify that DevmodeSize is large enough to hold the public and private members of the structure.
198     if (DevmodeSize < pDevmode->dmSize + pDevmode->dmDriverExtra)
199         goto Failure;
200 
201     // If the structure has private members, the public structure must be 32-bit packed.
202     if (pDevmode->dmDriverExtra && pDevmode->dmSize % 4)
203         goto Failure;
204 
205     // Now determine the minimum possible dmSize based on the given fields in dmFields.
206     wRequiredSize = FIELD_OFFSET(DEVMODEW, dmFields) + RTL_FIELD_SIZE(DEVMODEW, dmFields);
207 
208     while (pTable->dwField)
209     {
210         if (pDevmode->dmFields & pTable->dwField)
211         {
212             wRequiredSize = pTable->wSize;
213             break;
214         }
215 
216         pTable++;
217     }
218 
219     // Verify that the value in dmSize is big enough for the used fields.
220     if (pDevmode->dmSize < wRequiredSize)
221         goto Failure;
222 
223     // Check if dmDeviceName and (if used) dmFormName are null-terminated.
224     // Fix this if they aren't.
225     _FixStringW(pDevmode->dmDeviceName, sizeof(pDevmode->dmDeviceName));
226     if (pDevmode->dmFields & DM_FORMNAME)
227         _FixStringW(pDevmode->dmFormName, sizeof(pDevmode->dmFormName));
228 
229     // Return success without setting the error code.
230     return TRUE;
231 
232 Failure:
233     SetLastError(ERROR_INVALID_DATA);
234     return FALSE;
235 }
236 
237 BOOL WINAPI
238 IsValidDevmodeNoSizeW(PDEVMODEW pDevmode)
239 {
240     PMINIMUM_SIZE_TABLE pTable = MinimumSizeW;
241     WORD wRequiredSize;
242 
243     TRACE("IsValidDevmodeNoSizeW(%p)\n", pDevmode);
244 
245     // Check if a Devmode was given at all.
246     if (!pDevmode)
247         goto Failure;
248 
249     // If the structure has private members, the public structure must be 32-bit packed.
250     if (pDevmode->dmDriverExtra && pDevmode->dmSize % 4)
251         goto Failure;
252 
253     // Now determine the minimum possible dmSize based on the given fields in dmFields.
254     wRequiredSize = FIELD_OFFSET(DEVMODEW, dmFields) + RTL_FIELD_SIZE(DEVMODEW, dmFields);
255 
256     while (pTable->dwField)
257     {
258         if (pDevmode->dmFields & pTable->dwField)
259         {
260             wRequiredSize = pTable->wSize;
261             break;
262         }
263 
264         pTable++;
265     }
266 
267     // Verify that the value in dmSize is big enough for the used fields.
268     if (pDevmode->dmSize < wRequiredSize)
269         goto Failure;
270 
271     // Check if dmDeviceName and (if used) dmFormName are null-terminated.
272     // Fix this if they aren't.
273     _FixStringW(pDevmode->dmDeviceName, sizeof(pDevmode->dmDeviceName));
274     if (pDevmode->dmFields & DM_FORMNAME)
275         _FixStringW(pDevmode->dmFormName, sizeof(pDevmode->dmFormName));
276 
277     // Return success without setting the error code.
278     return TRUE;
279 
280 Failure:
281     SetLastError(ERROR_INVALID_DATA);
282     return FALSE;
283 }
284 
285 void RosConvertAnsiDevModeToUnicodeDevmode(PDEVMODEA pDevModeInput, PDEVMODEW *pDevModeOutput)
286 {
287     // FIXME: This function should become ConvertAnsiDevModeToUnicodeDevmode when its parameters are known!
288 
289     // Check if a pDevModeInput and pDevModeOutput are both not NULL.
290     if (!pDevModeInput || !pDevModeOutput)
291         return;
292 
293     *pDevModeOutput = GdiConvertToDevmodeW(pDevModeInput);
294 }
295 
296 // Internal counterpart to GdiConvertToDevmodeW from gdi32
297 static __inline DEVMODEA*
298 _ConvertToDevmodeA(const DEVMODEW *dmW)
299 {
300     DEVMODEA *dmA;
301     WORD dmA_size, dmW_size;
302     size_t BytesToCopy;
303 
304     dmW_size = dmW->dmSize;
305 
306     /* this is the minimal dmSize that XP accepts */
307     if (dmW_size < FIELD_OFFSET(DEVMODEW, dmFields))
308         return NULL;
309 
310     // Guard against callers that set dmSize incorrectly.
311     if (dmW_size > sizeof(DEVMODEW))
312         dmW_size = sizeof(DEVMODEW);
313 
314     // dmA_size must become dmW_size without the additional 1 byte per character for each Unicode string (dmDeviceName and dmFormName).
315     dmA_size = dmW_size - CCHDEVICENAME;
316     if (dmW_size >= FIELD_OFFSET(DEVMODEW, dmFormName) + CCHFORMNAME * sizeof(WCHAR))
317         dmA_size -= CCHFORMNAME;
318 
319     // Allocate the required bytes, that is dmSize for the ANSI DEVMODEA structure plus any extra bytes requested through dmDriverExtra.
320     dmA = HeapAlloc(GetProcessHeap(), 0, dmA_size + dmW->dmDriverExtra);
321     if (!dmA) return NULL;
322 
323     // Every valid DEVMODEW has a dmDeviceName, which we convert to ANSI here.
324     WideCharToMultiByte(CP_ACP, 0, dmW->dmDeviceName, -1, (LPSTR)dmA->dmDeviceName, CCHDEVICENAME, NULL, NULL);
325 
326     // Copy everything up to dmFormName or the remaining dmW_size, whatever is smaller.
327     BytesToCopy = min(FIELD_OFFSET(DEVMODEW, dmFormName) - FIELD_OFFSET(DEVMODEW, dmSpecVersion), dmW_size - CCHDEVICENAME * sizeof(WCHAR));
328     memcpy(&dmA->dmSpecVersion, &dmW->dmSpecVersion, BytesToCopy);
329 
330     // Handle dmFormName if the input DEVMODEW is large enough to contain one.
331     if (dmW_size >= FIELD_OFFSET(DEVMODEW, dmFormName) + CCHFORMNAME * sizeof(WCHAR))
332     {
333         if (dmW->dmFields & DM_FORMNAME)
334             WideCharToMultiByte(CP_ACP, 0, dmW->dmFormName, -1, (LPSTR)dmA->dmFormName, CCHFORMNAME, NULL, NULL);
335         else
336             dmA->dmFormName[0] = 0;
337 
338         // Copy the remaining fields.
339         if (dmW_size > FIELD_OFFSET(DEVMODEW, dmLogPixels))
340             memcpy(&dmA->dmLogPixels, &dmW->dmLogPixels, dmW_size - FIELD_OFFSET(DEVMODEW, dmLogPixels));
341     }
342 
343     // Append dmDriverExtra if required.
344     if (dmW->dmDriverExtra)
345         memcpy((char *)dmA + dmA_size, (const char *)dmW + dmW_size, dmW->dmDriverExtra);
346 
347     // Set the corrected dmSize and we are done.
348     dmA->dmSize = dmA_size;
349 
350     return dmA;
351 }
352 
353 void RosConvertUnicodeDevModeToAnsiDevmode(PDEVMODEW pDevModeInput, PDEVMODEA pDevModeOutput)
354 {
355     PDEVMODEA pTmp;
356 
357     // FIXME: This function should become ConvertUnicodeDevModeToAnsiDevmode when its parameters are known!
358 
359     // Check if a pDevModeInput and pDevModeOutput are both not NULL.
360     if (!pDevModeInput || !pDevModeOutput)
361         return;
362 
363     pTmp = _ConvertToDevmodeA(pDevModeInput);
364     memcpy( pDevModeOutput, pTmp, pTmp->dmSize + pTmp->dmDriverExtra); // Copy into a Wide char (Larger) buffer.
365     HeapFree(hProcessHeap, 0, pTmp);
366 }
367