extensions/updater/Updater.cpp
author Nicholas Hastings <skamonkey@gmail.com>
Mon May 13 15:33:55 2013 -0400 (2013-05-13)
changeset 3914 ec79ea87c2e7
parent 3630 b72450a8be42
child 4188 89782d5fc91b
permissions -rw-r--r--
One more dota build fix.
dvander@2562
     1
/**
dvander@2562
     2
 * vim: set ts=4 :
dvander@2562
     3
 * =============================================================================
dvander@2562
     4
 * SourceMod Updater Extension
dvander@2562
     5
 * Copyright (C) 2004-2009 AlliedModders LLC.  All rights reserved.
dvander@2562
     6
 * =============================================================================
dvander@2562
     7
 *
dvander@2562
     8
 * This program is free software; you can redistribute it and/or modify it under
dvander@2562
     9
 * the terms of the GNU General Public License, version 3.0, as published by the
dvander@2562
    10
 * Free Software Foundation.
dvander@2562
    11
 * 
dvander@2562
    12
 * This program is distributed in the hope that it will be useful, but WITHOUT
dvander@2562
    13
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
dvander@2562
    14
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
dvander@2562
    15
 * details.
dvander@2562
    16
 *
dvander@2562
    17
 * You should have received a copy of the GNU General Public License along with
dvander@2562
    18
 * this program.  If not, see <http://www.gnu.org/licenses/>.
dvander@2562
    19
 *
dvander@2562
    20
 * As a special exception, AlliedModders LLC gives you permission to link the
dvander@2562
    21
 * code of this program (as well as its derivative works) to "Half-Life 2," the
dvander@2562
    22
 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
dvander@2562
    23
 * by the Valve Corporation.  You must obey the GNU General Public License in
dvander@2562
    24
 * all respects for all other code used.  Additionally, AlliedModders LLC grants
dvander@2562
    25
 * this exception to all derivative works.  AlliedModders LLC defines further
dvander@2562
    26
 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
dvander@2562
    27
 * or <http://www.sourcemod.net/license.php>.
dvander@2562
    28
 *
dvander@2562
    29
 * Version: $Id$
dvander@2562
    30
 */
dvander@2562
    31
dvander@2783
    32
#include <sourcemod_version.h>
dvander@2562
    33
#include <stdlib.h>
dvander@2562
    34
#include "extension.h"
dvander@2562
    35
#include "Updater.h"
dvander@2562
    36
#include "md5.h"
dvander@2562
    37
dvander@2562
    38
#define USTATE_NONE			0
dvander@2562
    39
#define USTATE_FOLDERS		1
dvander@2562
    40
#define USTATE_CHANGED		2
dvander@2562
    41
#define USTATE_CHANGE_FILE	3
dvander@2630
    42
#define USTATE_ERRORS		4
dvander@2562
    43
dvander@2562
    44
using namespace SourceMod;
dvander@2562
    45
dvander@2573
    46
UpdateReader::UpdateReader() : partFirst(NULL), partLast(NULL)
dvander@2562
    47
{
dvander@2562
    48
}
dvander@2562
    49
dvander@2562
    50
UpdateReader::~UpdateReader()
dvander@2562
    51
{
dvander@2562
    52
}
dvander@2562
    53
dvander@2562
    54
void UpdateReader::ReadSMC_ParseStart()
dvander@2562
    55
{
dvander@2562
    56
	ignoreLevel = 0;
dvander@2562
    57
	ustate = USTATE_NONE;
dvander@2562
    58
}
dvander@2562
    59
dvander@2562
    60
SMCResult UpdateReader::ReadSMC_NewSection(const SMCStates *states, const char *name)
dvander@2562
    61
{
dvander@2562
    62
	if (ignoreLevel)
dvander@2562
    63
	{
dvander@2562
    64
		ignoreLevel++;
dvander@2562
    65
		return SMCResult_Continue;
dvander@2562
    66
	}
dvander@2562
    67
dvander@2562
    68
	switch (ustate)
dvander@2562
    69
	{
dvander@2562
    70
	case USTATE_NONE:
dvander@2562
    71
		{
dvander@2562
    72
			if (strcmp(name, "Folders") == 0)
dvander@2562
    73
			{
dvander@2562
    74
				ustate = USTATE_FOLDERS;
dvander@2562
    75
			}
dvander@2562
    76
			else if (strcmp(name, "Changed") == 0)
dvander@2562
    77
			{
dvander@2562
    78
				ustate = USTATE_CHANGED;
dvander@2562
    79
			}
dvander@2630
    80
			else if (strcmp(name, "Errors") == 0)
dvander@2630
    81
			{
dvander@2630
    82
				ustate = USTATE_ERRORS;
dvander@2630
    83
			}
dvander@2562
    84
			else
dvander@2562
    85
			{
dvander@2562
    86
				ignoreLevel++;
dvander@2562
    87
			}
dvander@2562
    88
			break;
dvander@2562
    89
		}
dvander@2562
    90
	case USTATE_FOLDERS:
dvander@2562
    91
	case USTATE_CHANGE_FILE:
dvander@2562
    92
		{
dvander@2562
    93
			ignoreLevel++;
dvander@2562
    94
			break;
dvander@2562
    95
		}
dvander@2562
    96
	case USTATE_CHANGED:
dvander@2562
    97
		{
dvander@2562
    98
			curfile.assign(name);
dvander@2562
    99
			url.clear();
dvander@2562
   100
			checksum[0] = '\0';
dvander@2562
   101
			ustate = USTATE_CHANGE_FILE;
dvander@2562
   102
			break;
dvander@2562
   103
		}
dvander@2562
   104
	}
dvander@2562
   105
dvander@2562
   106
	return SMCResult_Continue;
dvander@2562
   107
}
dvander@2562
   108
dvander@2562
   109
SMCResult UpdateReader::ReadSMC_KeyValue(const SMCStates *states,
dvander@2562
   110
										 const char *key,
dvander@2562
   111
										 const char *value)
dvander@2562
   112
{
dvander@2562
   113
	if (ignoreLevel)
dvander@2562
   114
	{
dvander@2562
   115
		return SMCResult_Continue;
dvander@2562
   116
	}
dvander@2562
   117
dvander@2562
   118
	switch (ustate)
dvander@2562
   119
	{
dvander@2562
   120
	case USTATE_CHANGE_FILE:
dvander@2562
   121
		{
dvander@2562
   122
			if (strcmp(key, "md5sum") == 0)
dvander@2562
   123
			{
dvander@2562
   124
				if (strlen(value) != 32)
dvander@2562
   125
				{
dvander@2562
   126
					return SMCResult_Continue;
dvander@2562
   127
				}
dvander@2562
   128
				strcpy(checksum, value);
dvander@2562
   129
			}
dvander@2562
   130
			else if (strcmp(key, "location") == 0)
dvander@2562
   131
			{
dvander@2575
   132
				url.assign(update_url);
dvander@2562
   133
				url.append(value);
dvander@2562
   134
			}
dvander@2562
   135
			break;
dvander@2562
   136
		}
dvander@2630
   137
	case USTATE_ERRORS:
dvander@2630
   138
		{
dvander@2630
   139
			if (strcmp(key, "error") == 0)
dvander@2630
   140
			{
dvander@2630
   141
				AddUpdateError("%s", value);
dvander@2630
   142
			}
dvander@2630
   143
			break;
dvander@2630
   144
		}
dvander@2562
   145
	case USTATE_FOLDERS:
dvander@2562
   146
		{
dvander@2562
   147
			HandleFolder(value);
dvander@2562
   148
			break;
dvander@2562
   149
		}
dvander@2562
   150
	}
dvander@2562
   151
dvander@2562
   152
	return SMCResult_Continue;
dvander@2562
   153
}
dvander@2562
   154
dvander@2562
   155
