Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Smuglr - Utility for Building and Extracting Compressed, Base64-Encoded Recursive Directories

5.00/5 (1 vote)
21 Aug 2020CPOL3 min read 5.8K  
In this tip, you will learn how to transfer that sample code from your home computer to your locked-down computer.

Introduction

Your enterprise corporate laptop is a fortress where the USB ports and optical drives are disabled for data transfer. All network traffic is monitored. Despite the vetting and background checks you endured to get your job which are probably the envy of the Defense Department, every website you try to hit that doesn't make it through the DNS filter lets you know that your employer doesn't trust you. One wonders why they bother with the background checks in the first place. Forget about connecting to a proxy server, your time will be better spent finding a good lawyer. But as you know, all this lack of trust makes it more difficult to do your job and makes your organization very inefficient compared to a nimble, high-trust one.

An Idea

Even if you want to zip up a project and send it to your personal email as an attachment so that you can study or work on your own code off location, forget it. It's a pity, because sometimes you know you have some code at home that is sure to help solve some problem you need for your job, but there's just no way to get that code to yourself on your company workstation. In other cases, your home equipment may be faster with more memory or have better development or diagnostic tools installed. When using email under such constraints, you should expect at least some automated process to be opening and examining everything you send or receive, even inside zip files. If it looks like source code, it will be flagged and prevented from reaching its destination and you'll end up on someone's special list to follow up with.

Yet there can still be a way. I'm not saying you should try using what I'm about to propose in this article to transmit and receive source code; I'm saying merely that this will work. But if you've checked your HR/IT policies or you're feeling particularly assertive, read on further and may God have mercy on your soul.

There are other well-known approaches such as embedding data in a large graphics file, but this concept is much simpler. Starting from a root folder, recursively read the contents of each file, optionally filtering out binary files, and create one large pre-compressed Base64 encoded text file with enough metadata in it to be able to recreate the directory structure and contents on the other end.

One final thing... the source code for the utility itself is a single file small enough to simply copy, paste and build wherever you are. This article contains the Program.cs file for a utility I call SMUGLR, which is less than 200 lines of code, so you should be able to see exactly how it works. This version targets .NET Framework and requires the ICSharpCode.SharpZipLib package your should be able to install from Nuget, even at the office. If you build this utility both at home and at work, you're able to easily send and receive entire application repositories at will (er, in Minecraft of course ;). The utility will produce or consume a text (.txt) file which will be base64 encoded compressed data. Play around with it and let me know what you think to the extent it will comply with your company's IT security policy.

C#
using ICSharpCode.SharpZipLib.Zip;
using System;
using System.IO;
using System.Linq;

