Skip to content
Snippets Groups Projects
mongoose.c 173 KiB
Newer Older
  HANDLE handles[] = {cv->signal, cv->broadcast};
  ReleaseMutex(*mutex);
  WaitForMultipleObjects(2, handles, FALSE, INFINITE);
  return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1;
}

static int pthread_cond_signal(pthread_cond_t *cv) {
  return SetEvent(cv->signal) == 0 ? -1 : 0;
}

static int pthread_cond_broadcast(pthread_cond_t *cv) {
  // Implementation with PulseEvent() has race condition, see
  // http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
  return PulseEvent(cv->broadcast) == 0 ? -1 : 0;
}

static int pthread_cond_destroy(pthread_cond_t *cv) {
  return CloseHandle(cv->signal) && CloseHandle(cv->broadcast) ? 0 : -1;
}

// For Windows, change all slashes to backslashes in path names.
static void change_slashes_to_backslashes(char *path) {
  int i;

  for (i = 0; path[i] != '\0'; i++) {
    if (path[i] == '/')
      path[i] = '\\';
    // i > 0 check is to preserve UNC paths, like \\server\file.txt
    if (path[i] == '\\' && i > 0)
      while (path[i + 1] == '\\' || path[i + 1] == '/')
        (void) memmove(path + i + 1,
            path + i + 2, strlen(path + i + 1));
  }
}

// Encode 'path' which is assumed UTF-8 string, into UNICODE string.
// wbuf and wbuf_len is a target buffer and its length.
static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len) {
  char buf[PATH_MAX], buf2[PATH_MAX];

  mg_strlcpy(buf, path, sizeof(buf));
  change_slashes_to_backslashes(buf);

  // Convert to Unicode and back. If doubly-converted string does not
  // match the original, something is fishy, reject.
  memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
  MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
  WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
                      NULL, NULL);
  if (strcmp(buf, buf2) != 0) {
    wbuf[0] = L'\0';
  }
}

#if defined(_WIN32_WCE)
static time_t time(time_t *ptime) {
  time_t t;
  SYSTEMTIME st;
  FILETIME ft;

  GetSystemTime(&st);
  SystemTimeToFileTime(&st, &ft);
  t = SYS2UNIX_TIME(ft.dwLowDateTime, ft.dwHighDateTime);

  if (ptime != NULL) {
    *ptime = t;
  }

  return t;
}

static struct tm *localtime(const time_t *ptime, struct tm *ptm) {
  int64_t t = ((int64_t) *ptime) * RATE_DIFF + EPOCH_DIFF;
  FILETIME ft, lft;
  SYSTEMTIME st;
  TIME_ZONE_INFORMATION tzinfo;

  if (ptm == NULL) {
    return NULL;
  }

  * (int64_t *) &ft = t;
  FileTimeToLocalFileTime(&ft, &lft);
  FileTimeToSystemTime(&lft, &st);
  ptm->tm_year = st.wYear - 1900;
  ptm->tm_mon = st.wMonth - 1;
  ptm->tm_wday = st.wDayOfWeek;
  ptm->tm_mday = st.wDay;
  ptm->tm_hour = st.wHour;
  ptm->tm_min = st.wMinute;
  ptm->tm_sec = st.wSecond;
  ptm->tm_yday = 0; // hope nobody uses this
  ptm->tm_isdst =
    GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_DAYLIGHT ? 1 : 0;

  return ptm;
}

static struct tm *gmtime(const time_t *ptime, struct tm *ptm) {
  // FIXME(lsm): fix this.
  return localtime(ptime, ptm);
}

static size_t strftime(char *dst, size_t dst_size, const char *fmt,
                       const struct tm *tm) {
  (void) snprintf(dst, dst_size, "implement strftime() for WinCE");
  return 0;
}
#endif

// Windows happily opens files with some garbage at the end of file name.
// For example, fopen("a.cgi    ", "r") on Windows successfully opens
// "a.cgi", despite one would expect an error back.
// This function returns non-0 if path ends with some garbage.
static int path_cannot_disclose_cgi(const char *path) {
  static const char *allowed_last_characters = "_-";
  int last = path[strlen(path) - 1];
  return isalnum(last) || strchr(allowed_last_characters, last) != NULL;
}

