smbtxt.c 16.5 KB
Newer Older
1 2 3 4 5 6
/* Synchronet message base (SMB) message text library routines */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
rswindell's avatar
rswindell committed
7
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *																			*
 * This library is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU Lesser General Public License		*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU Lesser General Public License for more details: lgpl.txt or	*
 * http://www.fsf.org/copyleft/lesser.html									*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

/* ANSI */
23 24
#include <stdlib.h>	/* malloc/realloc/free */
#include <string.h>	/* strlen */
25 26 27

/* SMB-specific */
#include "smblib.h"
28
#include "base64.h"
29
#include "lzh.h"
30

31
char* smb_getmsgtxt(smb_t* smb, smbmsg_t* msg, ulong mode)
32
{
33
	char*	buf;
34
	char*	preamble;
35 36
	char*	lzhbuf;
	char*	p;
37
	char*	str;
deuce's avatar
64-bit  
deuce committed
38
	uint16_t	xlat;
39 40
	uint 	i;
	int		lzh;	/* BOOL */
41 42
	long	l=0,lzhlen,length;

43
	if((buf=(char*)malloc(sizeof(char)))==NULL) {
44
		sprintf(smb->last_error
45 46
			,"%s malloc failure of %" XP_PRIsize_t "u bytes for buffer"
			,__FUNCTION__, sizeof(char));
47 48 49 50
		return(NULL);
	}
	*buf=0;

51 52 53 54 55 56 57 58
	if(!(mode&GETMSGTXT_NO_HFIELDS)) {
		for(i=0;i<(uint)msg->total_hfields;i++) {			/* comment headers are part of text */
			if(msg->hfield[i].type!=SMB_COMMENT && msg->hfield[i].type!=SMTPSYSMSG)
				continue;
			str=(char*)msg->hfield_dat[i];
			length=strlen(str)+2;	/* +2 for crlf */
			if((p=(char*)realloc(buf,l+length+1))==NULL) {
				sprintf(smb->last_error
59 60
					,"%s realloc failure of %ld bytes for comment buffer"
					, __FUNCTION__, l+length+1);
rswindell's avatar
rswindell committed
61 62
				free(buf);
				return(NULL);
63 64 65
			}
			buf=p;
			l+=sprintf(buf+l,"%s\r\n",str);
66
		}
rswindell's avatar
rswindell committed
67 68 69
		if(l) {	/* Add a blank line after comments */
			if((p=(char*)realloc(buf,l+3))==NULL) {
				sprintf(smb->last_error
70 71
					,"%s realloc failure of %ld bytes for comment buffer"
					, __FUNCTION__, l+3);
rswindell's avatar
rswindell committed
72 73 74 75 76 77 78 79 80 81 82 83 84 85
				free(buf);
				return(NULL);
			}
			buf=p;
			l+=sprintf(buf+l,"\r\n");
		}
		unsigned answers = 0;
		for(i=0;i<(uint)msg->total_hfields;i++) {			/* Poll Answers are part of text */
			if(msg->hfield[i].type!=SMB_POLL_ANSWER)
				continue;
			char tmp[128];
			length = safe_snprintf(tmp, sizeof(tmp), "%2u: %s\r\n", ++answers, (char*)msg->hfield_dat[i]);
			if((p=(char*)realloc(buf,l+length+1))==NULL) {
				sprintf(smb->last_error
86 87
					,"%s realloc failure of %ld bytes for comment buffer"
					, __FUNCTION__, l+length+1);
rswindell's avatar
rswindell committed
88 89 90 91 92 93 94 95
				free(buf);
				return(NULL);
			}
			buf=p;
			memcpy(buf+l, tmp, length);
			l += length;
			buf[l] = 0;
		}
96
	}
97
	preamble = strdup(buf);
98

99
	for(i=0;i<(uint)msg->hdr.total_dfields;i++) {
100
		if(msg->dfield[i].length<=sizeof(xlat))
101
			continue;
102 103 104 105 106 107 108 109 110 111 112 113
		switch(msg->dfield[i].type) {
			case TEXT_BODY:
				if(mode&GETMSGTXT_NO_BODY)
					continue;
				break;
			case TEXT_TAIL:
				if(!(mode&GETMSGTXT_TAILS))
					continue;
				break;
			default:	/* ignore other data types */
				continue;
		}
114 115
		fseek(smb->sdt_fp,msg->hdr.offset+msg->dfield[i].offset
			,SEEK_SET);
116 117
		if(fread(&xlat, 1, sizeof(xlat), smb->sdt_fp) != sizeof(xlat))
			continue;
118 119 120
		lzh=0;
		if(xlat==XLAT_LZH) {
			lzh=1;
121 122
			if(fread(&xlat, 1, sizeof(xlat), smb->sdt_fp) != sizeof(xlat))
				continue;
123 124 125 126
		}
		if(xlat!=XLAT_NONE) 	/* no other translations currently supported */
			continue;

127
		length=msg->dfield[i].length-sizeof(xlat);
128
		if(lzh) {
129
			length-=sizeof(xlat);
130 131
			if(length<1)
				continue;
132
			if((lzhbuf=(char*)malloc(length))==NULL) {
133
				sprintf(smb->last_error
134 135
					,"%s malloc failure of %ld bytes for LZH buffer"
					, __FUNCTION__, length);
rswindell's avatar
rswindell committed
136
				free(buf);
137
				free(preamble);
rswindell's avatar
rswindell committed
138
				return(NULL);
139
			}
140 141 142 143 144 145
			if(smb_fread(smb,lzhbuf,length,smb->sdt_fp) != length) {
				sprintf(smb->last_error
					,"%s read failure of %ld bytes for LZH data"
					, __FUNCTION__, length);
				free(lzhbuf);
				free(buf);
146
				free(preamble);
147 148
				return(NULL);
			}
deuce's avatar
deuce committed
149
			lzhlen=*(int32_t*)lzhbuf;
150
			if((p=(char*)realloc(buf,l+lzhlen+3L))==NULL) {
151
				sprintf(smb->last_error
152 153
					,"%s realloc failure of %ld bytes for text buffer"
					, __FUNCTION__, l+lzhlen+3L);
154
				free(lzhbuf);
rswindell's avatar
rswindell committed
155
				free(buf);
156
				free(preamble);
157
				return(NULL);
158 159
			}
			buf=p;
deuce's avatar
deuce committed
160
			lzh_decode((uint8_t *)lzhbuf,length,(uint8_t *)buf+l);
161
			free(lzhbuf);
162
			l+=lzhlen;
163 164
		}
		else {
165
			if((p=(char*)realloc(buf,l+length+3L))==NULL) {
166
				sprintf(smb->last_error
167 168
					,"%s realloc failure of %ld bytes for text buffer"
					, __FUNCTION__, l+length+3L);
rswindell's avatar
rswindell committed
169
				free(buf);
170
				free(preamble);
rswindell's avatar
rswindell committed
171
				return(NULL);
172 173 174 175 176 177 178 179 180 181
			}
			buf=p;
			p=buf+l;
			l+=fread(p,1,length,smb->sdt_fp);
		}
		if(!l)
			continue;
		l--;
		while(l && buf[l]==0) l--;
		l++;
182
		*(buf+l)='\r';	/* CR */
183
		l++;
184
		*(buf+l)='\n';	/* LF */
185
		l++;
186
		*(buf+l)=0;
187
	}
188

189 190
	if(mode&GETMSGTXT_PLAIN) {
		char* plaintext = smb_getplaintext(msg, buf);
191 192 193 194 195 196 197 198 199 200
		if(plaintext != NULL) {
			buf = malloc(strlen(preamble) + strlen(plaintext) + 1);
			if(buf == NULL)
				buf = plaintext;
			else {
				strcpy(buf, preamble);
				strcat(buf, plaintext);
				free(plaintext);
			}
		}
201
	}
202
	free(preamble);
203 204 205
	return(buf);
}

