Szymon Kobalczyk's Blog

A Developer's Notebook

  Home  |   Contact  |   Syndication    |   Login
  81 Posts | 5 Stories | 150 Comments | 380 Trackbacks

News

Skype Me™! View Szymon Kobalczyk's profile on LinkedIn

Twitter












Article Categories

Archives

Post Categories

Image Galleries

Blogs I Read

Tools I Use

"Uuencode is a set of algorithms for converting files into a series of 7-bit ASCII characters that can be transmitted over the Internet. Originally, uuencode stood for Unix-to-Unix encode, but it has since become a universal protocol used to transfer files between different platforms such as Unix, Windows, and Macintosh. Uuencoding is especially popular for sending e-mail attachments. Nearly all e-mail applications support uuencoding for sending attachments and uudecoding for receiving attachments."

You can learn much more here or here. However, UUencoding has a few problems. Most important, no RFC describes the algorithm UUencoding and UUdecoding use to convert binaries. Consequently, various software's implementations of UUencoding can differ (as we learned when trying to open message sent from Outlook in Thunderbird). Generally, it's quite an oldtimer and nowadays should be replaced with Base64 encoding in most cases.

As I couldn't find any implementation of this encoding for .NET (at least none that actually worked for the attachments I had to deal with) I did quick research and found nice C implementation in KDE sources. Below you will find my port of this code. Most significant change is that I used Streams instead of byte arrays (the algorithm is forward only and doesn't need to load all the data to memory) and removed the part in UUDecode dealing with Unix 'begin'/'end' tags as these were already removed from the attachments I had to handle.

    public static class Codecs
    {
        static readonly byte[] UUEncMap = new byte[]
        {
          0x60, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
          0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
          0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
          0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
          0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
          0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
          0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
          0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F
        };

        static readonly byte[] UUDecMap = new byte[]
        {
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
          0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
          0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
          0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
          0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
          0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
          0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
          0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
        };
        
        public static void UUDecode(System.IO.Stream input, System.IO.Stream output)
        {
            if (input == null)
                throw new ArgumentNullException("input");

            if (output == null)
                throw new ArgumentNullException("output");

            long len = input.Length;
            if (len == 0)
                return;
                
            long didx = 0;
            int nextByte = input.ReadByte();
            while (nextByte >= 0)
            {
                // get line length (in number of encoded octets)
                int line_len = UUDecMap[nextByte];

                // ascii printable to 0-63 and 4-byte to 3-byte conversion
                long end = didx + line_len;
                byte A, B, C, D;
                if (end > 2)
                {
                    while (didx < end - 2)
                    {
                        A = UUDecMap[input.ReadByte()];
                        B = UUDecMap[input.ReadByte()];
                        C = UUDecMap[input.ReadByte()];
                        D = UUDecMap[input.ReadByte()];

                        output.WriteByte((byte)(((A << 2) & 255) | ((B >> 4) & 3)));
                        output.WriteByte((byte)(((B << 4) & 255) | ((C >> 2) & 15)));
                        output.WriteByte((byte)(((C << 6) & 255) | (D & 63)));
                        didx += 3;
                    }
                }

                if (didx < end)
                {
                    A = UUDecMap[input.ReadByte()];
                    B = UUDecMap[input.ReadByte()];
                    output.WriteByte((byte)(((A << 2) & 255) | ((B >> 4) & 3)));
                    didx++;
                }

                if (didx < end)
                {
                    B = UUDecMap[input.ReadByte()];
                    C = UUDecMap[input.ReadByte()];
                    output.WriteByte((byte)(((B << 4) & 255) | ((C >> 2) & 15)));
                    didx++;
                }

                // skip padding
                do 
                {
                    nextByte = input.ReadByte();
                }
                while (nextByte >= 0 && nextByte != '\n' && nextByte != '\r');

                // skip end of line
                do
                {
                    nextByte = input.ReadByte();
                }
                while (nextByte >= 0 && (nextByte == '\n' || nextByte == '\r'));
            }
        }
        
        public static void UUEncode(System.IO.Stream input, System.IO.Stream output)
        {
            if (input == null)
                throw new ArgumentNullException("input");

            if (output == null)
                throw new ArgumentNullException("output");

            long len = input.Length;
            if (len == 0)
                return;

            int sidx = 0;
            int line_len = 45;
            byte[] nl = Encoding.ASCII.GetBytes(Environment.NewLine);

            byte A, B, C;
            // split into lines, adding line-length and line terminator
            while (sidx + line_len < len)
            {
                // line length
                output.WriteByte(UUEncMap[line_len]);

                // 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
                for (int end = sidx+line_len; sidx < end; sidx += 3)
                {
                    A = (byte)input.ReadByte();
                    B = (byte)input.ReadByte();
                    C = (byte)input.ReadByte();

                    output.WriteByte(UUEncMap[(A >> 2) & 63]);
                    output.WriteByte(UUEncMap[(B >> 4) & 15 | (A << 4) & 63]);     
                    output.WriteByte(UUEncMap[(C >> 6) & 3 | (B << 2) & 63]);    
                    output.WriteByte(UUEncMap[C & 63]);      
                }

                // line terminator
                for (int idx = 0; idx < nl.Length; idx++)
                    output.WriteByte(nl[idx]);
            }

            // line length
            output.WriteByte(UUEncMap[len-sidx]);

            // 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
            while (sidx + 2 < len)
            {
                A = (byte)input.ReadByte();
                B = (byte)input.ReadByte();
                C = (byte)input.ReadByte();

                output.WriteByte(UUEncMap[(A >> 2) & 63]);
                output.WriteByte(UUEncMap[(B >> 4) & 15 | (A << 4) & 63]);
                output.WriteByte(UUEncMap[(C >> 6) & 3 | (B << 2) & 63]);
                output.WriteByte(UUEncMap[C & 63]);
                sidx += 3;
            }

            if (sidx < len-1)
            {
                A = (byte)input.ReadByte();
                B = (byte)input.ReadByte();

                output.WriteByte(UUEncMap[(A >> 2) & 63]);
                output.WriteByte(UUEncMap[(B >> 4) & 15 | (A << 4) & 63]);
                output.WriteByte(UUEncMap[(B << 2) & 63]);
                output.WriteByte(UUEncMap[0]);
            }
            else if (sidx < len)
            {
                A = (byte)input.ReadByte();
                
                output.WriteByte(UUEncMap[(A >> 2) & 63]);
                output.WriteByte(UUEncMap[(A << 4) & 63]);
                output.WriteByte(UUEncMap[0]);
                output.WriteByte(UUEncMap[0]);
            }

            // line terminator
            for (int idx = 0; idx < nl.Length; idx++)
                output.WriteByte(nl[idx]);
        }
   }
   
