|
|
tical.c - ics2txt - convert icalendar .ics file to plain text |
|
|
 |
git clone git://bitreich.org/ics2txt git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/ics2txt (git://bitreich.org) |
|
|
 |
Log |
|
|
 |
Files |
|
|
 |
Refs |
|
|
 |
Tags |
|
|
 |
README |
|
|
|
--- |
|
|
|
tical.c (5753B) |
|
|
|
--- |
|
|
|
1 #include "ical.h" |
|
|
|
2 |
|
|
|
3 #include <assert.h> |
|
|
|
4 #include <errno.h> |
|
|
|
5 #include <stdio.h> |
|
|
|
6 #include <stdlib.h> |
|
|
|
7 #include <string.h> |
|
|
|
8 #include <strings.h> /* strcase* */ |
|
|
|
9 |
|
|
|
10 #include "util.h" |
|
|
|
11 |
|
|
|
12 enum ical_err ical_errno; |
|
|
|
13 |
|
|
|
14 int |
|
|
|
15 ical_getline(char **line, char **ln, size_t *sz, FILE *fp) |
|
|
|
16 { |
|
|
|
17 int c; |
|
|
|
18 void *v; |
|
|
|
19 |
|
|
|
20 if ((v = realloc(*line, 1)) == NULL) |
|
|
|
21 return -ICAL_ERR_SYSTEM; |
|
|
|
22 *line = v; |
|
|
|
23 (*line)[0] = '\0'; |
|
|
|
24 |
|
|
|
25 do { top: |
|
|
|
26 if (getline(ln, sz, fp) <= 0) |
|
|
|
27 return ferror(fp) ? -ICAL_ERR_SYSTEM : 0; |
|
|
|
28 strchomp(*ln); |
|
|
|
29 if (**ln == '\0') |
|
|
|
30 goto top; |
|
|
|
31 if (strappend(line, *ln) < 0) |
|
|
|
32 return -ICAL_ERR_SYSTEM; |
|
|
|
33 if ((c = fgetc(fp)) == EOF) |
|
|
|
34 return ferror(fp) ? -ICAL_ERR_SYSTEM : 1; |
|
|
|
35 } while (c == ' '); |
|
|
|
36 |
|
|
|
37 ungetc(c, fp); |
|
|
|
38 assert(!ferror(fp)); |
|
|
|
39 return 1; |
|
|
|
40 } |
|
|
|
41 |
|
|
|
42 char * |
|
|
|
43 ical_strerror(int i) |
|
|
|
44 { |
|
|
|
45 enum ical_err err = (i > 0) ? i : -i; |
|
|
|
46 |
|
|
|
47 switch (err) { |
|
|
|
48 case ICAL_ERR_OK: |
|
|
|
49 return "no error"; |
|
|
|
50 case ICAL_ERR_SYSTEM: |
|
|
|
51 return "system error"; |
|
|
|
52 case ICAL_ERR_END_MISMATCH: |
|
|
|
53 return "END: does not match its corresponding BEGIN:"; |
|
|
|
54 case ICAL_ERR_MISSING_BEGIN: |
|
|
|
55 return "unexpected content line before any BEGIN:"; |
|
|
|
56 case ICAL_ERR_MISSING_COLUMN: |
|
|
|
57 return "missing ':' character from line"; |
|
|
|
58 case ICAL_ERR_MISSING_SEMICOLUMN: |
|
|
|
59 return "missing ';' character before ':'"; |
|
|
|
60 case ICAL_ERR_MISSING_EQUAL: |
|
|
|
61 return "missing '=' character in parameter before ':'"; |
|
|
|
62 case ICAL_ERR_MIN_NESTED: |
|
|
|
63 return "too many END: for the number of BEGIN:"; |
|
|
|
64 case ICAL_ERR_MAX_NESTED: |
|
|
|
65 return "maximum nesting level reached"; |
|
|
|
66 case ICAL_ERR_LENGTH: |
|
|
|
67 assert(!"used internally, should not happen"); |
|
|
|
68 } |
|
|
|
69 assert(!"unknown error code"); |
|
|
|
70 return "not a valid ical error code"; |
|
|
|
71 } |
|
|
|
72 |
|
|
|
73 struct ical_value * |
|
|
|
74 ical_new_value(char const *line) |
|
|
|
75 { |
|
|
|
76 struct ical_value *new; |
|
|
|
77 size_t len; |
|
|
|
78 |
|
|
|
79 len = strlen(line); |
|
|
|
80 if ((new = calloc(1, sizeof *new + len + 1)) == NULL) |
|
|
|
81 return NULL; |
|
|
|
82 memcpy(new->buf, line, len + 1); |
|
|
|
83 return new; |
|
|
|
84 } |
|
|
|
85 |
|
|
|
86 void |
|
|
|
87 ical_free_value(struct ical_value *value) |
|
|
|
88 { |
|
|
|
89 map_free(&value->param, NULL); |
|
|
|
90 free(value); |
|
|
|
91 } |
|
|
|
92 |
|
|
|
93 int |
|
|
|
94 ical_parse_value(struct ical_value *value) |
|
|
|
95 { |
|
|
|
96 char *column, *equal, *param, *cp; |
|
|
|
97 int e = errno; |
|
|
|
98 |
|
|
|
99 value->name = value->buf; |
|
|
|
100 |
|
|
|
101 if ((column = strchr(value->buf, ':')) == NULL) |
|
|
|
102 return -ICAL_ERR_MISSING_COLUMN; |
|
|
|
103 *column = '\0'; |
|
|
|
104 value->value = column + 1; |
|
|
|
105 |
|
|
|
106 if ((cp = strchr(value->buf, ';')) != NULL) |
|
|
|
107 *cp++ = '\0'; |
|
|
|
108 while ((param = strsep(&cp, ";")) != NULL) { |
|
|
|
109 if ((equal = strchr(param, '=')) == NULL) |
|
|
|
110 return -ICAL_ERR_MISSING_EQUAL; |
|
|
|
111 *equal = '\0'; |
|
|
|
112 if (map_set(&value->param, param, equal + 1) < 0) |
|
|
|
113 return -ICAL_ERR_SYSTEM; |
|
|
|
114 } |
|
|
|
115 |
|
|
|
116 assert(errno == e); |
|
|
|
117 return 0; |
|
|
|
118 } |
|
|
|
119 |
|
|
|
120 struct ical_vnode * |
|
|
|
121 ical_new_vnode(char const *name) |
|
|
|
122 { |
|
|
|
123 struct ical_vnode *new; |
|
|
|
124 size_t sz; |
|
|
|
125 |
|
|
|
126 if ((new = calloc(1, sizeof *new)) == NULL) |
|
|
|
127 return NULL; |
|
|
|
128 sz = sizeof new->name; |
|
|
|
129 if (strlcpy(new->name, name, sz) >= sz) { |
|
|
|
130 errno = EMSGSIZE; |
|
|
|
131 goto err; |
|
|
|
132 } |
|
|
|
133 return new; |
|
|
|
134 err: |
|
|
|
135 ical_free_vnode(new); |
|
|
|
136 return NULL; |
|
|
|
137 } |
|
|
|
138 |
|
|
|
139 static void |
|
|
|
140 ical_free_value_void(void *v) |
|
|
|
141 { |
|
|
|
142 ical_free_value(v); |
|
|
|
143 } |
|
|
|
144 |
|
|
|
145 static void |
|
|
|
146 ical_free_vnode_void(void *v) |
|
|
|
147 { |
|
|
|
148 ical_free_vnode(v); |
|
|
|
149 } |
|
|
|
150 |
|
|
|
151 void |
|
|
|
152 ical_free_vnode(struct ical_vnode *node) |
|
|
|
153 { |
|
|
|
154 if (node == NULL) |
|
|
|
155 return; |
|
|
|
156 map_free(&node->values, ical_free_value_void); |
|
|
|
157 map_free(&node->childs, ical_free_vnode_void); |
|
|
|
158 ical_free_vnode(node->next); |
|
|
|
159 free(node); |
|
|
|
160 } |
|
|
|
161 |
|
|
|
162 int |
|
|
|
163 ical_push_nested(struct ical_vcalendar *vcal, struct ical_vnode *new) |
|
|
|
164 { |
|
|
|
165 struct ical_vnode **node; |
|
|
|
166 |
|
|
|
167 node = vcal->nested; |
|
|
|
168 for (int i = 0; *node != NULL; node++, i++) { |
|
|
|
169 if (i >= ICAL_NESTED_MAX) |
|
|
|
170 return -ICAL_ERR_MAX_NESTED; |
|
|
|
171 } |
|
|
|
172 node[0] = new; |
|
|
|
173 node[1] = NULL; |
|
|
|
174 return 0; |
|
|
|
175 } |
|
|
|
176 |
|
|
|
177 struct ical_vnode * |
|
|
|
178 ical_pop_nested(struct ical_vcalendar *vcal) |
|
|
|
179 { |
|
|
|
180 struct ical_vnode **node, **prev = vcal->nested, *old; |
|
|
|
181 |
|
|
|
182 for (prev = node = vcal->nested; *node != NULL; node++) { |
|
|
|
183 vcal->current = *prev; |
|
|
|
184 prev = node; |
|
|
|
185 old = *node; |
|
|
|
186 } |
|
|
|
187 *prev = NULL; |
|
|
|
188 if (vcal->nested[0] == NULL) |
|
|
|
189 vcal->current = NULL; |
|
|
|
190 return old; |
|
|
|
191 } |
|
|
|
192 |
|
|
|
193 int |
|
|
|
194 ical_begin_vnode(struct ical_vcalendar *vcal, char const *name) |
|
|
|
195 { |
|
|
|
196 struct ical_vnode *new; |
|
|
|
197 int e; |
|
|
|
198 |
|
|
|
199 if ((new = ical_new_vnode(name)) == NULL) |
|
|
|
200 return -ICAL_ERR_SYSTEM; |
|
|
|
201 if ((e = ical_push_nested(vcal, new)) < 0) |
|
|
|
202 goto err; |
|
|
|
203 if (vcal->root == NULL) { |
|
|
|
204 vcal->root = new; |
|
|
|
205 } else { |
|
|
|
206 new->next = map_get(&vcal->current->childs, new->name); |
|
|
|
207 if (map_set(&vcal->current->childs, new->name, new) < 0) { |
|
|
|
208 e = -ICAL_ERR_SYSTEM; |
|
|
|
209 goto err; |
|
|
|
210 } |
|
|
|
211 } |
|
|
|
212 vcal->current = new; |
|
|
|
213 return 0; |
|
|
|
214 err: |
|
|
|
215 ical_free_vnode(new); |
|
|
|
216 return e; |
|
|
|
217 } |
|
|
|
218 |
|
|
|
219 int |
|
|
|
220 ical_end_vnode(struct ical_vcalendar *vcal, char const *name) |
|
|
|
221 { |
|
|
|
222 struct ical_vnode *old; |
|
|
|
223 |
|
|
|
224 if ((old = ical_pop_nested(vcal)) == NULL) |
|
|
|
225 return -ICAL_ERR_MIN_NESTED; |
|
|
|
226 if (strcasecmp(name, old->name) != 0) |
|
|
|
227 return -ICAL_ERR_END_MISMATCH; |
|
|
|
228 return 0; |
|
|
|
229 } |
|
|
|
230 |
|
|
|
231 int |
|
|
|
232 ical_push_value(struct ical_vcalendar *vcal, struct ical_value *new) |
|
|
|
233 { |
|
|
|
234 if (strcasecmp(new->name, "BEGIN") == 0) { |
|
|
|
235 int e = ical_begin_vnode(vcal, new->value); |
|
|
|
236 ical_free_value(new); |
|
|
|
237 return e; |
|
|
|
238 } |
|
|
|
239 if (strcasecmp(new->name, "END") == 0) { |
|
|
|
240 int e = ical_end_vnode(vcal, new->value); |
|
|
|
241 ical_free_value(new); |
|
|
|
242 return e; |
|
|
|
243 } |
|
|
|
244 |
|
|
|
245 if (vcal->current == NULL) |
|
|
|
246 return -ICAL_ERR_MISSING_BEGIN; |
|
|
|
247 |
|
|
|
248 new->next = map_get(&vcal->current->values, new->name); |
|
|
|
249 if (map_set(&vcal->current->values, new->name, new) < 0) |
|
|
|
250 return -ICAL_ERR_SYSTEM; |
|
|
|
251 |
|
|
|
252 return 0; |
|
|
|
253 } |
|
|
|
254 |
|
|
|
255 int |
|
|
|
256 ical_read_vcalendar(struct ical_vcalendar *vcal, FILE *fp) |
|
|
|
257 { |
|
|
|
258 char *line = NULL, *ln = NULL; |
|
|
|
259 size_t sz = 0; |
|
|
|
260 ssize_t r; |
|
|
|
261 int e; |
|
|
|
262 |
|
|
|
263 memset(vcal, 0, sizeof *vcal); |
|
|
|
264 |
|
|
|
265 while ((r = ical_getline(&line, &ln, &sz, fp)) > 0) { |
|
|
|
266 struct ical_value *new; |
|
|
|
267 |
|
|
|
268 if ((new = ical_new_value(line)) == NULL) { |
|
|
|
269 e = -ICAL_ERR_SYSTEM; |
|
|
|
270 goto err; |
|
|
|
271 } |
|
|
|
272 if ((e = ical_parse_value(new)) < 0) |
|
|
|
273 goto err; |
|
|
|
274 if ((e = ical_push_value(vcal, new)) < 0) |
|
|
|
275 goto err; |
|
|
|
276 } |
|
|
|
277 e = (r == 0) ? 0 : -ICAL_ERR_SYSTEM; |
|
|
|
278 err: |
|
|
|
279 free(line); |
|
|
|
280 free(ln); |
|
|
|
281 return e; |
|
|
|
282 } |
|
|
|
283 |
|
|
|
284 void |
|
|
|
285 ical_free_vcalendar(struct ical_vcalendar *vcal) |
|
|
|
286 { |
|
|
|
287 ical_free_vnode(vcal->root); |
|
|
|
288 } |
|