about summary refs log tree commit diff
path: root/sternenblog/entry.c
blob: 0318311a25680b11739d66b3bc1758a6b197183a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "core.h"
#include "../config.h" // TODO: make independent?
#include "cgiutil.h"
#include "entry.h"

int make_entry(const char *blog_dir, char *script_name, char *path_info, struct entry *entry) {
    // TODO: allow subdirectories?
    // TODO: no status code return?

    // TODO: url encoding of links

    // intialize pointers
    entry->time = 0;
    entry->link = NULL;
    entry->path = NULL;
    entry->title = NULL;

    // won't be handled by make_entry
    entry->text = NULL;
    entry->text_size = 0;

    // validate path_info
    if(path_info == NULL) {
        fprintf(stderr, "Missing PATH_INFO\n");
        return 500;
    }

    size_t path_info_len = strlen(path_info);

    // if path_info is empty make_entry shouldn't be called
    // as per RFC3875 expect it to start with a slash
    if(path_info_len == 0 || path_info[0] != '/') {
        fprintf(stderr, "Malformed PATH_INFO: \"%s\"\n", path_info);
        return 400;
    }

    // check if the path_info segments are alright
    // should be sane coming from a webserver
    int last_was_slash = 0;
    for(size_t i = 0; i < path_info_len; i++) {
        if(last_was_slash) {
            switch(path_info[i]) {
                case '/':
                    // TODO: necessary?
                    fprintf(stderr, "Double slash in PATH_INFO: \"%s\"\n", path_info);
                    return 400;
                    break;
                case '.':
                    fprintf(stderr, "Dot file or dir in PATH_INFO: \"%s\"\n", path_info);
                    return 403;
                    break;
                default:
                    last_was_slash = 0;
            }
        } else if(path_info[i] == '/') {
            last_was_slash = 1;
        }
    }

    // set title (PATH_INFO without the slash)
    if(path_info_len < 2) {
        // shouldn't be called with just "/"
        return 500;
    }

    // title length is exactly path_info_len (-1 for slash, +1 for null byte)
    entry->title = malloc(sizeof(char) * path_info_len);
    memcpy(entry->title, path_info + 1, sizeof(char) * path_info_len);

    // build path to entry's file
    size_t blog_dir_len = strlen(blog_dir);

    entry->path = malloc(sizeof(char) * (path_info_len + blog_dir_len + 1));

    memcpy(entry->path, blog_dir, blog_dir_len * sizeof(char));

    // prevent double slash
    if(entry->path[blog_dir_len - 1] == '/') {
        blog_dir_len--;
    }

    memcpy(entry->path + blog_dir_len, path_info, path_info_len);
    entry->path[path_info_len + blog_dir_len] = '\0';

    struct stat file_info;
    memset(&file_info, 0, sizeof(struct stat));

    if(stat(entry->path, &file_info) == -1) {
        return http_errno(errno);
    }

    int regular_file = (file_info.st_mode & S_IFMT) == S_IFREG;

    // strict access check requires files to be owned by the webserver's
    // group or user in order to be processed. can be disabled in config.h
    bool access = !BLOG_STRICT_ACCESS;
    if(BLOG_STRICT_ACCESS) {
        gid_t gid = getegid();
        uid_t uid = geteuid();
        access = file_info.st_gid == gid || file_info.st_uid == uid;
    }

    if(!access) {
        return http_errno(EACCES);
    } else if(!regular_file) {
        return http_errno(ENOENT);
    }

    // use POSIX compatible version, since we don't need nanoseconds
    entry->time = file_info.st_mtime;

    // build the link using SCRIPT_NAME
    if(script_name == NULL) {
        fprintf(stderr, "Missing SCRIPT_NAME\n");
        return 500;
    }

    // don't check SCRIPT_NAME validity, since we
    // don't depend on it starting with a slash

    size_t script_name_len = strlen(script_name);
    size_t link_size = script_name_len + path_info_len + 1;

    entry->link = malloc(sizeof(char) * link_size);

    if(script_name_len != 0) {
        memcpy(entry->link, script_name, script_name_len);
    }

    memcpy(entry->link + script_name_len, path_info, path_info_len);

    entry->link[link_size - 1] = '\0';

    if(urlencode_realloc(&entry->link, link_size) <= 0) {
        return 500;
    }

    return 200;
}

int entry_get_text(struct entry *entry) {
    // TODO set errno correctly in all cases
    if(entry->text != NULL) {
        // nothing to do
        return 0;
    }

    int fd = open(entry->path, O_RDONLY);

    if(fd == -1) {
        return -1;
    }

    struct stat file_info;

    if(fstat(fd, &file_info) == -1) {
        return -1;
    }

    if(file_info.st_size == 0) {
        close(fd);
        return 0;
    }

    entry->text = mmap(NULL, file_info.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

    if(entry->text == MAP_FAILED) {
        entry->text = NULL;
        close(fd);
        return -1;
    }

    entry->text_size = file_info.st_size;

    if(close(fd) == -1) {
        return -1;
    }

    return 0;
}

void entry_unget_text(struct entry *entry) {
    if(entry->text_size > 0 && entry->text != NULL &&
       munmap(entry->text, entry->text_size) != -1) {
        entry->text_size = -1;
        entry->text = NULL;
    }
}

void free_entry(struct entry *entry) {
    if(entry->path != NULL) {
        free(entry->path);
    }

    if(entry->link != NULL) {
        free(entry->link);
    }

    if(entry->title != NULL) {
        free(entry->title);
    }

    entry_unget_text(entry);
}