I have tried to decipher what your objective was with the code, and I've read the tea-leaves and arrived at the conclusion (perhaps wrongly) that the crux of your question is aimed at input and branching control that on the encryption itself.
The "Confucius" comment was only half a half-hearted suggestion as the "Never Skimp on Buffer Size" goes to the heart of your ability to control what branches are taken by prompting the user for directions. Why?
- If, as in your original code, you have a buffer size of
20
, and tell fgets
to read at most 10
chars, then if the user enters anything more than 10
chars, the remainder are left in stdin
unread.
- any characters that are left in
stdin
unread, will happily be used as input for each of your later getchar()
statements causing you to lose complete control over how your code branches.
- using an adequately sized buffer and allowing
fgets
to consume a complete line at a time will ensure there are no characters left unread in stdin
(your input buffer). You will find this to be key on taking most user input in C.
So ensuring you have an adequately sized buffer and consume a line of input at each time is fundamental to being able to control where your code goes next.
An alternative to providing a buffer capable of holding each line of input is to read the wanted number of characters and then manually emptying stdin
before the next input. That can be as easy as reading characters until a '\n'
or EOF
is reached. But... there is a catch. You have to know that characters remain before attempting to empty stdin
with whatever function you use (e.g. getchar()
will block -- waiting for input until input is present to be read). If you attempt to empty stdin
in this manner when there is nothing to be emptied, your user will be left staring at a blinking cursor wondering what to do.
Back to your code. The logic is a bit hard to follow, but from the included comments, it appears that you consider a correct passphrase for purposes of encrypting it, a phrase with 1-5
characters. An empty passphrase or a passphrase with 6 or more characters is considered invalid. (it doesn't matter what the actual numbers are from a passphase standpoint -- this is an exercise about input and branching control)
If I understand the 1-5
char (encrypt) or 0
or 6
or more (invalid) logic, then you will want to adjust the structure of your code to follow that a bit closer. From an overview standpoint, you either have a good passphrase to encrypt or you don't. That would control the primary branches in your code. You can provide the logic in a single if {..} else {..}
statement, e.g.
/* if input is longer than 0 but shorter than 6, encrypt data */
if (length && length < 6) {
/* all your good pass phrase code goes here */
}
else { /* invalid pass phrase to begin with */
fputs ("error: pass phrase empty or greater than 5 chars.\n", stderr);
return 1;
}
Contrast this above with the multiple independent statement groups you have in your example and it should make sense why you were having trouble with the decision tree in your code.
Now on to your prompts to the user to press Enter. When you attempt to catch that input with a single getchar()
it is horribly fragile and subject to being skipped if any characters exist in stdin
and it has the same problem of leaving characters in stdin
unread if more than Enter is pressed. Here, just declare another character array to use for temporary input the same size as your input[]
array. Now instead of getchar()
, just use fgets()
again even to capture the Enter alone.
(which it is quite capable of doing, since it reads and include the '\n'
in the buffer it fills, it will not block waiting on input like getchar()
will)
Concerned about size in using 512-bytes in two buffers -- don't be. Most OS's provide a minimum of 1-Meg of stack space, so that 512-bytes represents only 0.048%
of the stack storage you have available. (you still have 1048576 - 512 = 1048064
byte of stack space avaialble)
With that change, your code can be rewritten to contain all logic related to handling a good/encrypted password within the first if {...}
block, e.g.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 256 /* if you need a constant, #define one (or more) */
int main (void) {
char input[MAXC], /* buffer to hold pass phrase */
tmp[MAXC]; /* temporary buffer for [enter] input */
size_t i, /* declares an UNINTIALIZED size_t */
length; /* (ditto) */
fputs ("Your pass phrase is: ", stdout); /* no conversion, fputs is fine */
if (!fgets (input, MAXC, stdin)) {
fputs ("(user canceled input)\n", stderr);
return 1;
}
/* remove '\n' by overwriting with '\0' (if present)
* saving length of input in length
*/
input[(length = strcspn(input, "\n"))] = 0;
/* if input is longer than 0 but shorter than 6, encrypt data */
if (length && length < 6) {
int encrypted = 0; /* flag keeping state of if pw encrypted */
for (i = 0; i < length; i++) /* encrypt the pass phrase */
input[i] ^= 0x7f;
encrypted = 1; /* set flag true */
/* print encrypted text in an hex format (change as desired) */
fputs ("\nTo see the encrypted text press [enter]: ", stdout);
if (fgets (tmp, MAXC, stdin) && *tmp == '\n') {
for (i = 0; i < length; i++)
printf (" %02x", input[i]);
putchar ('\n');
}
/* decrypt restoring plain-text pass phrase */
fputs ("\ndecrypted pass phrase, press [enter]: ", stdout);
if (fgets (tmp, MAXC, stdin) && *tmp == '\n') {
for (i = 0; i < length; i++)
input[i] ^= 0x7f;
encrypted = 0; /* set flag false after decryption */
}
else { /* if user pressed any other key (or generated EOF) */
fputs ("error: user chose not to decrypt pass phrase.\n", stderr);
return 1;
}
/* output decrypted plain-text pass pharase (if decrypted) */
fputs ("\nTo see recovered text press [enter]: ", stdout);
if (fgets (tmp, MAXC, stdin) && *tmp == '\n' && !encrypted) {
for (i = 0; i < length; i++)
putchar (input[i]);
putchar ('\n');
}
}
else { /* invalid pass phrase to begin with */
fputs ("error: pass phrase empty or greater than 5 chars.\n", stderr);
return 1;
}
return 0;
}
(note: above the encrypted
flag is simply used to hold the state of whether the contents of input[]
is currently encrypted or not to provide an additional conditional check before you attempt to print the decrypted passphrase)
Example Use/Output
Valid passphrse case:
$ ./bin/pass0-5
Your pass phrase is: abcde
To see the encrypted text press [enter]:
1e 1d 1c 1b 1a
decrypted pass phrase, press [enter]:
To see recovered text press [enter]:
abcde
User chooses not to decrypt case:
$ ./bin/pass0-5
Your pass phrase is: abcde
To see the encrypted text press [enter]:
1e 1d 1c 1b 1a
decrypted pass phrase, press [enter]: No, I don't want to!
error: user chose not to decrypt pass phrase.
Passphrase too long case:
$ ./bin/pass0-5
Your pass phrase is: abcdef
error: pass phrase empty or greater than 5 chars.
Passphrase empty case:
$ ./bin/pass0-5
Your pass phrase is:
error: pass phrase empty or greater than 5 chars.
I don't know if I read the tea-leaves properly, but from your comments and original question it looked like this area was the one that was actually your focus. Look things over and let me know if you have further questions, and if I got the main gist of your question wrong, let me know and I'm happy to help further.