PageRenderTime 31ms CodeModel.GetById 2ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/win_updater/updater.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 516 lines | 347 code | 76 blank | 93 comment | 34 complexity | 407e5eee8d4215fbd48bd3778f2f7fd0 MD5 | raw file
  1/** 
  2 * @file updater.cpp
  3 * @brief Windows auto-updater
  4 *
  5 * $LicenseInfo:firstyear=2002&license=viewerlgpl$
  6 * Second Life Viewer Source Code
  7 * Copyright (C) 2010, Linden Research, Inc.
  8 * 
  9 * This library is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU Lesser General Public
 11 * License as published by the Free Software Foundation;
 12 * version 2.1 of the License only.
 13 * 
 14 * This library is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 17 * Lesser General Public License for more details.
 18 * 
 19 * You should have received a copy of the GNU Lesser General Public
 20 * License along with this library; if not, write to the Free Software
 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 22 * 
 23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 24 * $/LicenseInfo$
 25 */
 26
 27//
 28// Usage: updater -url <url>
 29//
 30
 31// We use dangerous fopen, strtok, mbstowcs, sprintf
 32// which generates warnings on VC2005.
 33// *TODO: Switch to fopen_s, strtok_s, etc.
 34#define _CRT_SECURE_NO_DEPRECATE
 35
 36#include <windows.h>
 37#include <wininet.h>
 38#include <stdio.h>
 39#include <string>
 40#include <iostream>
 41#include <stdexcept>
 42#include <sstream>
 43#include <fstream>
 44
 45#define BUFSIZE 8192
 46
 47int  gTotalBytesRead = 0;
 48DWORD gTotalBytes = -1;
 49HWND gWindow = NULL;
 50WCHAR gProgress[256];
 51char* gUpdateURL = NULL;
 52
 53#if _DEBUG
 54std::ofstream logfile;
 55#define DEBUG(expr) logfile << expr << std::endl
 56#else
 57#define DEBUG(expr) /**/
 58#endif
 59
 60char* wchars_to_utf8chars(const WCHAR* in_chars)
 61{
 62	int tlen = 0;
 63	const WCHAR* twc = in_chars;
 64	while (*twc++ != 0)
 65	{
 66		tlen++;
 67	}
 68	char* outchars = new char[tlen];
 69	char* res = outchars;
 70	for (int i=0; i<tlen; i++)
 71	{
 72		int cur_char = (int)(*in_chars++);
 73		if (cur_char < 0x80)
 74		{
 75			*outchars++ = (char)cur_char;
 76		}
 77		else
 78		{
 79			*outchars++ = '?';
 80		}
 81	}
 82	*outchars = 0;
 83	return res;
 84}
 85
 86class Fetcher
 87{
 88public:
 89    Fetcher(const std::wstring& uri)
 90    {
 91        // These actions are broken out as separate methods not because it
 92        // makes the code clearer, but to avoid triggering AntiVir and
 93        // McAfee-GW-Edition virus scanners (DEV-31680).
 94        mInet = openInet();
 95        mDownload = openUrl(uri);
 96    }
 97
 98    ~Fetcher()
 99    {
100        DEBUG("Calling InternetCloseHandle");
101        InternetCloseHandle(mDownload);
102        InternetCloseHandle(mInet);
103    }
104
105    unsigned long read(char* buffer, size_t bufflen) const;
106
107    DWORD getTotalBytes() const
108    {
109        DWORD totalBytes;
110        DWORD sizeof_total_bytes = sizeof(totalBytes);
111        HttpQueryInfo(mDownload, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER,
112                      &totalBytes, &sizeof_total_bytes, NULL);
113        return totalBytes;
114    }
115
116    struct InetError: public std::runtime_error
117    {
118        InetError(const std::string& what): std::runtime_error(what) {}
119    };
120
121private:
122    // We test results from a number of different MS functions with different
123    // return types -- but the common characteristic is that 0 (i.e. (! result))
124    // means an error of some kind.
125    template <typename RESULT>
126    static RESULT check(const std::string& desc, RESULT result)
127    {
128        if (result)
129        {
130            // success, show caller
131            return result;
132        }
133        DWORD err = GetLastError();
134        std::ostringstream out;
135        out << desc << " Failed: " << err;
136        DEBUG(out.str());
137        throw InetError(out.str());
138    }
139
140    HINTERNET openUrl(const std::wstring& uri) const;
141    HINTERNET openInet() const;
142
143    HINTERNET mInet, mDownload;
144};
145
146HINTERNET Fetcher::openInet() const
147{
148    DEBUG("Calling InternetOpen");
149    // Init wininet subsystem
150    return check("InternetOpen",
151                 InternetOpen(L"LindenUpdater", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0));
152}
153
154HINTERNET Fetcher::openUrl(const std::wstring& uri) const
155{
156    DEBUG("Calling InternetOpenUrl: " << wchars_to_utf8chars(uri.c_str()));
157    return check("InternetOpenUrl",
158                 InternetOpenUrl(mInet, uri.c_str(), NULL, 0, INTERNET_FLAG_NEED_FILE, NULL));
159}
160
161unsigned long Fetcher::read(char* buffer, size_t bufflen) const
162{
163    unsigned long bytes_read = 0;
164    DEBUG("Calling InternetReadFile");
165    check("InternetReadFile",
166          InternetReadFile(mDownload, buffer, bufflen, &bytes_read));
167    return bytes_read;
168}
169
170int WINAPI get_url_into_file(const std::wstring& uri, const std::string& path, int *cancelled)
171{
172	int success = FALSE;
173	*cancelled = FALSE;
174
175    DEBUG("Opening '" << path << "'");
176
177	FILE* fp = fopen(path.c_str(), "wb");		/* Flawfinder: ignore */
178
179	if (!fp)
180	{
181        DEBUG("Failed to open '" << path << "'");
182		return success;
183	}
184
185    // Note, ctor can throw, since it uses check() function.
186    Fetcher fetcher(uri);
187    gTotalBytes = fetcher.getTotalBytes();
188
189/*==========================================================================*|
190    // nobody uses total_bytes?!? What's this doing here?
191	DWORD total_bytes = 0;
192	success = check("InternetQueryDataAvailable",
193                    InternetQueryDataAvailable(hdownload, &total_bytes, 0, 0));
194|*==========================================================================*/
195
196	success = FALSE;
197	while(!success && !(*cancelled))
198	{
199        char data[BUFSIZE];		/* Flawfinder: ignore */
200        unsigned long bytes_read = fetcher.read(data, sizeof(data));
201
202		if (!bytes_read)
203		{
204            DEBUG("InternetReadFile Read " << bytes_read << " bytes.");
205		}
206
207        DEBUG("Reading Data, bytes_read = " << bytes_read);
208		
209		if (bytes_read == 0)
210		{
211			// If InternetFileRead returns TRUE AND bytes_read == 0
212			// we've successfully downloaded the entire file
213			wsprintf(gProgress, L"Download complete.");
214			success = TRUE;
215		}
216		else
217		{
218			// write what we've got, then continue
219			fwrite(data, sizeof(char), bytes_read, fp);
220
221			gTotalBytesRead += int(bytes_read);
222
223			if (gTotalBytes != -1)
224				wsprintf(gProgress, L"Downloaded: %d%%", 100 * gTotalBytesRead / gTotalBytes);
225			else
226				wsprintf(gProgress, L"Downloaded: %dK", gTotalBytesRead / 1024);
227
228		}
229
230        DEBUG("Calling InvalidateRect");
231		
232		// Mark the window as needing redraw (of the whole thing)
233		InvalidateRect(gWindow, NULL, TRUE);
234
235		// Do the redraw
236        DEBUG("Calling UpdateWindow");
237		UpdateWindow(gWindow);
238
239        DEBUG("Calling PeekMessage");
240		MSG msg;
241		while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
242		{
243			TranslateMessage(&msg);
244			DispatchMessage(&msg);
245
246			if (msg.message == WM_QUIT)
247			{
248				// bail out, user cancelled
249				*cancelled = TRUE;
250			}
251		}
252	}
253
254	fclose(fp);
255	return success;
256}
257
258LRESULT CALLBACK WinProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
259{
260	HDC hdc;			// Drawing context
261	PAINTSTRUCT ps;
262
263	switch(message)
264	{
265	case WM_PAINT:
266		{
267			hdc = BeginPaint(hwnd, &ps);
268
269			RECT rect;
270			GetClientRect(hwnd, &rect);
271			DrawText(hdc, gProgress, -1, &rect, 
272				DT_SINGLELINE | DT_CENTER | DT_VCENTER);
273
274			EndPaint(hwnd, &ps);
275			return 0;
276		}
277	case WM_CLOSE:
278	case WM_DESTROY:
279		// Get out of full screen
280		// full_screen_mode(false);
281		PostQuitMessage(0);
282		return 0;
283	}
284	return DefWindowProc(hwnd, message, wparam, lparam);
285}
286
287#define win_class_name L"FullScreen"
288
289int parse_args(int argc, char **argv)
290{
291	int j;
292
293	for (j = 1; j < argc; j++) 
294	{
295		if ((!strcmp(argv[j], "-url")) && (++j < argc)) 
296		{
297			gUpdateURL = argv[j];
298		}
299	}
300
301	// If nothing was set, let the caller know.
302	if (!gUpdateURL)
303	{
304		return 1;
305	}
306	return 0;
307}
308	
309int WINAPI
310WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
311{
312	// Parse the command line.
313	LPSTR cmd_line_including_exe_name = GetCommandLineA();
314
315	const int MAX_ARGS = 100;
316	int argc = 0;
317	char* argv[MAX_ARGS];		/* Flawfinder: ignore */
318
319#if _DEBUG
320	logfile.open("updater.log", std::ios_base::out);
321    DEBUG("Parsing command arguments");
322#endif
323	
324	char *token = NULL;
325	if( cmd_line_including_exe_name[0] == '\"' )
326	{
327		// Exe name is enclosed in quotes
328		token = strtok( cmd_line_including_exe_name, "\"" );
329		argv[argc++] = token;
330		token = strtok( NULL, " \t," );
331	}
332	else
333	{
334		// Exe name is not enclosed in quotes
335		token = strtok( cmd_line_including_exe_name, " \t," );
336	}
337
338	while( (token != NULL) && (argc < MAX_ARGS) )
339	{
340		argv[argc++] = token;
341		/* Get next token: */
342		if (*(token + strlen(token) + 1) == '\"')		/* Flawfinder: ignore */
343		{
344			token = strtok( NULL, "\"");
345		}
346		else
347		{
348			token = strtok( NULL, " \t," );
349		}
350	}
351
352	gUpdateURL = NULL;
353
354	/////////////////////////////////////////
355	//
356	// Process command line arguments
357	//
358
359    DEBUG("Processing command arguments");
360	
361	//
362	// Parse the command line arguments
363	//
364	int parse_args_result = parse_args(argc, argv);
365	
366	WNDCLASSEX wndclassex = { 0 };
367	//DEVMODE dev_mode = { 0 };
368
369	const int WINDOW_WIDTH = 250;
370	const int WINDOW_HEIGHT = 100;
371
372	wsprintf(gProgress, L"Connecting...");
373
374	/* Init the WNDCLASSEX */
375	wndclassex.cbSize = sizeof(WNDCLASSEX);
376	wndclassex.style = CS_HREDRAW | CS_VREDRAW;
377	wndclassex.hInstance = hInstance;
378	wndclassex.lpfnWndProc = WinProc;
379	wndclassex.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
380	wndclassex.lpszClassName = win_class_name;
381	
382	RegisterClassEx(&wndclassex);
383	
384	// Get the size of the screen
385	//EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dev_mode);
386	
387	gWindow = CreateWindowEx(NULL, win_class_name, 
388		L"Second Life Updater",
389		WS_OVERLAPPEDWINDOW, 
390		CW_USEDEFAULT, 
391		CW_USEDEFAULT, 
392		WINDOW_WIDTH, 
393		WINDOW_HEIGHT,
394		NULL, NULL, hInstance, NULL);
395
396	ShowWindow(gWindow, nShowCmd);
397	UpdateWindow(gWindow);
398
399	if (parse_args_result)
400	{
401		MessageBox(gWindow, 
402				L"Usage: updater -url <url> [-name <window_title>] [-program <program_name>] [-silent]",
403				L"Usage", MB_OK);
404		return parse_args_result;
405	}
406
407	// Did we get a userserver to work with?
408	if (!gUpdateURL)
409	{
410		MessageBox(gWindow, L"Please specify the download url from the command line",
411			L"Error", MB_OK);
412		return 1;
413	}
414
415	// Can't feed GetTempPath into GetTempFile directly
416	char temp_path[MAX_PATH];		/* Flawfinder: ignore */
417	if (0 == GetTempPathA(sizeof(temp_path), temp_path))
418	{
419		MessageBox(gWindow, L"Problem with GetTempPath()",
420			L"Error", MB_OK);
421		return 1;
422	}
423    std::string update_exec_path(temp_path);
424	update_exec_path.append("Second_Life_Updater.exe");
425
426	WCHAR update_uri[4096];
427    mbstowcs(update_uri, gUpdateURL, sizeof(update_uri));
428
429	int success = 0;
430	int cancelled = 0;
431
432	// Actually do the download
433    try
434    {
435        DEBUG("Calling get_url_into_file");
436        success = get_url_into_file(update_uri, update_exec_path, &cancelled);
437    }
438    catch (const Fetcher::InetError& e)
439    {
440        (void)e;
441        success = FALSE;
442        DEBUG("Caught: " << e.what());
443    }
444
445	// WinInet can't tell us if we got a 404 or not.  Therefor, we check
446	// for the size of the downloaded file, and assume that our installer
447	// will always be greater than 1MB.
448	if (gTotalBytesRead < (1024 * 1024) && ! cancelled)
449	{
450		MessageBox(gWindow,
451			L"The Second Life auto-update has failed.\n"
452			L"The problem may be caused by other software installed \n"
453			L"on your computer, such as a firewall.\n"
454			L"Please visit http://secondlife.com/download/ \n"
455			L"to download the latest version of Second Life.\n",
456			NULL, MB_OK);
457		return 1;
458	}
459
460	if (cancelled)
461	{
462		// silently exit
463		return 0;
464	}
465
466	if (!success)
467	{
468		MessageBox(gWindow, 
469			L"Second Life download failed.\n"
470			L"Please try again later.", 
471			NULL, MB_OK);
472		return 1;
473	}
474
475	// TODO: Make updates silent (with /S to NSIS)
476	//char params[256];		/* Flawfinder: ignore */
477	//sprintf(params, "/S");	/* Flawfinder: ignore */
478	//MessageBox(gWindow, 
479	//	L"Updating Second Life.\n\nSecond Life will automatically start once the update is complete.  This may take a minute...",
480	//	L"Download Complete",
481	//	MB_OK);
482		
483/*==========================================================================*|
484    // DEV-31680: ShellExecuteA() causes McAfee-GW-Edition and AntiVir
485    // scanners to flag this executable as a probable virus vector.
486    // Less than or equal to 32 means failure
487	if (32 >= (int) ShellExecuteA(gWindow, "open", update_exec_path.c_str(), NULL, 
488		"C:\\", SW_SHOWDEFAULT))
489|*==========================================================================*/
490    // from http://msdn.microsoft.com/en-us/library/ms682512(VS.85).aspx
491    STARTUPINFOA si;
492    PROCESS_INFORMATION pi;
493    ZeroMemory(&si, sizeof(si));
494    si.cb = sizeof(si);
495    ZeroMemory(&pi, sizeof(pi));
496
497    if (! CreateProcessA(update_exec_path.c_str(), // executable file
498                  NULL,                            // command line
499                  NULL,             // process cannot be inherited
500                  NULL,             // thread cannot be inherited
501                  FALSE,            // do not inherit existing handles
502                  0,                // process creation flags
503                  NULL,             // inherit parent's environment
504                  NULL,             // inherit parent's current dir
505                  &si,              // STARTUPINFO
506                  &pi))             // PROCESS_INFORMATION
507	{
508		MessageBox(gWindow, L"Update failed.  Please try again later.", NULL, MB_OK);
509		return 1;
510	}
511
512	// Give installer some time to open a window
513	Sleep(1000);
514
515	return 0;
516}