0

I have to take three inputs in a single string.
The code to take the input is:

char msg_send[1000];
gets(msg_send);

The input is like

GET /api HTTP/1.1  

id=1&name=phoenix&mail=bringchills@ppks.com

That means there is a newline after the fist line GET /api HTTP/1.1. The next line is an empty newline. The input taking function should terminate after taking the 3rd newline.

Also, I have to terminate the input after the first line GET /something HTTP/1.1 if the line doesn't have a /api word at the place of /something word.

But when I'm using gets(), it terminates the string after taking GET /api HTTP/1.1 part of the input. When using scanf, it terminates after taking only the first GET word. Is there any way to take the input as a single string?

User 1999
  • 13
  • 6
  • 8
    Never use `gets()` — it is [far too dangerous](https://stackoverflow.com/q/1694036/15168) and is no longer a part of Standard C. – Jonathan Leffler Jun 16 '22 at 05:22
  • I also used scanf() here and the could only get the first word of the whole input string. That's why I used gets() to see if it can take up until a newline @JonathanLeffler – User 1999 Jun 16 '22 at 05:25
  • Aren't the line endings in HTTP CRLF (`'\r\n'`)? That's not particularly important yet. Anyway, no, there isn't a simple way to read three lines in a single standard function call. There's nothing to stop you reading the first line into the first part of the buffer, then working out how long the first part is, and reading the second part immediately after the first part, and then getting the length again and then reading the third part. Using `fgets()` will support that. Note that `fgets()` keeps the newline at the end of the line. And you could write a function to do this, of course. – Jonathan Leffler Jun 16 '22 at 05:25
  • The third part doesn't have a fixed length. So, each time new length has to be calculated. But that'll only happen when i get to read the input as a whole. – User 1999 Jun 16 '22 at 05:27
  • What determines when input ends? 3 lines? End-of-file (ctrl-d ln Linux console)? What? – hyde Jun 16 '22 at 05:39
  • after taking 3 newline the scanning should end. @hyde – User 1999 Jun 16 '22 at 05:41
  • `gets` [is not a part of the C language](https://port70.net/~nsz/c/c11/n1570.html). If you found it in a book, it's too old. [Your compiler should be yelling at you](https://godbolt.org/z/13fY7cGWY). If it doesn't, it's too old. – n. m. could be an AI Jun 16 '22 at 05:56
  • Would you consider reading three strings and then joining them instead of reading one single string? – LexFerrinson Jun 16 '22 at 06:01

3 Answers3

2

Reading from the FILE * should be done with fgets(), not gets() which cannot be used safely. Testing for the proper URL should be strict: strstr(msg_send, "/api") is not sufficient: GET /something/api will match. Comparing the request to GET /api HTTP/1.1 or possibly omitting the /1.1 seems safer.

Here is a modified version:

// read the HTTP request, return the number of lines
int get_http_request(FILE *fp, char *buf, size_t size) {
    char line2[100];
    char line3[4096];

    *buf = '\0';
    if (!fgets(buf, size, fp))
        return 0;  // fp at end of file

    if (strncmp(buf, "GET /api HTTP/", 14))
        return 1;  // not the proper protocol or URL

    size_len = strlen(buf);
    if (!fgets(buf + len, size - len, fp))
        return 1;  // missing blank line

    size_len += strlen(buf + len);
    if (!fgets(buf + len, size - len, fp))
        return 2;  // missing contents

    return 3;  // 3 lines were read
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • *"`GET /something HTTP/1.1` if the line doesn't have a `/api` word at the place of `/something` word"* in the question seems to imply, that the path may be longer than just "/api", it just has to start that way... Not totally clear. – hyde Jun 16 '22 at 07:05
  • The main application has also cases of not only `GET`. It also has `PUSH`, `DELETE`, `POST` and many more. Though the objective was not to get just `/api`, thanks for the code you provided. – User 1999 Jun 16 '22 at 07:57
0

In cases like these, you should just use getchar in a loop:

#define MAX_MSG_BUF 1000

char char msg_send[MAX_MSG_BUF];    

// you should wrap the below code in a function, like get3lines(buf, buf_size)
unsigned index = 0;
unsigned line_count = 0; 
const unsigned buf_size = MAX_MSG_BUF;
do {
    int tmp = getchar();
    if (tmp < 0) break; // end of file or error
    msg_send[index++] = tmp;
    if (tmp == '\n') {
        ++line_count;
        if (line_count == 1) {
            // check that first line contains /api                
            if (!first_line_valid(msg_send, index)) break;
        }
    }
} while (index < (MAX_MSG_BUF-1) && line_count < 3;
msg_send[index] = '\0';
// print error if line_count < 3 ?

The first_line_valid function might look like this (note: needs #include <stdbool.h> for bool type):

bool starts_with(const char *str, size_t length, const char *prefix) {
    size_t i = 0;
    for(;;) {            
        if (i == length || prefix[i] == '\0') return true;
        if (str[i] != prefix[i]) return false; // does catch end of str
        ++i;
     }
}

bool first_line_valid(const char *buf, size_t buf_size) {
    // note: buf might not be NUL-terminated so use buf_size
    if (starts_with(buf, buf_size, "GET /api ")) return true;
    if (starts_with(buf, buf_size, "GET /api/")) return true;
    return false;
}
hyde
  • 60,639
  • 21
  • 115
  • 176
  • Thanks for the answer. but I needed to end taking input at the first line if `/api` word is used at the first line. – User 1999 Jun 16 '22 at 06:07
  • @User1999 Added check for that – hyde Jun 16 '22 at 06:10
  • @User1999 Yeah, just try to take in at least two things in from that code: 1. do not allow your code to have buffer overflow (tedious in C, I know... consider learning use of POSIX `getline` if you're on Linux/Mac). 2. always check input for errors or end-of-file (return value of that `getline()` here). – hyde Jun 16 '22 at 06:16
  • `if (!strnstr(msg_send, "/api", index) break;` does not make sense. `index` is the length of `msg_send`. You should check for `"GET /api "` at the start of line 1 using `strncmp`. – chqrlie Jun 16 '22 at 06:50
  • @chqrlie It's a bit unclear what kind of a check the OP really wants. Current length of the string is needed because `msg_send` is not null-terminated at that point. But I abstracted that check so OP can easily modify it to suit their needs. – hyde Jun 16 '22 at 06:59
  • @hyde: setting the null terminator would the use of standard function `strstr` instead of BSD specific `strnstr` whose API is non obvious (does the size apply to the string or the needle?). Regarding the OP's intent, I am not sure either, but the requirement is unambiguous: *Also, I have to terminate the input after the first line `GET /something HTTP/1.1` if the line doesn't have a `/api` word at the place of `/something` word.* – chqrlie Jun 16 '22 at 07:06
  • @chqrlie Ah, read a man page badly, it indeed said *"The strstr() function conforms to ISO/IEC 9899:1990 ("ISO C90")."* only in thtat man page.... But the code no longer uses that so not relevant – hyde Jun 16 '22 at 07:09
  • Also the tests in `first_line_valid` will always fail because you compare the full input line with `"GET /api "` which is always false since the first line ends with a newline. You should instead set the null terminator before calling `first_line_valid` and use `strncmp(buf, "GET /api ", 9)` – chqrlie Jun 16 '22 at 07:10
  • @chqrlie Gah. This is how not having the basic utilities available (from somewhere, anywhere) creates stupid bugs. – hyde Jun 16 '22 at 09:58
-1

So, I figured out the answer. The answer is I should use a condition if there is /api in the command. The code is like:

char msg_send[1000], blank[4], data[300];
fgets(msg_send, 1000, stdin);
        char *header = strstr(msg_send, "/api");

        if (header)
        {
            fgets(blank, 4, stdin);
            fgets(data, 300, stdin);
            strcat(msg_send, " ");
            strcat(msg_send, data);
        }

I should take the first part as a string and then judge if it has the word /api. If it doesn't contain, the input taking should end. Thanks to everyone for your useful opinions.

User 1999
  • 13
  • 6
  • 1
    It bears repeating: do not use `gets()`. Also, your `msg_send` will be missing the newlines, it will just have the 3 lines joined as one line, which is probably not what you want. Or is it? – hyde Jun 16 '22 at 06:06
  • I wnated all of those in a single string. That was the main objective of the question. I also questioned that can we take the input as a single string – User 1999 Jun 16 '22 at 06:10
  • the right replacement for `gets` is `fgets`. Using `scanf` like that, without buffer size, also has the exact same problem as `gets`. Also, first line is more than 1 word, so you can't use that kind of `scanf` to read it anyway. – hyde Jun 16 '22 at 06:13
  • *"I wnated all of those in a single string."* Do you mean, single line in single string, or actually 3 lines in single string (which is what I'd expect, and what my answer does, so if you want to replace newline with space or something, you need to tweak it)? – hyde Jun 16 '22 at 06:26
  • Your answer is correct. user input was 3 different strings which were separated with newlines. My objective was to take them in a single string. Also the code was in cpp. forgive my mistake :). That's why there wasn't any error or warning when compiling ig? – User 1999 Jun 16 '22 at 06:31
  • cpp as in C++? If you have option to use that, C++ has things like `std::string` and `std::getline`, and can spare you from a ton of pain in C string handling... :-) Unless your goal is to learn C string handling, of course. – hyde Jun 16 '22 at 06:38
  • You test `strstr(msg_sent, "/api")` will match `GET /something/api HTTP/1.1`. You should use `strncmp(msg_sent, "GET /api ", 9) || strncmp(msg_sent, "GET /api/", 9)` – chqrlie Jun 16 '22 at 06:42
  • You should always check whether input functions succeed — you should test `if (fgets(msg_send, sizeof(msg_send), stdin) == NULL) { …EOF detected… }` or something equivalent. Using `sizeof` is sensible too; it reduces the amount of code to change when you find that you need to change the strings to 2048 bytes instead of just 1000. – Jonathan Leffler Jun 16 '22 at 14:29