posted on Sunday, December 18, 2005 9:18 PM

Feedback

# re: Implementation of UUEncoding in C# 5/30/2008 4:18 PM Jarin
Szymon, thanks for porting the code into C#. While doing some testing of your UUDecode code, i found a bug.
Instead of:
if (didx < end)
{
A = UUDecMap[input.ReadByte()];
B = UUDecMap[input.ReadByte()];
output.WriteByte((byte)(((A << 2) & 255) | ((B >> 4) & 3)));
didx++;
}

if (didx < end)
{
B = UUDecMap[input.ReadByte()];
C = UUDecMap[input.ReadByte()];
output.WriteByte((byte)(((B << 4) & 255) | ((C >> 2) & 15)));
didx++;
}


there should be:
if (didx < end)
{
A = UUDecMap[input.ReadByte()];
B = UUDecMap[input.ReadByte()];
output.WriteByte((byte)(((A << 2) & 255) | ((B >> 4) & 3)));
didx++;

if (didx < end)
{
C = UUDecMap[input.ReadByte()];
output.WriteByte((byte)(((B << 4) & 255) | ((C >> 2) & 15)));
didx++;
}
}

If you check the original KDE code, you will see that while they're using the same B in both "if" blocks, you're doing an extra ReadByte() in the second "if" block.

# re: Implementation of UUEncoding in C# 5/30/2008 4:23 PM Szymon
Hey Jarin,

Thanks for pointing this. That's an copy&paste bug of course :-)

-Szymon

# re: Implementation of UUEncoding in C# 7/18/2008 9:12 PM Fred
I don't see what the point of using the maps is? Can't you just add or subtract 32 from the value instead?

Also, it looks like the // skip end of line code that comes after // skip padding is redundant?


Post Feedback

Title:
Name:
Email: (never displayed)
Url:
Comments: 
Please add 2 and 8 and type the answer here: