comparison src/lspst.c @ 43:f6db1f060a95

start on outlook 2003 64 bit format
author carl
date Sun, 06 Jan 2008 14:47:06 -0800
parents b88ceb81dba2
children b2a7f2e0926a
comparison
equal deleted inserted replaced
42:7a97f50c39c5 43:f6db1f060a95
4 * Author: Joe Nahmias <joe@nahmias.net> 4 * Author: Joe Nahmias <joe@nahmias.net>
5 * Based on readpst.c by by David Smith <dave.s@earthcorp.com> 5 * Based on readpst.c by by David Smith <dave.s@earthcorp.com>
6 * 6 *
7 */ 7 */
8 8
9 // header file includes {{{1 9 // header file includes
10 #include <stdio.h> 10 #include <stdio.h>
11 #include <stdlib.h> 11 #include <stdlib.h>
12 #include <time.h> 12 #include <time.h>
13 #include <string.h> 13 #include <string.h>
14 #include <ctype.h> 14 #include <ctype.h>
15 #include <errno.h> 15 #include <errno.h>
16 16
17 #include "libpst.h" 17 #include "libpst.h"
18 #include "define.h" 18 #include "define.h"
19 #include "timeconv.h" 19 #include "timeconv.h"
20 // }}}1 20
21 // struct file_ll {{{1
22 struct file_ll { 21 struct file_ll {
23 char *name; 22 char *dname;
24 char *dname; 23 int32_t stored_count;
25 FILE * output; 24 int32_t email_count;
26 int32_t stored_count; 25 int32_t skip_count;
27 int32_t email_count; 26 int32_t type;
28 int32_t skip_count;
29 int32_t type;
30 struct file_ll *next;
31 }; 27 };
32 // }}}1 28
33 // Function Declarations {{{1 29
34 void canonicalize_filename(char *fname); 30 void canonicalize_filename(char *fname);
35 int chr_count(char *str, char x);
36 void debug_print(char *fmt, ...); 31 void debug_print(char *fmt, ...);
37 char *rfc2426_escape(char *str); 32
38 char *rfc2445_datetime_format(FILETIME *ft); 33 // global settings
39 // }}}1 34 pst_file pstfile;
40 #ifndef LSPST_DEBUG_MAIN 35
41 #define LSPST_DEBUG_MAIN(x) debug_print x; 36
42 #endif 37 void create_enter_dir(struct file_ll* f, pst_item *item)
43 // int main(int argc, char** argv) {{{1 38 {
39 f->email_count = 0;
40 f->skip_count = 0;
41 f->type = item->type;
42 f->stored_count = (item->folder) ? item->folder->email_count : 0;
43 f->dname = (char*) xmalloc(strlen(item->file_as)+1);
44 strcpy(f->dname, item->file_as);
45 }
46
47
48 void close_enter_dir(struct file_ll *f)
49 {
50 free(f->dname);
51 }
52
53
54 void process(pst_item *outeritem, pst_desc_ll *d_ptr)
55 {
56 struct file_ll ff;
57 pst_item *item = NULL;
58
59 DEBUG_ENT("process");
60 memset(&ff, 0, sizeof(ff));
61 create_enter_dir(&ff, outeritem);
62
63 while (d_ptr) {
64 DEBUG_MAIN(("main: New item record, d_ptr = %p.\n", d_ptr));
65 if (!d_ptr->desc) {
66 DEBUG_WARN(("main: ERROR ?? item's desc record is NULL\n"));
67 ff.skip_count++;
68 }
69 else {
70 DEBUG_MAIN(("main: Desc Email ID %x [d_ptr->id = %x]\n", d_ptr->desc->id, d_ptr->id));
71
72 item = _pst_parse_item(&pstfile, d_ptr);
73 DEBUG_MAIN(("main: About to process item @ %p.\n", item));
74 if (item) {
75 if (item->message_store) {
76 // there should only be one message_store, and we have already done it
77 DIE(("main: A second message_store has been found. Sorry, this must be an error.\n"));
78 }
79
80 if (item->folder && d_ptr->child) {
81 // if this is a folder, we want to recurse into it
82 printf("Folder \"%s\"\n", item->file_as);
83 process(item, d_ptr->child);
84
85 } else if (item->contact && (item->type == PST_TYPE_CONTACT)) {
86 // Process Contact item
87 if (ff.type != PST_TYPE_CONTACT) {
88 DEBUG_MAIN(("main: I have a contact, but the folder isn't a contacts folder. Processing anyway\n"));
89 }
90 printf("Contact");
91 if (item->contact->fullname != NULL)
92 printf("\t%s", pst_rfc2426_escape(item->contact->fullname));
93 printf("\n");
94
95 } else if (item->email && (item->type == PST_TYPE_NOTE || item->type == PST_TYPE_REPORT)) {
96 // Process Email item
97 if ((ff.type != PST_TYPE_NOTE) && (ff.type != PST_TYPE_REPORT)) {
98 DEBUG_MAIN(("main: I have an email, but the folder isn't an email folder. Processing anyway\n"));
99 }
100 printf("Email");
101 if (item->email->outlook_sender_name != NULL)
102 printf("\tFrom: %s", item->email->outlook_sender_name);
103 if (item->email->subject->subj != NULL)
104 printf("\tSubject: %s", item->email->subject->subj);
105 printf("\n");
106
107 } else if (item->journal && (item->type == PST_TYPE_JOURNAL)) {
108 // Process Journal item
109 if (ff.type != PST_TYPE_JOURNAL) {
110 DEBUG_MAIN(("main: I have a journal entry, but folder isn't specified as a journal type. Processing...\n"));
111 }
112 printf("Journal\t%s\n", pst_rfc2426_escape(item->email->subject->subj));
113
114 } else if (item->appointment && (item->type == PST_TYPE_APPOINTMENT)) {
115 // Process Calendar Appointment item
116 DEBUG_MAIN(("main: Processing Appointment Entry\n"));
117 if (ff.type != PST_TYPE_APPOINTMENT) {
118 DEBUG_MAIN(("main: I have an appointment, but folder isn't specified as an appointment type. Processing...\n"));
119 }
120 printf("Appointment");
121 if (item->email != NULL && item->email->subject != NULL)
122 printf("\tSUMMARY: %s", pst_rfc2426_escape(item->email->subject->subj));
123 if (item->appointment != NULL) {
124 if (item->appointment->start != NULL)
125 printf("\tSTART: %s", pst_rfc2445_datetime_format(item->appointment->start));
126 if (item->appointment->end != NULL)
127 printf("\tEND: %s", pst_rfc2445_datetime_format(item->appointment->end));
128 printf("\tALL DAY: %s", (item->appointment->all_day==1 ? "Yes" : "No"));
129 }
130 printf("\n");
131
132 } else {
133 ff.skip_count++;
134 DEBUG_MAIN(("main: Unknown item type. %i. Ascii1=\"%s\"\n",
135 item->type, item->ascii_type));
136 }
137 _pst_freeItem(item);
138 } else {
139 ff.skip_count++;
140 DEBUG_MAIN(("main: A NULL item was seen\n"));
141 }
142 d_ptr = d_ptr->next;
143 }
144 }
145 close_enter_dir(&ff);
146 }
147
148
44 int main(int argc, char** argv) { 149 int main(int argc, char** argv) {
45 150 pst_item *item = NULL;
46 // declarations {{{2 151 pst_desc_ll *d_ptr;
47 pst_item *item = NULL; 152 char *temp = NULL; //temporary char pointer
48 pst_file pstfile; 153 char *d_log = NULL;
49 pst_desc_ll *d_ptr; 154 struct file_ll *f = NULL, *head = NULL;
50 char *temp = NULL; //temporary char pointer 155
51 int skip_child = 0; 156 if (argc <= 1) DIE(("Missing PST filename.\n"));
52 struct file_ll *f = NULL, *head = NULL; 157
53 // }}}2 158 DEBUG_INIT(d_log);
54 159 DEBUG_REGISTER_CLOSE();
55 if (argc <= 1) 160 DEBUG_ENT("main");
56 DIE(("Missing PST filename.\n")); 161
57 162 // Open PST file
58 // Open PST file 163 if (pst_open(&pstfile, argv[1], "r")) DIE(("Error opening File\n"));
59 if ( pst_open(&pstfile, argv[1], "r") ) 164
60 DIE(("Error opening File\n")); 165 // Load PST index
61 // Load PST index 166 if (pst_load_index(&pstfile)) DIE(("Index Error\n"));
62 if ( pst_load_index(&pstfile) ) 167
63 DIE(("Index Error\n")); 168 pst_load_extended_attributes(&pstfile);
64 pst_load_extended_attributes(&pstfile); 169
65 170 d_ptr = pstfile.d_head; // first record is main record
66 d_ptr = pstfile.d_head; // first record is main record 171 item = _pst_parse_item(&pstfile, d_ptr);
67 if ((item = _pst_parse_item(&pstfile, d_ptr)) == NULL || item->message_store == NULL) { 172 if (!item || !item->message_store) {
68 DIE(("main: Could not get root record\n")); 173 DEBUG_RET();
69 } 174 DIE(("main: Could not get root record\n"));
70 175 }
71 // default the file_as to the same as the main filename if it doesn't exist 176
72 if (item->file_as == NULL) { 177 // default the file_as to the same as the main filename if it doesn't exist
73 if ((temp = strrchr(argv[1], '/')) == NULL) 178 if (!item->file_as) {
74 if ((temp = strrchr(argv[1], '\\')) == NULL) 179 if (!(temp = strrchr(argv[1], '/')))
75 temp = argv[1]; 180 if (!(temp = strrchr(argv[1], '\\')))
76 else 181 temp = argv[1];
77 temp++; // get past the "\\" 182 else
78 else 183 temp++; // get past the "\\"
79 temp++; // get past the "/" 184 else
80 item->file_as = (char*)xmalloc(strlen(temp)+1); 185 temp++; // get past the "/"
81 strcpy(item->file_as, temp); 186 item->file_as = (char*)xmalloc(strlen(temp)+1);
82 } 187 strcpy(item->file_as, temp);
83 fprintf(stderr, "item->file_as = '%s'.\n", item->file_as); 188 }
84 189 fprintf(stderr, "item->file_as = '%s'.\n", item->file_as);
85 // setup head file_ll 190
86 head = (struct file_ll*) malloc(sizeof(struct file_ll)); 191 d_ptr = pst_getTopOfFolders(&pstfile, item);
87 memset(head, 0, sizeof(struct file_ll)); 192 if (!d_ptr) DIE(("Top of folders record not found. Cannot continue\n"));
88 head->email_count = 0; 193 DEBUG_MAIN(("d_ptr(TOF) = %p.\n", d_ptr));
89 head->skip_count = 0; 194
90 head->next = NULL; 195 process(item, d_ptr->child); // do the childred of TOPF
91 head->name = "mbox"; 196 _pst_freeItem(item);
92 head->dname = (char*) malloc(strlen(item->file_as)+1); 197 pst_close(&pstfile);
93 strcpy(head->dname, item->file_as); 198
94 head->type = item->type; 199 DEBUG_RET();
95 LSPST_DEBUG_MAIN(("head @ %p: name = '%s', dname = '%s', next = %p.\n", head, head->name, head->dname, head->next)); 200 return 0;
96 201 }
97 if ((d_ptr = pst_getTopOfFolders(&pstfile, item)) == NULL) { 202
98 DIE(("Top of folders record not found. Cannot continue\n")); 203
99 } 204 // This function will make sure that a filename is in cannonical form. That
100 LSPST_DEBUG_MAIN(("d_ptr(TOF) = %p.\n", d_ptr));
101
102 if (item){
103 _pst_freeItem(item);
104 item = NULL;
105 }
106
107 d_ptr = d_ptr->child; // do the children of TOPF
108 LSPST_DEBUG_MAIN(("d_ptr(TOF->child) = %p.\n", d_ptr));
109
110 LSPST_DEBUG_MAIN(("main: About to do email stuff\n"));
111 while (d_ptr != NULL) {
112 // Process d_ptr {{{2
113 LSPST_DEBUG_MAIN(("main: New item record, d_ptr = %p.\n", d_ptr));
114 if (d_ptr->desc == NULL) {
115 DEBUG_WARN(("main: ERROR ?? item's desc record is NULL\n"));
116 f->skip_count++;
117 goto check_parent;
118 }
119 LSPST_DEBUG_MAIN(("main: Desc Email ID %x [d_ptr->id = %x]\n", d_ptr->desc->id, d_ptr->id));
120
121 item = _pst_parse_item(&pstfile, d_ptr);
122 LSPST_DEBUG_MAIN(("main: About to process item @ %p.\n", item));
123 if (item != NULL) {
124
125 // there should only be one message_store, and we have already
126 // done it
127 if (item->message_store != NULL) {
128 DIE(("ERROR(main): A second message_store has been found.\n"));
129 }
130
131 if (item->folder != NULL) {
132 // Process Folder item {{{3
133 // if this is a folder, we want to recurse into it
134 printf("Folder");
135 if (item->file_as != NULL)
136 printf("\t%s/", item->file_as);
137 printf("\n");
138
139 LSPST_DEBUG_MAIN(("main: I think I may try to go into folder \"%s\"\n", item->file_as));
140 f = (struct file_ll*) malloc(sizeof(struct file_ll));
141 memset(f, 0, sizeof(struct file_ll));
142 f->next = head;
143 f->email_count = 0;
144 f->type = item->type;
145 f->stored_count = item->folder->email_count;
146 head = f;
147 f->name = "mbox";
148 f->dname = (char*) xmalloc(strlen(item->file_as)+1);
149 strcpy(f->dname, item->file_as);
150
151 LSPST_DEBUG_MAIN(("main: f->name = %s\nitem->folder_name = %s\n", f->name, item->file_as));
152 canonicalize_filename(f->name);
153
154 if (d_ptr->child != NULL) {
155 d_ptr = d_ptr->child;
156 skip_child = 1;
157 } else {
158 LSPST_DEBUG_MAIN(("main: Folder has NO children. Creating directory, and closing again\n"));
159 // printf("\tNo items to process in folder \"%s\", should have been %i\n", f->dname, f->stored_count);
160 head = f->next;
161 if (f->output != NULL)
162 fclose(f->output);
163 free(f->dname);
164 free(f->name);
165 free(f);
166
167 f = head;
168 }
169 _pst_freeItem(item);
170 item = NULL; // just for the odd situations!
171 goto check_parent;
172 // }}}3
173 } else if (item->contact != NULL) {
174 // Process Contact item {{{3
175 if (f->type != PST_TYPE_CONTACT) {
176 LSPST_DEBUG_MAIN(("main: I have a contact, but the folder isn't a contacts folder. "
177 "Will process anyway\n"));
178 }
179 if (item->type != PST_TYPE_CONTACT) {
180 LSPST_DEBUG_MAIN(("main: I have an item that has contact info, but doesn't say that"
181 " it is a contact. Type is \"%s\"\n", item->ascii_type));
182 LSPST_DEBUG_MAIN(("main: Processing anyway\n"));
183 }
184
185 printf("Contact");
186 if (item->contact->fullname != NULL)
187 printf("\t%s", rfc2426_escape(item->contact->fullname));
188 printf("\n");
189 // }}}3
190 } else if (item->email != NULL &&
191 (item->type == PST_TYPE_NOTE || item->type == PST_TYPE_REPORT)) {
192 // Process Email item {{{3
193 printf("Email");
194 if (item->email->outlook_sender_name != NULL)
195 printf("\tFrom: %s", item->email->outlook_sender_name);
196 if (item->email->subject->subj != NULL)
197 printf("\tSubject: %s", item->email->subject->subj);
198 printf("\n");
199 // }}}3
200 } else if (item->type == PST_TYPE_JOURNAL) {
201 // Process Journal item {{{3
202 if (f->type != PST_TYPE_JOURNAL) {
203 LSPST_DEBUG_MAIN(("main: I have a journal entry, but folder isn't specified as a journal type. Processing...\n"));
204 }
205
206 printf("Journal\t%s\n", rfc2426_escape(item->email->subject->subj));
207 // }}}3
208 } else if (item->type == PST_TYPE_APPOINTMENT) {
209 // Process Calendar Appointment item {{{3
210 // deal with Calendar appointments
211
212 LSPST_DEBUG_MAIN(("main: Processing Appointment Entry\n"));
213 if (f->type != PST_TYPE_APPOINTMENT) {
214 LSPST_DEBUG_MAIN(("main: I have an appointment, but folder isn't specified as an appointment type. Processing...\n"));
215 }
216
217 printf("Appointment");
218 if (item->email != NULL && item->email->subject != NULL)
219 printf("\tSUMMARY: %s", rfc2426_escape(item->email->subject->subj));
220 if (item->appointment != NULL) {
221 if (item->appointment->start != NULL)
222 printf("\tSTART: %s", rfc2445_datetime_format(item->appointment->start));
223 if (item->appointment->end != NULL)
224 printf("\tEND: %s", rfc2445_datetime_format(item->appointment->end));
225 printf("\tALL DAY: %s", (item->appointment->all_day==1 ? "Yes" : "No"));
226 }
227 printf("\n");
228
229 // }}}3
230 } else {
231 f->skip_count++;
232 LSPST_DEBUG_MAIN(("main: Unknown item type. %i. Ascii1=\"%s\"\n", \
233 item->type, item->ascii_type));
234 }
235 } else {
236 f->skip_count++;
237 LSPST_DEBUG_MAIN(("main: A NULL item was seen\n"));
238 }
239
240 check_parent:
241 // _pst_freeItem(item);
242 while (!skip_child && d_ptr->next == NULL && d_ptr->parent != NULL) {
243 LSPST_DEBUG_MAIN(("main: Going to Parent\n"));
244 head = f->next;
245 if (f->output != NULL)
246 fclose(f->output);
247 LSPST_DEBUG_MAIN(("main: Email Count for folder %s is %i\n", f->dname, f->email_count));
248 /*
249 printf("\t\"%s\" - %i items done, skipped %i, should have been %i\n", \
250 f->dname, f->email_count, f->skip_count, f->stored_count);
251 */
252
253 free(f->name);
254 free(f->dname);
255 free(f);
256 f = head;
257 if (head == NULL) { //we can't go higher. Must be at start?
258 LSPST_DEBUG_MAIN(("main: We are now trying to go above the highest level. We must be finished\n"));
259 break; //from main while loop
260 }
261 d_ptr = d_ptr->parent;
262 skip_child = 0;
263 }
264
265 if (item != NULL) {
266 LSPST_DEBUG_MAIN(("main: Freeing memory used by item\n"));
267 _pst_freeItem(item);
268 item = NULL;
269 }
270
271 if (!skip_child)
272 d_ptr = d_ptr->next;
273 else
274 skip_child = 0;
275
276 if (d_ptr == NULL) { LSPST_DEBUG_MAIN(("main: d_ptr is now NULL\n")); }
277
278 // }}}2
279 } // end while(d_ptr != NULL)
280 LSPST_DEBUG_MAIN(("main: Finished.\n"));
281
282 // Cleanup {{{2
283 pst_close(&pstfile);
284 while (f != NULL) {
285 if (f->output != NULL)
286 fclose(f->output);
287 free(f->name);
288 free(f->dname);
289
290 head = f->next;
291 free(f);
292 f = head;
293 }
294 DEBUG_RET();
295 // }}}2
296
297 return 0;
298 }
299 // }}}1
300 // void canonicalize_filename(char *fname) {{{1
301 // This function will make sure that a filename is in cannonical form. That
302 // is, it will replace any slashes, backslashes, or colons with underscores. 205 // is, it will replace any slashes, backslashes, or colons with underscores.
303 void canonicalize_filename(char *fname) { 206 void canonicalize_filename(char *fname) {
304 DEBUG_ENT("canonicalize_filename"); 207 DEBUG_ENT("canonicalize_filename");
305 if (fname == NULL) { 208 if (fname == NULL) {
306 DEBUG_RET(); 209 DEBUG_RET();
307 return; 210 return;
308 } 211 }
309 while ((fname = strpbrk(fname, "/\\:")) != NULL) 212 while ((fname = strpbrk(fname, "/\\:")) != NULL)
310 *fname = '_'; 213 *fname = '_';
311 DEBUG_RET(); 214 DEBUG_RET();
312 } 215 }
313 // }}}1 216
314 // int chr_count(char *str, char x) {{{1 217
315 int chr_count(char *str, char x) {
316 int r = 0;
317 if (str == NULL) return 0;
318 while (*str != '\0') {
319 if (*str == x)
320 r++;
321 str++;
322 }
323 return r;
324 }
325 // }}}1
326 // void debug_print(char *fmt, ...) {{{1
327 void debug_print(char *fmt, ...) { 218 void debug_print(char *fmt, ...) {
328 // shamlessly stolen from minprintf() in K&R pg. 156 219 // shamlessly stolen from minprintf() in K&R pg. 156
329 va_list ap; 220 va_list ap;
330 char *p, *sval; 221 char *p, *sval;
331 void *pval; 222 void *pval;
332 int ival; 223 int ival;
333 double dval; 224 double dval;
334 FILE *fp = stderr; 225 FILE *fp = stderr;
335 226
336 va_start(ap, fmt); 227 va_start(ap, fmt);
337 for(p = fmt; *p; p++) { 228 for(p = fmt; *p; p++) {
338 if (*p != '%') { 229 if (*p != '%') {
339 fputc(*p, fp); 230 fputc(*p, fp);
340 continue; 231 continue;
341 } 232 }
342 switch (tolower(*++p)) { 233 switch (tolower(*++p)) {
343 case 'd': case 'i': 234 case 'd': case 'i':
344 ival = va_arg(ap, int); 235 ival = va_arg(ap, int);
345 fprintf(fp, "%d", ival); 236 fprintf(fp, "%d", ival);
346 break; 237 break;
347 case 'f': 238 case 'f':
348 dval = va_arg(ap, double); 239 dval = va_arg(ap, double);
349 fprintf(fp, "%f", dval); 240 fprintf(fp, "%f", dval);
350 break; 241 break;
351 case 's': 242 case 's':
352 for (sval = va_arg(ap, char *); *sval; ++sval) 243 for (sval = va_arg(ap, char *); *sval; ++sval)
353 fputc(*sval, fp); 244 fputc(*sval, fp);
354 break; 245 break;
355 case 'p': 246 case 'p':
356 pval = va_arg(ap, void *); 247 pval = va_arg(ap, void *);
357 fprintf(fp, "%p", pval); 248 fprintf(fp, "%p", pval);
358 break; 249 break;
359 case 'x': 250 case 'x':
360 ival = va_arg(ap, int); 251 ival = va_arg(ap, int);
361 fprintf(fp, "%#010x", ival); 252 fprintf(fp, "%#010x", ival);
362 break; 253 break;
363 default: 254 default:
364 fputc(*p, fp); 255 fputc(*p, fp);
365 break; 256 break;
366 } 257 }
367 } 258 }
368 va_end(ap); 259 va_end(ap);
369 } 260 }
370 // }}}1 261
371 // char *rfc2426_escape(char *str) {{{1 262
372 char *rfc2426_escape(char *str) {
373 static char *buf = NULL;
374 char *a, *b;
375 int y, z;
376
377 DEBUG_ENT("rfc2426_escape");
378 if (str == NULL) {
379 DEBUG_RET();
380 return NULL;
381 }
382
383 // calculate space required to escape all the commas, semi-colons, backslashes, and newlines
384 y = chr_count(str, ',') + chr_count(str, '\\') + chr_count(str, ';') + chr_count(str, '\n');
385 // count how many carriage-returns we have to skip
386 z = chr_count(str, '\r');
387
388 if (y == 0 && z == 0) {
389 // there isn't any work required
390 DEBUG_RET();
391 return str;
392 }
393
394 buf = (char *) realloc( buf, strlen(str) + y - z + 1 );
395 for (a = str, b = buf; *a != '\0'; ++a, ++b)
396 switch (*a) {
397 case ',' : case '\\': case ';' : case '\n':
398 // insert backslash to escape
399 *(b++) = '\\';
400 *b = *a;
401 break;
402 case '\r':
403 // skip
404 break;
405 default:
406 *b = *a;
407 }
408 *b = '\0'; // NUL-terminate the string
409
410 DEBUG_RET();
411 return buf;
412 }
413 // }}}1
414 // char *rfc2445_datetime_format(FILETIME *ft) {{{1
415 char *rfc2445_datetime_format(FILETIME *ft) {
416 static char* buffer = NULL;
417 struct tm *stm = NULL;
418 DEBUG_ENT("rfc2445_datetime_format");
419 if (buffer == NULL)
420 buffer = malloc(30); // should be enough
421 stm = fileTimeToStructTM(ft);
422 if (strftime(buffer, 30, "%Y%m%dT%H%M%SZ", stm)==0) {
423 DEBUG_INFO(("Problem occured formatting date\n"));
424 }
425 DEBUG_RET();
426 return buffer;
427 }
428 // }}}1
429
430 // vim:sw=4 ts=4:
431 // vim600: set foldlevel=0 foldmethod=marker: