writemsg.cpp 51.5 KB
Newer Older
1 2 3 4 5 6
/* Synchronet message creation routines */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
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 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										*
 *																			*
 * 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.	*
 ****************************************************************************/

#include "sbbs.h"
23
#include "wordwrap.h"
24
#include "utf8.h"
25 26
#include "git_branch.h"
#include "git_hash.h"
27

28
#define MAX_LINES		10000
rswindell's avatar
rswindell committed
29
#define MAX_LINE_LEN	(cols - 1)
30

31
const char *quote_fmt=" > %.*s\r\n";
32 33
void quotestr(char *str);

34 35 36
/****************************************************************************/
/* Returns temporary message text filename (for message/text editors)		*/
/****************************************************************************/
37
char* sbbs_t::msg_tmp_fname(int xedit, char* path, size_t len)
38
{
39
	safe_snprintf(path, len, "%sINPUT.MSG", cfg.temp_dir);
40

41
	if(xedit && chk_ar(cfg.xedit[xedit-1]->ar, &useron, &client)) {
42
		if(cfg.xedit[xedit-1]->misc&QUICKBBS)
43
			safe_snprintf(path, len, "%sMSGTMP", cfg.node_dir);	/* QuickBBS editors are dumb */
44
		if(cfg.xedit[xedit-1]->misc&XTRN_LWRCASE)
45
			strlwr(getfname(path));
46 47
	}

48 49 50 51 52 53 54 55
	return path;
}

/****************************************************************************/
/****************************************************************************/
char* sbbs_t::quotes_fname(int xedit, char *path, size_t len)
{
	safe_snprintf(path, len, "%sQUOTES.TXT", cfg.node_dir);
56 57 58
	if(xedit 
		&& chk_ar(cfg.xedit[xedit-1]->ar, &useron, &client) 
		&& (cfg.xedit[xedit-1]->misc&XTRN_LWRCASE))
59 60 61 62 63 64
		strlwr(getfname(path));
	return path;
}

/****************************************************************************/
/****************************************************************************/
65
bool sbbs_t::quotemsg(smb_t* smb, smbmsg_t* msg, bool tails)
66
{
67 68 69 70
	char	fname[MAX_PATH+1];
	char*	buf;
	char*	wrapped=NULL;
	FILE*	fp;
71
	ushort useron_xedit = useron.xedit;
72 73
	uint8_t	org_cols = TERM_COLS_DEFAULT;

74 75
	if(msg->columns != 0)
		org_cols = msg->columns;
76

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

	quotes_fname(useron_xedit,fname,sizeof(fname));
81
	(void)removecase(fname);
82

83
	if((fp=fopen(fname,"wb"))==NULL) {
84
		errormsg(WHERE,ERR_OPEN,fname,0);
85
		return false; 
86 87
	}

88 89 90
	if(useron_xedit > 0 && cfg.xedit[useron_xedit - 1]->type == XTRN_WWIV)
		fputs("#\r\n\r\n", fp);	// WWIV adds 2 lines of metadata, BRedit checks for the # symbol

91
	bool result = false;
92 93 94 95 96
	ulong mode = GETMSGTXT_PLAIN;
	if(tails) mode |= GETMSGTXT_TAILS;
	if((buf=smb_getmsgtxt(smb, msg, mode)) != NULL) {
		strip_invalid_attr(buf);
		truncsp(buf);
97
		BOOL is_utf8 = FALSE;
98 99 100 101 102 103
		if(!str_is_ascii(buf)) {
			if(smb_msg_is_utf8(msg)) {
				if(term_supports(UTF8)
					&& (!useron_xedit || (cfg.xedit[useron_xedit-1]->misc&XTRN_UTF8)))
					is_utf8 = TRUE;
				else {
104
					utf8_to_cp437_inplace(buf);
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
				}
			} else { // CP437
				char* orgtxt;
				if(term_supports(UTF8)
					&& (!useron_xedit || (cfg.xedit[useron_xedit-1]->misc&XTRN_UTF8))
					&& (orgtxt = strdup(buf)) != NULL) {
					is_utf8 = TRUE;
					size_t max = strlen(buf) * 4;
					char* newbuf = (char*)realloc(buf, max + 1);
					if(newbuf != NULL) {
						buf = newbuf;
						cp437_to_utf8_str(orgtxt, buf, max, /* minval: */'\x80');
					}
					free(orgtxt);
				}
120
			}
121
		}
122
		if(!useron_xedit || (useron_xedit && (cfg.xedit[useron_xedit-1]->misc&QUOTEWRAP))) {
123 124 125
			int wrap_cols = 0;
			if(useron_xedit > 0)
				wrap_cols = cfg.xedit[useron_xedit-1]->quotewrap_cols;
126 127
			if(wrap_cols == 0)
				wrap_cols = cols - 1;
128
			wrapped=::wordwrap(buf, wrap_cols, org_cols - 1, /* handle_quotes: */TRUE, is_utf8);
129
		}
130 131 132 133 134 135 136 137 138
		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));