206
void smb_freemsgtxt(char* buf)
207 208
{
	if(buf!=NULL)
209
		free(buf);
210
}
rswindell's avatar
rswindell committed
211

212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
enum content_transfer_encoding {
	CONTENT_TRANFER_ENCODING_NONE,
	CONTENT_TRANFER_ENCODING_BASE64,
	CONTENT_TRANFER_ENCODING_QUOTED_PRINTABLE,
	CONTENT_TRANFER_ENCODING_OTHER
};

/* Decode quoted-printable content-transfer-encoded text */
/* Ignores (strips) unsupported ctrl chars and non-ASCII chars */
/* Does not enforce 76 char line length limit */
char* qp_decode(char* buf)
{
	uchar*	p=(uchar*)buf;
	uchar*	dest=p;

227
	for(;*p != 0; p++) {
228
		if(*p==' ' || (*p>='!' && *p<='~' && *p!='=') || *p=='\t'|| *p=='\r'|| *p=='\n')
229 230 231
			*dest++=*p;
		else if(*p=='=') {
			p++;
232 233 234
			if(*p == '\r')	/* soft link break */
				p++;
			if(*p == 0)
235
				break;
236 237
			if(*p == '\n')
				continue;
238
			if(IS_HEXDIGIT(*p) && IS_HEXDIGIT(*(p+1))) {
239
				uchar ch = HEX_CHAR_TO_INT(*p) << 4;
240
				p++;
241 242 243
				ch |= HEX_CHAR_TO_INT(*p);
				if(ch == '\t' || ch >= ' ')
					*dest++=ch;
244 245 246 247 248 249
			} else {	/* bad encoding */
				*dest++='=';
				*dest++=*p;
			}
		}
	}
250 251
	*dest++='\r';
	*dest++='\n';
252 253 254 255
	*dest=0;
	return buf;
}

256 257 258 259 260 261 262 263
static size_t strStartsWith_i(const char* buf, const char* match)
{
	size_t len = strlen(match);
	if (strnicmp(buf, match, len) == 0)
		return len;
	return 0;
}

264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
static enum content_transfer_encoding mime_encoding(const char* str)
{
	const char* p = str;

	if(str ==  NULL)
		return CONTENT_TRANFER_ENCODING_NONE;
	SKIP_WHITESPACE(p);
	if(strnicmp(p, "base64", 6) == 0)
		return CONTENT_TRANFER_ENCODING_BASE64;
	if(strnicmp(p, "quoted-printable", 16) == 0)
		return CONTENT_TRANFER_ENCODING_QUOTED_PRINTABLE;
	if(strnicmp(p, "7bit", 4) == 0 || strnicmp(p, "8bit", 4) == 0 || strnicmp(p, "binary", 6) == 0)
		return CONTENT_TRANFER_ENCODING_NONE;
	return CONTENT_TRANFER_ENCODING_OTHER;
}

280
static enum content_transfer_encoding mime_getxferencoding(const char* beg, const char* end)
281
{
282
	const char* p = beg;
283 284 285

	while(p < end) {
		SKIP_WHITESPACE(p);
286 287
		size_t len = strStartsWith_i(p, "content-transfer-encoding:");
		if(len < 1) {
288 289 290
			FIND_CHAR(p, '\n');
			continue;
		}
291
		return mime_encoding(p + len);
292 293 294 295 296 297
	}

	return CONTENT_TRANFER_ENCODING_NONE;
}

/* ToDo: parse and return the "modification-date" value */
298
static BOOL mime_getattachment(const char* beg, const char* end, char* attachment, size_t attachment_len)
299
{
300
	char fname[MAX_PATH+1];
301
	const char* p = beg;
302 303 304

	while(p < end) {
		SKIP_WHITESPACE(p);
305 306
		size_t len = strStartsWith_i(p, "content-disposition:");
		if(len < 1) {
307 308 309
			FIND_CHAR(p, '\n');
			continue;
		}
310
		p += len;
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
		SKIP_WHITESPACE(p);
		if(strnicmp(p, "inline", 6) == 0) {
			FIND_CHAR(p, '\n');
			continue;
		}
		char* filename = strstr(p, "filename=");
		if(filename == NULL) {
			FIND_CHAR(p, '\n');
			continue;
		}
		filename += 9;
		char* term;
		if(*filename == '"') {
			filename++;
			term = strchr(filename, '"');
326 327 328
		} else {
			char* wsp = filename;
			FIND_WHITESPACE(wsp);
329
			term = strchr(filename, ';');
330 331 332
			if(term > wsp)
				term = wsp;
		}
333 334 335 336
		if(term == NULL) {
			term = filename;
			FIND_WHITESPACE(term);
		}
337 338
		if(term - filename >= sizeof(fname))
			term = filename + sizeof(fname) - 1;
339 340
		memcpy(fname, filename, term - filename);
		fname[term - filename] = 0;
341 342
		if(attachment != NULL && attachment_len > 0) {
			strncpy(attachment, getfname(fname), attachment_len);
343
			attachment[attachment_len - 1] = '\0';
344
		}
345 346 347 348 349
		return TRUE;
	}
	return FALSE;
}

350
// Parses a MIME text/* content-type header field
351
void smb_parse_content_type(const char* content_type, char** subtype, char** charset)
352 353 354 355 356 357 358 359 360 361 362 363
{
	if(subtype != NULL) {
		FREE_AND_NULL(*subtype);
	}
	if(charset != NULL) {
		FREE_AND_NULL(*charset);
	}
	if(content_type == NULL)
		return;
	char buf[512];
	SAFECOPY(buf, content_type);
	char* p;
364 365
	if((p = strstr(buf, "\r\n\r\n")) != NULL)	/* Don't parse past the end of header */
		*p = 0;
366 367 368
	size_t len = strStartsWith_i(buf, "text/");
	if(len > 0) {
		p = buf + len;
369
		if(subtype != NULL) {
370 371 372 373 374 375 376 377
			if((*subtype = strdup(p)) != NULL) {
				char* tp = *subtype;
				FIND_WHITESPACE(tp);
				*tp = 0;
				tp = *subtype;
				FIND_CHAR(tp, ';');
				*tp = 0;
			}
378
		}
379 380
		char* parms = p;
		if(charset != NULL && ((p = strcasestr(parms, " charset=")) != NULL || (p = strcasestr(parms, ";charset=")) != NULL)) {
381 382 383 384
			BOOL quoted = FALSE;
			p += 9;
			if(*p == '"') {
				quoted = TRUE;
385
				p++;
386
			}
387 388 389 390
			char* tp = p;
			FIND_WHITESPACE(tp);
			*tp = 0;
			tp = p;
391 392 393 394 395
			if(quoted) {
				FIND_CHAR(tp, '"');
			} else {
				FIND_CHAR(tp, ';');
			}
396 397 398 399 400 401
			*tp = 0;
			*charset = strdup(p);
		}
	}
}

402 403
/* Find the specified content-type in a multi-part MIME-encoded message body, recursively */
static const char* mime_getpart(const char* buf, const char* content_type, const char* content_match
404
	,int depth, enum content_transfer_encoding* encoding, char** charset, char* attachment, size_t attachment_len, int index)
rswindell's avatar
rswindell committed
405
{
406
	const char*	txt;
rswindell's avatar
rswindell committed
407 408
	char*	p;
	char	boundary[256];
409 410 411
	char	match1[128];
	char	match2[128];
	int		match_len = 0;
412
	int		found = 0;
413

414
	if(content_match != NULL) {
415 416
		match_len = sprintf(match1, "%s;", content_match);
					sprintf(match2, "%s\r", content_match);
417
	}
rswindell's avatar
rswindell committed
418

419 420
	if(depth > 2)
		return NULL;
rswindell's avatar
rswindell committed
421
	if(content_type == NULL)	/* Not MIME-encoded */
422
		return NULL;
423 424 425 426 427
	size_t len;
	if(((len = strStartsWith_i(content_type, "multipart/alternative;")) < 1)
	&& ((len = strStartsWith_i(content_type, "multipart/mixed;")) < 1)
	&& ((len = strStartsWith_i(content_type, "multipart/report;")) < 1)
	&& ((len = strStartsWith_i(content_type, "multipart/")) < 1))
428
		return NULL;
429 430
	content_type += len;
	p = strcasestr(content_type, "boundary=");
rswindell's avatar
rswindell committed
431
	if(p == NULL)
432
		return NULL;
433 434 435 436 437 438
	p += 9;
	if(*p == '"')
		p++;
	SAFEPRINTF(boundary, "--%s", p);
	if((p = strchr(boundary,'"')) != NULL)
		*p = 0;
rswindell's avatar
rswindell committed
439 440 441
	txt = buf;
	while((p = strstr(txt, boundary)) != NULL) {
		txt = p+strlen(boundary);
442 443
		if(strncmp(txt, "--\r\n", 4) == 0)
			break;
rswindell's avatar
rswindell committed
444 445 446 447
		SKIP_WHITESPACE(txt);
		p = strstr(txt, "\r\n\r\n");	/* End of header */
		if(p==NULL)
			continue;
448 449
		for(content_type = txt; content_type < p; content_type++) {
			SKIP_WHITESPACE(content_type);
450 451
			if((len = strStartsWith_i(content_type, "Content-Type:")) > 0) {
				content_type += len;
452
				SKIP_WHITESPACE(content_type);
453
				break;
454
			}
455 456 457 458
			FIND_CHAR(content_type, '\r');
		}
		if(content_type >= p)
			continue;
459
		const char* cp;
460
		if((match_len && strnicmp(content_type, match1, match_len) && strnicmp(content_type, match2, match_len))
461
			|| (attachment != NULL && !mime_getattachment(txt, p, attachment, attachment_len))) {
462
			if((cp = mime_getpart(p, content_type, content_match, depth + 1, encoding, charset, attachment, attachment_len, index)) != NULL)
463
				return cp;
464 465 466
			continue;
		}
		if(found++ != index) {
467
			if((cp = mime_getpart(p, content_type, content_match, depth + 1, encoding, charset, attachment, attachment_len, index)) != NULL)
468
				return cp;
469 470
			continue;
		}
471 472
		if(encoding != NULL)
			*encoding = mime_getxferencoding(txt, p);
473 474
		if(charset != NULL)
			smb_parse_content_type(content_type, NULL, charset);
475

476
		txt = p + 4;	// strlen("\r\n\r\n")
rswindell's avatar
rswindell committed
477 478 479
		SKIP_WHITESPACE(txt);
		if((p = strstr(txt, boundary)) != NULL)
			*p = 0;
480
		return txt;
rswindell's avatar
rswindell committed
481
	}
482 483 484
	return NULL;
}

485 486
/* Get just the (first) plain-text or HTML portion of a MIME-encoded multi-part message body */
/* Returns NULL if there is no MIME-encoded plain-text/html portion of the message */
487
char* smb_getplaintext(smbmsg_t* msg, char* buf)
488
{
489
	size_t len;
490
	const char*	txt;
491
	enum content_transfer_encoding xfer_encoding = CONTENT_TRANFER_ENCODING_NONE;
492

493
	if(msg->mime_version == NULL || msg->content_type == NULL)	/* not MIME */
494
		return NULL;
495 496
	if(strStartsWith_i(msg->content_type, "multipart/") > 0) {
		txt = mime_getpart(buf, msg->content_type, "text/plain", 0, &xfer_encoding, &msg->text_charset
497
			,/* attachment: */NULL, /* attachment_len: */0, /* index: */0);
498 499 500 501 502 503 504 505 506 507 508 509 510 511
		if(txt == NULL) {
			txt = mime_getpart(buf, msg->content_type, "text/html", 0, &xfer_encoding, &msg->text_charset
				,/* attachment: */NULL, /* attachment_len: */0, /* index: */0);
			if(txt == NULL)
				return NULL;
			free(msg->text_subtype);
			msg->text_subtype = strdup("html");
		} else {
			free(msg->text_subtype);
			msg->text_subtype = strdup("plain");
		}

		len = strlen(txt);
		memmove(buf, txt, len + 1);
512
	} else {
513 514 515 516 517 518 519
		/* Single-part MIME */
		if(strStartsWith_i(msg->content_type, "text/") < 1) {
			*buf = '\0';
			return buf;
		}
		xfer_encoding = mime_encoding(msg->content_encoding);
		len = strlen(buf);
520
	}
521
	if(len == 0)	/* No decoding necessary */
522 523 524 525 526 527 528
		return buf;
	if(xfer_encoding == CONTENT_TRANFER_ENCODING_QUOTED_PRINTABLE)
		qp_decode(buf);
	else if(xfer_encoding == CONTENT_TRANFER_ENCODING_BASE64) {
		char* decoded = strdup(buf);
		if(decoded == NULL)
			return NULL;
529
		if(b64_decode(decoded, len, buf, len) > 0)
530 531
			strcpy(buf, decoded);
		free(decoded);
532
	}
rswindell's avatar
rswindell committed
533 534
	return buf;
}
535

536 537 538
/* Get an attachment (just one) from single-part or multi-part MIME-encoded message body */
/* This function can be destructive (over-write 'buf' with decoded attachment)! */
uint8_t* smb_getattachment(smbmsg_t* msg, char* buf, char* filename, size_t filename_len, uint32_t* filelen, int index)
539
{
540
	const char*	txt;
541 542
	enum content_transfer_encoding xfer_encoding = CONTENT_TRANFER_ENCODING_NONE;

543
	if(msg->mime_version == NULL || msg->content_type == NULL)	/* not MIME */
544
		return NULL;
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
	if(strStartsWith_i(msg->content_type, "multipart/") > 0) {
		txt = mime_getpart(buf, msg->content_type, /* match-type: */NULL, 0, &xfer_encoding, /* charset: */NULL
			,/* attachment: */filename, filename_len, index);
		if(txt != NULL && xfer_encoding == CONTENT_TRANFER_ENCODING_BASE64) {
			size_t len = strlen(txt);
			memmove(buf, txt, len + 1);
			int result = b64_decode(buf, len, buf, len);
			if(result < 1)
				return NULL;
			if(filelen != NULL)
				*filelen = result;
			return (uint8_t*)buf;
		}
		return NULL;	/* No attachment */
	}
	/* Single-part MIME */
	if(index > 0)
		return NULL;
	if(strStartsWith_i(msg->content_type, "text/") > 0)
		return NULL;
	if(filename != NULL) {
		char* fname = strstr(msg->content_type, "name=");
		if(fname == NULL)
			return NULL;
		fname += 5;
		char* term = NULL;
		if(*fname == '"') {
			fname++;
			term = strchr(fname, '"');
		} else
			term = strchr(fname, ';');
		if(term != NULL)
			*term = '\0';
		strncpy(filename, fname, filename_len);
		filename[filename_len - 1] = '\0';
	}
	if(mime_encoding(msg->content_encoding) == CONTENT_TRANFER_ENCODING_BASE64) {
		size_t len = strlen(buf);
		int result = b64_decode(buf, len, buf, len);
584 585
		if(result < 1)
			return NULL;
586 587
		if(filelen != NULL)
			*filelen = result;
588 589 590
	} else {
		if(filelen != NULL)
			*filelen = strlen(buf);
591
	}
592
	return (uint8_t*)buf;
593
}
594 595 596

/* Return number of file attachments contained in MIME-encoded message body */
/* 'body' may be NULL if the body text is not already read/available */
597
ulong smb_countattachments(smb_t* smb, smbmsg_t* msg, const char* body)
598
{
599
	if(msg->mime_version == NULL || msg->content_type == NULL)	/* not MIME */
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614
		return 0;

	ulong count = 0;
	char* buf;

	if(body == NULL)
		buf = smb_getmsgtxt(smb, msg, GETMSGTXT_ALL);
	else
		buf = strdup(body);

	if(buf == NULL)
		return 0;

	char* tmp;
	while((tmp = strdup(buf)) != NULL) {
615 616
		char filename[MAX_PATH + 1];
		uint8_t* attachment = smb_getattachment(msg, tmp, filename, sizeof(filename), NULL, count);
617 618 619 620 621 622 623 624 625
		free(tmp);
		if(attachment == NULL)
			break;
		count++;
	}

	free(buf);
	return count;
}