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.
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.
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_iterator
s. 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.
Suppose we don’t want to Base-64 encode whole files, but only std::string
s. 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!
|
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::string
s.
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/