Yuan Fu
Yuan Fu

Reputation: 361

C socket Invalid range header

I'm trying to make a program that serve a file for others to download. It listens on a port and accept any request and send files through socket. I'm not worrying about NAT yet.

The problem I have is that Downloading that file with aria2c (or wget) gives me "Invalid range header error". However, downloading locally (download from 127.0.0.1) works fine.

Knowing it might cause a problem, I disabled multi-threading download on aria2c by adding -x1 flag.

What am I doing wrong?


The error is:

12/23 20:04:07 [ERROR] CUID#8 - Download aborted. URI=http://104.38.127.225:8000
Exception: [AbstractCommand.cc:351] errorCode=8 URI=http://104.38.127.225:8000
  -> [HttpResponse.cc:86] errorCode=8 Invalid range header. Request: 98304-187031/187032, Response: 0-187031/187032

A bit of explanation on the code:

  1. findFilename just generate file name from file path
  2. serveFile opens a socket and listen on it to send anyone that connects to it the specified file

My code :

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

#include <netdb.h>
#include <netinet/in.h>

#include <string.h>

#include <unistd.h> /* for close() */

/* find file name from path */
/* file name have to be shorter than 512 characters*/
/* remember to deallocate memory */
char *findFilename(char *filepath)
{
  size_t pt;
  size_t filenamePt = 0;
  char ch;
  char *filename = (char*) malloc(512);

  for (pt = 0; pt < strlen(filepath); pt++)
  {
    ch = filepath[pt];
    filename[filenamePt] = ch;
    filenamePt++;

    if(ch == '/')
    {
      bzero(filename, 512);
      filenamePt = 0;
    }
  }

  filename[filenamePt + 1] = '\0';

  return filename;
}

int serveFile(char* filepath, int port)
{
  FILE *file;
  char *buffer;
  long int fileLength;
  size_t bufferSize;

  /* get file name */
  char* filename = findFilename(filepath);
  if (!filename)
  {
    fprintf(stderr, "file path error\n");
    return -1;
  }



  /* open file */
  file = fopen(filepath, "rb");
  if (!file)
  {
    fprintf(stderr, "file open error\n");
    return -1;
  }

  /* get file length */
  fseek(file, 0, SEEK_END);
  fileLength=ftell(file);
  fseek(file, 0, SEEK_SET);
  bufferSize = fileLength + 1;

  /* allocate memory */
  buffer=(char *)malloc(bufferSize);
  if (!buffer)
  {
      fprintf(stderr, "allocate memory error\n");
      fclose(file);
      return -1;
  }

  /* read file */
  fread(buffer, fileLength, 1, file);
  fclose(file);

  /* compose header */
  char header[1024];

  sprintf(header,
          "HTTP/1.1 200 OK\n"
          "Content-Length: %li\n"
          "Accept-Ranges: bytes\n"
          "Content-Disposition: attachment; filename=\"%s\"\n"
          "\n", fileLength, filename);

  /* do not need filename anymore */
  free(filename);

  /* create socket */
  int socketfd = socket(PF_INET, SOCK_STREAM, 0);

  /* configure socket */
  struct sockaddr_in address;

  bzero(&address, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_port = htons(port); /* host to network short */
  address.sin_addr.s_addr = INADDR_ANY;

  /* bind & listen */
  if(bind(socketfd, (struct sockaddr*) &address, sizeof(address)) != 0)
  {
    fprintf(stderr, "bind error\n");
    return -1;
  }

  if (listen(socketfd, 16)!=0)
  {
    fprintf(stderr, "listen error\n");
    return -1;
  }

  /* accept client */
  while (1)
  {
    socklen_t size = sizeof(address);
    int clientSocket = accept(socketfd, (struct sockaddr*) &address, &size);

    puts("client connected");

    if (fork() == 0)
    {
      write(clientSocket, header, strlen(header));
      write(clientSocket, buffer, bufferSize);
      exit(0);
    }
    else
    {
      close(clientSocket);
    }
  }

}


int main(int argc, char *argv[])
{
  if (argc < 3 || argc > 3)
  {
    fprintf(stderr, "needs 2 arguments: file path & port\n");
    return -1;
  }
  char *filepath = argv[1];
  int port = atoi(argv[2]);

  printf("serving %s on port %d\n"
       "file name(not path) cannot exceed 512 characters\n"
         "keyboard interrupt to kill\n", filepath, port);
  serveFile(filepath, port);
}

Upvotes: 0

Views: 1158

Answers (1)

Myst
Myst

Reputation: 19221

12/23 20:04:07 [ERROR] CUID#8 - Download aborted. URI=http://104.38.127.225:8000
Exception: [AbstractCommand.cc:351] errorCode=8 URI=http://104.38.127.225:8000
   -> [HttpResponse.cc:86] errorCode=8 Invalid range header. Request: 98304-187031/187032, Response: 0-187031/187032

The error you attaches said it all, really.

Your code assumes that every request made to the server is a request asking for the whole of the file.

For many good reasons, this isn't always the case (see edit).

Range requests (using the Range header) allow clients to request a part of the file, which allows breaking the download into chunks and resume downloads even after network failures.

However, your code also sends the whole file, regardless of the request made.

Moreover, your code is sending the Accept-Ranges header, indicating that Range requests will be respected (even though the aren't).

In this case, the client is complaining about your code, indicating it requested a chunk and your code sent the whole thing.

EDIT

As pointed out by @RemyLebeau, the Range function is optional, though widely used.

This is why you only receive the error sometimes...

aria2c seems to require the range functionality to a degree, or it wouldn't be able to work as a download manager (managing downloads is the purpose of Range functionality, after all).

Other downloading tools will not require (or leverage) this functionality.

Upvotes: 1

Related Questions