static int mg_stat(struct mg_connection *conn, const char *path,
                   struct file *filep) {
  wchar_t wbuf[PATH_MAX];
  WIN32_FILE_ATTRIBUTE_DATA info;

  if (!is_file_in_memory(conn, path, filep)) {
    to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
    if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) {
      filep->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh);
      filep->modification_time = SYS2UNIX_TIME(
          info.ftLastWriteTime.dwLowDateTime,
          info.ftLastWriteTime.dwHighDateTime);
      filep->is_directory = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
      // If file name is fishy, reset the file structure and return error.
      // Note it is important to reset, not just return the error, cause
      // functions like is_file_opened() check the struct.
      if (!filep->is_directory && !path_cannot_disclose_cgi(path)) {
        memset(filep, 0, sizeof(*filep));
      }
    }
  }

  return filep->membuf != NULL || filep->modification_time != 0;
}

static int mg_remove(const char *path) {
  wchar_t wbuf[PATH_MAX];
  to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
  return DeleteFileW(wbuf) ? 0 : -1;
}

static int mg_mkdir(const char *path, int mode) {
  char buf[PATH_MAX];
  wchar_t wbuf[PATH_MAX];

  (void) mode;
  mg_strlcpy(buf, path, sizeof(buf));
  change_slashes_to_backslashes(buf);

  (void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, ARRAY_SIZE(wbuf));

  return CreateDirectoryW(wbuf, NULL) ? 0 : -1;
}

