- Add some checks to prevent crashes in unexpected situations and add useful error...
[reactos.git] / rostests / rosautotest / webservice.c
1 /*
2 * PROJECT: ReactOS Automatic Testing Utility
3 * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation
4 * PURPOSE: Submitting test results to the Web Service
5 * COPYRIGHT: Copyright 2008-2009 Colin Finck <colin@reactos.org>
6 */
7
8 #include "precomp.h"
9
10 static const CHAR ActionProp[] = "action=";
11 static const CHAR TestIDProp[] = "&testid=";
12 static const CHAR TestTypeProp[] = "&testtype=";
13 static const CHAR WineTestType[] = "wine";
14
15 /**
16 * Sends data to the ReactOS Web Test Manager web service.
17 *
18 * @param Data
19 * Pointer to a CHAR pointer, which contains the data to submit as HTTP POST data.
20 * The buffer behind this pointer had to be allocated with HeapAlloc.
21 * Returns the data received by the web service after the call.
22 *
23 * @param DataLength
24 * Pointer to a DWORD, which contains the length of the data to submit (in bytes).
25 * Returns the length of the data received by the web service after the call (in bytes).
26 *
27 * @return
28 * TRUE if everything went well, FALSE if an error occured while submitting the request.
29 * In case of an error, the function will output an appropriate error message through StringOut.
30 */
31 static BOOL
32 IntDoRequest(char** Data, PDWORD DataLength)
33 {
34 const WCHAR Headers[] = L"Content-Type: application/x-www-form-urlencoded";
35
36 BOOL ReturnValue = FALSE;
37 HINTERNET hHTTP = NULL;
38 HINTERNET hHTTPRequest = NULL;
39 HINTERNET hInet = NULL;
40
41 /* Establish an internet connection to the "testman" server */
42 hInet = InternetOpenW(L"rosautotest", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
43
44 if(!hInet)
45 {
46 StringOut("InternetOpenW failed\n");
47 goto Cleanup;
48 }
49
50 hHTTP = InternetConnectW(hInet, SERVER_HOSTNAME, INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
51
52 if(!hHTTP)
53 {
54 StringOut("InternetConnectW failed\n");
55 goto Cleanup;
56 }
57
58 /* Post our test results to the web service */
59 hHTTPRequest = HttpOpenRequestW(hHTTP, L"POST", SERVER_FILE, NULL, NULL, NULL, INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE, 0);
60
61 if(!hHTTPRequest)
62 {
63 StringOut("HttpOpenRequestW failed\n");
64 goto Cleanup;
65 }
66
67 if(!HttpSendRequestW(hHTTPRequest, Headers, wcslen(Headers), *Data, *DataLength))
68 {
69 StringOut("HttpSendRequestW failed\n");
70 goto Cleanup;
71 }
72
73 HeapFree(hProcessHeap, 0, *Data);
74 *Data = NULL;
75
76 /* Get the response */
77 if(!InternetQueryDataAvailable(hHTTPRequest, DataLength, 0, 0))
78 {
79 StringOut("InternetQueryDataAvailable failed\n");
80 goto Cleanup;
81 }
82
83 *Data = HeapAlloc(hProcessHeap, 0, *DataLength + 1);
84
85 if(!InternetReadFile(hHTTPRequest, *Data, *DataLength, DataLength))
86 {
87 StringOut("InternetReadFile failed\n");
88 goto Cleanup;
89 }
90
91 (*Data)[*DataLength] = 0;
92 ReturnValue = TRUE;
93
94 Cleanup:
95 if(hHTTPRequest)
96 InternetCloseHandle(hHTTPRequest);
97
98 if(hHTTP)
99 InternetCloseHandle(hHTTP);
100
101 if(hInet)
102 InternetCloseHandle(hInet);
103
104 return ReturnValue;
105 }
106
107 /**
108 * Determines whether a string contains entirely numeric values.
109 *
110 * @param Input
111 * The string to check.
112 *
113 * @return
114 * TRUE if the string is entirely numeric, FALSE otherwise.
115 */
116 static BOOL
117 IsNumber(PCHAR Input)
118 {
119 do
120 {
121 if(!isdigit(*Input))
122 return FALSE;
123
124 ++Input;
125 }
126 while(*Input);
127
128 return TRUE;
129 }
130
131 /**
132 * Requests a Test ID from the web service for our test run.
133 *
134 * @param TestType
135 * Value from the TESTTYPES enum indicating the type of test we are about to submit.
136 *
137 * @return
138 * Returns the Test ID as a CHAR array if successful or NULL otherwise.
139 * The caller needs to HeapFree the returned pointer in case of success.
140 */
141 PCHAR
142 GetTestID(TESTTYPES TestType)
143 {
144 const CHAR GetTestIDAction[] = "gettestid";
145
146 DWORD DataLength;
147 PCHAR Data;
148 PCHAR ReturnValue = NULL;
149
150 /* Build the full request string */
151 DataLength = sizeof(ActionProp) - 1 + sizeof(GetTestIDAction) - 1;
152 DataLength += strlen(AuthenticationRequestString) + strlen(SystemInfoRequestString);
153 DataLength += sizeof(TestTypeProp) - 1;
154
155 switch(TestType)
156 {
157 case WineTest:
158 DataLength += sizeof(WineTestType) - 1;
159 break;
160 }
161
162 Data = HeapAlloc(hProcessHeap, 0, DataLength + 1);
163 strcpy(Data, ActionProp);
164 strcat(Data, GetTestIDAction);
165 strcat(Data, AuthenticationRequestString);
166 strcat(Data, SystemInfoRequestString);
167 strcat(Data, TestTypeProp);
168
169 switch(TestType)
170 {
171 case WineTest:
172 strcat(Data, WineTestType);
173 break;
174 }
175
176 if(!IntDoRequest(&Data, &DataLength))
177 goto Cleanup;
178
179 /* Verify that this is really a number */
180 if(!IsNumber(Data))
181 {
182 StringOut("Expected Test ID, but received:\n");
183 StringOut(Data);
184 StringOut("\n");
185 goto Cleanup;
186 }
187
188 ReturnValue = Data;
189
190 Cleanup:
191 if(Data && ReturnValue != Data)
192 HeapFree(hProcessHeap, 0, Data);
193
194 return ReturnValue;
195 }
196
197 /**
198 * Requests a Suite ID from the web service for our module/test combination.
199 *
200 * @param TestType
201 * Value from the TESTTYPES enum indicating the type of test we are about to submit.
202 *
203 * @param TestData
204 * Pointer to a *_GETSUITEID_DATA structure appropriate for our selected test type.
205 * Contains other input information for this request.
206 *
207 * @return
208 * Returns the Suite ID as a CHAR array if successful or NULL otherwise.
209 * The caller needs to HeapFree the returned pointer in case of success.
210 */
211 PCHAR
212 GetSuiteID(TESTTYPES TestType, const PVOID TestData)
213 {
214 const CHAR GetSuiteIDAction[] = "getsuiteid";
215 const CHAR ModuleProp[] = "&module=";
216 const CHAR TestProp[] = "&test=";
217
218 DWORD DataLength;
219 PCHAR Data;
220 PCHAR ReturnValue = NULL;
221 PWINE_GETSUITEID_DATA WineData;
222
223 DataLength = sizeof(ActionProp) - 1 + sizeof(GetSuiteIDAction) - 1;
224 DataLength += strlen(AuthenticationRequestString);
225 DataLength += sizeof(TestTypeProp) - 1;
226
227 switch(TestType)
228 {
229 case WineTest:
230 DataLength += sizeof(WineTestType) - 1;
231
232 WineData = (PWINE_GETSUITEID_DATA)TestData;
233 DataLength += sizeof(ModuleProp) - 1;
234 DataLength += strlen(WineData->Module);
235 DataLength += sizeof(TestProp) - 1;
236 DataLength += strlen(WineData->Test);
237
238 break;
239 }
240
241 Data = HeapAlloc(hProcessHeap, 0, DataLength + 1);
242 strcpy(Data, ActionProp);
243 strcat(Data, GetSuiteIDAction);
244 strcat(Data, AuthenticationRequestString);
245 strcat(Data, TestTypeProp);
246
247 switch(TestType)
248 {
249 case WineTest:
250 strcat(Data, WineTestType);
251
252 /* Stupid GCC and MSVC: WineData is already initialized above, still it's reported as a potentially uninitialized variable :-( */
253 WineData = (PWINE_GETSUITEID_DATA)TestData;
254 strcat(Data, ModuleProp);
255 strcat(Data, WineData->Module);
256 strcat(Data, TestProp);
257 strcat(Data, WineData->Test);
258
259 break;
260 }
261
262 if(!IntDoRequest(&Data, &DataLength))
263 goto Cleanup;
264
265 /* Verify that this is really a number */
266 if(!IsNumber(Data))
267 {
268 StringOut("Expected Suite ID, but received:\n");
269 StringOut(Data);
270 StringOut("\n");
271 goto Cleanup;
272 }
273
274 ReturnValue = Data;
275
276 Cleanup:
277 if(Data && ReturnValue != Data)
278 HeapFree(hProcessHeap, 0, Data);
279
280 return ReturnValue;
281 }
282
283 /**
284 * Submits the result of one test call to the web service.
285 *
286 * @param TestType
287 * Value from the TESTTYPES enum indicating the type of test we are about to submit.
288 *
289 * @param TestData
290 * Pointer to a *_SUBMIT_DATA structure appropriate for our selected test type.
291 * Contains other input information for this request.
292 *
293 * @return
294 * TRUE if everything went well, FALSE otherwise.
295 */
296 BOOL
297 Submit(TESTTYPES TestType, const PVOID TestData)
298 {
299 const CHAR SubmitAction[] = "submit";
300 const CHAR SuiteIDProp[] = "&suiteid=";
301 const CHAR LogProp[] = "&log=";
302
303 BOOL ReturnValue = FALSE;
304 DWORD DataLength;
305 PCHAR Data;
306 PCHAR pData;
307 PGENERAL_SUBMIT_DATA GeneralData;
308 PWINE_SUBMIT_DATA WineData;
309
310 /* Compute the full length of the POST data */
311 DataLength = sizeof(ActionProp) - 1 + sizeof(SubmitAction) - 1;
312 DataLength += strlen(AuthenticationRequestString);
313
314 GeneralData = (PGENERAL_SUBMIT_DATA)TestData;
315 DataLength += sizeof(TestIDProp) - 1;
316 DataLength += strlen(GeneralData->TestID);
317 DataLength += sizeof(SuiteIDProp) - 1;
318 DataLength += strlen(GeneralData->SuiteID);
319
320 /* The rest of the POST data depends on the test type */
321 DataLength += sizeof(TestTypeProp) - 1;
322
323 switch(TestType)
324 {
325 case WineTest:
326 DataLength += sizeof(WineTestType) - 1;
327
328 WineData = (PWINE_SUBMIT_DATA)TestData;
329 DataLength += sizeof(LogProp) - 1;
330 DataLength += 3 * strlen(WineData->Log);
331
332 break;
333 }
334
335 /* Now collect all the POST data */
336 Data = HeapAlloc(hProcessHeap, 0, DataLength + 1);
337 strcpy(Data, ActionProp);
338 strcat(Data, SubmitAction);
339 strcat(Data, AuthenticationRequestString);
340
341 strcat(Data, TestIDProp);
342 strcat(Data, GeneralData->TestID);
343 strcat(Data, SuiteIDProp);
344 strcat(Data, GeneralData->SuiteID);
345
346 strcat(Data, TestTypeProp);
347
348 switch(TestType)
349 {
350 case WineTest:
351 strcat(Data, WineTestType);
352
353 /* Stupid GCC and MSVC: WineData is already initialized above, still it's reported as a potentially uninitialized variable :-( */
354 WineData = (PWINE_SUBMIT_DATA)TestData;
355
356 strcat(Data, LogProp);
357 pData = Data + strlen(Data);
358 EscapeString(pData, WineData->Log);
359
360 break;
361 }
362
363 /* DataLength still contains the maximum length of the buffer, but not the actual data length we need for the request.
364 Determine that one now. */
365 DataLength = strlen(Data);
366
367 /* Send all the stuff */
368 if(!IntDoRequest(&Data, &DataLength))
369 goto Cleanup;
370
371 /* Output the response */
372 StringOut("The server responded:\n");
373 StringOut(Data);
374 StringOut("\n");
375
376 if(!strcmp(Data, "OK"))
377 ReturnValue = TRUE;
378
379 Cleanup:
380 if(Data)
381 HeapFree(hProcessHeap, 0, Data);
382
383 return ReturnValue;
384 }
385
386 /**
387 * Finishes a test run for the web service.
388 *
389 * @param TestType
390 * Value from the TESTTYPES enum indicating the type of test we are about to submit.
391 *
392 * @param TestData
393 * Pointer to a *_FINISH_DATA structure appropriate for our selected test type.
394 * Contains other input information for this request.
395 *
396 * @return
397 * TRUE if everything went well, FALSE otherwise.
398 */
399 BOOL
400 Finish(TESTTYPES TestType, const PVOID TestData)
401 {
402 const CHAR FinishAction[] = "finish";
403
404 BOOL ReturnValue = FALSE;
405 DWORD DataLength;
406 PCHAR Data;
407 PGENERAL_FINISH_DATA GeneralData;
408
409 /* Build the full request string */
410 DataLength = sizeof(ActionProp) - 1 + sizeof(FinishAction) - 1;
411 DataLength += strlen(AuthenticationRequestString);
412
413 GeneralData = (PGENERAL_FINISH_DATA)TestData;
414 DataLength += sizeof(TestIDProp) - 1;
415 DataLength += strlen(GeneralData->TestID);
416
417 DataLength += sizeof(TestTypeProp) - 1;
418
419 switch(TestType)
420 {
421 case WineTest:
422 DataLength += sizeof(WineTestType) - 1;
423 break;
424 }
425
426 Data = HeapAlloc(hProcessHeap, 0, DataLength + 1);
427 strcpy(Data, ActionProp);
428 strcat(Data, FinishAction);
429 strcat(Data, AuthenticationRequestString);
430 strcat(Data, TestIDProp);
431 strcat(Data, GeneralData->TestID);
432 strcat(Data, TestTypeProp);
433
434 switch(TestType)
435 {
436 case WineTest:
437 strcat(Data, WineTestType);
438 break;
439 }
440
441 if(!IntDoRequest(&Data, &DataLength))
442 goto Cleanup;
443
444 if(!strcmp(Data, "OK"))
445 ReturnValue = TRUE;
446
447 Cleanup:
448 if(Data)
449 HeapFree(hProcessHeap, 0, Data);
450
451 return ReturnValue;
452 }