Home > Mobile >  Unexpected behavior when reading from socket
Unexpected behavior when reading from socket

Time:11-24

I've wrote the following function that reads http response from the server through the socket. I had no problems reading text pages like images

the reading goes on without adding data to the buffer, even though the read returns the correct byte amount.

The function:

unsigned char *read_unknown_size(int fd) {
    int available_buf_size = 1000, tot_read = 0, curr_read_size;
    unsigned char *buf = calloc(available_buf_size, 1), *tmp_ptr;
    if (buf) {
        while ((curr_read_size = (int) read(fd, buf   tot_read, available_buf_size - tot_read)) != 0) {
            if (curr_read_size == -1) {
                perror("failed to read\n");
                //todo free mem
                exit(EXIT_FAILURE);
            } else {
                tot_read  = curr_read_size;
                if (tot_read >= available_buf_size) { //the buffer is full
                    available_buf_size *= 2;
                    tmp_ptr = realloc(buf, available_buf_size   tot_read);
                    if (tmp_ptr) {
                        buf = tmp_ptr;
                        memset(buf tot_read, 0, available_buf_size - tot_read);
                    }
                    else {
                        fprintf(stderr,"realloc failed\n");
                        exit(EXIT_FAILURE);
                    }
                }
            }
        }
    } else {
        fprintf(stderr,"calloc failed\n");
        exit(EXIT_FAILURE);
    }
    return buf;
}

The buffer after one reading of size 1000:

0x563a819da130 "HTTP/1.1 200 OK\r\nDate: Tue, 23 Nov 2021 19:32:01 GMT\r\nServer: Apache\r\nUpgrade: h2,h2c\r\nConnection: Upgrade, close\r\nLast-Modified: Sat, 11 Jan 2014 01:32:55 GMT\r\nAccept-Ranges: bytes\r\nContent-Length: 3900\r\nCache-Control: max-age=2592000\r\nExpires: Thu, 23 Dec 2021 19:32:01 GMT\r\nContent-Type: image/jpeg\r\n\r\nGIF89", <incomplete sequence \375>

A total of 379 character.

Edit: After reading the data, I'm writing it to a new file, the text pages works fine but I can't open images.

CodePudding user response:

I believe that read_unknown_size is working, but the caller is simply printing out the buffer up until the first NUL character by using printf("%s", buf) or similar.[1] This is wrong for two reasons:

  • If the data read contains a NUL, it will stop outputting too soon.
  • If the data read doesn't contain a NUL, it will read beyond the end of the buffer.

The caller needs to output exactly the number of characters in the buffer. However, the caller has no way to determine the how many characters are in the buffer. So, in order to do anything useful with the result of the function, the function needs to return not just the buffer but the number of characters it read.

// Reads until EOF is encountered.
// Returns 0 on success.
// Returns -1 and sets errno on error.
int read_rest(int fd, unsigned char **buf_ptr, size_t *total_read_ptr) {
   unsigned char *buf        = NULL;
   size_t         buf_size   = 0;
   size_t         total_read = 0;

   while (1) {
      if ( total_read == buf_size ) {
         buf_size *= 2;  // Refine this.
         unsigned char *tmp = realloc(buf, buf_size);
         if (!tmp)
            goto ERROR;

         buf = tmp;
      }

      ssize_t chunk_size = read(fd, buf   total_read, buf_size - total_read);
      if ( chunk_size < 0 )
         goto ERROR;

      if ( chunk_size == 0 ) {
         unsigned char *tmp = realloc(buf, total_read);
         if (tmp)
            buf = tmp;

         *buf_ptr        = buf;
         *total_read_ptr = total_read;
         return 0;
      }

      total_read  = chunk_size;
   }

ERROR:
   free(buf);
   *buf_ptr        = NULL;
   *total_read_ptr = 0;
   return -1;
}

Sample caller:

unsigned char *buf;
size_t         size;

if ( read_rest(in_fd, &buf, &size) == -1 ) {
   perror("Can't read from socket");
   exit(EXIT_FAILURE);
}

Now you have enough information to print out the contents of the buffer (e.g. using write).

// Returns 0 on success.
// Returns -1 and sets errno on error.
int write_full(int fd, const unsigned char *buf, size_t count) {
   while ( count > 0 ) {
      ssize_t chunk_size = write(fd, buf, count);
      if ( chunk_size < 0 )
         return -1;

      buf    = chunk_size;
      count -= chunk_size;
   }
}

Sample caller:

if ( write_full(out_fd, buf, size) == -1 ) {
   perror("Can't write to file");
   exit(EXIT_FAILURE);
}

Comments on the original code:

  • Think very hard before using casts. Using (int)read(...) makes no sense. This is incorrect.
  • It's best to include the actual error (as perror does) when an error occurs.
  • Printing out error messages is best done outside of the I/O function.

  1. Keep in mind that NULs are common in GIF files, and you could have one as early as the 7th character (right after GIF89a).
  • Related