- •Introduction
- •Saving Time with This Book
- •Conventions Used in This Book
- •Part II: Working with the Pre-Processor
- •Part III: Types
- •Part IV: Classes
- •Part V: Arrays and Templates
- •Part VI: Input and Output
- •Part VII: Using the Built-in Functionality
- •Part VIII: Utilities
- •Part IX: Debugging C++ Applications
- •Part X: The Scary (or Fun!) Stuff
- •Icons Used in This Book
- •Creating and Implementing an Encapsulated Class
- •Creating a Mailing-List Application
- •Testing the Mailing-List Application
- •Customizing a Class with Polymorphism
- •Testing the Virtual Function Code
- •Why Do the Destructors Work?
- •Delayed Construction
- •The cDate Class
- •Testing the cDate Class
- •Creating the Header File
- •Testing the Header File
- •The Assert Problem
- •Fixing the Assert Problem
- •Using the const Construct
- •Identifying the Errors
- •Fixing the Errors
- •Fixing What Went Wrong with the Macro
- •Using Macros Appropriately
- •Using the sizeof Function
- •Evaluating the Results
- •Using sizeof with Pointers
- •Implementing the Range Class
- •Testing the Range Class
- •Creating the Matrix Class
- •Matrix Operations
- •Multiplying a Matrix by a Scalar Value
- •Multiplying a Matrix by Scalar Values, Take 2
- •Testing the Matrix Class
- •Implementing the Enumeration Class
- •Testing the Enumeration Class
- •Implementing Structures
- •Interpreting the Output
- •Defining Constants
- •Testing the Constant Application
- •Using the const Keyword
- •Illustrating Scope
- •Interpreting the Output
- •Using Casts
- •Addressing the Compiler Problems
- •Testing the Changes
- •Implementing Member-Function Pointers
- •Updating Your Code with Member-Function Pointers
- •Testing the Member Pointer Code
- •Customizing Functions We Wrote Ourselves
- •Testing the Default Code
- •Fixing the Problem
- •Testing the Complete Class
- •Implementing Virtual Inheritance
- •Correcting the Code
- •Rules for Creating Overloaded Operators
- •Using Conversion Operators
- •Using Overloaded Operators
- •Testing the MyString Class
- •Rules for Implementing new and delete Handlers
- •Overloading new and delete Handlers
- •Testing the Memory Allocation Tracker
- •Implementing Properties
- •Testing the Property Class
- •Implementing Data Validation with Classes
- •Testing Your SSN Validator Class
- •Creating the Date Class
- •Testing the Date Class
- •Some Final Thoughts on the Date Class
- •Creating a Factory Class
- •Testing the Factory
- •Enhancing the Manager Class
- •Implementing Mix-In Classes
- •Testing the Template Classes
- •Implementing Function Templates
- •Creating Method Templates
- •Using the Vector Class
- •Creating the String Array Class
- •Working with Vector Algorithms
- •Creating an Array of Heterogeneous Objects
- •Creating the Column Class
- •Creating the Row Class
- •Creating the Spreadsheet Class
- •Testing Your Spreadsheet
- •Working with Streams
- •Testing the File-Reading Code
- •Creating the Test File
- •Reading Delimited Files
- •Testing the Code
- •Creating the XML Writer
- •Testing the XML Writer
- •Creating the Configuration-File Class
- •Setting Up Your Test File
- •Building the Language Files
- •Creating an Input Text File
- •Reading the International File
- •Testing the String Reader
- •Creating a Translator Class
- •Testing the Translator Class
- •Creating a Virtual File Class
- •Testing the Virtual File Class
- •Using the auto_ptr Class
- •Creating a Memory Safe Buffer Class
- •Throwing and Logging Exceptions
- •Dealing with Unhandled Exceptions
- •Re-throwing Exceptions
- •Creating the Wildcard Matching Class
- •Testing the Wildcard Matching Class
- •Creating the URL Codec Class
- •Testing the URL Codec Class
- •Testing the Rot13 Algorithm
- •Testing the XOR Algorithm
- •Implementing the transform Function to Convert Strings
- •Testing the String Conversions
- •Implementing the Serialization Interface
- •Creating the Buffer Class
- •Testing the Buffer Class
- •Creating the Multiple-Search-Path Class
- •Testing the Multiple-Search-Path Class
- •Testing the Flow Trace System
- •The assert Macro
- •Logging
- •Testing the Logger Class
- •Design by Contract
- •Adding Logging to the Application
- •Making Functions Inline
- •Avoiding Temporary Objects
- •Passing Objects by Reference
- •Choosing Initialization Instead of Assignment
- •Learning How Code Operates
- •Testing the Properties Class
- •Creating the Locking Mechanism
- •Testing the Locking Mechanism
- •Testing the File-Guardian Class
- •Implementing the Complex Class
- •Creating the Conversion Code
- •Testing the Conversion Code
- •A Sample Program
- •Componentizing
- •Restructuring
- •Specialization
- •Index
Testing the Rot13 Algorithm 345
if ( strIn )
{
_encrypt = rot13( strIn );
}
return _encrypt.c_str();
}
string String(void) const
{
return _encrypt;
}
};
ostream& operator<<( ostream& out, const Rot13Encryption& r13 )
{
out << r13.String().c_str(); return out;
}
}
The code in the above listing implements a simple Rot13 algorithm. The bulk of the work is done in the Rot13 function, which simply rotates characters 13 positions in the alphabet. If you look at the code at 1, you can see how this works. As the comment in this function specifies, it assumes that the alphabet is contiguous for the character set you are working with. This means that this code will not work on older EBCDIC systems, nor will it work with non-English character sets. Unfortunately, this is true of most textbased encryption algorithms. The other methods of the class, such as the operator << method, are utility functions that can be used to convert the encrypted string for output, or to stream it to an output file.
3. Save the source file in your text editor.
This class will handle the Rot13 algorithm. This algorithm works by simply rotating data about in the alphabet. The string is then unreadable by humans, which is the entire point of encryption.
Testing the Rot13 Algorithm
After you create a class, you should create a test driver that not only ensures that your code is correct, but also shows people how to use your code.
The following list shows you how to create a test driver that illustrates various kinds of input from the user, and shows how the class is intended to be used.
1. In the code editor of your choice, reopen the source file to hold the code for your test program.
In this example, I named the test program ch57.cpp.
2. Type the code from Listing 57-2 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 57-2: TESTING THE ROT13 ENCRYPTION CLASS
int main( int argc, char **argv )
{
Rot13Encryption r13(“This is a test”); cout << r13.String().c_str() << endl; cout <<
Rot13Encryption(r13.String().c_str()) << endl;
return 0;
}
3. Compile the source file, using your favorite compiler on your favorite operating system.
4. Run the application in the console.
If you have done everything properly, you should see the following output on the console window:
Guvf |
vf |
n |
grfg |
|
2 |
This |
is |
a |
test |
|
346 Technique 57: Encrypting and Decrypting Strings
The first output (shown at |
|
2) is the rotated ver- |
|
sion of the string. It |
remains human-readable, at |
||
|
|
|
least up to a point, because the substituted characters are all in the alphabet), but it certainly provides no clue to the semantic content of the text it is encrypting. Thus the purpose of encryption is preserved.
Encryption is intended to hide the purpose of the text from the user, not to make the text vanish or compress. Note that the string used is embedded in the application; this particular program does not accept any input from the user. It is a very simple test driver.
Unfortunately, Rot13 is one of the most common algorithms in use; hackers know it like the backs of their hands. We need a slightly better approach.
Implementing the
XOR Algorithm
The next encryption algorithm we examine is the XOR algorithm. XOR stands for Exclusive OR, which means that it uses the mathematical “exclusive or” operator in order to convert text. One property of the exclusive or operation is that a character that is exclusively or’d with another character can be returned to its original state by or’ing it again with the same character. This means that an encryption password can be used to both encode and decode a string using XOR. In this technique, we build a simple class that implements the XOR algorithm and provides methods for encoding and decoding strings.
1. In the code editor of your choice, reopen the source file to hold the code for the source file of the technique.
In this example, the file is named ch57.cpp, although you can use whatever you choose.
2. Append the code from Listing 57-3 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 57-3: THE XORENCRYPTION CLASS
class XOREncryption |
|
|
{ |
|
|
private: |
|
|
char |
*_encrypt; |
|
int |
_length; |
|
string |
_key; |
|
protected: |
|
|
char *do_xor( const char *sIn, int |
3 |
|
{ length, const string& key) |
||
int idx = 0; |
|
char *strOut = new char[ length ];
if ( !key.length() ) return strOut;
for ( int i=0; i<length; ++i )
{
char c = (sIn[i] ^ key[idx]); strOut[i] = c;
idx ++;
if ( idx >= key.length() ) idx = 0;
}
return strOut;
}
public:
XOREncryption(void)
{
_encrypt = NULL;
}
XOREncryption( const char *strIn, int length, const char *keyIn )
{
if ( keyIn )
_key = keyIn; if ( strIn )
{
_length = length; _encrypt = do_xor( strIn,
Testing the XOR Algorithm 347
length, _key );
}
}
XOREncryption( const XOREncryption& aCopy )
{
_encrypt = new char [ aCopy._length
];
memcpy ( _encrypt, aCopy._encrypt, aCopy._length );
_key = aCopy._key; _length = aCopy._length;
}
XOREncryption operator=( const XOREncryption& aCopy )
{
_encrypt = new char [ aCopy._length ];
memcpy ( _encrypt, aCopy._encrypt, aCopy._length );
_key = aCopy._key; _length = aCopy._length; return *this;
}
~XOREncryption(void)
{
delete _encrypt;
}
const char *operator=( const char *strIn
)
{
if ( _encrypt ) delete _encrypt;
if ( strIn )
{
_encrypt = do_xor( strIn, strlen(strIn), _key );
}
return _encrypt;
}
const char *operator<<( const char *strIn )
{
if ( strIn )
{
_encrypt = do_xor( strIn, strlen(strIn), _key );
}
return _encrypt;
}
const char *String(void) const
{
return _encrypt;
}
int Length(void) const
{
return _length;
}
};
The code in this class implements a simple XOR algorithm. The main functionality of the class is shown in the do_xor method, shown at 3. As you can see, the method takes the input encryption key and XORs it with the string that is provided by the user. The class requires two strings, one that is a “key” used for encrypting or decrypting strings. The second string is the input string to be encrypted or decrypted. Running the algorithm with the same inputs twice results in the original string.
3. Save the source file in your text editor.
Testing the XOR Algorithm
The following steps show you how to create a test driver that illustrates various kinds of input from the user, and show how the class is intended to be used:
1. In the code editor of your choice, reopen the source file to hold the code for your test program.
In this example, I named the test program ch57.cpp.
2. Type the code from Listing 57-4 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
348 Technique 57: Encrypting and Decrypting Strings
LISTING 57-4: TESTING THE XORENCRYPTION CLASS
int main( int argc, char **argv )
{
Rot13Encryption r13(“This is a test”); cout << r13.String().c_str() << endl; cout <<
Rot13Encryption(r13.String().c_str()) << endl;
XOREncryption x1(“This is a test”, |
|
|
|
strlen(“This is a |
test”), “C++Test”); |
|
|
cout << x1.String() |
<< endl; |
|
|
XOREncryption x2(x1.String(), |
|
4 |
|
x1.Length(), “C++Test”); |
|
||
cout << x2.String() |
<< endl; |
|
return 0;
}
3. Compile the source file, using your favorite compiler on your favorite operating system.
4. Run the application in the console.
If you have done everything properly, you should see the following output on the console window:
$ ./a.exe |
|
|
Guvf vf n grfg |
|
|
This is a test |
|
5 |
_CB’E_cJ_ |
|
|
This is a test |
6 |
|
|
|
|
Note that the above output includes the Rot13 encryption that we developed earlier in this technique for comparison. The strings are all hard-coded into the application, and your output might vary depending on the font and terminal type you are using. The output for the XOREncryption class is shown at 5 and 6. Our original string is This is a test and the two lines following it show how that line is first encrypted and then decrypted using the same key. In this case, our “key” is the string
C++Test.
The XOREncryption class does not use a string to hold the encrypted version of the input (see 4 in Listing 57-4), nor does it return the value as a string object. This is because the string class holds only alphanumeric data. The xor operation can result in non-alphanumeric values, and at times can cause the string class to return only a portion of the original string.
Never use a string object to store character buffers that might contain nulls or control characters. String classes assume that the null character terminates the string and will not store any characters past the null.
58 Converting the Case
of a String
Technique
Save Time By
Using modern techniques to convert the case of input strings
Using the Standard Template Library’s transform function
Interpreting output
In the good old days of C programming, converting the case of a string was a simple matter. You just called the strupr function and the string was instantly converted to uppercase. Similarly, if you called the
strlwr function, the string was converted to lowercase. Have things really changed all that much since then? Well, in some ways, things have changed a lot. For example, the following code is not permissible:
string myString = “Hello world” strupr( myString );
This code will not compile, since the strupr function does not accept a string argument. Nor can you write the following code and expect it to work:
strupr(myString.c_str());
This code will not compile either; the strupr function cannot accept a const character pointer — which is what the c_str method returns. So how do you write code to convert modern string objects to upperand lowercase? You could use the brute-force technique, like this:
for ( int i=0; i<myString.length(); ++i ) myString[i] = toupper(myString[i]);
This code certainly does work, but it is not very elegant and it does not anticipate all circumstances. It assumes, for example, that the string characters are in contiguous order in memory — which is a bad assumption to make about any Standard Template Library (STL) collection. The entire purpose of the STL is to provide the developer with a way in which to access containers (strings are just containers of characters) without any assumptions about how they are organized in memory. The better choice, of course, is to use an iterator (see Technique 49 for more on iterators). However, the STL provides an even better approach to the whole thing, which is the transform function found in the algorithms of the STL. The transform function allows you to operate in a container-independent