139

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

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

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

155
	for(l=i=0;buf[l] && i<maxlines;l++) {
156
		if((uchar)buf[l] == FIDO_SOFT_CR && useron_xedit) {
157
			i++;
158 159 160 161 162 163
			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;
164 165 166
				case XEDIT_SOFT_CR_RETAIN:
				case XEDIT_SOFT_CR_UNDEFINED:
					break;
167
			}
168 169
		}
		/* Expand LF to CRLF? */
170 171
		if(buf[l]==LF && (!l || buf[l-1]!=CR) && useron_xedit
			&& cfg.xedit[useron_xedit-1]->misc&EXPANDLF) {
172 173 174 175
			len+=fwrite(crlf,1,2,stream);
			i++;
			continue; 
		}
rswindell's avatar
rswindell committed
176 177 178 179
		if(buf[l] == CTRL_A) {
			/* Strip FidoNet Kludge Lines? */
			if(useron_xedit
				&& cfg.xedit[useron_xedit-1]->misc&STRIPKLUDGE) {
180 181 182 183 184 185 186 187 188
				l++;
				char* kludge = buf + l;
				if(editor_details[0] == 0 && strncmp(kludge, "NOTE:", 5) == 0) {
					kludge += 5;
					SKIP_WHITESPACE(kludge);
					SAFECOPY(editor_details, kludge);
					truncstr(editor_details, "\r\n");
				}
				while(buf[l] != 0 && buf[l] != '\r' && buf[l] != '\n')
rswindell's avatar
rswindell committed
189
					l++;
190
				if(buf[l] == 0)
rswindell's avatar
rswindell committed
191
					break;
192 193
				if(buf[l] == '\r' && buf[l + 1] == '\n')
					l++;
rswindell's avatar
rswindell committed
194 195 196 197 198
				continue;
			}
			/* Convert invalid or dangerous Ctrl-A codes */
			if(!valid_ctrl_a_attr(buf[l + 1]))
				buf[l] = '@';
199
		}
rswindell's avatar
rswindell committed
200