// Implementation of POSIX opendir/closedir/readdir for Windows.
static DIR * opendir(const char *name) {
  DIR *dir = NULL;
  wchar_t wpath[PATH_MAX];
  DWORD attrs;

  if (name == NULL) {
    SetLastError(ERROR_BAD_ARGUMENTS);
  } else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) {
    SetLastError(ERROR_NOT_ENOUGH_MEMORY);
  } else {
    to_unicode(name, wpath, ARRAY_SIZE(wpath));
    attrs = GetFileAttributesW(wpath);
    if (attrs != 0xFFFFFFFF &&
        ((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {
      (void) wcscat(wpath, L"\\*");
      dir->handle = FindFirstFileW(wpath, &dir->info);
      dir->result.d_name[0] = '\0';
    } else {
      free(dir);
      dir = NULL;
    }
  }

  return dir;
}

static int closedir(DIR *dir) {
  int result = 0;

  if (dir != NULL) {
    if (dir->handle != INVALID_HANDLE_VALUE)
      result = FindClose(dir->handle) ? 0 : -1;

    free(dir);
  } else {
    result = -1;
    SetLastError(ERROR_BAD_ARGUMENTS);
  }

  return result;
}

static struct dirent *readdir(DIR *dir) {
  struct dirent *result = 0;

  if (dir) {
    if (dir->handle != INVALID_HANDLE_VALUE) {
      result = &dir->result;
      (void) WideCharToMultiByte(CP_UTF8, 0,
          dir->info.cFileName, -1, result->d_name,
          sizeof(result->d_name), NULL, NULL);

      if (!FindNextFileW(dir->handle, &dir->info)) {
        (void) FindClose(dir->handle);
        dir->handle = INVALID_HANDLE_VALUE;
      }

    } else {
      SetLastError(ERROR_FILE_NOT_FOUND);
    }
  } else {
    SetLastError(ERROR_BAD_ARGUMENTS);
  }

  return result;
}

#ifndef HAVE_POLL
static int poll(struct pollfd *pfd, int n, int milliseconds) {
  struct timeval tv;
  fd_set set;
Sergey Lyubka's avatar
Sergey Lyubka committed
  int i, result;
  SOCKET maxfd = 0;

  tv.tv_sec = milliseconds / 1000;
  tv.tv_usec = (milliseconds % 1000) * 1000;
  FD_ZERO(&set);

  for (i = 0; i < n; i++) {
    FD_SET((SOCKET) pfd[i].fd, &set);
    pfd[i].revents = 0;

    if (pfd[i].fd > maxfd) {
        maxfd = pfd[i].fd;
    }
Sergey Lyubka's avatar
Sergey Lyubka committed
  if ((result = select(maxfd + 1, &set, NULL, NULL, &tv)) > 0) {
    for (i = 0; i < n; i++) {
      if (FD_ISSET(pfd[i].fd, &set)) {
        pfd[i].revents = POLLIN;
      }
    }
  }

  return result;
}
#endif // HAVE_POLL

static void set_close_on_exec(SOCKET sock) {
  (void) SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0);
}

int mg_start_thread(mg_thread_func_t f, void *p) {
  return (long)_beginthread((void (__cdecl *)(void *)) f, 0, p) == -1L ? -1 : 0;
}

static HANDLE dlopen(const char *dll_name, int flags) {
  wchar_t wbuf[PATH_MAX];
  (void) flags;
  to_unicode(dll_name, wbuf, ARRAY_SIZE(wbuf));
  return LoadLibraryW(wbuf);
}

#if !defined(NO_CGI)
#define SIGKILL 0
static int kill(pid_t pid, int sig_num) {
  (void) TerminateProcess(pid, sig_num);
  (void) CloseHandle(pid);
  return 0;
}

static void trim_trailing_whitespaces(char *s) {
  char *e = s + strlen(s) - 1;
  while (e > s && isspace(* (unsigned char *) e)) {
    *e-- = '\0';
  }
}

static pid_t spawn_process(struct mg_connection *conn, const char *prog,
                           char *envblk, char *envp[], int fdin,
                           int fdout, const char *dir) {
  HANDLE me;
  char *p, *interp, full_interp[PATH_MAX], full_dir[PATH_MAX],
       cmdline[PATH_MAX], buf[PATH_MAX];
  struct file file = STRUCT_FILE_INITIALIZER;
  STARTUPINFOA si;
  PROCESS_INFORMATION pi = { 0 };

  (void) envp;

  memset(&si, 0, sizeof(si));
  si.cb = sizeof(si);

  // TODO(lsm): redirect CGI errors to the error log file
  si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
  si.wShowWindow = SW_HIDE;

  me = GetCurrentProcess();
  DuplicateHandle(me, (HANDLE) _get_osfhandle(fdin), me,
                  &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
  DuplicateHandle(me, (HANDLE) _get_osfhandle(fdout), me,
                  &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);

  // If CGI file is a script, try to read the interpreter line
  interp = conn->ctx->config[CGI_INTERPRETER];
  if (interp == NULL) {
    buf[0] = buf[1] = '\0';

    // Read the first line of the script into the buffer
    snprintf(cmdline, sizeof(cmdline), "%s%c%s", dir, '/', prog);
    if (mg_fopen(conn, cmdline, "r", &file)) {
      p = (char *) file.membuf;
      mg_fgets(buf, sizeof(buf), &file, &p);
      mg_fclose(&file);
      buf[sizeof(buf) - 1] = '\0';
    }

    if (buf[0] == '#' && buf[1] == '!') {
      trim_trailing_whitespaces(buf + 2);
    } else {
      buf[2] = '\0';
    }
    interp = buf + 2;
  }

  if (interp[0] != '\0') {
    GetFullPathNameA(interp, sizeof(full_interp), full_interp, NULL);
    interp = full_interp;
  }
  GetFullPathNameA(dir, sizeof(full_dir), full_dir, NULL);

  mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%s\"%s\\%s\"",
              interp, interp[0] == '\0' ? "" : " ", full_dir, prog);

  DEBUG_TRACE(("Running [%s]", cmdline));
  if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
        CREATE_NEW_PROCESS_GROUP, envblk, NULL, &si, &pi) == 0) {
    cry(conn, "%s: CreateProcess(%s): %ld",
        __func__, cmdline, ERRNO);
    pi.hProcess = (pid_t) -1;
  }

  (void) CloseHandle(si.hStdOutput);
  (void) CloseHandle(si.hStdInput);
  (void) CloseHandle(pi.hThread);

  return (pid_t) pi.hProcess;
}
#endif // !NO_CGI

