comparison src/pst2ldif.cpp @ 99:b7f456946c5b

add configure option --enable-dii=no to remove dependency on libgd. many fixes in pst2ldif by Robert Harris.
author Carl Byington <carl@five-ten-sg.com>
date Sun, 28 Sep 2008 17:08:52 -0700
parents 56fa05fd5271
children 39ba19372732
comparison
equal deleted inserted replaced
98:e12db0edd80a 99:b7f456946c5b
18 #include "libstrfunc.h" 18 #include "libstrfunc.h"
19 #include "libpst.h" 19 #include "libpst.h"
20 #include "common.h" 20 #include "common.h"
21 #include "timeconv.h" 21 #include "timeconv.h"
22 #include "lzfu.h" 22 #include "lzfu.h"
23 } 23 #include "stdarg.h"
24 24 #include "iconv.h"
25 int32_t usage(); 25 }
26 int32_t version(); 26
27 char *my_stristr(char *haystack, char *needle); 27 int32_t usage();
28 int32_t version();
28 char *check_filename(char *fname); 29 char *check_filename(char *fname);
29 const char *single(char *str); 30 char *dn_escape(const char *str);
30 const char *folded(char *str); 31 void print_ldif(const char *dn, const char *value);
31 void multi(const char *fmt, char *str); 32 void print_ldif_single(const char *dn, const char *value);
32 char *rfc2426_escape(char *str); 33 void print_ldif_multi(const char *dn, const char *value);
33 int32_t chr_count(char *str, char x); 34 void print_ldif_two(const char *dn, const char *value1, const char *value2);
35 void build_cn(char *cn, size_t len, int nvalues, char *value, ...);
34 36
35 char *prog_name; 37 char *prog_name;
36 pst_file pstfile; 38 pst_file pstfile;
37 char *ldap_base = NULL; // 'o=some.domain.tld, c=US' 39 char *ldap_base = NULL; // 'o=some.domain.tld, c=US'
38 char *ldap_class = NULL; // 'newPerson' 40 char *ldap_class = NULL; // 'newPerson'
39 char *ldap_org = NULL; // 'o=some.domain.tld', computed from ldap_base 41 char *ldap_org = NULL; // 'some.domain.tld', computed from ldap_base
42 iconv_t cd = 0; // Character set conversion descriptor
40 43
41 44
42 //////////////////////////////////////////////// 45 ////////////////////////////////////////////////
43 // define our ordering 46 // define our ordering
44 struct ltstr { 47 struct ltstr {
74 if (i != s.end()) return *i; 77 if (i != s.end()) return *i;
75 char *x = strdup(name); 78 char *x = strdup(name);
76 s.insert(x); 79 s.insert(x);
77 return x; 80 return x;
78 } 81 }
82
79 83
80 //////////////////////////////////////////////// 84 ////////////////////////////////////////////////
81 // register a global string 85 // register a global string
82 // 86 //
83 static const char* register_string(const char *name); 87 static const char* register_string(const char *name);
101 if (i == all_strings.end()) return register_string(n); 105 if (i == all_strings.end()) return register_string(n);
102 } 106 }
103 } 107 }
104 108
105 109
106 ////////////////////////////////////////////////
107 // remove leading and trailing blanks
108 //
109 static char *trim(char *name);
110 static char *trim(char *name) {
111 char *p;
112 while (*name == ' ') name++;
113 p = name + strlen(name) - 1;
114 while ((p >= name) && (*p == ' ')) *p-- = '\0';
115 return name;
116 }
117
118
119 static void process(pst_desc_ll *d_ptr); 110 static void process(pst_desc_ll *d_ptr);
120 static void process(pst_desc_ll *d_ptr) { 111 static void process(pst_desc_ll *d_ptr) {
121 pst_item *item = NULL; 112 pst_item *item = NULL;
122 while (d_ptr) { 113 while (d_ptr) {
123 if (d_ptr->desc) { 114 if (d_ptr->desc) {
130 process(d_ptr->child); 121 process(d_ptr->child);
131 122
132 } else if (item->contact && (item->type == PST_TYPE_CONTACT)) { 123 } else if (item->contact && (item->type == PST_TYPE_CONTACT)) {
133 // deal with a contact 124 // deal with a contact
134 char cn[1000]; 125 char cn[1000];
135 snprintf(cn, sizeof(cn), "%s %s %s %s", 126 build_cn(cn, sizeof(cn), 4,
136 single(item->contact->display_name_prefix), 127 item->contact->display_name_prefix,
137 single(item->contact->first_name), 128 item->contact->first_name,
138 single(item->contact->surname), 129 item->contact->surname,
139 single(item->contact->suffix)); 130 item->contact->suffix);
140 if (strcmp(cn, " ")) { 131 if (cn[0] != 0) {
141 // have a valid cn 132 // have a valid cn
142 const char *ucn = unique_string(folded(trim(cn))); 133 const char *ucn = unique_string(cn);
143 printf("dn: cn=%s, %s\n", ucn, ldap_base); 134 char dn[strlen(ucn) + strlen(ldap_base) + 6];
144 printf("cn: %s\n", ucn); 135
136 sprintf(dn, "cn=%s, %s", ucn, ldap_base);
137 print_ldif_single("dn", dn);
138 print_ldif_single("cn", ucn);
145 if (item->contact->first_name) { 139 if (item->contact->first_name) {
146 snprintf(cn, sizeof(cn), "%s %s", 140 print_ldif_two("givenName",
147 single(item->contact->display_name_prefix), 141 item->contact->display_name_prefix,
148 single(item->contact->first_name)); 142 item->contact->first_name);
149 printf("givenName: %s\n", trim(cn));
150 } 143 }
151 if (item->contact->surname) { 144 if (item->contact->surname) {
152 snprintf(cn, sizeof(cn), "%s %s", 145 print_ldif_two("sn",
153 single(item->contact->surname), 146 item->contact->surname,
154 single(item->contact->suffix)); 147 item->contact->suffix);
155 printf("sn: %s\n", trim(cn));
156 } 148 }
157 else if (item->contact->company_name) { 149 else if (item->contact->company_name) {
158 printf("sn: %s\n", single(item->contact->company_name)); 150 print_ldif_single("sn", item->contact->company_name);
159 } 151 }
160 else 152 else
161 printf("sn: %s\n", ucn); // use cn as sn if we cannot find something better 153 print_ldif_single("sn", ucn); // use cn as sn if we cannot find something better
162 154
163 if (item->contact->job_title) 155 if (item->contact->job_title)
164 printf("personalTitle: %s\n", single(item->contact->job_title)); 156 print_ldif_single("personalTitle", item->contact->job_title);
165 if (item->contact->company_name) 157 if (item->contact->company_name)
166 printf("company: %s\n", single(item->contact->company_name)); 158 print_ldif_single("company", item->contact->company_name);
167 if (item->contact->address1 && *item->contact->address1) 159 if (item->contact->address1 && *item->contact->address1)
168 printf("mail: %s\n", single(item->contact->address1)); 160 print_ldif_single("mail", item->contact->address1);
169 if (item->contact->address2 && *item->contact->address2) 161 if (item->contact->address2 && *item->contact->address2)
170 printf("mail: %s\n", single(item->contact->address2)); 162 print_ldif_single("mail", item->contact->address2);
171 if (item->contact->address3 && *item->contact->address3) 163 if (item->contact->address3 && *item->contact->address3)
172 printf("mail: %s\n", single(item->contact->address3)); 164 print_ldif_single("mail", item->contact->address3);
173 if (item->contact->address1a && *item->contact->address1a) 165 if (item->contact->address1a && *item->contact->address1a)
174 printf("mail: %s\n", single(item->contact->address1a)); 166 print_ldif_single("mail", item->contact->address1a);
175 if (item->contact->address2a && *item->contact->address2a) 167 if (item->contact->address2a && *item->contact->address2a)
176 printf("mail: %s\n", single(item->contact->address2a)); 168 print_ldif_single("mail", item->contact->address2a);
177 if (item->contact->address3a && *item->contact->address3a) 169 if (item->contact->address3a && *item->contact->address3a)
178 printf("mail: %s\n", single(item->contact->address3a)); 170 print_ldif_single("mail", item->contact->address3a);
179 if (item->contact->business_address) { 171 if (item->contact->business_address) {
180 if (item->contact->business_po_box) 172 if (item->contact->business_po_box)
181 printf("postalAddress: %s\n", single(item->contact->business_po_box)); 173 print_ldif_single("postalAddress", item->contact->business_po_box);
182 if (item->contact->business_street) 174 if (item->contact->business_street)
183 multi("postalAddress: %s\n", item->contact->business_street); 175 print_ldif_multi("postalAddress", item->contact->business_street);
184 if (item->contact->business_city) 176 if (item->contact->business_city)
185 printf("l: %s\n", single(item->contact->business_city)); 177 print_ldif_single("l", item->contact->business_city);
186 if (item->contact->business_state) 178 if (item->contact->business_state)
187 printf("st: %s\n", single(item->contact->business_state)); 179 print_ldif_single("st", item->contact->business_state);
188 if (item->contact->business_postal_code) 180 if (item->contact->business_postal_code)
189 printf("postalCode: %s\n", single(item->contact->business_postal_code)); 181 print_ldif_single("postalCode", item->contact->business_postal_code);
190 } 182 }
191 else if (item->contact->home_address) { 183 else if (item->contact->home_address) {
192 if (item->contact->home_po_box) 184 if (item->contact->home_po_box)
193 printf("postalAddress: %s\n", single(item->contact->home_po_box)); 185 print_ldif_single("postalAddress", item->contact->home_po_box);
194 if (item->contact->home_street) 186 if (item->contact->home_street)
195 multi("postalAddress: %s\n", item->contact->home_street); 187 print_ldif_multi("postalAddress", item->contact->home_street);
196 if (item->contact->home_city) 188 if (item->contact->home_city)
197 printf("l: %s\n", single(item->contact->home_city)); 189 print_ldif_single("l", item->contact->home_city);
198 if (item->contact->home_state) 190 if (item->contact->home_state)
199 printf("st: %s\n", single(item->contact->home_state)); 191 print_ldif_single("st", item->contact->home_state);
200 if (item->contact->home_postal_code) 192 if (item->contact->home_postal_code)
201 printf("postalCode: %s\n", single(item->contact->home_postal_code)); 193 print_ldif_single("postalCode", item->contact->home_postal_code);
202 } 194 }
203 else if (item->contact->other_address) { 195 else if (item->contact->other_address) {
204 if (item->contact->other_po_box) 196 if (item->contact->other_po_box)
205 printf("postalAddress: %s\n", single(item->contact->other_po_box)); 197 print_ldif_single("postalAddress", item->contact->other_po_box);
206 if (item->contact->other_street) 198 if (item->contact->other_street)
207 multi("postalAddress: %s\n", item->contact->other_street); 199 print_ldif_multi("postalAddress", item->contact->other_street);
208 if (item->contact->other_city) 200 if (item->contact->other_city)
209 printf("l: %s\n", single(item->contact->other_city)); 201 print_ldif_single("l", item->contact->other_city);
210 if (item->contact->other_state) 202 if (item->contact->other_state)
211 printf("st: %s\n", single(item->contact->other_state)); 203 print_ldif_single("st", item->contact->other_state);
212 if (item->contact->other_postal_code) 204 if (item->contact->other_postal_code)
213 printf("postalCode: %s\n", single(item->contact->other_postal_code)); 205 print_ldif_single("postalCode", item->contact->other_postal_code);
214 } 206 }
215 if (item->contact->business_fax) 207 if (item->contact->business_fax)
216 printf("facsimileTelephoneNumber: %s\n", single(item->contact->business_fax)); 208 print_ldif_single("facsimileTelephoneNumber", item->contact->business_fax);
217 else if (item->contact->home_fax) 209 else if (item->contact->home_fax)
218 printf("facsimileTelephoneNumber: %s\n", single(item->contact->home_fax)); 210 print_ldif_single("facsimileTelephoneNumber", item->contact->home_fax);
219 211
220 if (item->contact->business_phone) 212 if (item->contact->business_phone)
221 printf("telephoneNumber: %s\n", single(item->contact->business_phone)); 213 print_ldif_single("telephoneNumber", item->contact->business_phone);
222 if (item->contact->home_phone) 214 if (item->contact->home_phone)
223 printf("homePhone: %s\n", single(item->contact->home_phone)); 215 print_ldif_single("homePhone", item->contact->home_phone);
224 216
225 if (item->contact->car_phone) 217 if (item->contact->car_phone)
226 printf("mobile: %s\n", single(item->contact->car_phone)); 218 print_ldif_single("mobile", item->contact->car_phone);
227 else if (item->contact->mobile_phone) 219 else if (item->contact->mobile_phone)
228 printf("mobile: %s\n", single(item->contact->mobile_phone)); 220 print_ldif_single("mobile", item->contact->mobile_phone);
229 else if (item->contact->other_phone) 221 else if (item->contact->other_phone)
230 printf("mobile: %s\n", single(item->contact->other_phone)); 222 print_ldif_single("mobile", item->contact->other_phone);
231 223
232 224
233 if (item->comment) 225 if (item->comment)
234 printf("description: %s\n", single(item->comment)); 226 print_ldif_single("description", item->comment);
235 227
236 printf("objectClass: %s\n\n", ldap_class); 228 print_ldif("objectClass", ldap_class);
229 putchar('\n');
237 } 230 }
238 } 231 }
239 else { 232 else {
240 DEBUG_INFO(("item is not a contact\n")); 233 DEBUG_INFO(("item is not a contact\n"));
241 } 234 }
245 d_ptr = d_ptr->next; 238 d_ptr = d_ptr->next;
246 } 239 }
247 } 240 }
248 241
249 242
243 void print_ldif(const char *dn, const char *value)
244 {
245 printf("%s: %s\n", dn, value);
246 }
247
248
249 // Prints a Distinguished Name together with its value.
250 // If the value isn't a "SAFE STRING" (as defined in RFC2849),
251 // then it is output as a BASE-64 encoded value
252 void print_ldif_single(const char *dn, const char *value)
253 {
254 size_t len;
255 bool is_safe_string = true;
256 bool needs_code_conversion = false;
257 bool space_flag = false;
258
259 // Strip leading spaces
260 while (*value == ' ') value++;
261 len = strlen(value) + 1;
262 char buffer[len];
263 char *p = buffer;
264 // See if "value" is a "SAFE STRING"
265
266 // First check characters that are safe but not safe as initial characters
267 if (*value == ':' || *value == '<')
268 is_safe_string = false;
269 for (;;) {
270 char ch = *value++;
271
272 if (ch == 0 || ch == '\n')
273 break;
274 else if (ch == '\r')
275 continue;
276 else if (ch == ' ') {
277 space_flag = true;
278 continue;
279 }
280 else {
281 if ((ch & 0x80) == 0x80) {
282 needs_code_conversion = true;
283 is_safe_string = false;
284 }
285 if (space_flag) {
286 *p++ = ' ';
287 space_flag = false;
288 }
289 *p++ = ch;
290 }
291 }
292 *p = 0;
293 if (is_safe_string) {
294 printf("%s: %s\n", dn, buffer);
295 return;
296 }
297
298 if (needs_code_conversion && cd != 0) {
299 size_t inlen = p - buffer;
300 size_t utf8_len = 2 * inlen + 1;
301 char utf8_buffer[utf8_len];
302 char *utf8_p = utf8_buffer;
303
304 iconv(cd, NULL, NULL, NULL, NULL);
305 p = buffer;
306 int ret = iconv(cd, &p, &inlen, &utf8_p, &utf8_len);
307
308 if (ret >= 0) {
309 *utf8_p = 0;
310 p = base64_encode(utf8_buffer, utf8_p - utf8_buffer);
311 }
312 else
313 p = base64_encode(buffer, strlen(buffer));
314 }
315 else
316 p = base64_encode(buffer, strlen(buffer));
317 printf("%s:: %s\n", dn, p);
318 free(p);
319 }
320
321
322 void print_ldif_multi(const char *dn, const char *value)
323 {
324 const char *n;
325 while ((n = strchr(value, '\n'))) {
326 print_ldif_single(dn, value);
327 value = n + 1;
328 }
329 print_ldif_single(dn, value);
330 }
331
332
333 void print_ldif_two(const char *dn, const char *value1, const char *value2)
334 {
335 size_t len1, len2;
336 if (value1 && *value1)
337 len1 = strlen(value1);
338 else {
339 print_ldif_single(dn, value2);
340 return;
341 }
342
343 if (value2 && *value2)
344 len2 = strlen(value2);
345 else {
346 print_ldif_single(dn, value1);
347 return;
348 }
349
350 char value[len1 + len2 + 2];
351 memcpy(value, value1, len1);
352 value[len1] = ' ';
353 memcpy(value + len1 + 1, value2, len2 + 1);
354 print_ldif_single(dn, value);
355 }
356
357
358 void build_cn(char *cn, size_t len, int nvalues, char *value, ...)
359 {
360 bool space_flag = false;
361 int i = 0;
362 va_list ap;
363
364 va_start(ap, value);
365
366 while (!value) {
367 nvalues--;
368 if (nvalues == 0) {
369 va_end(ap);
370 return;
371 }
372 value = va_arg(ap, char *);
373 }
374 for (;;) {
375 char ch = *value++;
376
377 if (ch == 0 || ch == '\n') {
378 do {
379 value = NULL;
380 nvalues--;
381 if (nvalues == 0) break;
382 value = va_arg(ap, char *);
383 } while (!value);
384 if (!value) break;
385 space_flag = true;
386 }
387 else if (ch == '\r')
388 continue;
389 else if (ch == ' ') {
390 space_flag = true;
391 continue;
392 }
393 else {
394 if (space_flag) {
395 if (i > 0) {
396 if (i < (len - 2)) cn[i++] = ' ';
397 else break;
398 }
399 space_flag = false;
400 }
401 if (i < (len - 1)) cn[i++] = ch;
402 else break;
403 }
404 }
405 cn[i] = 0;
406 va_end(ap);
407 }
408
409
250 int main(int argc, char** argv) { 410 int main(int argc, char** argv) {
251 pst_desc_ll *d_ptr; 411 pst_desc_ll *d_ptr;
252 char *fname = NULL; 412 char *fname = NULL;
253 char *temp = NULL; //temporary char pointer 413 char *temp = NULL; //temporary char pointer
254 char c; 414 int c;
255 char *d_log = NULL; 415 char *d_log = NULL;
256 prog_name = argv[0]; 416 prog_name = argv[0];
257 pst_item *item = NULL; 417 pst_item *item = NULL;
258 418
259 while ((c = getopt(argc, argv, "b:c:d:Vh"))!= -1) { 419 while ((c = getopt(argc, argv, "b:c:C:d:Vh"))!= -1) {
260 switch (c) { 420 switch (c) {
261 case 'b': 421 case 'b':
262 ldap_base = optarg; 422 ldap_base = optarg;
263 temp = strchr(ldap_base, ','); 423 temp = strchr(ldap_base, ',');
264 if (temp) { 424 if (temp) {
267 *temp = ','; 427 *temp = ',';
268 } 428 }
269 break; 429 break;
270 case 'c': 430 case 'c':
271 ldap_class = optarg; 431 ldap_class = optarg;
432 break;
433 case 'C':
434 cd = iconv_open("UTF-8", optarg);
435 if (cd == (iconv_t)(-1)) {
436 fprintf(stderr, "I don't know character set \"%s\"!\n\n", optarg);
437 fprintf(stderr, "Type: \"iconv --list\" to get list of known character sets\n");
438 return 1;
439 }
272 break; 440 break;
273 case 'd': 441 case 'd':
274 d_log = optarg; 442 d_log = optarg;
275 break; 443 break;
276 case 'h': 444 case 'h':
342 version(); 510 version();
343 printf("Usage: %s [OPTIONS] {PST FILENAME}\n", prog_name); 511 printf("Usage: %s [OPTIONS] {PST FILENAME}\n", prog_name);
344 printf("OPTIONS:\n"); 512 printf("OPTIONS:\n");
345 printf("\t-h\t- Help. This screen\n"); 513 printf("\t-h\t- Help. This screen\n");
346 printf("\t-V\t- Version. Display program version\n"); 514 printf("\t-V\t- Version. Display program version\n");
347 printf("\t-b ldapbase\t- set the ldap base value\n"); 515 printf("\t-b ldapbase\t- set the LDAP base value\n");
348 printf("\t-c class \t- set the class of the ldap objects\n"); 516 printf("\t-c class \t- set the class of the LDAP objects\n");
517 printf("\t-C charset \t- assumed character set of non-ASCII characters\n");
349 return 0; 518 return 0;
350 } 519 }
351 520
352 521
353 int version() { 522 int version() {
364 #endif 533 #endif
365 return 0; 534 return 0;
366 } 535 }
367 536
368 537
369 // my_stristr varies from strstr in that its searches are case-insensitive
370 char * my_stristr(char *haystack, char *needle) {
371 char *x=haystack, *y=needle, *z = NULL;
372 if (haystack == NULL || needle == NULL)
373 return NULL;
374 while (*y != '\0' && *x != '\0') {
375 if (tolower(*y) == tolower(*x)) {
376 // move y on one
377 y++;
378 if (z == NULL) {
379 z = x; // store first position in haystack where a match is made
380 }
381 } else {
382 y = needle; // reset y to the beginning of the needle
383 z = NULL; // reset the haystack storage point
384 }
385 x++; // advance the search in the haystack
386 }
387 return z;
388 }
389
390
391 char *check_filename(char *fname) { 538 char *check_filename(char *fname) {
392 char *t = fname; 539 char *t = fname;
393 if (t == NULL) { 540 if (t == NULL) {
394 return fname; 541 return fname;
395 } 542 }
398 *t = '_'; //replace them with an underscore 545 *t = '_'; //replace them with an underscore
399 } 546 }
400 return fname; 547 return fname;
401 } 548 }
402 549
403 550 #if 0
404 const char *single(char *str) { 551 // This function escapes Distinguished Names (as per RFC4514)
405 if (!str) return ""; 552 char *dn_escape(const char *str) {
406 char *ret = rfc2426_escape(str); 553 static char* buf = NULL;
407 char *n = strchr(ret, '\n'); 554 const char *a;
408 if (n) *n = '\0'; 555 char *ret, *b;
556 if (str == NULL)
557 ret = NULL;
558 else {
559 // Calculate maximum space needed (if every character must be escaped)
560 int x = 2 * strlen(str) + 1; // don't forget room for the NUL
561 buf = (char*) realloc(buf, x);
562 a = str;
563 b = buf;
564
565 // remove leading spaces (RFC says escape them)
566 while (*a == ' ')
567 a++;
568
569 // escape initial '#'
570 if (*a == '#')
571 *b++ = '\\';
572
573 while (*a != '\0') {
574 switch(*a) {
575 case '\\':
576 case '"' :
577 case '+' :
578 case ';' :
579 case '<' :
580 case '>' :
581 *(b++)='\\';
582 *b=*a;
583 break;
584 case '\r': // skip cr
585 b--;
586 break;
587 default:
588 *b=*a;
589 }
590 b++;
591 a++;
592 }
593 *b = '\0'; // NUL-terminate the string (buf)
594 ret = buf;
595 }
409 return ret; 596 return ret;
410 } 597 }
411 598 #endif
412
413 const char *folded(char *str) {
414 if (!str) return "";
415 char *ret = rfc2426_escape(str);
416 char *n = ret;
417 while ((n = strchr(n, '\n'))) {
418 *n = ' ';
419 }
420 n = ret;
421 while ((n = strchr(n, ','))) {
422 *n = ' ';
423 }
424 return ret;
425 }
426
427
428 void multi(const char *fmt, char *str) {
429 if (!str) return;
430 char *ret = rfc2426_escape(str);
431 char *n = ret;
432 while ((n = strchr(ret, '\n'))) {
433 *n = '\0';
434 printf(fmt, ret);
435 ret = n+1;
436 }
437 if (*ret) printf(fmt, ret);
438 }
439
440
441 char *rfc2426_escape(char *str) {
442 static char* buf = NULL;
443 char *ret, *a, *b;
444 int x = 0, y, z;
445 if (str == NULL)
446 ret = str;
447 else {
448
449 // calculate space required to escape all the following characters
450 y = chr_count(str, '\\')
451 + chr_count(str, ';');
452 z = chr_count(str, '\r');
453 if (y == 0 && z == 0)
454 // there isn't any extra space required
455 ret = str;
456 else {
457 x = strlen(str) + y - z + 1; // don't forget room for the NUL
458 buf = (char*) realloc(buf, x);
459 a = str;
460 b = buf;
461 while (*a != '\0') {
462 switch(*a) {
463 case '\\':
464 case ';' :
465 *(b++)='\\';
466 *b=*a;
467 break;
468 case '\r': // skip cr
469 b--;
470 break;
471 default:
472 *b=*a;
473 }
474 b++;
475 a++;
476 }
477 *b = '\0'; // NUL-terminate the string (buf)
478 ret = buf;
479 }
480 }
481 return ret;
482 }
483
484
485 int chr_count(char *str, char x) {
486 int r = 0;
487 while (*str != '\0') {
488 if (*str == x)
489 r++;
490 str++;
491 }
492 return r;
493 }
494