201
		if(!(mode&(WM_EMAIL|WM_NETMAIL|WM_EDIT))
202 203 204 205 206 207 208 209 210 211
			&& (!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++;
	}

212
	if(buf[l]) {
213
		bprintf(text[NoMoreLines], i);
214 215
		buf[l] = 0;
	}
216 217 218 219 220 221 222 223

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

/****************************************************************************/
/****************************************************************************/
224
int sbbs_t::process_edited_file(const char* src, const char* dest, long mode, unsigned* lines, unsigned maxlines)
225 226 227 228 229
{
	char*	buf;
	long	len;
	FILE*	fp;

230
	if((len=(long)flength(src))<1)
231 232 233 234 235
		return -1;

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

rswindell's avatar
rswindell committed
236 237
	if((fp=fopen(src,"rb"))==NULL) {
		free(buf);
238
		return -3;
rswindell's avatar
rswindell committed
239
	}
240 241 242 243 244 245

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

	if((fp=fopen(dest,"wb"))!=NULL) {
246
		len=process_edited_text(buf, fp, mode, lines, maxlines);
247 248 249 250 251
		fclose(fp);
	}
	free(buf);

	return len;
252 253
}

254 255 256 257 258 259
/****************************************************************************/
/* 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.        */
/****************************************************************************/
260
bool sbbs_t::writemsg(const char *fname, const char *top, char *subj, long mode, uint subnum
261
	,const char *to, const char* from, const char** editor, const char** charset)
262
{
263
	char	str[256],quote[128],c,*buf,*p,*tp
264
				,useron_level;
265
	char	msgtmp[MAX_PATH+1];
266
	char	tagfile[MAX_PATH+1];
267 268
	char	draft_desc[128];
	char	draft[MAX_PATH + 1];
269 270
	char 	tmp[512];
	int		i,j,file,linesquoted=0;
271
	long	length,qlen=0,qtime=0,ex_mode=0;
272
	ulong	l;
273 274
	FILE*	stream;
	FILE*	fp;
275
	unsigned lines;
276 277
	ushort useron_xedit = useron.xedit;

rswindell's avatar
rswindell committed
278 279 280 281 282
	if(cols < 2) {
		errormsg(WHERE, ERR_CHK, "columns", cols);
		return false;
	}

283 284 285
	if(top == NULL)
		top = "";

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

	useron_level=useron.level;

291 292 293
	if(editor!=NULL)
		*editor=NULL;

rswindell's avatar
rswindell committed
294
	if((buf=(char*)malloc((cfg.level_linespermsg[useron_level]*MAX_LINE_LEN) + 1))
295 296
		==NULL) {
		errormsg(WHERE,ERR_ALLOC,fname
rswindell's avatar
rswindell committed
297
			,(cfg.level_linespermsg[useron_level]*MAX_LINE_LEN) +1);
298 299
		return(false); 
	}
300 301 302 303 304

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

305
	msg_tmp_fname(useron_xedit, msgtmp, sizeof(msgtmp));
306
	(void)removecase(msgtmp);
307
	SAFEPRINTF(tagfile,"%seditor.tag",cfg.temp_dir);
308
	(void)removecase(tagfile);
309 310
	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);
311

312
	bool draft_restored = false;
313 314 315 316
	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;
317
			(void)removecase(quotes_fname(useron_xedit, str, sizeof(str)));
318
		} else
rswindell's avatar
rswindell committed
319
			lprintf(LOG_ERR, "ERROR %d (%s) restoring draft message: %s", errno, strerror(errno), draft);
320
	}
321
	else if(mode&WM_QUOTE && !(useron.rest&FLAG('J'))
322 323 324 325 326 327
		&& ((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 */

328 329
		if(useron_xedit && cfg.xedit[useron_xedit-1]->misc&QUOTEALL) {
			quotes_fname(useron_xedit, str, sizeof(str));
330
			if((stream=fnopen(NULL,str,O_RDONLY))==NULL) {
331
				errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
332
				free(buf);
333 334
				return(false); 
			}
335 336 337 338
			if(cfg.xedit[useron_xedit - 1]->type == XTRN_WWIV) { // 2 lines of metadata
				fgets(str, sizeof(str), stream);
				fgets(str, sizeof(str), stream);
			}
339 340
			if((file=nopen(msgtmp,O_WRONLY|O_CREAT|O_TRUNC))==-1) {
				errormsg(WHERE,ERR_OPEN,msgtmp,O_WRONLY|O_CREAT|O_TRUNC);
341
				free(buf);
342
				fclose(stream);
343 344
				return(false); 
			}
345 346

			while(!feof(stream) && !ferror(stream)) {
347
				if(!fgets(str,sizeof(str),stream))
348 349
					break;
				quotestr(str);
350
				SAFEPRINTF2(tmp,quote_fmt,cols-4,str);
351
				write(file,tmp,strlen(tmp));
352 353
				linesquoted++; 
			}
354
			fclose(stream);
355 356
			close(file); 
		}
357 358 359

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

360
		else if(useron_xedit && cfg.xedit[useron_xedit-1]->misc&QUOTENONE)
361 362 363
			;

		else if(yesno(text[QuoteMessageQ])) {
364
			quotes_fname(useron_xedit, str, sizeof(str));
365 366
			if((stream=fnopen(&file,str,O_RDONLY))==NULL) {
				errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
367
				free(buf);
368 369
				return(false); 
			}
370

371 372 373 374 375
			if(useron_xedit > 0 && cfg.xedit[useron_xedit - 1]->type == XTRN_WWIV) { // 2 lines of metadata
				fgets(str, sizeof(str), stream);
				fgets(str, sizeof(str), stream);
			}

376 377
			if((file=nopen(msgtmp,O_WRONLY|O_CREAT|O_TRUNC))==-1) {
				errormsg(WHERE,ERR_OPEN,msgtmp,O_WRONLY|O_CREAT|O_TRUNC);
378
				free(buf);
379
				fclose(stream);
380 381
				return(false); 
			}
382

383
			l=(long)ftell(stream);			/* l now points to start of message */
384 385

			while(online) {
386
				SAFEPRINTF(str,text[QuoteLinesPrompt],linesquoted ? text[Done] : text[All]);
387 388 389 390 391
				mnemonics(str);
				i=getstr(quote,10,K_UPPER);
				if(sys_status&SS_ABORT) {
					fclose(stream);
					close(file);
392
					free(buf);
393 394
					return(false); 
				}
395 396
				if(!i && linesquoted)
					break;
397
				if(!i || quote[0]==all_key()) {                   /* Quote all */
398 399
					fseek(stream,l,SEEK_SET);
					while(!feof(stream) && !ferror(stream)) {
400
						if(!fgets(str,sizeof(str),stream))
401 402
							break;
						quotestr(str);
403
						SAFEPRINTF2(tmp,quote_fmt,cols-4,str);
404
						write(file,tmp,strlen(tmp));
405 406 407 408
						linesquoted++; 
					}
					break; 
				}
409
				if(quote[0]==list_key()) {
410 411 412 413 414
					fseek(stream,l,SEEK_SET);
					i=1;
					CRLF;
					attr(LIGHTGRAY);
					while(!feof(stream) && !ferror(stream) && !msgabort()) {
415
						if(!fgets(str,sizeof(str),stream))
416 417
							break;
						quotestr(str);
418
						bprintf(P_AUTO_UTF8, "%4d: %.*s\r\n", i, (int)cols-7, str);
419 420 421 422
						i++; 
					}
					continue; 
				}
423

424
				if(!IS_DIGIT(quote[0]))
425 426 427
					break;
				p=quote;
				while(p) {
428
					if(*p==',' || *p==' ')
429 430 431 432 433 434 435
						p++;
					i=atoi(p);
					if(!i)
						break;
					fseek(stream,l,SEEK_SET);
					j=1;
					while(!feof(stream) && !ferror(stream) && j<i) {
436
						if(!fgets(tmp,sizeof(tmp),stream))
437
							break;
438 439
						j++; /* skip beginning */
					}		
440 441 442 443
					tp=strchr(p,'-');   /* tp for temp pointer */
					if(tp) {		 /* range */
						i=atoi(tp+1);
						while(!feof(stream) && !ferror(stream) && j<=i) {
444
							if(!fgets(str,sizeof(str),stream))
445 446
								break;
							quotestr(str);
447
							SAFEPRINTF2(tmp,quote_fmt,cols-4,str);
448 449
							write(file,tmp,strlen(tmp));
							linesquoted++;
450 451 452
							j++; 
						} 
					}
453
					else {			/* one line */
454
						if(fgets(str,sizeof(str),stream)) {
455
							quotestr(str);
456
							SAFEPRINTF2(tmp,quote_fmt,cols-4,str);
457
							write(file,tmp,strlen(tmp));
458 459 460
							linesquoted++; 
						} 
					}
461
					p=strchr(p,',');
462
					// if(!p) p=strchr(p,' ');  02/05/96 huh?
463 464
				} 
			}
465 466

			fclose(stream);
467 468 469
			close(file); 
		} 
	}
470
	else {
471
		(void)removecase(quotes_fname(useron_xedit, str, sizeof(str)));
472
	}
473 474

	if(!online || sys_status&SS_ABORT) {
475
		free(buf);
476 477
		return(false); 
	}
478

479
	if(!(mode&(WM_EXTDESC|WM_SUBJ_RO))) {
rswindell's avatar
Bugfix:  
rswindell committed
480 481
		int	max_title_len;

482 483
		if(mode&WM_FILE) {
			CRLF;
484 485
			bputs(text[Filename]); 
		}
486
		else {
487 488
			bputs(text[SubjectPrompt]); 
		}
rswindell's avatar
Bugfix:  
rswindell committed
489 490 491
		max_title_len=cols-column-1;
		if(max_title_len > LEN_TITLE)
			max_title_len = LEN_TITLE;
492 493
		if(draft_restored)
			user_get_property(&cfg, useron.number, draft_desc, "subject", subj, max_title_len);
494
		if(!getstr(subj,max_title_len,mode&WM_FILE ? K_LINE|K_TRIM : K_LINE|K_EDIT|K_AUTODEL|K_TRIM)
495
			&& useron_level && useron.logons) {
496
			free(buf);
497 498
			return(false); 
		}
499
		if((mode&WM_FILE) && !checkfname(subj)) {
500
			free(buf);
501
			bprintf(text[BadFilename], subj);
502 503
			return(false);
		}
504 505
		if(!(mode&(WM_EMAIL|WM_NETMAIL)) && cfg.sub[subnum]->misc&SUB_QNET
			&& !SYSOP
506 507
			&& (!stricmp(subj,"DROP") || !stricmp(subj,"ADD")
			|| !strnicmp(to,"SBBS",4))) {
508
			free(buf);   /* Users can't post DROP or ADD in QWK netted subs */
509 510 511
			return(false); /* or messages to "SBBS" */
		}
	}
512 513

	if(!online || sys_status&SS_ABORT) {
514
		free(buf);
515 516
		return(false); 
	}
517

518
	editor_details[0] = 0;
519 520
	smb.subnum = subnum;	/* Allow JS msgeditors to use bbs.smb_sub* */

521
	if(console&CON_RAW_IN) {
522 523

		if(editor != NULL)
524
			*editor = "Synchronet writemsg " GIT_BRANCH "/" GIT_HASH;
525

526 527 528 529 530
		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);
531 532
			l=strlen((char *)buf); 
		}
533 534 535 536 537
		else
			l=0;
		while(l<(ulong)(cfg.level_linespermsg[useron_level]*MAX_LINE_LEN)) {
			c=getkey(0);
			if(sys_status&SS_ABORT) {  /* Ctrl-C */
538
				free(buf);
539 540
				return(false); 
			}
541
			if((c==ESC || c==CTRL_A) && useron.rest&FLAG('A')) /* ANSI restriction */
542
				continue;
543
			if(c==BEL && useron.rest&FLAG('B'))   /* Beep restriction */
544 545 546 547
				continue;
			if(!(console&CON_RAW_IN))	/* Ctrl-Z was hit */
				break;
			outchar(c);
548 549
			buf[l++]=c; 
		}
550 551
		buf[l]=0;
		if(l==(ulong)cfg.level_linespermsg[useron_level]*MAX_LINE_LEN)
552 553
			bputs(text[OutOfBytes]); 
	}