static int set_non_blocking_mode(SOCKET sock) {
  unsigned long on = 1;
  return ioctlsocket(sock, FIONBIO, &on);
}

#else
static int mg_stat(struct mg_connection *conn, const char *path,
                   struct file *filep) {
  struct stat st;

  if (!is_file_in_memory(conn, path, filep) && !stat(path, &st)) {
    filep->size = st.st_size;
    filep->modification_time = st.st_mtime;
    filep->is_directory = S_ISDIR(st.st_mode);
  } else {
    filep->modification_time = (time_t) 0;
  }

  return filep->membuf != NULL || filep->modification_time != (time_t) 0;
}

static void set_close_on_exec(int fd) {
  fcntl(fd, F_SETFD, FD_CLOEXEC);
}

int mg_start_thread(mg_thread_func_t func, void *param) {
  pthread_t thread_id;
  pthread_attr_t attr;

  (void) pthread_attr_init(&attr);
  (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

#if USE_STACK_SIZE > 1
  // Compile-time option to control stack size, e.g. -DUSE_STACK_SIZE=16384
  (void) pthread_attr_setstacksize(&attr, USE_STACK_SIZE);
#endif
  result = pthread_create(&thread_id, &attr, func, param);
  pthread_attr_destroy(&attr);

  return result;
}

#ifndef NO_CGI
static pid_t spawn_process(struct mg_connection *conn, const char *prog,
                           char *envblk, char *envp[], int fdin,
                           int fdout, const char *dir) {
  pid_t pid;
  const char *interp;

  (void) envblk;

  if ((pid = fork()) == -1) {
    // Parent
    send_http_error(conn, 500, http_500_error, "fork(): %s", strerror(ERRNO));
  } else if (pid == 0) {
    // Child
    if (chdir(dir) != 0) {
      cry(conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO));
    } else if (dup2(fdin, 0) == -1) {
      cry(conn, "%s: dup2(%d, 0): %s", __func__, fdin, strerror(ERRNO));
    } else if (dup2(fdout, 1) == -1) {
      cry(conn, "%s: dup2(%d, 1): %s", __func__, fdout, strerror(ERRNO));
      // Not redirecting stderr to stdout, to avoid output being littered
      // with the error messages.
      (void) close(fdin);
      (void) close(fdout);

      // After exec, all signal handlers are restored to their default values,
      // with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's
      // implementation, SIGCHLD's handler will leave unchanged after exec
      // if it was set to be ignored. Restore it to default action.
      signal(SIGCHLD, SIG_DFL);

      interp = conn->ctx->config[CGI_INTERPRETER];
      if (interp == NULL) {
        (void) execle(prog, prog, NULL, envp);
        cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO));
      } else {
        (void) execle(interp, interp, prog, NULL, envp);
        cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog,
            strerror(ERRNO));
      }
    }
    exit(EXIT_FAILURE);
  }

  return pid;
}
#endif // !NO_CGI

static int set_non_blocking_mode(SOCKET sock) {
  int flags;

  flags = fcntl(sock, F_GETFL, 0);
  (void) fcntl(sock, F_SETFL, flags | O_NONBLOCK);

  return 0;
}
#endif // _WIN32

// Write data to the IO channel - opened file descriptor, socket or SSL
// descriptor. Return number of bytes written.
static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
                    int64_t len) {
  int64_t sent;
  int n, k;

  (void) ssl;  // Get rid of warning
  sent = 0;
  while (sent < len) {

    // How many bytes we send in this iteration
    k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);

#ifndef NO_SSL
    if (ssl != NULL) {
      n = SSL_write(ssl, buf + sent, k);
    } else
#endif
      if (fp != NULL) {
      n = (int) fwrite(buf + sent, 1, (size_t) k, fp);
      if (ferror(fp))
        n = -1;
    } else {
      n = send(sock, buf + sent, (size_t) k, MSG_NOSIGNAL);
    }

    if (n <= 0)
      break;

    sent += n;
  }

  return sent;
}

// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
// Return negative value on error, or number of bytes read on success.
static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) {
  int nread;

  if (fp != NULL) {
    // Use read() instead of fread(), because if we're reading from the CGI
    // pipe, fread() may block until IO buffer is filled up. We cannot afford
    // to block and must pass all read bytes immediately to the client.
    nread = read(fileno(fp), buf, (size_t) len);
#ifndef NO_SSL
  } else if (conn->ssl != NULL) {
    nread = SSL_read(conn->ssl, buf, len);
#endif
  } else {
    nread = recv(conn->client.sock, buf, (size_t) len, 0);
  }

  return conn->ctx->stop_flag ? -1 : nread;
}

static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) {
  int n, nread = 0;

  while (len > 0 && conn->ctx->stop_flag == 0) {
    n = pull(fp, conn, buf + nread, len);
    if (n < 0) {
      nread = n;  // Propagate the error
      break;
    } else if (n == 0) {
      break;  // No more data to read
    } else {
      conn->consumed_content += n;
      nread += n;
      len -= n;
    }
  }

  return nread;
}

int mg_read(struct mg_connection *conn, void *buf, size_t len) {
  int n, buffered_len, nread;
  const char *body;

  // If Content-Length is not set, read until socket is closed
  if (conn->consumed_content == 0 && conn->content_len == 0) {
    conn->content_len = INT64_MAX;
    conn->must_close = 1;
  }

  nread = 0;
  if (conn->consumed_content < conn->content_len) {
    // Adjust number of bytes to read.
    int64_t to_read = conn->content_len - conn->consumed_content;
    if (to_read < (int64_t) len) {
      len = (size_t) to_read;
    }

    // Return buffered data
    body = conn->buf + conn->request_len + conn->consumed_content;
    buffered_len = &conn->buf[conn->data_len] - body;
    if (buffered_len > 0) {
      if (len < (size_t) buffered_len) {
        buffered_len = (int) len;
      }
      memcpy(buf, body, (size_t) buffered_len);
      len -= buffered_len;
      conn->consumed_content += buffered_len;
      nread += buffered_len;
      buf = (char *) buf + buffered_len;
    }

    // We have returned all buffered data. Read new data from the remote socket.
    n = pull_all(NULL, conn, (char *) buf, (int) len);
    nread = n >= 0 ? nread + n : n;
  }
  return nread;
}

int mg_write(struct mg_connection *conn, const void *buf, size_t len) {
  time_t now;
  int64_t n, total, allowed;

  if (conn->throttle > 0) {
    if ((now = time(NULL)) != conn->last_throttle_time) {
      conn->last_throttle_time = now;
      conn->last_throttle_bytes = 0;
    }
    allowed = conn->throttle - conn->last_throttle_bytes;
    if (allowed > (int64_t) len) {
      allowed = len;
    }
    if ((total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
                      (int64_t) allowed)) == allowed) {
      buf = (char *) buf + total;
      conn->last_throttle_bytes += total;
      while (total < (int64_t) len && conn->ctx->stop_flag == 0) {
        allowed = conn->throttle > (int64_t) len - total ?
          (int64_t) len - total : conn->throttle;
        if ((n = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
                      (int64_t) allowed)) != allowed) {
          break;
        }
        sleep(1);
        conn->last_throttle_bytes = allowed;
        conn->last_throttle_time = time(NULL);
        buf = (char *) buf + n;
        total += n;
      }
    }
  } else {
    total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
                 (int64_t) len);
  }
  return (int) total;
}

// Alternative alloc_vprintf() for non-compliant C runtimes
static int alloc_vprintf2(char **buf, const char *fmt, va_list ap) {
  va_list ap_copy;
  int size = MG_BUF_LEN;
  int len = -1;

  *buf = NULL;
  while (len == -1) {
    if (*buf) free(*buf);
    *buf = malloc(size *= 4);
    if (!*buf) break;
    va_copy(ap_copy, ap);
    len = vsnprintf(*buf, size, fmt, ap_copy);
  }

  return len;
}