SMCResult UpdateReader::ReadSMC_LeavingSection(const SMCStates *states)
dvander@2562
   156
{
dvander@2562
   157
	if (ignoreLevel)
dvander@2562
   158
	{
dvander@2562
   159
		ignoreLevel--;
dvander@2562
   160
		return SMCResult_Continue;
dvander@2562
   161
	}
dvander@2562
   162
dvander@2562
   163
	switch (ustate)
dvander@2562
   164
	{
dvander@2562
   165
	case USTATE_FOLDERS:
dvander@2562
   166
	case USTATE_CHANGED:
dvander@2630
   167
	case USTATE_ERRORS:
dvander@2562
   168
		{
dvander@2562
   169
			ustate = USTATE_NONE;
dvander@2562
   170
			break;
dvander@2562
   171
		}
dvander@2562
   172
	case USTATE_CHANGE_FILE:
dvander@2562
   173
		{
dvander@2562
   174
			if (url.size() != 0 && checksum[0] != '\0')
dvander@2562
   175
			{
dvander@2562
   176
				HandleFile();
dvander@2562
   177
			}
dvander@2562
   178
			else
dvander@2562
   179
			{
dvander@2562
   180
				AddUpdateError("Incomplete file definition in update manifest");
dvander@2562
   181
			}
dvander@2562
   182
			ustate = USTATE_CHANGED;
dvander@2562
   183
			break;
dvander@2562
   184
		}
dvander@2562
   185
	}
dvander@2562
   186
dvander@2562
   187
	return SMCResult_Continue;
dvander@2562
   188
}
dvander@2562
   189
dvander@2573
   190
void UpdateReader::LinkPart(UpdatePart *part)
dvander@2573
   191
{
dvander@2573
   192
	part->next = NULL;
dvander@2573
   193
	if (partFirst == NULL)
dvander@2573
   194
	{
dvander@2573
   195
		partFirst = part;
dvander@2573
   196
		partLast = part;
dvander@2573
   197
	}
dvander@2573
   198
	else
dvander@2573
   199
	{
dvander@2573
   200
		partLast->next = part;
dvander@2573
   201
		partLast = part;
dvander@2573
   202
	}
dvander@2573
   203
}
dvander@2573
   204
dvander@2562
   205
void UpdateReader::HandleFile()
dvander@2562
   206
{
dvander@2562
   207
	MD5 md5;
dvander@2562
   208
	char real_checksum[33];
dvander@2562
   209
dvander@2562
   210
	mdl.Reset();
dvander@2562
   211
	
dvander@2562
   212
	if (!xfer->Download(url.c_str(), &mdl, NULL))
dvander@2562
   213
	{
dvander@2562
   214
		AddUpdateError("Could not download \"%s\"", url.c_str());
dvander@2562
   215
		AddUpdateError("Error: %s", xfer->LastErrorMessage());
dvander@2562
   216
		return;
dvander@2562
   217
	}
dvander@2562
   218
dvander@2562
   219
	md5.update((unsigned char *)mdl.GetBuffer(), mdl.GetSize());
dvander@2562
   220
	md5.finalize();
dvander@2562
   221
	md5.hex_digest(real_checksum);
dvander@2562
   222
dvander@2573
   223
	if (mdl.GetSize() == 0)
dvander@2573
   224
	{
dvander@2573
   225
		AddUpdateError("Zero-length file returned for \"%s\"", curfile.c_str());
dvander@2573
   226
		return;
dvander@2573
   227
	}
dvander@2573
   228
dvander@2562
   229
	if (strcasecmp(checksum, real_checksum) != 0)
dvander@2562
   230
	{
dvander@2562
   231
		AddUpdateError("Checksums for file \"%s\" do not match:", curfile.c_str());
dvander@2562
   232
		AddUpdateError("Expected: %s Real: %s", checksum, real_checksum);
dvander@2562
   233
		return;
dvander@2562
   234
	}
dvander@2562
   235
dvander@2573
   236
	UpdatePart *part = new UpdatePart;
dvander@2573
   237
	part->data = (char*)malloc(mdl.GetSize());
dvander@2630
   238
	memcpy(part->data, mdl.GetBuffer(), mdl.GetSize());
dvander@2573
   239
	part->file = strdup(curfile.c_str());
dvander@2573
   240
	part->length = mdl.GetSize();
dvander@2573
   241
	LinkPart(part);
dvander@2562
   242
}
dvander@2562
   243
dvander@2562
   244
void UpdateReader::HandleFolder(const char *folder)
dvander@2562
   245
{
dvander@2573
   246
	UpdatePart *part = new UpdatePart;
dvander@2573
   247
	part->data = NULL;
dvander@2573
   248
	part->length = 0;
dvander@2573
   249
	part->file = strdup(folder);
dvander@2573
   250
	LinkPart(part);
dvander@2562
   251
}
dvander@2562
   252
dvander@2562
   253
static bool md5_file(const char *file, char checksum[33])
dvander@2562
   254
{
dvander@2562
   255
	MD5 md5;
dvander@2562
   256
	FILE *fp;
dvander@2562
   257
	long length;
dvander@2562
   258
	void *fdata;
dvander@2562
   259
dvander@2630
   260
	if ((fp = fopen(file, "rb")) == NULL)
dvander@2562
   261
	{
dvander@2562
   262
		return false;
dvander@2562
   263
	}
dvander@2562
   264
dvander@2562
   265
	fseek(fp, 0, SEEK_END);
dvander@2562
   266
	length = ftell(fp);
dvander@2562
   267
	fseek(fp, 0, SEEK_SET);
dvander@2562
   268
	fdata = malloc(length);
dvander@2562
   269
	if (fread(fdata, 1, length, fp) != size_t(length))
dvander@2562
   270
	{
dvander@2562
   271
		free(fdata);
dvander@2562
   272
		fclose(fp);
dvander@2562
   273
		return false;
dvander@2562
   274
	}
dvander@2562
   275
	fclose(fp);
dvander@2562
   276
dvander@2562
   277
	md5.update((unsigned char*)fdata, length);
dvander@2562
   278
	md5.finalize();
dvander@2562
   279
	md5.hex_digest(checksum);
dvander@2562
   280
dvander@2562
   281
	free(fdata);
dvander@2562
   282
dvander@2562
   283
	return true;
dvander@2562
   284
}
dvander@2562
   285
dvander@2562
   286
/* Path should be sourcemod relative, not gamedata relative */
dvander@2562
   287
static bool add_file(IWebForm *form, const char *file, unsigned int &num_files)
dvander@2562
   288
{
dvander@2562
   289
	char path[PLATFORM_MAX_PATH];
dvander@2562
   290
dvander@2562
   291
	smutils->BuildPath(Path_SM, path, sizeof(path), "%s", file);
dvander@2562
   292
dvander@2562
   293
	char checksum[33];
dvander@2562
   294
	if (!md5_file(path, checksum))
dvander@2562
   295
	{
dvander@2562
   296
		return false;
dvander@2562
   297
	}
dvander@2562
   298
dvander@2562
   299
	char name[32];
dvander@2562
   300
	smutils->Format(name, sizeof(name), "file_%d_name", num_files);
dvander@2562
   301
	form->AddString(name, file);
dvander@2562
   302
	smutils->Format(name, sizeof(name), "file_%d_md5", num_files);
dvander@2562
   303
	form->AddString(name, checksum);
dvander@2562
   304
dvander@2562
   305
	num_files++;
dvander@2562
   306
dvander@2562
   307
	return true;
dvander@2562
   308
}
dvander@2562
   309
dvander@2562
   310
