Sanitize username inputs in the database layer
[blerg.git] / database / tags.c
1 #include <string.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <unistd.h>
5 #include <fcntl.h>
6 #include <sys/types.h>
7 #include <sys/stat.h>
8 #include <sys/file.h>
9 #include <sys/mman.h>
10 #include "tags.h"
11 #include "util.h"
12 #include "config.h"
13
14 #define MAX_TAG_LENGTH 64
15 #define MAX_TAGS 1024
16
17 #define TAG_CHAR(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_')
18
19 int tag_scan(const char *author, const char *data, int len, uint64_t record) {
20         char *taglist[MAX_TAGS];
21         int n_tags = 0;
22         int i, j;
23
24         for (i = 0; i < len; i++) {
25 tag_scan_start:
26                 if (data[i] == '#' || data[i] == '@') {
27                         if (n_tags == MAX_TAGS) {
28                                 fprintf(stderr, "Too many tags in message\n");
29                                 break;
30                         }
31                         int begin = i;
32                         int start = ++i;
33                         while (i < len && TAG_CHAR(data[i]) && (i - start < MAX_TAG_LENGTH)) {
34                                 i++;
35                         }
36                         if (start - i == 0) continue;
37                         char *tag = calloc(1, MAX_TAG_LENGTH + 2);
38                         memcpy(tag, &data[begin], i - begin);
39                         for (j = 0; j < n_tags; j++) {
40                                 if (!strncmp(tag, taglist[j], MAX_TAG_LENGTH)) {
41                                         // We already have this tag.  Start over.
42                                         free(tag);
43                                         goto tag_scan_start;
44                                 }
45                         }
46                         taglist[n_tags] = tag;
47                         n_tags++;
48                         // We goto here so i doesn't get incremented
49                         goto tag_scan_start;
50                 }
51         }
52
53         for (i = 0; i < n_tags; i++) {
54                 tag_add(author, taglist[i], record);
55                 free(taglist[i]);
56         }
57 }
58
59 int tag_add(const char *author, const char *tag, uint64_t record) {
60         char filename[512];
61         struct tag t;
62
63         memset(t.author, 0, 32);
64         strncpy(t.author, author, 32);
65         t.record = record;
66
67         switch(tag[0]) {
68         case '#':
69                 snprintf(filename, 512, "%s/%s", HASH_TAGS_PATH, tag + 1);
70                 break;
71         case '@':
72                 snprintf(filename, 512, "%s/%s", REF_TAGS_PATH, tag + 1);
73                 break;
74         default:
75                 fprintf(stderr, "Invalid tag type: %s\n", tag);
76                 return 0;
77         }
78
79         int tag_fd = open(filename, O_WRONLY | O_APPEND | O_CREAT, 0600);
80         flock(tag_fd, LOCK_EX);
81         if (tag_fd == -1) {
82                 perror("Could not open tag file");
83                 return 0;
84         }
85         int n = write(tag_fd, &t, sizeof(struct tag));
86         if (n < sizeof(struct tag)) {
87                 perror("Could not write new tag");
88                 return 0;
89         }
90         flock(tag_fd, LOCK_UN);
91         close(tag_fd);
92
93         return 1;
94 }
95
96 struct tag * tag_list(const char *tag, uint64_t offset, int *count, int direction) {
97         char filename[512];
98         struct stat st;
99         struct tag *taglist;
100         struct tag *retlist;
101         uint64_t n_tag_records;
102
103         if (!valid_name(tag + 1))
104                 return NULL;
105         
106         switch(tag[0]) {
107         case '#':
108                 snprintf(filename, 512, "%s/%s", HASH_TAGS_PATH, tag + 1);
109                 break;
110         case '@':
111                 snprintf(filename, 512, "%s/%s", REF_TAGS_PATH, tag + 1);
112                 break;
113         default:
114                 fprintf(stderr, "Invalid tag type: %s\n", tag);
115                 return NULL;
116         }
117
118         int tag_fd = open(filename, O_RDONLY, 0600);
119         if (tag_fd == -1) {
120                 perror("Could not open tag file");
121                 *count = 0;
122                 return NULL;
123         }
124
125         fstat(tag_fd, &st);
126         if (st.st_size == 0) {
127                 close(tag_fd);
128                 *count = 0;
129                 return NULL;
130         }
131         n_tag_records = st.st_size / sizeof(struct tag);
132         if (*count > n_tag_records)
133                 *count = n_tag_records;
134         if (offset > n_tag_records) {
135                 fprintf(stderr, "Cannot access tag record beyond end\n");
136                 return NULL;
137         }
138
139         taglist = (struct tag *) mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, tag_fd, 0);
140         if (taglist == MAP_FAILED) {
141                 perror("Could not mmap tag file");
142                 goto tag_list_map_failed;
143         }
144         retlist = (struct tag *) malloc(sizeof(struct tag) * *count);
145         if (retlist == NULL) {
146                 perror("Could not allocate memory for tag list");
147                 goto tag_list_malloc_failed;
148         }
149         switch(direction) {
150         case 1:
151                 memcpy(retlist, taglist + offset, sizeof(struct tag) * *count);
152                 break;
153         case -1:
154                 memcpy(retlist, taglist + (n_tag_records - *count - offset), sizeof(struct tag) * *count);
155                 break;
156         }
157
158         munmap(taglist, st.st_size);
159         close(tag_fd);
160         return retlist;
161
162 tag_list_malloc_failed:
163         munmap(taglist, st.st_size);
164 tag_list_map_failed:
165         close(tag_fd);
166 tag_list_open_failed:
167         return NULL;
168 }
169
170 int tag_exists(const char *tag) {
171         char filename[512];
172
173         if (!valid_name(tag + 1))
174                 return 0;
175
176         if (!(tag[0] == '@' || tag[0] == '#')) {
177                 fprintf(stderr, "Invalid tag: %s\n", tag);
178                 return 0;
179         }
180
181         switch(tag[0]) {
182         case '#':
183                 snprintf(filename, 512, "%s/%s", HASH_TAGS_PATH, tag + 1);
184                 break;
185         case '@':
186                 snprintf(filename, 512, "%s/%s", REF_TAGS_PATH, tag + 1);
187                 break;
188         default:
189                 fprintf(stderr, "Invalid tag type: %s\n", tag);
190                 return 0;
191         }
192         if (access(filename, F_OK) == -1)
193                 return 0;
194         else
195                 return 1;
196 }