Brainfart Saturday

I need to switch coffee brands…

Yesterday, for no apparent reason, I thought it may be a good idea to create a file transfer app that will asynchronously send files in slices(chunks) and still ensure the receiving party’s checksum will mach the sender’s. I was planning on adding public key security to the whole thing, but I can’t seem to get past step 1 without issues.

I tried testing splitting a file into slices and merging them immediately after and it seems to work just fine for small files.

Blob blob = FileUtils.GetBlob("C:\\Users\\Portable\\Downloads\\smalldoc.pdf");

FileUtils.SplitSlices(ref blob);

// Change the filename
blob.Path = "C:\\Users\\Portable\\Downloads\\smalldocCopy.pdf";
// Merge the slices back into one under the new filename
FileUtils.MergeSlices(ref blob);

The head-scratching starts when splitting and merging a large-ish file (50Mb+). The “Size on disk” identical to the original, but the “Size” is smaller than the original Size. Meaning it’s taking up the same disk allocation, but some bits got lost along the way. The funny thing is that if I then split the merged copy and merge it again into another copy, then this third copy is identical to the second. So original is still the odd one out.

I can’t seem to find the reason for this other than I’m missing something really obvious or this is a platform issue. I hope it’s the former because cursing at the latter feels… weird.

Here’s the “Slice” class where data would be stored and sent/received async.

public class Slice
{
	// Slice Id (Checksum / Currently not used)
	public string Id { get; set; }
	
	// File(Blob) Id (Checksum)
	public string SourceId { get; set; }

	// Blob location index
	public int Index { get; set; }

	// Slice byte length
	public int Size { get; set; }

	// Slice data
	public string Data { get; set; }

	public bool Complete { get; set; }

	public Slice()
	{
		Complete = false;
	}
}

And the “Blob” class that use the above slice(s)

public class Blob
{
	// File Id (Checksum)
	public string Id { get; set; }

	// Slice collection
	public SortedDictionary<int, Slice> Slices { get; set; }

	// Save path
	public string Path { get; set; }

	// File size
	public int Size { get; set; }

	// Assembled file size
	public int CompletedSize { get; set; }

	public Blob()
	{
		Slices = new SortedDictionary<int, Slice>();
		Size = 0;
		CompletedSize = 0;
	}
}

And of course, the uglier-than-sin FileUtils class (those with weak hearts, avert your eyes).

public static class FileUtils
{
	private static int _blockSize = 65536;

	public static void SplitSlices(ref Blob blob)
	{
		FileInfo info = new FileInfo(blob.Path);
		string source = info.FullName;
		string dir = info.DirectoryName;

		using (FileStream fs = new FileStream(source, FileMode.Open, FileAccess.Read))
		{
			foreach (KeyValuePair<int, Slice> kv in blob.Slices)
			{
				Slice slice = kv.Value;
				byte[] data = new byte[slice.Size];
				int read = 0;

				fs.Seek(slice.Index, SeekOrigin.Begin);
				if ((read = fs.Read(data, 0, slice.Size)) > 0)
				{
					WriteSlice(ref slice, data, dir);
				}
			}
		}
	}