554

555
	else if(useron_xedit) {
556

557
		if(editor!=NULL)
558
			*editor=cfg.xedit[useron_xedit-1]->name;
559 560 561
		if(!str_is_ascii(subj)) {
			if(utf8_str_is_valid(subj)) {
				if(!term_supports(UTF8) || !(cfg.xedit[useron_xedit-1]->misc & XTRN_UTF8)) {
562
					utf8_to_cp437_inplace(subj);
563 564 565 566 567 568 569 570
				}
			} else { // CP437
				if(term_supports(UTF8) && (cfg.xedit[useron_xedit-1]->misc & XTRN_UTF8)) {
					cp437_to_utf8_str(subj, str, sizeof(str) - 1, /* minval: */'\x80');
					safe_snprintf(subj, LEN_TITLE, "%s", str);
				}
			}
		}
571
		editor_inf(useron_xedit,to,from,subj,mode,subnum,tagfile);
572
		if(cfg.xedit[useron_xedit-1]->type) {
573
			gettimeleft();
574
			xtrndat(mode&WM_ANON ? text[Anonymous]:from,cfg.node_dir,cfg.xedit[useron_xedit-1]->type
575
 			   ,timeleft,cfg.xedit[useron_xedit-1]->misc); 
576 577
		}

578
		if(cfg.xedit[useron_xedit-1]->misc&XTRN_STDIO) {
579
			ex_mode|=EX_STDIO;
580
			if(cfg.xedit[useron_xedit-1]->misc&WWIVCOLOR)
581 582
				ex_mode|=EX_WWIV; 
		}
583
		if(cfg.xedit[useron_xedit-1]->misc&XTRN_NATIVE)
584
			ex_mode|=EX_NATIVE;
585
		if(cfg.xedit[useron_xedit-1]->misc&XTRN_SH)
586
			ex_mode|=EX_SH;
587

588 589
		if(!draft_restored) {
			if(!linesquoted)
590
				(void)removecase(msgtmp);
591 592 593 594
			else {
				qlen=(long)flength(msgtmp);
				qtime=(long)fdate(msgtmp); 
			}
595
		}
596

597 598
		CLS;
		rioctl(IOCM|PAUSE|ABORT);
599
		const char* cmd = cmdstr(cfg.xedit[useron_xedit-1]->rcmd, msgtmp, nulstr, NULL, ex_mode);
600 601
		int result = external(cmd, ex_mode, cfg.node_dir);
		lprintf(LOG_DEBUG, "'%s' returned %d", cmd, result);
602 603
		rioctl(IOSM|PAUSE|ABORT); 

604
		checkline();
605
		if(!online && flength(msgtmp) > 0)	 { // save draft message due to disconnection
606 607 608
			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));