// Print message to buffer. If buffer is large enough to hold the message,
// return buffer. If buffer is to small, allocate large enough buffer on heap,
// and return allocated buffer.
static int alloc_vprintf(char **buf, size_t size, const char *fmt, va_list ap) {
  va_list ap_copy;
  int len;

  // Windows is not standard-compliant, and vsnprintf() returns -1 if
  // buffer is too small. Also, older versions of msvcrt.dll do not have
  // _vscprintf().  However, if size is 0, vsnprintf() behaves correctly.
  // Therefore, we make two passes: on first pass, get required message length.
  // On second pass, actually print the message.
  va_copy(ap_copy, ap);
  len = vsnprintf(NULL, 0, fmt, ap_copy);

  if (len < 0) {
    // C runtime is not standard compliant, vsnprintf() returned -1.
    // Switch to alternative code path that uses incremental allocations.
    va_copy(ap_copy, ap);
    len = alloc_vprintf2(buf, fmt, ap);
  } else if (len > (int) size &&
      (size = len + 1) > 0 &&
      (*buf = (char *) malloc(size)) == NULL) {
    len = -1;  // Allocation failed, mark failure
  } else {
    va_copy(ap_copy, ap);
    vsnprintf(*buf, size, fmt, ap_copy);
  }

  return len;
}

int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap) {
  char mem[MG_BUF_LEN], *buf = mem;
  int len;

  if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
    len = mg_write(conn, buf, (size_t) len);
  }
  if (buf != mem && buf != NULL) {
    free(buf);
  }

  return len;
}

int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
  va_list ap;
  va_start(ap, fmt);
  return mg_vprintf(conn, fmt, ap);
}

int mg_url_decode(const char *src, int src_len, char *dst,
                  int dst_len, int is_form_url_encoded) {
  int i, j, a, b;
#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')

  for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
    if (src[i] == '%' && i < src_len - 2 &&
        isxdigit(* (const unsigned char *) (src + i + 1)) &&
        isxdigit(* (const unsigned char *) (src + i + 2))) {
      a = tolower(* (const unsigned char *) (src + i + 1));
      b = tolower(* (const unsigned char *) (src + i + 2));
      dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
      i += 2;
    } else if (is_form_url_encoded && src[i] == '+') {
      dst[j] = ' ';
    } else {
      dst[j] = src[i];
    }
  }

  dst[j] = '\0'; // Null-terminate the destination

  return i >= src_len ? j : -1;
}

int mg_get_var(const char *data, size_t data_len, const char *name,
               char *dst, size_t dst_len) {
  const char *p, *e, *s;
  size_t name_len;
  int len;

  if (dst == NULL || dst_len == 0) {
    len = -2;
  } else if (data == NULL || name == NULL || data_len == 0) {
    len = -1;
    dst[0] = '\0';
  } else {
    name_len = strlen(name);
    e = data + data_len;
    len = -1;
    dst[0] = '\0';

    // data is "var1=val1&var2=val2...". Find variable first
    for (p = data; p + name_len < e; p++) {
      if ((p == data || p[-1] == '&') && p[name_len] == '=' &&
          !mg_strncasecmp(name, p, name_len)) {

        // Point p to variable value
        p += name_len + 1;

        // Point s to the end of the value
        s = (const char *) memchr(p, '&', (size_t)(e - p));
        if (s == NULL) {
          s = e;
        }
        assert(s >= p);

        // Decode variable into destination buffer
        len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);

        // Redirect error code from -1 to -2 (destination buffer too small).
        if (len == -1) {
          len = -2;
        }
        break;
      }
    }
  }

  return len;
}

int mg_get_cookie(const char *cookie_header, const char *var_name,
                  char *dst, size_t dst_size) {
  const char *s, *p, *end;
  int name_len, len = -1;

  if (dst == NULL || dst_size == 0) {
    len = -2;
  } else if (var_name == NULL || (s = cookie_header) == NULL) {
    len = -1;
    dst[0] = '\0';
  } else {
    name_len = (int) strlen(var_name);
    end = s + strlen(s);
    dst[0] = '\0';

    for (; (s = mg_strcasestr(s, var_name)) != NULL; s += name_len) {
      if (s[name_len] == '=') {
        s += name_len + 1;
        if ((p = strchr(s, ' ')) == NULL)
          p = end;
        if (p[-1] == ';')
          p--;
        if (*s == '"' && p[-1] == '"' && p > s + 1) {
          s++;
          p--;
        }
        if ((size_t) (p - s) < dst_size) {
          len = p - s;
          mg_strlcpy(dst, s, (size_t) len + 1);
        } else {
          len = -3;
        }
        break;
      }
    }
  }
  return len;
}

static void convert_uri_to_file_name(struct mg_connection *conn, char *buf,
                                     size_t buf_len, struct file *filep) {
  struct vec a, b;
  const char *rewrite, *uri = conn->request_info.uri,
        *root = conn->ctx->config[DOCUMENT_ROOT];
  char *p;
  int match_len;
  char gz_path[PATH_MAX];
  char const* accept_encoding;

  // Using buf_len - 1 because memmove() for PATH_INFO may shift part
  // of the path one byte on the right.
  // If document_root is NULL, leave the file empty.
  mg_snprintf(conn, buf, buf_len - 1, "%s%s",
              root == NULL ? "" : root,
              root == NULL ? "" : uri);

  rewrite = conn->ctx->config[REWRITE];
  while ((rewrite = next_option(rewrite, &a, &b)) != NULL) {
    if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
      mg_snprintf(conn, buf, buf_len - 1, "%.*s%s", (int) b.len, b.ptr,
                  uri + match_len);
      break;
    }
  }

  if (mg_stat(conn, buf, filep)) return;

  // if we can't find the actual file, look for the file
  // with the same name but a .gz extension. If we find it,
  // use that and set the gzipped flag in the file struct
  // to indicate that the response need to have the content-
  // encoding: gzip header
  // we can only do this if the browser declares support
  if ((accept_encoding = mg_get_header(conn, "Accept-Encoding")) != NULL) {
    if (strstr(accept_encoding,"gzip") != NULL) {
      snprintf(gz_path, sizeof(gz_path), "%s.gz", buf);
      if (mg_stat(conn, gz_path, filep)) {
        filep->gzipped = 1;
        return;
      }
    }
  }

  // Support PATH_INFO for CGI scripts.
  for (p = buf + strlen(buf); p > buf + 1; p--) {
    if (*p == '/') {
      *p = '\0';
      if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
                       strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 &&
          mg_stat(conn, buf, filep)) {
        // Shift PATH_INFO block one character right, e.g.
        //  "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
        // conn->path_info is pointing to the local variable "path" declared
        // in handle_request(), so PATH_INFO is not valid after
        // handle_request returns.
        conn->path_info = p + 1;
        memmove(p + 2, p + 1, strlen(p + 1) + 1);  // +1 is for trailing \0
        p[1] = '/';
        break;
      } else {
        *p = '/';
      }
    }
  }
}

// Check whether full request is buffered. Return:
//   -1  if request is malformed
//    0  if request is not yet fully buffered
//   >0  actual request length, including last \r\n\r\n
static int get_request_len(const char *buf, int buflen) {
  const char *s, *e;
  int len = 0;

  for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++)
    // Control characters are not allowed but >=128 is.
    if (!isprint(* (const unsigned char *) s) && *s != '\r' &&
        *s != '\n' && * (const unsigned char *) s < 128) {
      len = -1;
      break;  // [i_a] abort scan as soon as one malformed character is found;
              // don't let subsequent \r\n\r\n win us over anyhow
    } else if (s[0] == '\n' && s[1] == '\n') {
      len = (int) (s - buf) + 2;
    } else if (s[0] == '\n' && &s[1] < e &&
        s[1] == '\r' && s[2] == '\n') {
      len = (int) (s - buf) + 3;
    }

  return len;
}