	public static void WriteSlice(ref Slice slice, byte[] data, string dir)
	{
		string slicePath = SourceFromSlice(slice, dir);
		using (FileStream ofs =
			new FileStream(slicePath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
		{
			ofs.Write(data, 0, slice.Size);
			slice.Complete = true;
		}
	}

	public static void MergeSlices(ref Blob blob)
	{
		FileInfo blobInfo = new FileInfo(blob.Path);
		string dir = blobInfo.DirectoryName;

		using (FileStream outfs =
			new FileStream(blobInfo.FullName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
		{
			foreach (KeyValuePair<int, Slice> kv in blob.Slices)
			{
				Slice slice = kv.Value;
				if (slice.Complete)
				{
					byte[] bytes = ReadSlice(ref slice, dir, true);
					outfs.Seek(slice.Index, SeekOrigin.Begin);
					outfs.Write(bytes, 0, slice.Size);

					// Update the completed count
					blob.CompletedSize += slice.Size;
				}
			}
		}
	}

	public static byte[] ReadSlice(ref Slice slice, string dir, bool delAfterReading)
	{
		int read = 0;
		byte[] data = new byte[slice.Size];
		string slicePath = SourceFromSlice(slice, dir);

		using (FileStream ifs = new FileStream(slicePath, FileMode.Open, FileAccess.Read))
		{
			read = ifs.Read(data, 0, slice.Size);
		}

		if (delAfterReading)
			File.Delete(slicePath);

		return data;
	}

	public static void InitBlob(ref Blob blob)
	{
		int sliceCount = 0;
		int sliceSize;

		// Catch remaining byte length after splitting
		int remainder = (blob.Size > _blockSize)? (blob.Size % _blockSize) : 0;

		// If this is a big file that can be split...
		if (blob.Size > _blockSize)
		{
			sliceCount = blob.Size / _blockSize;
			sliceSize = blob.Size / sliceCount;
		}
		else // Slice size same as blob size and only one slice needed
		{
			sliceCount = 1;
			sliceSize = blob.Size;
		}

		for (int i = 0; i < sliceCount; i++)
		{
			Slice slice = new Slice();
			slice.SourceId = blob.Id;
			slice.Size = (i == 0) ? sliceSize + remainder : sliceSize;
			slice.Index = i * slice.Size;

			blob.Slices.Add(slice.Index, slice);
		}
	}

	public static Blob GetBlob(string source)
	{
		Blob blob = new Blob();
		FileInfo info = new FileInfo(source);

		blob.Id = FileId(source);
		blob.Size = LengthToInt(info.Length);
		blob.Path = info.FullName;
		blob.CompletedSize = LengthToInt(info.Length);

		InitBlob(ref blob);
		return blob;
	}

	public static string GetChecksum(string source, string mode = "md5", bool isFile = false)
	{
		byte[] bytes = { };
		Stream fs;

		if (isFile)
			fs = new BufferedStream(File.OpenRead(source), 120000);
		else
			fs = new MemoryStream(Encoding.UTF8.GetBytes(source));

		switch (mode.ToLower())
		{
			case "sha1":
				using (SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider())
					bytes = sha1.ComputeHash(fs);
				break;

			case "sha256":
				using (SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider())
					bytes = sha256.ComputeHash(fs);
				break;

			case "sha512":
				using (SHA512CryptoServiceProvider sha512 = new SHA512CryptoServiceProvider())
					bytes = sha512.ComputeHash(fs);
				break;

			case "md5":
			default:
				using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
					bytes = md5.ComputeHash(fs);
				break;
		}

		// Cleanup
		fs.Close();
		fs = null;

		return BitConverter
			.ToString(bytes)
			.Replace("-", "")
			.ToLower();
	}

	private static int LengthToInt(long length)
	{
		return (int)Math.Ceiling((double)length);
	}

	private static string FileId(string source)
	{
		return GetChecksum(new FileInfo(source).FullName, "sha256", true);
	}

	private static string SourceFromSlice(Slice slice, string dir)
	{
		return dir + "\\" + slice.SourceId + "_" + slice.Index + ".slice";
	}
}
Advertisements

Cryptographically secure One-time Pads

It’s the end of Christmas day… And I’ve got a splitting headache (because I don’t drink and had to watch everything). Luckily I’m not covered in someone else’s puke or urine, which is always nice.  No one drove into any trees (as far as I know), lost their pants or an eye.

Super…

Just finished checking my email and it seems a quite a few people were interested in my last post on the one-time pad and one frequent question was how to make it secure enough for truly private communication. First off, the JavaScript version is out if you want true security. Second, it’s best to implement something in a reusable class that can be used perhaps in a web application or client-side app. I chose an MVC web app in C# for this demonstration because my brain won’t let me stay up for much longer.

To make a large enough one-time pad that is both cryptographically secure and still computationally practical, we have to balance between how strong the algorithm is and how often it is iterated. In my original example, I opted to shuffle the character pool using the Fisher-Yates algorithm before a character is picked and each time, the pick was also random from the shuffled pool.

Considering JavaScript’s inherent weakness in that it must rely on the browser’s scripting engine for the psudorandom number, these were necessary improvements even with the added overhead. However, since modern browers do have optimized JS engines, this wasn’t a huge concren.

The problem is when we move toward much stronger cryptographic functions where computation starts to become non-trivial.

In the case of the RNGCryptoServiceProvider, the class provides a much stronger random number than the simple Random function. The output is spectrally white and is better suited for generating keys. The main point here is that it is cryptographically secure. I.E. It’s random enough to be used for proper encryption while still being a psudorandom number generator. The down side is that it is more computationally intensive than just plain Random.

The solution is to not shuffle the pool between character picks, but randomize between “segments”; the separated 6-8 character “words” in the pad. This strikes a good balance between randomness and speed.

Then, the issue comes down to how the pad is generated and presented to the user. Conventionally, this was in the form of plain text or as a downloadable file, however one of the requests I received was to make something that can do both. If the one-time pad can be saved as an image file, it can be sent via encrypted email to the recipient. The risk is that browsers store images and the like and the browser cache must be emptied after each generation. If a printer is used, it must also be cleared of its cache because some printers save a history of printed documents.

The following is a quick class that has both text and image generation. The GetImg function can be used to turn any sort of text into an image byte array, not just pad text.

/**
 * THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

using System;
using System.Security;
using System.Security.Cryptography;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Text;

namespace OneTimePad
{
	public class PadModel
	{
		// Render settings
		FontFamily renderFont = FontFamily.GenericMonospace;
		FontStyle renderStyle = FontStyle.Bold;
		GraphicsUnit renderUnit = GraphicsUnit.Pixel;
		int renderSize = 10;

		// Creates formatted pages of keys
		public string RenderPad(int s, int l, string chars)
		{
			// Result
			StringBuilder sb = new StringBuilder();

			// First page
			int p = 1;

			for (int i = 0; i < l; i++)
			{
				// First page number
				if (p == 1 && i == 0)
					sb.Append("1.\n\n");

				// Generate segment
				sb.Append(GenerateRandomString(s, chars));

				// Page, number and segment separartion
				if (i % 63 == 62)
				{
					if (i + 1 < l)
					{
						p++;
						sb.Append("\n\n\n");
						sb.Append(p);
						sb.Append(".\n\n");
					}
				}
				else if (i % 7 == 6) // Line separation
				{
					sb.Append("\n");
				}
				else // Segment separation
				{
					sb.Append("   ");
				}
			}

			return sb.ToString();
		}

		// Generates a random string of given length
		public static string GenerateRandomString(int len, string range)
		{
			Byte[] _bytes = new Byte[len];
			char[] _chars = new char[len];

			// Shuffle the range first
			range = Shuffle(range);

			using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
			{
				rng.GetBytes(_bytes);

				for (int i = 0; i < len; i++)
					_chars[i] = range[(int)_bytes[i] % range.Length];
			}

			return new string(_chars);
		}

		// Implements the Fisher-Yates algorithm to shuffle the range
		public static string Shuffle(string range)
		{
			char[] _chars = range.ToCharArray();
			int len = _chars.Length;
			Byte[] _bytes = new Byte[len];

			using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
			{
				for (int i = len - 1; i > 1; i--)
				{
					// New set of random bytes
					rng.GetBytes(_bytes);

					int r = (int)_bytes[i] % len;
					char c = _chars[i];
					_chars[i] = _chars[r]; // Swap
					_chars[r] = c;
				}
			}

			return new string(_chars);
		}

		// Generates a jpeg of given text
		public byte[] GetImg(string txt)
		{
			// Blank image
			Bitmap bmp = new Bitmap(1, 1);
			Graphics gfx = Graphics.FromImage(bmp);

			// Font settings
			Font fnt = new Font(renderFont, renderSize, renderStyle, renderUnit);

			// Image dimensions
			int w = (int)gfx.MeasureString(txt, fnt).Width;
			int h = (int)gfx.MeasureString(txt, fnt).Height;

			// New image to text size
			bmp = new Bitmap(bmp, new Size(w, h));

			gfx = Graphics.FromImage(bmp);

			// Defaults
			gfx.Clear(Color.White);
			gfx.SmoothingMode = SmoothingMode.Default;

			gfx.TextRenderingHint =
				System.Drawing.Text.TextRenderingHint.AntiAlias;

			gfx.DrawString(txt, fnt, new SolidBrush(Color.Black), 0, 0);

			// Cleanup
			gfx.Flush();

			MemoryStream ms = new MemoryStream();
			bmp.Save(ms, ImageFormat.Png);
			return ms.ToArray();
		}
	}
}

 

To use this in an MVC application, for example, I would use the following ActionResult.

public ActionResult Index(FormCollection collection)
{
	// Generate
	string chars = "2346789ABCDEFGHKLMNPQRTWXYZ";

	// Default values
	int ds = 8, dl = 882;

	// Store (s = segment size, l = number of segments)
	int s = 0, l = 0;

	// Assign values
	if (Int32.TryParse(collection["s"], out s) || Int32.TryParse(collection["l"], out l))
	{
		// Set defaults to practical limits
		s = (s > 0 && s <= 20) ? s : ds;
		l = (l > 0 && l <= 1000) ? l : dl;
	}
	else
	{
		// Set defaults
		s = ds;
		l = dl;
	}

	PadModel model = new PadModel();
	string txt = model.RenderPad(s, l, chars);
	string img = Convert.ToBase64String(model.GetImg(txt));

	ViewData["s"] = s; // Segment size to be shown
	ViewData["l"] = l; // Total segment count
	ViewData["pad"] = txt; // Plain text version of the pad

	// Instead of sending binary data, I opted to use a base64 encoded image instead
	// since most modern browsers support it anyway
	ViewData["img"] = string.Format("data:image/png;base64,{0}", img);

	return View();
}

 

And in the View page, we can display both the plain text version and the image version of the pad using the following two lines

<img src="<%= ViewData["img"] %>" alt="PadImage" />
<pre><%= ViewData["Pad"] %></pre>

 

As an added benefit of sending a base64 encoded image instead of a binary file is that the base64 text itself can be sent via encrypted email that can be reconstituted by the recipient. This added layer of security is also handy if the user intends to hide the pad in its entirety in another data file.

The image version should produce something like this…

An image based one time pad that can be printed

 

I generated that pad just a little while ago and printed it out on my laser printer. Of course the quality is rubbish because my printer is low-res and only meant for office documents, but with a proper high resolution inkjet, we can get much finer details.

 

One-time pad close-up

 

Because the sheets are numbered, the previous message can contain details on which sheet to use for the next communication. Ideally, these pages should be cut into individual sheets and destroyed after each use.

Hope this was helpful.

Now I’m off to hit the hay!

Forum core source dump (as of July 31)

I would have finished this a while ago, but I’m in the middle of moving to a new apartment. Until that’s over, I figured I’ll dump what I have so far on the forum.

And I’ve finally settled on a name for this. I shall hereby call this forum script : theForum. Yeah, points for originality there. And of course, this will contain errors, incomplete functions/functionality and other terrible things, so be careful when using any part of this in your own projects. And on top of that the WordPress source code formatting is a bit off making error checking even more vital.

WARNING: This will be a very, very, very long post (7 pages total)!

index.php

/*

	#### theForum : A brand you can trust ####

*/



/******************************************************************************

	Modifying anything below will void the (non-existent) warranty!

*******************************************************************************/

// WARNING: Keep these intact.

// Root location
$rt = dirname(__FILE__) . '/';

// Get the main logic
require_once($rt . 'lib/main.php');

config.php

/******************************************************************************

	Edit your database settings here... (These values go in quotes)

*******************************************************************************/

// Your database server
$presets['db_host'] = 'localhost';

// Database name
$presets['db_name'] = 'wrex';

// Database admin name
$presets['db_user'] = 'wrex_user';

// Database admin password
$presets['db_pass'] = 'password';

// Database type
$presets['db_type'] = 'MySQL';


/******************************************************************************

	Edit your forum settings here... (Some settings require quotes)

*******************************************************************************/


// Forum title (you can include some HTML tags, but they will be stripped for page <title> tags)
$presets['site_title'] = 'the<span>Forum</span>';

// Slogan
$presets['site_slogan'] = 'A brand you can trust';

// Copyright notice
$presets['site_copyright'] = 'Copyright © 2009. All rights reserved.';

// Full site url
$presets['site_url'] = 'http://localhost/';


// Topics per page
$presets['page_topic_limit'] = 15;

// Replies per page
$presets['page_reply_limit'] = 10;


// The relative path to the forums for various paths in themes
$presets['site_path'] = '/';

$presets['site_path_seperator'] = ' » ';

// Present friendly, SEO friendly, URLs by avoiding querystring usage
// (this requires a .htaccess file for Apache or a map/ISAPI filter for IIS)
$presets['site_friendly_urls'] = '1';

// Allow self service creation of accounts. Admins can still create users. (True = 1 / False = 0)
$presets['site_registration'] = '1';

// The default template and styles.
$presets['site_theme'] = 'Section5';

// Closed for maintenance. If set to true, the "Forums closed" message will be displayed
// True = 1 / False = 0
$presets['site_closed'] = '0';

// Forums closed message in HTML format
$presets['site_closed_message'] = '<p>The forums are closed for maintenance. Please check back later.</p>';

// Welcome message (Don't change the "WELCOME" parts). You need to use HTML.
$presets['site_welcome'] =< <<WELCOME



<h2>Welcome
<h3>This is a demonstration template</h3>
<p>Rockwell Accessible is an experimental theme designed to improve web accessibility while maintaining a logical HTML structure. This is one variation of the base theme which is an example of customizability. The idea is to make any changes to the stylesheet while leaving the rest of the HTML intact. It includes provisions for error/notification formatting as well as text and link highlighting.</p>
<p>Read the accompanying <a href="https://eksith.wordpress.com/2009/02/16/rockwell-accessible/">blog post</a>.</p>



WELCOME;

// Footer HTML including copyrights and links etc...
$presets['site_footer'] =< <<FOOTER

	<p>
	<p class="powered">
		<a href="https://eksith.wordpress.com/"><img src="/templates/RockwellGradient/img/ouroboros.png" alt="Design by Eksith" longdesc="https://eksith.wordpress.com/" height="30" width="30" /></a>
	</p>

FOOTER;


/******************************************************************************

	##################### End casual editing #####################
	Edit below (if you know what you're doing) at your own risk!

*******************************************************************************/



// Root location of this file. All file inclusions depend on this.
$presets['site_root'] = dirname(__FILE__) . '/';

// Write enabled directories are required. Please disable execute privileges on these as a security precaution.
$presets['site_upload_directory'] = $presets['site_root'] . 'data/uploads';
$presets['site_cache_directory'] = $presets['site_root'] . 'data/cache';
$presets['site_cache_duration'] = '160';

// The template url is dependent on the SITE_THEME and SITE_TEMPLATE values.

$presets['site_template_directory'] = $presets['site_path'] . 'templates/';
$presets['site_template_url'] = $presets['site_path'] . 'templates/' . $presets['site_theme'] . '/';

// Default language to use in two letter format (preset is English).
$presets['site_lang'] = 'en';


// Security regular expressions
$presets['core_regex_valid_username'] = '/^[\w-_]{4,50}/iu';
$presets['core_regex_valid_displayname'] = '/^[\w\s\.-_]{0,100}/iu';
$presets['core_regex_valid_email'] = '/^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z.]{2,5}$/i';
$presets['core_regex_valid_querystring'] = '/^[\d-_a-zA-Z]/i';
$presets['core_regex_valid_htmlparams'] = '/^[\w-_]{4,20}/i';
$presets['core_regex_valid_colors'] = '/^#(([a-fA-F0-9]{3}$)|([a-fA-F0-9]{6}$))/i';
$presets['core_regex_invalid_search'] = '/^[~!\|\'()\[\]\< \>;]/i';

// Formatting allowed HTML tags and allowed corresponding attributes

// Convert deprecated tags.
// Convention: array([old tag], [new tag + formatting], [end tag]);
$presets['ui_format_tags_deprecated'] = array(
	array('b', 'strong', 'strong'),
	array('i', 'em', 'em'),
	array('s', 'span style="text-decoration:strikethrough;"', 'span'),
	array('strike', 'span style="text-decoration:strikethrough;"', 'span'),
	array('center', 'div style="text-align:center"', 'div'),
	array('left', 'div style="text-align:left"', 'div'),
	array('right', 'div style="text-align:right"', 'div'),
	array('u', 'span style="text-decoration:underline;"', 'span')
);

// Allowed HTML tags. All others, except for those found above, will be stripped
// Convention: array([tag name], [allowed attributes]);
$presets['ui_format_tags'] = array(
	array('a', 'style,class,title,href,rel,target'),
	array('abbr', 'style,class,title'),
	array('acronym', 'style,class,title'),
	array('blockquote', 'style,class,title,dir,lang,xml:lang'),
	array('br', 'style,class'),
	array('caption', 'style,class,title'),
	array('cite', 'style,class,title'),
	array('code', 'style,class,title'),
	array('del', 'style,class,title'),
	array('em', 'style,class,title'),
	array('h1', 'style,class,title'),
	array('h2', 'style,class,title'),
	array('h3', 'style,class,title'),
	array('h4', 'style,class,title'),
	array('h5', 'style,class,title'),
	array('h6', 'style,class,title'),
	array('hr', 'style,class'),
	array('img', 'style,class,src,height,width,border,hspace,vspace,alt,longdesc'),
	array('ins', 'style,class,title,cite'),
	array('li', 'style,class,title'),
	array('ol', 'style,class,title'),
	array('p', 'style,class,title,align,dir,lang,xml:lang'),
	array('pre', 'style,class,title,dir,lang,xml:lang'),
	array('strong', 'style,class,title'),
	array('span', 'style,class,title,dir,lang,xml:lang'),
	array('table', 'style,class,title,border\-collapse,bgcolor,background'),
	array('tfoot', 'style,class,title,align'),
	array('thead', 'style,class,title,align'),
	array('tbody', 'style,class,title,align'),
	array('td', 'style,class,title,align,valign'),
	array('th', 'style,class,title,align,valign'),
	array('tr', 'style,class,title,align,valign'),
	array('ul', 'style,class,title')
);

// Prohibited keywords in attribute values (from W3C)
// It is STRONGLY recommended that you not remove any of these 
// as certain browsers may allow their execution in unpredictable ways.
$presets['ui_format_invalid_attributes'] = array(
	'onabort',
	'onactivate',
	'onafterprint',
	'onafterupdate',
	'onbeforeactivate',
	'onbeforecopy',
	'onbeforecut',
	'onbeforedeactivate',
	'onbeforeeditfocus',
	'onbeforepaste',
	'onbeforeprint',
	'onbeforeunload',
	'onbeforeupdate',
	'onblur',
	'onbounce',
	'oncellchange',
	'onchange',
	'onclick',
	'oncontextmenu',
	'oncontrolselect',
	'oncopy',
	'oncut',
	'ondataavaible',
	'ondatasetchanged',
	'ondatasetcomplete',
	'ondblclick',
	'ondeactivate',
	'ondrag',
	'ondragdrop',
	'ondragend',
	'ondragenter',
	'ondragleave',
	'ondragover',
	'ondragstart',
	'ondrop',
	'onerror',
	'onerrorupdate',
	'onfilterupdate',
	'onfinish',
	'onfocus',
	'onfocusin',
	'onfocusout',
	'onhelp',
	'onkeydown',
	'onkeypress',
	'onkeyup',
	'onlayoutcomplete',
	'onload',
	'onlosecapture',
	'onmousedown',
	'onmouseenter',
	'onmouseleave',
	'onmousemove',
	'onmoveout',
	'onmouseover',
	'onmouseup',
	'onmousewheel',
	'onmove',
	'onmoveend',
	'onmovestart',
	'onpaste',
	'onpropertychange',
	'onreadystatechange',
	'onreset',
	'onresize',
	'onresizeend',
	'onresizestart',
	'onrowexit',
	'onrowsdelete',
	'onrowsinserted',
	'onscroll',
	'onselect',
	'onselectionchange',
	'onselectstart',
	'onstart',
	'onstop',
	'onsubmit',
	'onunload'
);




/******************************************************************************

	######################## End editing ########################

*******************************************************************************/

// Class loader (required)
function __autoload($class)
{
	global $presets;
	
	// Try and get a class file
	$cfile = $presets['site_root'] . 'lib/' . $class . '.class.php';
	
	// Or module file
	$mfile = $presets['site_root'] . 'modules/' . strtolower($class) . '/module.php';
	
	if(file_exists($cfile))
		require_once($cfile);
	elseif(file_exists($mfile))
		require_once($mfile);
	else
		exit("Unable to Find $class in the library folder or plugin modules folder");
	
	if(!class_exists($class, false))
		exit("Unable to load $class class");
}