static void add_folders(IWebForm *form, const char *root, unsigned int &num_files)
dvander@2562
   311
{
dvander@2562
   312
	IDirectory *dir;
dvander@2562
   313
	char path[PLATFORM_MAX_PATH];
dvander@2562
   314
	char name[PLATFORM_MAX_PATH];
dvander@2562
   315
dvander@2562
   316
	smutils->BuildPath(Path_SM, path, sizeof(path), "%s", root);
dvander@2562
   317
	dir = libsys->OpenDirectory(path);
dvander@2562
   318
	if (dir == NULL)
dvander@2562
   319
	{
dvander@2562
   320
		AddUpdateError("Could not open folder: %s", path);
dvander@2562
   321
		return;
dvander@2562
   322
	}
dvander@2562
   323
dvander@2562
   324
	while (dir->MoreFiles())
dvander@2562
   325
	{
dvander@2562
   326
		if (strcmp(dir->GetEntryName(), ".") == 0 ||
dvander@2562
   327
			strcmp(dir->GetEntryName(), "..") == 0)
dvander@2562
   328
		{
dvander@2562
   329
			dir->NextEntry();
dvander@2562
   330
			continue;
dvander@2562
   331
		}
dvander@2562
   332
		smutils->Format(name, sizeof(name), "%s/%s", root, dir->GetEntryName());
dvander@2562
   333
		if (dir->IsEntryDirectory())
dvander@2562
   334
		{
dvander@2562
   335
			add_folders(form, name, num_files);
dvander@2562
   336
		}
dvander@2562
   337
		else if (dir->IsEntryFile())
dvander@2562
   338
		{
dvander@2562
   339
			add_file(form, name, num_files);
dvander@2562
   340
		}
dvander@2562
   341
		dir->NextEntry();
dvander@2562
   342
	}
dvander@2562
   343
dvander@2562
   344
	libsys->CloseDirectory(dir);
dvander@2562
   345
}
dvander@2562
   346
dvander@2575
   347
void UpdateReader::PerformUpdate(const char *url)
dvander@2562
   348
{
dvander@2562
   349
	IWebForm *form;
dvander@2562
   350
	MemoryDownloader master;
dvander@2562
   351
	SMCStates states = {0, 0};
dvander@2562
   352
dvander@2575
   353
	update_url = url;
dvander@2575
   354
dvander@2562
   355
	form = webternet->CreateForm();
dvander@2562
   356
	xfer = webternet->CreateSession();
dvander@2562
   357
	xfer->SetFailOnHTTPError(true);
dvander@2562
   358
asherkin@3630
   359
	form->AddString("version", SM_VERSION_STRING);
dvander@2562
   360
dvander@2562
   361
	unsigned int num_files = 0;
dvander@2562
   362
	add_folders(form, "gamedata", num_files);
dvander@2562
   363
dvander@2562
   364
	char temp[24];
dvander@2562
   365
	smutils->Format(temp, sizeof(temp), "%d", num_files);
dvander@2562
   366
	form->AddString("files", temp);
dvander@2562
   367
dvander@2575
   368
	if (!xfer->PostAndDownload(url, form, &master, NULL))
dvander@2562
   369
	{
dvander@2575
   370
		AddUpdateError("Could not download \"%s\"", url);
dvander@2562
   371
		AddUpdateError("Error: %s", xfer->LastErrorMessage());
dvander@2562
   372
		goto cleanup;
dvander@2562
   373
	}
dvander@2562
   374
	
dvander@2562
   375
	SMCError error;
dvander@2562
   376
	char errbuf[256];
dvander@2562
   377
	error = textparsers->ParseSMCStream(master.GetBuffer(),
dvander@2562
   378
										master.GetSize(),
dvander@2562
   379
										this,
dvander@2562
   380
										&states,
dvander@2562
   381
										errbuf,
dvander@2562
   382
										sizeof(errbuf));
dvander@2562
   383
	if (error != SMCError_Okay)
dvander@2562
   384
	{
dvander@2562
   385
		AddUpdateError("Parse error in update manifest: %s", errbuf);
dvander@2562
   386
		goto cleanup;
dvander@2562
   387
	}
dvander@2562
   388
dvander@2562
   389
cleanup:
dvander@2562
   390
	delete xfer;
dvander@2562
   391
	delete form;
dvander@2562
   392
}
dvander@2573
   393
dvander@2573
   394
UpdatePart *UpdateReader::DetachParts()
dvander@2573
   395
{
dvander@2573
   396
	UpdatePart *first;
dvander@2573
   397
dvander@2573
   398
	first = partFirst;
dvander@2573
   399
	partFirst = NULL;
dvander@2573
   400
	partLast = NULL;
dvander@2573
   401
dvander@2573
   402
	return first;
dvander@2573
   403
}
dvander@2783
   404