Base64 encoding and decoding files with POCO

Base64 encoding and decoding files with POCO

To transmit files over a channel that is not 8-bit clean (e.g. UUCP, old SMTP, NNTP etc…), it is necessary to encode binary files in such a way that only some characters are being used. A long time, ago, people used to uuencode(1) and uudecode(1) such files, but today, we would Base64-encode and -decode them.

On some systems (like FreeBSD), we can use the utilities b64encode and b64decode, that are already part of the system, to achieve the job. But on most other systems, we need to roll our own Base64 encoders and decoders.

Fortunately, POCO provides the classes Poco::Base64Encoder and Poco::Base64Decoder to do the job.

Base-64 encoding a file: b64encode.cpp

This is one possible implementation of b64encode using the Poco::Base64Encoder class:

// b64encode.cpp -- Base64 encode a file with Poco::Base64Encoder
  
#include <fstream>
#include <cstdlib>
#include "Poco/Base64Encoder.h"
  
int
main ( int argc, char *argv[])
{
   std::ifstream ifs(argv[1]);
   std::ofstream ofs(argv[2]);
   Poco::Base64Encoder b64out(ofs);
  
   std::copy(std::istreambuf_iterator< char >(ifs),
             std::istreambuf_iterator< char >(),
             std::ostreambuf_iterator< char >(b64out));
   b64out.close(); // always call this at the end!
  
   return EXIT_SUCCESS;
}

This is how to compile this program:

% c++ -O2 -I/usr/local/include -Wall   -c -o b64encode.o b64encode.cpp
% cc -L/usr/local/lib  b64encode.o  -lPocoFoundation -o b64encode

The program is not really that much different from copy4.cpp of the previous tutorial, which was, in a nutshell:

std::ifstream ifs(argv[1]);
std::ofstream ofs(argv[2]);
  
std::copy(std::istreambuf_iterator< char >(ifs),
           std::istreambuf_iterator< char >(),
           std::ostreambuf_iterator< char >(ofs));
  
ifs.close();
ofs.close();

The only difference is that we copy the output to b64out, which wraps the std::ofstream ofs in a Poco::Base64Encoder, and use b64out as the destination of the std::copy operation.

This is the result of Base64-encoding some big file:

% ./b64encode /boot/kernel/kernel /var/tmp/kernel.b64
  
% ls -l /boot/kernel/kernel /var/tmp/kernel.b64 
-r-xr-xr-x  1 root   wheel  12161158 Feb 24 12:47 /boot/kernel/kernel
-rw-r--r--  1 farid  wheel  16665292 Mar 21 16:09 /var/tmp/kernel.b64

As you can see, the base-64 encoded file is, as expected, larger. We can also peek into (the beginning) of both files:

% hd /boot/kernel/kernel | head -5
00000000  7f 45 4c 46 02 01 01 09  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 3e 00 01 00 00 00  10 b1 18 80 ff ff ff ff  |..>.............|
00000020  40 00 00 00 00 00 00 00  08 4f 9f 00 00 00 00 00  |@........O......|
00000030  00 00 00 00 40 00 38 00  05 00 40 00 25 00 22 00  |[email protected]...@.%.".|
00000040  06 00 00 00 05 00 00 00  40 00 00 00 00 00 00 00  |........@.......|
  
% hd /var/tmp/kernel.b64 | head -5
00000000  66 30 56 4d 52 67 49 42  41 51 6b 41 41 41 41 41  |f0VMRgIBAQkAAAAA|
00000010  41 41 41 41 41 41 49 41  50 67 41 42 41 41 41 41  |AAAAAAIAPgABAAAA|
00000020  45 4c 45 59 67 50 2f 2f  2f 2f 39 41 41 41 41 41  |ELEYgP////9AAAAA|
00000030  41 41 41 41 41 41 68 50  6e 77 41 41 41 41 41 41  |AAAAAAhPnwAAAAAA|
00000040  41 41 41 41 41 45 41 41  0d 0a 4f 41 41 46 41 45  |AAAAAEAA..OAAFAE|

We see that the second file contains only printable characters.

b64decode.cpp

The reverse operation is Base-64 decoding a file. Instead of Poco::Base64Encoder, we simply use a Poco::Base64Decoder, like this:

// b64decode.cpp -- Base64 decode a file with Poco::Base64Decoder
  
#include <fstream>
#include <cstdlib>
#include "Poco/Base64Decoder.h"
  
int
main ( int argc, char *argv[])
{
   std::ifstream ifs(argv[1]);
   Poco::Base64Decoder b64in(ifs);
   std::ofstream ofs(argv[2]);
  
   std::copy(std::istreambuf_iterator< char >(b64in),
             std::istreambuf_iterator< char >(),
             std::ostreambuf_iterator< char >(ofs));
  
   return EXIT_SUCCESS;
}

