Synchronet now requires the libarchive development package (e.g. libarchive-dev on Debian-based Linux distros, libarchive.org for more info) to build successfully.

writemsg.cpp 44.7 KB
Newer Older
1
/* Synchronet message creation routines */
2
// vi: tabstop=4
3 4 5 6 7 8 9

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
10
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU 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 General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"
38
#include "wordwrap.h"
39 40
#include "utf8.h"
#include "unicode.h"
41
#include "cp437defs.h"
42

43
#define MAX_LINES		10000
rswindell's avatar
rswindell committed
44
#define MAX_LINE_LEN	(cols - 1)
45

46
const char *quote_fmt=" > %.*s\r\n";
47 48
void quotestr(char *str);

49 50 51
/****************************************************************************/
/* Returns temporary message text filename (for message/text editors)		*/
/****************************************************************************/
52
char* sbbs_t::msg_tmp_fname(int xedit, char* path, size_t len)
53
{
54
	safe_snprintf(path, len, "%sINPUT.MSG", cfg.temp_dir);
55

56
	if(xedit && chk_ar(cfg.xedit[xedit-1]->ar, &useron, &client)) {
57
		if(cfg.xedit[xedit-1]->misc&QUICKBBS)
58
			safe_snprintf(path, len, "%sMSGTMP", cfg.node_dir);	/* QuickBBS editors are dumb */
59
		if(cfg.xedit[xedit-1]->misc&XTRN_LWRCASE)
60
			strlwr(getfname(path));
61 62
	}

63 64 65 66 67 68 69 70
	return path;
}

/****************************************************************************/
/****************************************************************************/
char* sbbs_t::quotes_fname(int xedit, char *path, size_t len)
{
	safe_snprintf(path, len, "%sQUOTES.TXT", cfg.node_dir);
71 72 73
	if(xedit 
		&& chk_ar(cfg.xedit[xedit-1]->ar, &useron, &client) 
		&& (cfg.xedit[xedit-1]->misc&XTRN_LWRCASE))
74 75 76 77 78 79
		strlwr(getfname(path));
	return path;
}

/****************************************************************************/
/****************************************************************************/
80
bool sbbs_t::quotemsg(smb_t* smb, smbmsg_t* msg, bool tails)
81
{
82 83 84 85
	char	fname[MAX_PATH+1];
	char*	buf;
	char*	wrapped=NULL;
	FILE*	fp;
86
	ushort useron_xedit = useron.xedit;
87 88
	uint8_t	org_cols = TERM_COLS_DEFAULT;

89 90
	if(msg->columns != 0)
		org_cols = msg->columns;
91

92 93 94 95
	if(useron_xedit && !chk_ar(cfg.xedit[useron_xedit-1]->ar, &useron, &client))
		useron_xedit = 0;

	quotes_fname(useron_xedit,fname,sizeof(fname));
96
	removecase(fname);
97

98 99
	if((fp=fopen(fname,"w"))==NULL) {
		errormsg(WHERE,ERR_OPEN,fname,0);
100
		return false; 
101 102
	}

103
	bool result = false;
104 105 106 107 108
	ulong mode = GETMSGTXT_PLAIN;
	if(tails) mode |= GETMSGTXT_TAILS;
	if((buf=smb_getmsgtxt(smb, msg, mode)) != NULL) {
		strip_invalid_attr(buf);
		truncsp(buf);
109 110 111 112 113 114 115
		BOOL is_utf8 = FALSE;
		if(smb_msg_is_utf8(msg)) {
			if(term_supports(UTF8))
				is_utf8 = TRUE;
			else {
				utf8_normalize_str(buf);
				utf8_replace_chars(buf, unicode_to_cp437
116
					,/* unsupported char: */CP437_INVERTED_QUESTION_MARK
117
					,/* unsupported zero-width ch: */0
118
					,/* decode error char: */CP437_INVERTED_EXCLAMATION_MARK);
119
			}
120
		}
121
		if(!useron_xedit || (useron_xedit && (cfg.xedit[useron_xedit-1]->misc&QUOTEWRAP))) {
122 123 124
			int wrap_cols = 0;
			if(useron_xedit > 0)
				wrap_cols = cfg.xedit[useron_xedit-1]->quotewrap_cols;
125 126
			if(wrap_cols == 0)
				wrap_cols = cols - 1;
127
			wrapped=::wordwrap(buf, wrap_cols, org_cols - 1, /* handle_quotes: */TRUE, is_utf8);
128
		}
129 130 131 132 133 134 135 136 137
		if(wrapped!=NULL) {
			fputs(wrapped,fp);
			free(wrapped);
		} else
			fputs(buf,fp);
		smb_freemsgtxt(buf); 
		result = true;
	} else if(smb_getmsgdatlen(msg)>2)
		errormsg(WHERE,ERR_READ,smb->file,smb_getmsgdatlen(msg));
138

139
	fclose(fp);
140
	return result;
141 142 143 144
}

/****************************************************************************/
/****************************************************************************/
145
int sbbs_t::process_edited_text(char* buf, FILE* stream, long mode, unsigned* lines, unsigned maxlines)
146
{
147
	unsigned i,l;
148
	int	len=0;
149 150 151 152
	ushort useron_xedit = useron.xedit;

	if(useron_xedit && !chk_ar(cfg.xedit[useron_xedit-1]->ar, &useron, &client))
		useron_xedit = 0;
153

154
	for(l=i=0;buf[l] && i<maxlines;l++) {
155
		if((uchar)buf[l] == FIDO_SOFT_CR && useron_xedit) {
156
			i++;
157 158 159 160 161 162
			switch(cfg.xedit[useron_xedit-1]->soft_cr) {
				case XEDIT_SOFT_CR_EXPAND:
					len += fwrite(crlf,1,2,stream);
					continue;
				case XEDIT_SOFT_CR_STRIP:
					continue;
163 164 165
				case XEDIT_SOFT_CR_RETAIN:
				case XEDIT_SOFT_CR_UNDEFINED:
					break;
166
			}
167 168
		}
		/* Expand LF to CRLF? */
169 170
		if(buf[l]==LF && (!l || buf[l-1]!=CR) && useron_xedit
			&& cfg.xedit[useron_xedit-1]->misc&EXPANDLF) {
171 172 173 174 175
			len+=fwrite(crlf,1,2,stream);
			i++;
			continue; 
		}
		/* Strip FidoNet Kludge Lines? */
176 177
		if(buf[l]==CTRL_A && useron_xedit
			&& cfg.xedit[useron_xedit-1]->misc&STRIPKLUDGE) {
178 179 180 181 182 183
			while(buf[l] && buf[l]!=LF) 
				l++;
			if(buf[l]==0)
				break;
			continue;
		}
184
		if(!(mode&(WM_EMAIL|WM_NETMAIL|WM_EDIT))
185 186 187 188 189 190 191 192 193 194
			&& (!l || buf[l-1]==LF)
			&& buf[l]=='-' && buf[l+1]=='-' && buf[l+2]=='-'
			&& (buf[l+3]==' ' || buf[l+3]==TAB || buf[l+3]==CR))
			buf[l+1]='+';
		if(buf[l]==LF)
			i++;
		fputc(buf[l],stream); 
		len++;
	}

195
	if(buf[l]) {
196
		bprintf(text[NoMoreLines], i);
197 198
		buf[l] = 0;
	}
199 200 201 202 203 204 205 206

	if(lines!=NULL)
		*lines=i;
	return len;
}

/****************************************************************************/
/****************************************************************************/
207
int sbbs_t::process_edited_file(const char* src, const char* dest, long mode, unsigned* lines, unsigned maxlines)
208 209 210 211 212
{
	char*	buf;
	long	len;
	FILE*	fp;

213
	if((len=(long)flength(src))<1)
214 215 216 217 218
		return -1;

	if((buf=(char*)malloc(len+1))==NULL)
		return -2;

rswindell's avatar
rswindell committed
219 220
	if((fp=fopen(src,"rb"))==NULL) {
		free(buf);
221
		return -3;
rswindell's avatar
rswindell committed
222
	}
223 224 225 226 227 228

	memset(buf,0,len+1);
	fread(buf,len,sizeof(char),fp);
	fclose(fp);

	if((fp=fopen(dest,"wb"))!=NULL) {
229
		len=process_edited_text(buf, fp, mode, lines, maxlines);
230 231 232 233 234
		fclose(fp);
	}
	free(buf);

	return len;
235 236
}

237 238 239 240 241 242
/****************************************************************************/
/* Creates a message (post or mail) using standard line editor. 'fname' is  */
/* is name of file to create, 'top' is a buffer to place at beginning of    */
/* message and 'title' is the title (70chars max) for the message.          */
/* 'dest' contains a text description of where the message is going.        */
/****************************************************************************/
243
bool sbbs_t::writemsg(const char *fname, const char *top, char *subj, long mode, uint subnum
244
	,const char *to, const char* from, const char** editor, const char** charset)
245
{
246
	char	str[256],quote[128],c,*buf,*p,*tp
247
				,useron_level;
248
	char	msgtmp[MAX_PATH+1];
249
	char	tagfile[MAX_PATH+1];
250 251
	char	draft_desc[128];
	char	draft[MAX_PATH + 1];
252 253
	char 	tmp[512];
	int		i,j,file,linesquoted=0;
254
	long	length,qlen=0,qtime=0,ex_mode=0;
255
	ulong	l;
256 257
	FILE*	stream;
	FILE*	fp;
258
	unsigned lines;
259 260
	ushort useron_xedit = useron.xedit;

rswindell's avatar
rswindell committed
261 262 263 264 265
	if(cols < 2) {
		errormsg(WHERE, ERR_CHK, "columns", cols);
		return false;
	}

266 267 268
	if(top == NULL)
		top = "";

269 270
	if(useron_xedit && !chk_ar(cfg.xedit[useron_xedit-1]->ar, &useron, &client))
		useron_xedit = 0;
271 272 273

	useron_level=useron.level;

274 275 276
	if(editor!=NULL)
		*editor=NULL;

rswindell's avatar
rswindell committed
277
	if((buf=(char*)malloc((cfg.level_linespermsg[useron_level]*MAX_LINE_LEN) + 1))
278 279
		==NULL) {
		errormsg(WHERE,ERR_ALLOC,fname
rswindell's avatar
rswindell committed
280
			,(cfg.level_linespermsg[useron_level]*MAX_LINE_LEN) +1);
281 282
		return(false); 
	}
283 284 285 286 287

	if(mode&WM_NETMAIL ||
		(!(mode&(WM_EMAIL|WM_NETMAIL)) && cfg.sub[subnum]->misc&SUB_PNET))
		mode|=WM_NOTOP;

288
	msg_tmp_fname(useron_xedit, msgtmp, sizeof(msgtmp));
289
	removecase(msgtmp);
290 291
	SAFEPRINTF(tagfile,"%seditor.tag",cfg.temp_dir);
	removecase(tagfile);
292 293
	SAFEPRINTF(draft_desc, "draft.%s.msg", subnum >= cfg.total_subs ? "mail" : cfg.sub[subnum]->code);
	SAFEPRINTF3(draft, "%suser/%04u.%s", cfg.data_dir, useron.number, draft_desc);
294

295
	bool draft_restored = false;
296 297 298 299
	if(flength(draft) > 0 && (time(NULL) - fdate(draft)) < 48L*60L*60L && yesno("Unsaved draft message found. Use it")) {
		if(mv(draft, msgtmp, /* copy: */true) == 0) {
			lprintf(LOG_NOTICE, "draft message restored: %s (%lu bytes)", draft, (ulong)flength(msgtmp));
			draft_restored = true;
300
			removecase(quotes_fname(useron_xedit, str, sizeof(str)));
301
		} else
rswindell's avatar
rswindell committed
302
			lprintf(LOG_ERR, "ERROR %d (%s) restoring draft message: %s", errno, strerror(errno), draft);
303
	}
304
	else if(mode&WM_QUOTE && !(useron.rest&FLAG('J'))
305 306 307 308 309 310
		&& ((mode&(WM_EMAIL|WM_NETMAIL) && cfg.sys_misc&SM_QUOTE_EM)
		|| (!(mode&(WM_EMAIL|WM_NETMAIL)) && (uint)subnum!=INVALID_SUB
			&& cfg.sub[subnum]->misc&SUB_QUOTE))) {

		/* Quote entire message to MSGTMP or INPUT.MSG */

311 312
		if(useron_xedit && cfg.xedit[useron_xedit-1]->misc&QUOTEALL) {
			quotes_fname(useron_xedit, str, sizeof(str));
313
			if((stream=fnopen(NULL,str,O_RDONLY))==NULL) {
314
				errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
315
				free(buf);
316 317
				return(false); 
			}
318 319
			if((file=nopen(msgtmp,O_WRONLY|O_CREAT|O_TRUNC))==-1) {
				errormsg(WHERE,ERR_OPEN,msgtmp,O_WRONLY|O_CREAT|O_TRUNC);
320
				free(buf);
321
				fclose(stream);
322 323
				return(false); 
			}
324 325

			while(!feof(stream) && !ferror(stream)) {
326
				if(!fgets(str,sizeof(str),stream))
327 328
					break;
				quotestr(str);
329
				SAFEPRINTF2(tmp,quote_fmt,cols-4,str);
330
				write(file,tmp,strlen(tmp));
331 332
				linesquoted++; 
			}
333
			fclose(stream);
334 335
			close(file); 
		}
336 337 338

		/* Quote nothing to MSGTMP or INPUT.MSG automatically */

339
		else if(useron_xedit && cfg.xedit[useron_xedit-1]->misc&QUOTENONE)
340 341 342
			;

		else if(yesno(text[QuoteMessageQ])) {
343
			quotes_fname(useron_xedit, str, sizeof(str));
344 345
			if((stream=fnopen(&file,str,O_RDONLY))==NULL) {
				errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
346
				free(buf);
347 348
				return(false); 
			}
349

350 351
			if((file=nopen(msgtmp,O_WRONLY|O_CREAT|O_TRUNC))==-1) {
				errormsg(WHERE,ERR_OPEN,msgtmp,O_WRONLY|O_CREAT|O_TRUNC);
352
				free(buf);
353
				fclose(stream);
354 355
				return(false); 
			}
356

357
			l=(long)ftell(stream);			/* l now points to start of message */
358 359

			while(online) {
360
				SAFEPRINTF(str,text[QuoteLinesPrompt],linesquoted ? "Done":"All");
361 362 363 364 365
				mnemonics(str);
				i=getstr(quote,10,K_UPPER);
				if(sys_status&SS_ABORT) {
					fclose(stream);
					close(file);
366
					free(buf);
367 368
					return(false); 
				}
369 370 371 372 373
				if(!i && linesquoted)
					break;
				if(!i || quote[0]=='A') {                   /* Quote all */
					fseek(stream,l,SEEK_SET);
					while(!feof(stream) && !ferror(stream)) {
374
						if(!fgets(str,sizeof(str),stream))
375 376
							break;
						quotestr(str);
377
						SAFEPRINTF2(tmp,quote_fmt,cols-4,str);
378
						write(file,tmp,strlen(tmp));
379 380 381 382
						linesquoted++; 
					}
					break; 
				}
383 384 385 386 387 388
				if(quote[0]=='L') {
					fseek(stream,l,SEEK_SET);
					i=1;
					CRLF;
					attr(LIGHTGRAY);
					while(!feof(stream) && !ferror(stream) && !msgabort()) {
389
						if(!fgets(str,sizeof(str),stream))
390 391 392
							break;
						quotestr(str);
						bprintf("%3d: %.74s\r\n",i,str);
393 394 395 396
						i++; 
					}
					continue; 
				}
397 398 399 400 401

				if(!isdigit(quote[0]))
					break;
				p=quote;
				while(p) {
402
					if(*p==',' || *p==' ')
403 404 405 406 407 408 409
						p++;
					i=atoi(p);
					if(!i)
						break;
					fseek(stream,l,SEEK_SET);
					j=1;
					while(!feof(stream) && !ferror(stream) && j<i) {
410
						if(!fgets(tmp,sizeof(tmp),stream))
411
							break;
412 413
						j++; /* skip beginning */
					}		
414 415 416 417
					tp=strchr(p,'-');   /* tp for temp pointer */
					if(tp) {		 /* range */
						i=atoi(tp+1);
						while(!feof(stream) && !ferror(stream) && j<=i) {
418
							if(!fgets(str,sizeof(str),stream))
419 420
								break;
							quotestr(str);
421
							SAFEPRINTF2(tmp,quote_fmt,cols-4,str);
422 423
							write(file,tmp,strlen(tmp));
							linesquoted++;
424 425 426
							j++; 
						} 
					}
427
					else {			/* one line */
428
						if(fgets(str,sizeof(str),stream)) {
429
							quotestr(str);
430
							SAFEPRINTF2(tmp,quote_fmt,cols-4,str);
431
							write(file,tmp,strlen(tmp));
432 433 434
							linesquoted++; 
						} 
					}
435
					p=strchr(p,',');
436
					// if(!p) p=strchr(p,' ');  02/05/96 huh?
437 438
				} 
			}
439 440

			fclose(stream);
441 442 443
			close(file); 
		} 
	}
444 445 446
	else {
		removecase(quotes_fname(useron_xedit, str, sizeof(str)));
	}
447 448

	if(!online || sys_status&SS_ABORT) {
449
		free(buf);
450 451
		return(false); 
	}
452

453
	if(!(mode&(WM_EXTDESC|WM_SUBJ_RO))) {
rswindell's avatar
Bugfix:  
rswindell committed
454 455
		int	max_title_len;

456 457
		if(mode&WM_FILE) {
			CRLF;
458 459
			bputs(text[Filename]); 
		}
460
		else {
461 462
			bputs(text[SubjectPrompt]); 
		}
rswindell's avatar
Bugfix:  
rswindell committed
463 464 465
		max_title_len=cols-column-1;
		if(max_title_len > LEN_TITLE)
			max_title_len = LEN_TITLE;
466 467
		if(draft_restored)
			user_get_property(&cfg, useron.number, draft_desc, "subject", subj, max_title_len);
468
		if(!getstr(subj,max_title_len,mode&WM_FILE ? K_LINE|K_TRIM : K_LINE|K_EDIT|K_AUTODEL|K_TRIM)
469
			&& useron_level && useron.logons) {
470
			free(buf);
471 472
			return(false); 
		}
473
		if((mode&WM_FILE) && !checkfname(subj)) {
474 475 476 477
			free(buf);
			bputs(text[BadFilename]);
			return(false);
		}
478 479
		if(!(mode&(WM_EMAIL|WM_NETMAIL)) && cfg.sub[subnum]->misc&SUB_QNET
			&& !SYSOP
480 481
			&& (!stricmp(subj,"DROP") || !stricmp(subj,"ADD")
			|| !strnicmp(to,"SBBS",4))) {
482
			free(buf);   /* Users can't post DROP or ADD in QWK netted subs */
483 484 485
			return(false); /* or messages to "SBBS" */
		}
	}
486 487

	if(!online || sys_status&SS_ABORT) {
488
		free(buf);
489 490
		return(false); 
	}
491

492 493
	smb.subnum = subnum;	/* Allow JS msgeditors to use bbs.smb_sub* */

494 495 496 497 498 499
	if(console&CON_RAW_IN) {
		bprintf(text[EnterMsgNowRaw]
			,(ulong)cfg.level_linespermsg[useron_level]*MAX_LINE_LEN);
		if(top[0] && !(mode&WM_NOTOP)) {
			strcpy((char *)buf,top);
			strcat((char *)buf,crlf);
500 501
			l=strlen((char *)buf); 
		}
502 503 504 505 506
		else
			l=0;
		while(l<(ulong)(cfg.level_linespermsg[useron_level]*MAX_LINE_LEN)) {
			c=getkey(0);
			if(sys_status&SS_ABORT) {  /* Ctrl-C */
507
				free(buf);
508 509
				return(false); 
			}
510
			if((c==ESC || c==CTRL_A) && useron.rest&FLAG('A')) /* ANSI restriction */
511
				continue;
512
			if(c==BEL && useron.rest&FLAG('B'))   /* Beep restriction */
513 514 515 516
				continue;
			if(!(console&CON_RAW_IN))	/* Ctrl-Z was hit */
				break;
			outchar(c);
517 518
			buf[l++]=c; 
		}
519 520
		buf[l]=0;
		if(l==(ulong)cfg.level_linespermsg[useron_level]*MAX_LINE_LEN)
521 522
			bputs(text[OutOfBytes]); 
	}
523

524
	else if(useron_xedit) {
525

526
		if(editor!=NULL)
527
			*editor=cfg.xedit[useron_xedit-1]->name;
528

529
		editor_inf(useron_xedit,to,from,subj,mode,subnum,tagfile);
530
		if(cfg.xedit[useron_xedit-1]->type) {
531
			gettimeleft();
532
			xtrndat(mode&WM_ANON ? text[Anonymous]:from,cfg.node_dir,cfg.xedit[useron_xedit-1]->type
533
 			   ,timeleft,cfg.xedit[useron_xedit-1]->misc); 
534 535
		}

536
		if(cfg.xedit[useron_xedit-1]->misc&XTRN_STDIO) {
537
			ex_mode|=EX_STDIO;
538
			if(cfg.xedit[useron_xedit-1]->misc&WWIVCOLOR)
539 540
				ex_mode|=EX_WWIV; 
		}
541
		if(cfg.xedit[useron_xedit-1]->misc&XTRN_NATIVE)
542
			ex_mode|=EX_NATIVE;
543
		if(cfg.xedit[useron_xedit-1]->misc&XTRN_SH)
544
			ex_mode|=EX_SH;
545

546 547 548 549 550 551 552
		if(!draft_restored) {
			if(!linesquoted)
				removecase(msgtmp);
			else {
				qlen=(long)flength(msgtmp);
				qtime=(long)fdate(msgtmp); 
			}
553
		}
554

555 556
		CLS;
		rioctl(IOCM|PAUSE|ABORT);
557 558 559
		const char* cmd = cmdstr(cfg.xedit[useron_xedit-1]->rcmd, msgtmp, nulstr, NULL);
		int result = external(cmd, ex_mode, cfg.node_dir);
		lprintf(LOG_DEBUG, "'%s' returned %d", cmd, result);
560 561
		rioctl(IOSM|PAUSE|ABORT); 

562
		checkline();
563
		if(!online && flength(msgtmp) > 0)	 { // save draft message due to disconnection
564 565 566
			if(mv(msgtmp, draft, /* copy: */true) == 0) {
				user_set_property(&cfg, useron.number, draft_desc, "subject", subj);
				user_set_time_property(&cfg, useron.number, draft_desc, "created", time(NULL));
567 568
				lprintf(LOG_NOTICE, "draft message saved: %s (%lu bytes)", draft, (ulong)flength(draft));
			} else
rswindell's avatar
rswindell committed
569
				lprintf(LOG_ERR, "ERROR %d (%s) saving draft message: %s", errno, strerror(errno), draft);
570 571 572
		}

		if(result != EXIT_SUCCESS || !fexistcase(msgtmp) || !online
573
			|| (linesquoted && qlen==flength(msgtmp) && qtime==fdate(msgtmp))) {
574
			free(buf);
575 576
			return(false); 
		}
577
		SAFEPRINTF(str,"%sRESULT.ED",cfg.node_dir);
578
		if(!(mode&(WM_EXTDESC|WM_FILE|WM_SUBJ_RO))
579
			&& !(cfg.xedit[useron_xedit-1]->misc&QUICKBBS) 
580 581 582 583 584
			&& fexistcase(str)) {
			if((fp=fopen(str,"r")) != NULL) {
				fgets(str,sizeof(str),fp);
				fgets(str,sizeof(str),fp);
				truncsp(str);
585
				safe_snprintf(subj,LEN_TITLE,"%s",str);
586 587 588 589
				fclose(fp);
			}
		}

590 591 592
		buf[0]=0;
		if(!(mode&WM_NOTOP))
			strcpy((char *)buf,top);
593 594
		if((file=nopen(msgtmp,O_RDONLY))==-1) {
			errormsg(WHERE,ERR_OPEN,msgtmp,O_RDONLY);
595
			free(buf);
596 597
			return(false); 
		}
598
		length=(long)filelength(file);
599 600 601 602
		l=strlen((char *)buf);	  /* reserve space for top and terminating null */
		/* truncate if too big */
		if(length>(long)((cfg.level_linespermsg[useron_level]*MAX_LINE_LEN)-(l+1))) {
			length=(cfg.level_linespermsg[useron_level]*MAX_LINE_LEN)-(l+1);
603 604
			bputs(text[OutOfBytes]); 
		}
605 606
		lread(file,buf+l,length);
		close(file);
607
		// remove(msgtmp); 	   /* no need to save the temp input file */
608 609
		buf[l+length]=0; 
	}
610 611
	else {
		buf[0]=0;
612
		if(linesquoted || draft_restored) {
613
			if((file=nopen(msgtmp,O_RDONLY))!=-1) {
614
				length=(long)filelength(file);
615 616
				l=length>(cfg.level_linespermsg[useron_level]*MAX_LINE_LEN)-1
					? (cfg.level_linespermsg[useron_level]*MAX_LINE_LEN)-1 : length;
617 618 619
				lread(file,buf,l);
				buf[l]=0;
				close(file);
620
				// remove(msgtmp);
621 622
			} 
		}
623
		if(!(msgeditor((char *)buf,mode&WM_NOTOP ? nulstr : top, subj))) {
624 625 626 627 628 629 630 631 632 633
			if(!online) {
				FILE* fp = fopen(draft, "wb");
				if(fp == NULL)
					errormsg(WHERE, ERR_CREATE, draft, O_WRONLY);
				else {
					fputs(buf, fp);
					fclose(fp);
					user_set_property(&cfg, useron.number, draft_desc, "subject", subj);
				}
			}
634
			free(buf);	/* Assertion here Dec-17-2003, think I fixed in block above (rev 1.52) */
635 636 637
			return(false); 
		} 
	}
638 639 640

	now=time(NULL);
	bputs(text[Saving]);
641
	removecase(fname);
642
	if((stream=fnopen(NULL,fname,O_WRONLY|O_CREAT|O_TRUNC))==NULL) {
643
		errormsg(WHERE,ERR_OPEN,fname,O_WRONLY|O_CREAT|O_TRUNC);
644
		free(buf);
645 646
		return(false); 
	}
647
	l=process_edited_text(buf,stream,mode,&lines,cfg.level_linespermsg[useron_level]);
648
	bool utf8 = !str_is_ascii(buf) && utf8_str_is_valid(buf);
649
	if(charset != NULL) {
650
		if(utf8)
651 652
			*charset = FIDO_CHARSET_UTF8;
	}
653

654
	if(!(mode&(WM_EXTDESC|WM_ANON))) {
655 656 657 658 659
		/* Signature file */
		if((subnum==INVALID_SUB && cfg.msg_misc&MM_EMAILSIG)
			|| (subnum!=INVALID_SUB && !(cfg.sub[subnum]->misc&SUB_NOUSERSIG))) {
			SAFEPRINTF2(str,"%suser/%04u.sig",cfg.data_dir,useron.number);
			FILE* sig;
660
			if(fexistcase(str) && (sig=fopen(str,"r"))!=NULL) {
661 662 663 664
				while(!feof(sig)) {
					if(!fgets(str,sizeof(str),sig))
						break;
					truncsp(str);
665 666 667 668 669 670
					if(utf8) {
						char buf[sizeof(str)*4];
						cp437_to_utf8_str(str, buf, sizeof(buf) - 1, /* minval: */'\x02');
						l+=fprintf(stream,"%s\r\n", buf);
					} else
						l+=fprintf(stream,"%s\r\n",str);
671 672 673 674 675 676 677 678 679 680 681 682 683
					lines++;		/* line counter */
				}
				fclose(sig);
			}
		}
		if(fexistcase(tagfile)) {
			FILE* tag;

			if((tag=fopen(tagfile,"r")) != NULL) {
				while(!feof(tag)) {
					if(!fgets(str,sizeof(str),tag))
						break;
					truncsp(str);
684 685 686 687 688 689
					if(utf8) {
						char buf[sizeof(str)*4];
						cp437_to_utf8_str(str, buf, sizeof(buf) - 1, /* minval: */'\x02');
						l+=fprintf(stream,"%s\r\n", buf);
					} else
						l+=fprintf(stream,"%s\r\n",str);
690 691 692
					lines++;		/* line counter */
				}
				fclose(tag);
693
			}
694 695 696
		}
	}

697
	remove(draft);
698
	fclose(stream);
699
	free((char *)buf);
700
	bprintf(text[SavedNBytes],l,lines);
701 702 703
	return(true);
}

704
void sbbs_t::editor_info_to_msg(smbmsg_t* msg, const char* editor, const char* charset)
705 706 707 708
{
	if(editor != NULL)
		smb_hfield_str(msg, SMB_EDITOR, editor);

709 710 711
	if(charset != NULL)
		smb_hfield_str(msg, FIDOCTRL, charset);

712 713 714 715 716 717 718 719 720
	ushort useron_xedit = useron.xedit;

	if(useron_xedit > 0 && !chk_ar(cfg.xedit[useron_xedit - 1]->ar, &useron, &client))
		useron_xedit = 0;

	if(editor == NULL || useron_xedit == 0 || (cfg.xedit[useron_xedit - 1]->misc&SAVECOLUMNS))
		smb_hfield_bin(msg, SMB_COLUMNS, cols);
}

721
/****************************************************************************/
722 723 724 725 726
/****************************************************************************/
/* Modify 'str' to for quoted format. Remove ^A codes, etc.                 */
/****************************************************************************/
void quotestr(char *str)
{
rswindell's avatar
rswindell committed
727 728
	truncsp(str);
	remove_ctrl_a(str,str);
729 730
}

731 732
/****************************************************************************/
/****************************************************************************/
733
void sbbs_t::editor_inf(int xeditnum, const char *to, const char* from, const char *subj, long mode
734
	,uint subnum, const char* tagfile)