// Convert month to the month number. Return -1 on error, or month number
static int get_month_index(const char *s) {
  size_t i;

  for (i = 0; i < ARRAY_SIZE(month_names); i++)
    if (!strcmp(s, month_names[i]))
      return (int) i;

  return -1;
}

static int num_leap_years(int year) {
  return year / 4 - year / 100 + year / 400;
}

// Parse UTC date-time string, and return the corresponding time_t value.
static time_t parse_date_string(const char *datetime) {
  static const unsigned short days_before_month[] = {
    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
  };
  char month_str[32];
  int second, minute, hour, day, month, year, leap_days, days;
  time_t result = (time_t) 0;

  if (((sscanf(datetime, "%d/%3s/%d %d:%d:%d",
               &day, month_str, &year, &hour, &minute, &second) == 6) ||
       (sscanf(datetime, "%d %3s %d %d:%d:%d",
               &day, month_str, &year, &hour, &minute, &second) == 6) ||
       (sscanf(datetime, "%*3s, %d %3s %d %d:%d:%d",
               &day, month_str, &year, &hour, &minute, &second) == 6) ||
       (sscanf(datetime, "%d-%3s-%d %d:%d:%d",
               &day, month_str, &year, &hour, &minute, &second) == 6)) &&
      year > 1970 &&
      (month = get_month_index(month_str)) != -1) {
    leap_days = num_leap_years(year) - num_leap_years(1970);
    year -= 1970;
    days = year * 365 + days_before_month[month] + (day - 1) + leap_days;
    result = days * 24 * 3600 + hour * 3600 + minute * 60 + second;
  }

  return result;
}

// Protect against directory disclosure attack by removing '..',
// excessive '/' and '\' characters
static void remove_double_dots_and_double_slashes(char *s) {
  char *p = s;

  while (*s != '\0') {
    *p++ = *s++;
    if (s[-1] == '/' || s[-1] == '\\') {
      // Skip all following slashes, backslashes and double-dots
      while (s[0] != '\0') {
        if (s[0] == '/' || s[0] == '\\') {
          s++;
        } else if (s[0] == '.' && s[1] == '.') {
          s += 2;
        } else {
          break;
        }
      }
    }
  }
  *p = '\0';
}

static const struct {
  const char *extension;
  size_t ext_len;
  const char *mime_type;
} builtin_mime_types[] = {
  {".html", 5, "text/html"},
  {".htm", 4, "text/html"},
  {".shtm", 5, "text/html"},
  {".shtml", 6, "text/html"},
  {".css", 4, "text/css"},
  {".js",  3, "application/x-javascript"},
  {".ico", 4, "image/x-icon"},
  {".gif", 4, "image/gif"},
  {".jpg", 4, "image/jpeg"},
  {".jpeg", 5, "image/jpeg"},
  {".png", 4, "image/png"},
  {".svg", 4, "image/svg+xml"},
  {".txt", 4, "text/plain"},
  {".torrent", 8, "application/x-bittorrent"},
  {".wav", 4, "audio/x-wav"},
  {".mp3", 4, "audio/x-mp3"},
  {".mid", 4, "audio/mid"},
  {".m3u", 4, "audio/x-mpegurl"},
  {".ogg", 4, "audio/ogg"},
  {".ram", 4, "audio/x-pn-realaudio"},
  {".xml", 4, "text/xml"},
  {".json",  5, "text/json"},
  {".xslt", 5, "application/xml"},
  {".xsl", 4, "application/xml"},
  {".ra",  3, "audio/x-pn-realaudio"},
  {".doc", 4, "application/msword"},
  {".exe", 4, "application/octet-stream"},
  {".zip", 4, "application/x-zip-compressed"},
  {".xls", 4, "application/excel"},
  {".tgz", 4, "application/x-tar-gz"},
  {".tar", 4, "application/x-tar"},
  {".gz",  3, "application/x-gunzip"},
  {".arj", 4, "application/x-arj-compressed"},