609 610
				lprintf(LOG_NOTICE, "draft message saved: %s (%lu bytes)", draft, (ulong)flength(draft));
			} else
rswindell's avatar
rswindell committed
611
				lprintf(LOG_ERR, "ERROR %d (%s) saving draft message: %s", errno, strerror(errno), draft);
612 613 614
		}

		if(result != EXIT_SUCCESS || !fexistcase(msgtmp) || !online
615
			|| (linesquoted && qlen==flength(msgtmp) && qtime==fdate(msgtmp))) {
616
			free(buf);
617 618
			return(false); 
		}
619
		SAFEPRINTF(str,"%sRESULT.ED",cfg.node_dir);
620
		if(!(mode&(WM_EXTDESC|WM_FILE))
621 622
			&& fexistcase(str)) {
			if((fp=fopen(str,"r")) != NULL) {
623 624 625 626
				if (fgets(str, sizeof(str), fp) != NULL) {
					str[0] = 0;
					if (fgets(str, sizeof(str), fp) != NULL) {
						truncsp(str);
627
						if(str[0] && !(mode&WM_SUBJ_RO))
628 629 630 631 632 633
							safe_snprintf(subj, LEN_TITLE, "%s", str);
                        if (fgets(editor_details, sizeof(editor_details), fp) != NULL) {
                            truncsp(editor_details);
                        }
					}
				}
634 635 636 637
				fclose(fp);
			}
		}

638 639 640
		buf[0]=0;
		if(!(mode&WM_NOTOP))
			strcpy((char *)buf,top);
641 642
		if((file=nopen(msgtmp,O_RDONLY))==-1) {
			errormsg(WHERE,ERR_OPEN,msgtmp,O_RDONLY);
643
			free(buf);
644 645
			return(false); 
		}
646
		length=(long)filelength(file);
Rob Swindell's avatar
Rob Swindell committed
647 648 649 650 651
		if(length < 0) {
			errormsg(WHERE, ERR_LEN, msgtmp, length);
			free(buf);
			return false;
		}
652 653 654 655
		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);
656 657
			bputs(text[OutOfBytes]); 
		}
Rob Swindell's avatar
Rob Swindell committed
658
		long rd = read(file,buf+l,length);
659
		close(file);
