Wednesday, February 13, 2008

File Splitting

This splitter has been built because I didn't find a decent one in 5 minutes search. The reason behind a splitter is simple: Send files as an attachment (in my case using the gmail & gspace).
As for the code, I'm posting the full program here, and some highlights afterwards.

using System;
using System.Collections.Generic;
using System.Text;

namespace FileSplitter
{
class Program
{
const int DefalutBlockSize = 1000000;
private static byte[] STAMP = { 1, 2, 3 };

private static byte[] GetMD5(byte[] buf)
{
System.Security.Cryptography.MD5CryptoServiceProvider md = new System.Security.Cryptography.MD5CryptoServiceProvider();
md.Initialize();
return md.ComputeHash(buf);
}
private static void Split(string Filename, int BlockSize)
{
try
{
Console.WriteLine("Splitting " + Filename + " to " + BlockSize.ToString() + " bytes parts");
int part = 0;
byte[] buf;
System.IO.StreamReader in_fl = new System.IO.StreamReader(Filename);
System.IO.BinaryReader in_b = new System.IO.BinaryReader(in_fl.BaseStream);
while (true)
{
Console.WriteLine("Reading part " + part.ToString());
buf = in_b.ReadBytes(BlockSize);
if (buf.Length == 0)
{
in_b.Close();
Console.WriteLine("No more parts");
return;
}
Console.WriteLine("Writing part " + part.ToString());
System.IO.StreamWriter out_fl = new System.IO.StreamWriter(Filename + "." + part.ToString());
System.IO.BinaryWriter out_b = new System.IO.BinaryWriter(out_fl.BaseStream);
out_b.Write(STAMP);
byte[] MD = GetMD5(buf);
byte len = System.Convert.ToByte(MD.Length);
out_b.Write(len);
out_b.Write(MD);
out_b.Write(buf);
out_b.Flush();
out_b.Close();
part++;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private static void Merge(string Filename)
{
try
{
Console.WriteLine("Merging " + Filename);
int part = 0;
byte[] buf;
System.IO.StreamWriter out_fl = new System.IO.StreamWriter(Filename);
System.IO.BinaryWriter out_b = new System.IO.BinaryWriter(out_fl.BaseStream);
while (System.IO.File.Exists(Filename + "." + part.ToString()))
{
Console.WriteLine("Reading part " + part.ToString());
System.IO.StreamReader in_fl = new System.IO.StreamReader(Filename + "." + part.ToString());
System.IO.BinaryReader in_b = new System.IO.BinaryReader(in_fl.BaseStream);
buf = in_b.ReadBytes(STAMP.Length);
for (int i = 0; i < STAMP.Length; i++)
{
if (buf[i] != STAMP[i])
{
Console.WriteLine("Incorrect STAMP!");
Environment.Exit(2);
}
}
byte len = in_b.ReadByte();
byte[] file_MD = in_b.ReadBytes(System.Convert.ToInt32(System.Convert.ToInt32(len)));
buf = in_b.ReadBytes(System.Convert.ToInt32(in_b.BaseStream.Length));
byte[] calc_MD = GetMD5(buf);
for (int i = 0; i < calc_MD.Length; i++)
{
if (file_MD[i] != calc_MD[i])
{
Console.WriteLine("Incorrect HASH!");
Environment.Exit(3);
}
}
out_b.Write(buf);
out_b.Flush();
Console.WriteLine("Wrote part " + part.ToString());
part++;
}
out_b.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private static void ShowUsage()
{
Console.WriteLine("Usage: FileSplitter.exe -Filename <filename> [-blocksize <size>] [-stamp <file stamp>] {-split | -merge}");
Console.WriteLine("filename - the original filename");
Console.WriteLine("size - the size of the splitted parts");
Console.WriteLine("stamp - the header of the splitted parts");
Console.WriteLine("Parameters can be used more than once in the string, each split/merge is ran with the \"current\" values");
}
static void Main(string[] args)
{
Console.WriteLine("File splitting / merging utility by Liron");
Console.WriteLine("This utility is available as is.\r\n");
string Filename = "";
int BlockSize = DefalutBlockSize;
for (int i = 0; i < args.Length; i++)
{
switch (args[i].ToLower())
{
case "-stamp":
i++;
STAMP = new byte[args[i].Length];
for (int j = 0; j < args[i].Length; j++)
{
STAMP[j] = System.Convert.ToByte(args[i][j]);
}
break;
case "-filename":
i++;
Filename = args[i];
break;
case "-blocksize":
i++;
BlockSize = System.Convert.ToInt32(args[i]);
break;
case "-split":
Split(Filename, BlockSize);
break;
case "-merge":
Merge(Filename);
break;
case "-h":
ShowUsage();
Environment.Exit(0);
break;
default:
ShowUsage();
Environment.Exit(255);
break;
}
}
}
}
}


Some highlights
Note the way the HASH is added. The resulting file is in the following structure:
[STAMP][HASH length][HASH][Original part]
This structure allows an easy reconnection of the parts, plus a check for errors.
Another thing related to the structure is the fact that the resulting size would be BLOCKSIZE + HEADERSIZE which is slightly beigger than the intended size. It can be fixed quite easily by reading BLOCKSIZE - HEADERSIZE bytes instead of BLOCKSIZE bytes. I didn't need it so I left it out.

The most important note: This code has the minimal checks, if you want to use it, you'll need to add some checks (users can do many strange things if there's no check)

No comments: