<commit_before>/*
 * rs-serve - (c) 2013 <NAME>
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "rs-serve.h"

#define EXTRACT_PATH(request) (evhttp_request_get_uri(request) + RS_STORAGE_PATH_LEN)

void storage_options(struct evhttp_request *request) {
  struct evkeyvalq *headers = evhttp_request_get_output_headers(request);
  add_cors_headers(headers);

  evhttp_send_reply(request, HTTP_OK, NULL, NULL);
}

void storage_get(struct evhttp_request *request, int sendbody) {
  struct evkeyvalq *headers = evhttp_request_get_output_headers(request);
  add_cors_headers(headers);

  if(! authorize_request(request)) {
    return;
  }
  const char *path = EXTRACT_PATH(request);
  size_t path_len = strlen(path);

  int disk_path_len = path_len + RS_STORAGE_ROOT_LEN;
  char disk_path[disk_path_len + 1];
  sprintf(disk_path, "%s%s", RS_STORAGE_ROOT, path);
  struct stat stat_buf;
  struct evbuffer *buf = evbuffer_new();

  if(stat(disk_path, &stat_buf) != 0) {
    // stat failed
    if(errno != ENOENT) {
      fprintf(stderr, "while getting %s, ", disk_path);
      perror("stat() failed");
    }
    evhttp_send_error(request, HTTP_NOTFOUND, NULL);
  } else if(path[path_len - 1] == '/') {
    // directory requested
    if(S_ISDIR(stat_buf.st_mode)) {
      // directory found
      evhttp_add_header(headers, "Content-Type", "application/json");
      if(sendbody) {
        // GET response
        DIR *dir = opendir(disk_path);
        if(dir == NULL) {
          perror("opendir() failed");
          evhttp_send_error(request, HTTP_INTERNAL, NULL);
        } else {
          struct dirent *entryp = malloc(offsetof(struct dirent, d_name) +
                                         pathconf(disk_path, _PC_NAME_MAX) + 1);
          struct dirent *resultp = NULL;
          if(entryp == NULL) {
            perror("malloc() failed while creating directory pointer");
            evhttp_send_error(request, HTTP_INTERNAL, NULL);
          } else {
            struct stat file_stat_buf;
            char rev[100];
            int entry_len;
            int first = 1;
            // FIXME: missing lots of error checking.
            for(;;) {
              readdir_r(dir, entryp, &resultp);
              if(resultp == NULL) {
                break;
              }
              if(strcmp(entryp->d_name, ".") == 0 ||
                 strcmp(entryp->d_name, "..") == 0) {
                // skip.
                continue;
              }
              if(first) {
                evbuffer_add(buf, "{", 1);
                first = 0;
              } else {
                evbuffer_add(buf, ",", 1);
              }
              entry_len = strlen(entryp->d_name);
              char full_path[disk_path_len + entry_len + 1];
              sprintf(full_path, "%s%s", disk_path, entryp->d_name);
              stat(full_path, &file_stat_buf);

              evbuffer_add(buf, "\"", 1);
              // FIXME: escape quotes!!!
              evbuffer_add(buf, entryp->d_name, entry_len);
              if(S_ISDIR(file_stat_buf.st_mode)) {
                evbuffer_add(buf, "/", 1);
              }
              evbuffer_add(buf, "\":", 2);

              snprintf(rev, 99, "%lld", ((long long)file_stat_buf.st_mtime) * 1000);
              evbuffer_add(buf, rev, strlen(rev));
            }
            if(first) {
              // empty directory.
              // FIXME: should be treated as non-existant, but that would require it to
              //   also not show up in the parent, which would require recursive checks
              //   for each listing item to see if there are any actual files below any
              //   directory item.
              evbuffer_add(buf, "{", 1);
            }<commit_msg>fixed empty directory listings, protect against directory-traversal attacks, escape names in listings correctly<commit_after>/*
 * rs-serve - (c) 2013 <NAME>
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "rs-serve.h"

#define EXTRACT_PATH(request) (evhttp_request_get_uri(request) + RS_STORAGE_PATH_LEN)

int validate_path(struct evhttp_request *request, const char *path) {
  if(strstr(path, "../") == NULL) {
    return 1;
  } else {
    evhttp_send_error(request, HTTP_BADREQUEST, NULL);
    return 0;
  }
}

char *escape_name(const char *name) {
  int max_len = strlen(name) * 2, i = 0;
  char *escaped = malloc(max_len + 1);
  if(escaped == NULL) {
    perror("malloc() failed");
    return NULL;
  }
  const char *name_p;
  for(name_p = name; *name_p != 0; name_p++) {
    if(*name_p == '"' || *name_p == '\\') {
      escaped[i++] = '\\';
    }
    escaped[i++] = *name_p;
  }
  escaped[i++] = 0;
  escaped = realloc(escaped, i);
  return escaped;
}

void storage_options(struct evhttp_request *request) {
  struct evkeyvalq *headers = evhttp_request_get_output_headers(request);
  add_cors_headers(headers);

  evhttp_send_reply(request, HTTP_OK, NULL, NULL);
}

void storage_get(struct evhttp_request *request, int sendbody) {
  struct evkeyvalq *headers = evhttp_request_get_output_headers(request);
  add_cors_headers(headers);

  if(! authorize_request(request)) {
    return;
  }
  const char *path = EXTRACT_PATH(request);
  size_t path_len = strlen(path);

  if(! validate_path(request, path)) {
    return;
  }

  int disk_path_len = path_len + RS_STORAGE_ROOT_LEN;
  char disk_path[disk_path_len + 1];
  sprintf(disk_path, "%s%s", RS_STORAGE_ROOT, path);
  struct stat stat_buf;
  struct evbuffer *buf = evbuffer_new();

  if(stat(disk_path, &stat_buf) != 0) {
    // stat failed
    if(errno != ENOENT) {
      fprintf(stderr, "while getting %s, ", disk_path);
      perror("stat() failed");
    }
    if(path[path_len - 1] == '/') {
      // empty dir listing.
      evhttp_add_header(headers, "Content-Type", "application/json");
      evbuffer_add(buf, "{}", 2);
      evhttp_send_reply(request, HTTP_OK, NULL, buf);
    } else {
      evhttp_send_error(request, HTTP_NOTFOUND, NULL);
    }
  } else if(path[path_len - 1] == '/') {
    // directory requested
    if(S_ISDIR(stat_buf.st_mode)) {
      // directory found
      evhttp_add_header(headers, "Content-Type", "application/json");
      if(sendbody) {
        // GET response
        DIR *dir = opendir(disk_path);
        if(dir == NULL) {
          perror("opendir() failed");
          evhttp_send_error(request, HTTP_INTERNAL, NULL);
        } else {
          struct dirent *entryp = malloc(offsetof(struct dirent, d_name) +
                                         pathconf(disk_path, _PC_NAME_MAX) + 1);
          struct dirent *resultp = NULL;
          if(entryp == NULL) {
            perror("malloc() failed while creating directory pointer");
            evhttp_send_error(request, HTTP_INTERNAL, NULL);
          } else {
            struct stat file_stat_buf;
            int entry_len;
            int first = 1;
            // FIXME: missing lots of error checking.
            for(;;) {
              readdir_r(dir, entryp, &resultp);
              if(resultp == NULL) {
                break;
              }
              if(strcmp(entryp->d_name, ".") == 0 ||
                 strcmp(entryp->d_name, "..") == 0) {
                // skip.
                continue;
              }
              if(first) {
                evbuffer_add(buf, "{", 1);
                first = 0;
              } else {
                evbuffer_add(buf, ",", 1);
              }
              entry_len = strlen(entryp->d_name);
              char full_path[disk_path_len + entry_len + 1];
              sprintf(full_path, "%s%s", disk_path, entryp->d_name);
              stat(full_path, &file_stat_buf);

              char *escaped_name = escape_name(entryp->d_name);
              if(! escaped_name) {
                // failed to allocate name
                evhttp_send_error(request, HTTP_INTERNAL, NULL);
                free(entryp);
                free(dir);
                return;
              }
              evbuffer_add_printf(buf, "\"%s%s\":%lld", escaped_name,
                                  S_ISDIR(file_stat_buf.st_mode) ? "/" : "",
                                  ((long long)file_stat_buf.st_mtime) * 1000);
              free(escaped_name);
            }
            if(first) {
              // empty directory.
              evbuffer_add(buf, "{", 1);
            }
{'fraction_non_alphanumeric': 0.1207868272737319, 'fraction_numerical': 0.006299038567797547, 'mean_word_length': 2.1206896551724137, 'pattern_counts': {'":': 2, '<': 7, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4460426', 'n_tokens_mistral': 2876, 'n_tokens_neox': 2774, 'n_words': 659}