namespace smuglr {
    class Program {
        static void Main(string[] args) {
            bool excludeBinary = false;
            bool validArgs = false;
            MODE mode = MODE.None;
            if ((args.Contains("-u")) && (args.Length == 3)) {
                validArgs = true;
                mode = MODE.Unzip;
            } else if (args.Contains("-z")) {
                mode = MODE.Zip;
                if (args.Contains("-src")) {
                    excludeBinary = true;
                    if (args.Length == 4) {
                        validArgs = true;
                    }
                } else {
                    if (args.Length == 3) {
                        validArgs = true;
                    }
                }
            }

            if (!validArgs) {
                Console.WriteLine("USAGE:  smuglr -z \\path\\to\\rootfolder 
                                  [-src] outputfilename.txt");
                Console.WriteLine("        package and compress all files at 
                                           and under rootfolder to outputfilename.txt");
                Console.WriteLine("        -src = exclude all binary files");
                Console.WriteLine("");
                Console.WriteLine("USAGE:  smuglr -u inputfilename.txt 
                                           \\path\\to\\rootfolder"); 
                Console.WriteLine("        decompress and deploy package from inputfilename.txt 
                                           to rootfolder creating/overwriting directories");
            } else {
                if (mode == MODE.Zip) {
                    string sourcePath = null;
                    string outputFile = null;
                    foreach (var arg in args) {
                        if ((!arg.Contains("-z")) && (!arg.Contains("-src"))) {
                            if (sourcePath == null) {
                                sourcePath = arg;
                            } else {
                                outputFile = arg;
                            }
                        }
                    }

                    if (!validateSourcePath(sourcePath)) {
                        Console.WriteLine(sourcePath + 
                                " is not valid path or no read permissions.");
                    } else {
                        try {
                            MemoryStream msZip = new MemoryStream();
                            var zip = ZipFile.Create(msZip);
                            AddDirectoryToZip(zip, sourcePath, sourcePath, excludeBinary);
                            zip.Close();
                            var sOut = Convert.ToBase64String(msZip.ToArray());
                            File.WriteAllText(outputFile, sOut);
                        } catch (Exception ex) {
                            Console.WriteLine("Error attempting to create " + outputFile);
                        }
                    }
                } else if (mode == MODE.Unzip) {
                    string sourceFile = null;
                    string targetPath = null;
                    foreach (var arg in args) {
                        if (!arg.Contains("-u")) {
                            if (sourceFile == null) {
                                sourceFile = arg;
                            } else {
                                targetPath = arg;
                            }
                        }
                    }
                    if (!validateTargetPath(targetPath)) {
                        Console.WriteLine(targetPath + " is not a valid target path 
                                          or no write permissions.");
                    } else {
                        if (!File.Exists(sourceFile)) {
                            Console.WriteLine(sourceFile + " file not found.");
                        } else {
                            var base64In = File.ReadAllText(sourceFile);
                            var bytes = Convert.FromBase64String(base64In);
                            MemoryStream msZip = new MemoryStream(bytes);
                            var zip = new FastZip();
                            zip.ExtractZip(msZip, targetPath, 
                            FastZip.Overwrite.Always, null, null, null, true, true, true);
                        }
                    }
                }
            }
        }

        static void AddDirectoryToZip(ZipFile archive, string baseFolder, 
                                      string dir, bool excludeBinary) {
            archive.BeginUpdate();
            AddDirectoryFilesToZip(archive, baseFolder, dir, true, excludeBinary);
            archive.CommitUpdate();
        }

        static void AddDirectoryFilesToZip(ZipFile zipArchive, 
        string baseFolder, string sourceDirectory, bool recurse, bool excludeBinary) {
            zipArchive.AddDirectory(sourceDirectory);
            if (recurse) {
                string[] directories = Directory.GetDirectories(sourceDirectory);
                foreach (string directory in directories) {
                    AddDirectoryFilesToZip(zipArchive, baseFolder, 
                                           directory, recurse, excludeBinary);
                }
            }

            string[] filenames = Directory.GetFiles(sourceDirectory);

            foreach (string filename in filenames) {
                string name = filename;
                if (name.StartsWith(@".\")) {
                    name = name.Substring(2, name.Length - 2);
                }
                bool exclude = false;
                if (excludeBinary) {
                    exclude = isBinary(filename);
                }
                if (!exclude) {
                    string relativeName = makeRelativePath(baseFolder, filename);
                    Console.WriteLine("{0} -> {1}", filename, relativeName);
                    zipArchive.Add(filename, relativeName);
                }
            }
        }

        static string makeRelativePath(string baseFolder, string filespec) {
            if (filespec.StartsWith(baseFolder)) {
                return (filespec.Substring(baseFolder.Length));
            }
            return filespec;
        }

        static bool validateSourcePath(string path) {
            try {
                var pi = new DirectoryInfo(path);
                if (pi.Parent == null) {
                    return false;           //a root path is not valid
                }
                if (!Directory.Exists(path)) {
                    return false;           //source path must exist
                }
                return true;

            } catch {
                return false;
            }
        }

        static bool validateTargetPath(string path) {
            try {
                var pi = new DirectoryInfo(path);
                if (pi.Parent == null) {
                    return false;           //a root path is not valid
                }
                if (Directory.Exists(path)) {
                    Console.WriteLine("Directory not empty. Overwrite (Y/N)?");
                    var overwrite = Console.ReadKey();
                    if ((overwrite.KeyChar == 'Y') || (overwrite.KeyChar == 'y')) {
                        Directory.Delete(path, true);
                        return true;
                    } else {
                        Console.WriteLine();
                        return false;
                    }
                }
                return true;
            } catch {
                return false;
            }
        }

        enum MODE { None, Zip, Unzip }

        static bool isBinary(string path) {
            using (StreamReader stream = new StreamReader(path)) {
                int ch;
                int count = 0;
                while ((ch = stream.Read()) != -1) {
                    if (ch == 0) {
                        count++;
                    }
                    if (count > 1) {
                        return true;
                    }
                }
            }
            return false;
        }
    }
}

History

  • 20th August, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)