735
{
736 737
	char	path[MAX_PATH+1];
	FILE*	fp;
738 739 740 741

	xeditnum--;

	if(cfg.xedit[xeditnum]->misc&QUICKBBS) {
742 743
		SAFEPRINTF2(path,"%s%s",cfg.node_dir, cfg.xedit[xeditnum]->misc&XTRN_LWRCASE ? "msginf":"MSGINF");
		removecase(path);
744 745
		if((fp=fopen(path,"wb"))==NULL) {
			errormsg(WHERE,ERR_OPEN,path,O_WRONLY|O_CREAT|O_TRUNC);
746 747
			return; 
		}
748
		fprintf(fp,"%s\r\n%s\r\n%s\r\n%u\r\n%s\r\n%s\r\n"
749
			,mode&WM_ANON ? text[Anonymous]:from,to,subj,1
750
			,mode&WM_NETMAIL ? "NetMail"
751
				:mode&WM_EMAIL ? "Electronic Mail"
752 753
					:subnum==INVALID_SUB ? nulstr
						:cfg.sub[subnum]->sname
754
			,mode&WM_PRIVATE ? "YES":"NO");
755 756 757 758 759
		/* the 7th line (the tag-line file) is a Synchronet extension, for SlyEdit */
		if((mode&WM_EXTDESC)==0 && tagfile!=NULL)
			fprintf(fp,"%s", tagfile);
		fprintf(fp,"\r\n");
		fclose(fp);
760
	}
761
	else {
762 763 764
		SAFEPRINTF(path,"%sresult.ed",cfg.node_dir);
		removecase(path);
		SAFEPRINTF2(path,"%s%s",cfg.node_dir,cfg.xedit[xeditnum]->misc&XTRN_LWRCASE ? "editor.inf" : "EDITOR.INF");
765 766 767
		removecase(path);
		if((fp=fopen(path,"wb"))==NULL) {
			errormsg(WHERE,ERR_OPEN,path,O_WRONLY|O_CREAT|O_TRUNC);
768 769
			return; 
		}
770
		fprintf(fp,"%s\r\n%s\r\n%u\r\n%s\r\n%s\r\n%u\r\n"
771 772 773
			,subj
			,to
			,useron.number
774
			,mode&WM_ANON ? text[Anonymous]:from
775 776
			,useron.name
			,useron.level);
777
		fclose(fp);
778
	}
779 780 781 782 783 784 785 786 787 788
}



/****************************************************************************/
/* Removes from file 'str' every LF terminated line that starts with 'str2' */
/* That is divisable by num. Function skips first 'skip' number of lines    */
/****************************************************************************/
void sbbs_t::removeline(char *str, char *str2, char num, char skip)
{
789
	char*	buf;
790
    size_t  slen;
791 792 793 794 795 796
    int     i,file;
	long	l=0,flen;
    FILE    *stream;

	if((file=nopen(str,O_RDONLY))==-1) {
		errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
797 798
		return; 
	}
799
	flen=(long)filelength(file);
800
	slen=strlen(str2);
deuce's avatar
deuce committed
801
	if((buf=(char *)malloc(flen))==NULL) {
802 803
		close(file);
		errormsg(WHERE,ERR_ALLOC,str,flen);
804 805
		return; 
	}
806 807 808
	if(lread(file,buf,flen)!=flen) {
		close(file);
		errormsg(WHERE,ERR_READ,str,flen);
809
		free(buf);
810 811
		return; 
	}
812 813 814 815
	close(file);
	if((stream=fnopen(&file,str,O_WRONLY|O_TRUNC))==NULL) {
		close(file);
		errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_TRUNC);
816
		free(buf);
817 818
		return; 
	}
819 820 821
	for(i=0;l<flen && i<skip;l++) {
		fputc(buf[l],stream);
		if(buf