This is, again, our file copy program, idiomatic version with std::copy and streambuf_iterators. In b64encode we wrapped ofs with Poco::Base64Encoder. Here, we wrapped ifs with Poco::Base64Decoder, resulting in an input stream b64in.

Compiling:

% c++ -O2 -I/usr/local/include -Wall   -c -o b64decode.o b64decode.cpp
% cc -L/usr/local/lib  b64decode.o  -lPocoFoundation -o b64decode

Now, let’s Base64-decode the file we’ve previously Base64-encoded:

% ./b64decode /var/tmp/kernel.b64 /var/tmp/kernel.decoded
  
% ls -l /boot/kernel/kernel /var/tmp/kernel.decoded 
-r-xr-xr-x  1 root   wheel  12161158 Feb 24 12:47 /boot/kernel/kernel
-rw-r--r--  1 farid  wheel  12161158 Mar 21 16:19 /var/tmp/kernel.decoded
  
% diff /boot/kernel/kernel /var/tmp/kernel.decoded
% rm /var/tmp/kernel.b64 /var/tmp/kernel.decoded

Of course, we’ve got the very same file that we’ve encoded previously.

Base64-encoding and -decoding strings: b64strings.cpp

Suppose we don’t want to Base-64 encode whole files, but only std::strings. One example could be that we want to compose Base64-encoded e-mail messages from some data that the user entered in a GUI element.

We could re-use Poco::Base64Encoder and Poco::Base64Decoder to transform strings, but there’s a little problem here: both classes need output- und input streams, respectively, and not strings! However, the signature of the functions we need are:

std::string toBase64 ( const std::string &source);
std::string fromBase64 ( const std::string &source);

Fortunately, we can easily transform a string to an input or output stream with std::istringstream and std::ostringstream from <sstream>. toBase64 could look like this:

std::string
toBase64 ( const std::string &source)
{
   std::istringstream in(source);
   std::ostringstream out;
   Poco::Base64Encoder b64out(out);
  
   std::copy(std::istreambuf_iterator< char >(in),
             std::istreambuf_iterator< char >(),
             std::ostreambuf_iterator< char >(b64out));
   b64out.close(); // always call this at the end!
  
   return out.str();
}

and fromBase64 would be:

std::string
fromBase64 ( const std::string &source)
{
   std::istringstream in(source);
   std::ostringstream out;
   Poco::Base64Decoder b64in(in);
  
   std::copy(std::istreambuf_iterator< char >(b64in),
             std::istreambuf_iterator< char >(),
             std::ostreambuf_iterator< char >(out));
  
   return out.str();
}

Here’s one possible main program:

// b64strings.cpp -- functions to Base64 encode and decode strings.
  
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <iostream>
#include <cstdlib>
#include "Poco/Base64Encoder.h"
#include "Poco/Base64Decoder.h"
  
std::string toBase64 ( const std::string &source);   // As shown above
std::string fromBase64 ( const std::string &source); // As shown above
  
int
main ( int argc, char *argv[])
{
   std::string clearText( "hello, world!" );
   std::string b64Text(toBase64(clearText));
   std::string clearAgain(fromBase64(b64Text));
  
   std::cout << "Clear1: " << clearText  << std::endl;
   std::cout << "Base64: " << b64Text    << std::endl;
   std::cout << "Clear2: " << clearAgain << std::endl;
  
   return EXIT_SUCCESS;
}

Compling and running it:

% c++ -O2 -I/usr/local/include -Wall   -c -o b64strings.o b64strings.cpp
% cc -L/usr/local/lib  b64strings.o  -lPocoFoundation -o b64strings
  
% ./b64strings 
Clear1: hello, world!
Base64: aGVsbG8sIHdvcmxkIQ==
Clear2: hello, world!

Summary

To overcome the (intentional) limitations of the C++ STL, it is necessary to use external libraries. We distinguish between closed-source and open-source libraries, between highly specialized and broad scope libraries, and between platform-specific and cross-platform libraries.

Good libraries include Boost, Poco, and Qt, but they are by no means the only ones. C++ isn’t Java: the standard doesn’t define what external libraries are best suited for your needs. The choice is yours to make.

As an example, we’ve used the input stream adapter Poco::Base64Encode from the POCO library to Base64-encode files (or streams, more generally), and Poco::Base64Decode to Base64-decode files (or streams). We’ve seen how to make use of std::istringstream and std::ostringstring in combination with the above mentioned POCO classes, to Base64-encode and Base64-decode std::strings.

Basically, we’re simply plumbing well-tested code components together and don’t reinvent the wheel.

 

转载:http://farid.hajji.name/blog/2010/03/21/cpp-tutorial-5/

你可能感兴趣的:(Base64 encoding and decoding files with POCO)