Rob Swindell's avatar
Rob Swindell committed
660 661 662 663 664
		if(rd != length) {
			errormsg(WHERE, ERR_READ, msgtmp, length);
			free(buf);
			return false;
		}
665
		// remove(msgtmp); 	   /* no need to save the temp input file */
666 667
		buf[l+length]=0; 
	}
668
	else {
669 670

		if(editor != NULL)
671
			*editor = "Synchronet msgeditor " GIT_BRANCH "/" GIT_HASH;
672

673
		buf[0]=0;
674
		if(linesquoted || draft_restored) {
675
			if((file=nopen(msgtmp,O_RDONLY))!=-1) {
676
				length=(long)filelength(file);
677 678
				l=length>(cfg.level_linespermsg[useron_level]*MAX_LINE_LEN)-1
					? (cfg.level_linespermsg[useron_level]*MAX_LINE_LEN)-1 : length;
679 680 681
				lread(file,buf,l);
				buf[l]=0;
				close(file);
682
				// remove(msgtmp);
683 684
			} 
		}
685
		if(!(msgeditor((char *)buf,mode&WM_NOTOP ? nulstr : top, subj))) {
686 687 688 689 690 691 692 693 694 695
			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);
				}
			}
696
			free(buf);	/* Assertion here Dec-17-2003, think I fixed in block above (rev 1.52) */
697 698 699
			return(false); 
		} 
	}
700 701 702

	now=time(NULL);
	bputs(text[Saving]);
703
	(void)removecase(fname);
704
	if((stream=fnopen(NULL,fname,O_WRONLY|O_CREAT|O_TRUNC))==NULL) {
705
		errormsg(WHERE,ERR_OPEN,fname,O_WRONLY|O_CREAT|O_TRUNC);
706
		free(buf);
707 708
		return(false); 
	}
709
	l=process_edited_text(buf,stream,mode,&lines,cfg.level_linespermsg[useron_level]);
710 711
	if(editor_details[0] && editor != NULL)
		*editor = editor_details;
712
	bool utf8 = !str_is_ascii(buf) && utf8_str_is_valid(buf);
713
	if(charset != NULL) {
714
		if(utf8)
715 716
			*charset = FIDO_CHARSET_UTF8;
	}
717

718
	if(!(mode&(WM_EXTDESC|WM_ANON))) {
719 720 721 722 723
		/* 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;
724
			if(fexistcase(str) && (sig=fopen(str,"r"))!=NULL) {
725 726 727
				while(!feof(sig)) {
					if(!fgets(str,sizeof(str),sig))
						break;
728
					truncnl(str);
729 730 731 732 733 734
					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);
735 736 737 738 739 740 741 742 743 744 745 746 747
					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);
748 749 750 751 752 753
					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);
754 755 756
					lines++;		/* line counter */
				}
				fclose(tag);
757
			}
758 759 760
		}
	}

761
	(void)remove(draft);
762
	fclose(stream);
763
	free((char *)buf);
764
	bprintf(text[SavedNBytes],l,lines);
765 766 767
	return(true);
}

768
void sbbs_t::editor_info_to_msg(smbmsg_t* msg, const char* editor, const char* charset)
769
{
770 771
	smb_hfield_string(msg, SMB_EDITOR, editor);
	smb_hfield_string(msg, FIDOCHARSET, charset);
772

773 774 775 776 777 778 779
	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);
780 781 782 783

	if(!str_is_ascii(msg->subj) && utf8_str_is_valid(msg->subj))
		msg->hdr.auxattr |= MSG_HFIELDS_UTF8;
}  
784

785
/****************************************************************************/
786 787 788 789 790
/****************************************************************************/
/* Modify 'str' to for quoted format. Remove ^A codes, etc.                 */
/****************************************************************************/
void quotestr(char *str)
{
rswindell's avatar
rswindell committed
791 792
	truncsp(str);
	remove_ctrl_a(str,str);
793 794
}

795 796
/****************************************************************************/
/****************************************************************************/
797
void sbbs_t::editor_inf(int xeditnum, const char *to, const char* from, const char *subj, long mode
798
	,uint subnum, const char* tagfile)
799
{
800