1

`

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

// function prototypes

int get_key(int argc, char *argk[]);

int main(int argc, char *argv[]){
    char *argv1 = argv[1];
    get_key(argc, argv1);        
    
    return 0;
}

// function defination.

int get_key(int argc, char *argk){
    // ensure single cmd line arg
    if(argc < 2 || argc > 2){
        printf("usage: ./caesar key(int) !\n");
        return 1;
    } 
    char str1[] = "2";
    char str2[] = "23";
    char str3[] = "a23";
    char str4[] = "23b";
    char str5[] = "a23b";
    
    int len = strlen(str3);
    int number;
    
    for(int i = 0; i < len; i++){ 
        if(isdigit(str3[i])){ // since isdigit() accept only char, not char*.
            number = atoi(str3); // meaning if str3 did contain digits 0-9 from ascii chart.
        }
    }
    return number;
    
}   

`

hello guys, please i want to convert argv[1] to integer, without trusting the user. since isdigit() accept only char, not char*, thats why i am iterating through the str3 and atoi accepts char*. it works fine with str1 and str2. but for other string variables, it returns digits within them, for which cant be converted using atoi() or any other calculations. i want to report error messsage if any of argv[1] elements contains non digits 0-9 from ascii chart. thanks.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
AbuAminu
  • 13
  • 4
  • 2
    Use `strol`. It will populate `endptr` which you can use to tell whether the whole string is a number. [man 3 strtol](https://man7.org/linux/man-pages/man3/strtol.3.html) has examples. – Eugene Sh. Jan 18 '22 at 21:16
  • 1
    First - don't use `atoi()`. It will **always** return a value that could have been a valid input. – Andrew Henle Jan 18 '22 at 21:17
  • `since isdigit() accept only char, not char*`. If you want to do it that way then check the *whole* string first *before* calling `atoi`. Otherwise use `strtol` as suggested already.' – kaylum Jan 18 '22 at 21:22
  • thanks, but from the man page: $ ./a.out 123abc , results: strtol() returned 123. – AbuAminu Jan 18 '22 at 21:28
  • @kaylum i thought i did that in the for loop, correct me please. – AbuAminu Jan 18 '22 at 21:38
  • Unrelated to your question, but you could simplify your `if` statement by checking if `argc != 2` – marco Jan 18 '22 at 21:39
  • No you didn't. You are calling `atoi` after checking *every* digit. You need to move the `atoi` to be outside the `for` loop. The `for` loop should return immediately if it finds a non-digit. If the loop completes without returning *then* it means the input is a number and you can call `atoi` at that point. – kaylum Jan 18 '22 at 21:40
  • 2
    "*from the man page*". You can't just blindly copy the example. The example code does not do the check you need. It checks whether the string has any digits at the start. But `strtol` can definetely be used to do the case you need. So you need to read the manual, understand the paramters and modify the example to do the extra checks to determine whether the string contains any non-digit characters. – kaylum Jan 18 '22 at 21:45
  • As mentioned at several points, if you want to robustly convert an integer from an untrustworthy source, `strtol` is probably the only way to go, *but* it's still tricky to get all the details and edge cases right. See [Why can't you just check if errno is equal to ERANGE?](https://stackoverflow.com/questions/36074422) and [Correct usage of strtol](https://stackoverflow.com/questions/14176123). – Steve Summit Jan 18 '22 at 23:55
  • `int i; if (sscanf (argv[1], "%d", &i) == 1) { /* you have a good int */ }` (lacks the detailed error reporting of `strtol()`, but for a simple validation of conversion to `int` it's fine) Do not use `atoi()` it has no error reporting and will silently return zero without indication of failure (e.g. `atoi ("my cow");`) – David C. Rankin Jan 19 '22 at 00:38

1 Answers1

1

Since you are stating that the user input cannot be trusted, I don't recommend using the function atoi, for two reasons:

  1. The behavior is undefined if the converted number is not representable as an int (i.e. if the number is out of range).

  2. The function will return 0 when the conversion failed. However, when this happens, you will not know whether the user actually entered 0 or whether you got this value due to conversion failure.

Both of these problems can be solved if you use the functon strtol instead of atoi.

In your question, you state that you want to reject the input if it contains any non-digit characters. One problem with the function strtol is that it will skip any leading whitespace characters (e.g. space and tab characters) before attempting to convert a number. If you instead want to reject the input if it contains any leading whitespace characters, then you will have to compare each character with isdigit, before using the function strtol. Doing this will also solve the problem of invalid characters after the number, so that input such as 123abc will get rejected.

Here is a program which will attempt to convert argv[1] to an integer, while performing full input validation:

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

int main( int argc, char *argv[] )
{
    long num;
    char *p;

    //verify that argv[1] is valid
    if ( argc < 2 )
    {
        printf( "not enough parameters!\n" );
        exit( EXIT_FAILURE );
    }

    //verify that argv[1] consists only of digits
    for ( p = argv[1]; *p != '\0'; p++ )
    {
        if ( !isdigit( (unsigned char)*p ) )
        {
            printf( "first program argument must consist only of digits!\n" );
            exit( EXIT_FAILURE );
        }
    }

    //attempt to convert argv[1] to integer
    errno = 0;
    num = strtol( argv[1], &p, 10 );

    //verify that conversion was successful
    if ( p == argv[1] )
    {
        printf( "unable to convert to integer\n" );
        exit( EXIT_FAILURE );
    }

    //verify that no range error occurred
    if ( errno == ERANGE )
    {
        printf( "input is out of range\n" );
        exit( EXIT_FAILURE );
    }

    //everything is ok, so print the result
    printf( "The result is: %ld\n", num );

    return 0;
}
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • 1
    I like to check to see if `*p` is 0 after `strtol()` to see if the entire string was used for the conversion. – Shawn Jan 18 '22 at 22:35
  • And the `ERANGE` check is only valid if it returns `LONG_MAX` or `LONG_MIN`, iirc. – Shawn Jan 18 '22 at 22:37
  • @Shawn: Actually, what I normally do is check all remaining characters of the string, to verify that they are whitespace characters. If they are not all whitespace characters, I reject the input. I normally do this for the following reason: Since `strtol` accepts and skips leading whitespace characters, it makes sense to also accept trailing whitespace characters. It would not make sense to accept leading, but reject trailing whitespace. – Andreas Wenzel Jan 18 '22 at 22:45
  • @Shawn: However, in this case, OP stated in the question that the input should only be allowed to consist of digits, so I have verified before the call to `strtol` that the entire string consists of only digits and nothing else (not even whitespace characters). For this reason, I don't see any need to check how much of the line was actually converted. Actually, I don't see any way how the function `strtol` could possibly fail, except for a range error. Therefore, the line `if ( p == argv[1] )` is probably redundant, too. – Andreas Wenzel Jan 18 '22 at 22:51
  • @Shawn: According to [§7.22.1.4 ¶8 of the ISO C11 standard](http://port70.net/~nsz/c/c11/n1570.html#7.22.1.4p8), if the converted value is not representable as a `long`, then `LONG_MIN` or `LONG_MAX` is returned and `errno` is set to `ERANGE`. This does not make checking for `ERANGE` only valid when one of these values is returned. – Andreas Wenzel Jan 18 '22 at 23:03
  • Nothing stops function A from calling function B which can return an error and then A returns a non-error value. If you just blindly check `errno`, even after setting it to 0 before calling A, you might get tripped up by such cases. It should only be considered when A actually indicates an error. – Shawn Jan 18 '22 at 23:10
  • @Shawn: Your previous comment is incorrect. According to [§7.5 ¶3 of the ISO C11 standard](http://port70.net/~nsz/c/c11/n1570.html#7.5p3), the function `strtol` is probibited from setting `errno`, except as documented in the description of the function. – Andreas Wenzel Jan 18 '22 at 23:26
  • @Shawn: It is not possible for the function `strtol` to indicate a range error, except through `errno`. If the function `strtol` returns `LONG_MAX`, then you have no way of knowing whether this indicates a range error or whether `LONG_MAX` is the actual value that the user entered. – Andreas Wenzel Jan 18 '22 at 23:28
  • I don't think you get what I'm saying, but I'm not in a mood to argue. – Shawn Jan 18 '22 at 23:42
  • @Shawn What you say about not using `errno` for detecting errors is true in general, but with one or two exceptions, and I believe `strtol` is one of those exceptions. – Steve Summit Jan 18 '22 at 23:51
  • So I'll leave it at this: `if ((num == LONG_MAX || num == LONG_MIN) && errno == ERANGE) { /* Error! */ }` is the better way to test for a range error. – Shawn Jan 18 '22 at 23:52
  • (I also think `strtol()` and friends should be an exception to the "library functions aren't supposed to set errno to 0" thing but that's another topic) – Shawn Jan 18 '22 at 23:54
  • @Shawn Fair enough on the point about also checking for LONG_{MIN,MAX}. This really is a stupidly complicated situation, for such a seemingly-simple question. Also discussed at [Why can't you just check if errno is equal to ERANGE?](https://stackoverflow.com/questions/36074422) and [Correct usage of strtol](https://stackoverflow.com/questions/14176123). – Steve Summit Jan 18 '22 at 23:59