cinputscanfdefensive-programming

Control user input in C when there is chars and ints mixed


So I'm trying making a simple menu to a program that only accepts the options 1,2 and 3 and I want to control the user input to prevent errors like when the user inputs char instead of int.

Right now it controls some cases like if the option is less than 1 or greater than 3, if the option is a char it also does not affect but when the user inputs something like " 02" or "2a" it runs option 2 but should invalidate that option.

Also if there´s more cases that I'm missing I would like to know them and how to overcome them.

#include <stdio.h>
#include <stdlib.h>

void empty_stdin(void);

int main() {
    int option;
    int rtn;

    do {
        printf("\n--\nOptions:\n1.Option 1\n2.Option 2\n3.Option 3\n--\n\nPlease chose option (1/2/3) to continue: ");
        rtn = scanf("%d", &option);

        if (rtn == 0 || option < 1 || option > 3) {
            printf("-Invalid Option-\n");
            empty_stdin();
        } else {

            empty_stdin();

            switch (option) {
                case 1:
                    printf("Option 1");
                    break;

                case 2:
                    printf("Option 2");
                    break;
                case 3:
                    printf("Option 3");
                    exit(0);

                default:
                    printf("\n-Invalid Option-\n");
            }
        }
    } while (option != 3);
    return 0;
}

void empty_stdin(void) {
    int c = getchar();

    while (c != '\n' && c != EOF)
        c = getchar();
}

Example Input/Output

--
Options:
1.Option 1
2.Option 2
3.Option 3
--

Please chose option (1/2/3) to continue: 1
Option 1
--
Options:
1.Option 1
2.Option 2
3.Option 3
--

Please chose option (1/2/3) to continue: 12
-Invalid Option-

--
Options:
1.Option 1
2.Option 2
3.Option 3
--

Please chose option (1/2/3) to continue: char
-Invalid Option-

--
Options:
1.Option 1
2.Option 2
3.Option 3
--

Please chose option (1/2/3) to continue: 02
Option 2
--
Options:
1.Option 1
2.Option 2
3.Option 3
--

Please chose option (1/2/3) to continue: 2a
Option 2
--
Options:
1.Option 1
2.Option 2
3.Option 3
--

Please chose option (1/2/3) to continue: 02a
Option 2
--
Options:
1.Option 1
2.Option 2
3.Option 3
-- 

Expected Input/Output

--
Options:
1.Option 1
2.Option 2
3.Option 3
--

Please chose option (1/2/3) to continue: 1
Option 1
--
Options:
1.Option 1
2.Option 2
3.Option 3
--

Please chose option (1/2/3) to continue: 12
-Invalid Option-

--
Options:
1.Option 1
2.Option 2
3.Option 3
--

Please chose option (1/2/3) to continue: char
-Invalid Option-

--
Options:
1.Option 1
2.Option 2
3.Option 3
--

Please chose option (1/2/3) to continue: 02
-Invalid Option-
--
Options:
1.Option 1
2.Option 2
3.Option 3
--

Please chose option (1/2/3) to continue: 2a
-Invalid Option-
--
Options:
1.Option 1
2.Option 2
3.Option 3
--

Please chose option (1/2/3) to continue: 02a
-Invalid Option-
--
Options:
1.Option 1
2.Option 2
3.Option 3
--


Solution

  • Consider using fgets to capture input and strtol to parse an integer.

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <limits.h>
    
    int fgetsint ( int *value, int min, int max, FILE *fin) {
        char line[100] = "";
        char extra = '\0';
        char *end = NULL;
        long int number = 0;
    
        if ( fgets ( line, sizeof line, fin)) {//read a line
            errno = 0;
            number = strtol ( line, &end, 10);
            if ( end == line) {// nothing was parsed. no digits
                printf ( "input [%s] MUST be a number\n", line);
                return 0;// return failure
            }
            if ( ( errno == ERANGE && ( number == LONG_MAX || number == LONG_MIN))
            || ( errno != 0 && number == 0)) {// parsing error from strtol
                perror ( "input error");
                return 0;
            }
            if ( 1 == sscanf ( end, " %c", &extra)) {//parse trailing character
                printf ( "enter one number only. try again\n");
                return 0;
            }
            if ( number > max || number < min) {
                printf ( "Input [%ld] out of range: min: %d max: %d\n", number, min, max);
                return 0;
            }
            if ( 1 != (int)( end - line)) {
                printf ( "input one digit\n");
                return 0;// return failure
            }
            *value = number;//assign number to pointer
        }
        else {
            fprintf ( stderr, "problem fgets\n");
            exit ( EXIT_FAILURE);
        }
    
        return 1;//success
    }
    
    int main ( void) {
        int option = 0;
    
        do {
            printf("\n--\nOptions:\n1.Option 1\n2.Option 2\n3.Option 3\n--\n\nPlease chose option (1/2/3) to continue: ");
    
            option = 0;
            fgetsint ( &option, 1, 3, stdin);
    
            switch (option) {
                case 1:
                    printf("Option 1");
                    break;
    
                case 2:
                    printf("Option 2");
                    break;
                case 3:
                    printf("Option 3");
                    exit(0);
    
                default:
                    printf("\n-Invalid Option-\n");
            }
        } while ( option != 3);
        return 0;
    }