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

prntfile.cpp 9.7 KB
Newer Older
1
/* prntfile.cpp */
2
// vi: tabstop=4
3 4 5

/* Synchronet file print/display routines */

6
/* $Id: prntfile.cpp,v 1.47 2020/05/26 03:07:05 rswindell Exp $ */
7 8 9 10 11

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
12
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
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 38 39
 *																			*
 * 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"
40
#include "utf8.h"
41
#include "petdefs.h"
42

43
#ifndef PRINTFILE_MAX_LINE_LEN
44
#define PRINTFILE_MAX_LINE_LEN (8*1024)
45
#endif
46
#ifndef PRINTFILE_MAX_FILE_LEN
47
#define PRINTFILE_MAX_FILE_LEN (2*1024*1024)
48
#endif
49

50 51 52 53 54
/****************************************************************************/
/* Prints a file remotely and locally, interpreting ^A sequences, checks    */
/* for pauses, aborts and ANSI. 'str' is the path of the file to print      */
/* Called from functions menu and text_sec                                  */
/****************************************************************************/
55
bool sbbs_t::printfile(const char* fname, long mode, long org_cols, JSObject* obj)
56
{
57
	char* buf;
58
	char fpath[MAX_PATH+1];
59
	char* p;
rswindell's avatar
rswindell committed
60
	int file;
61
	BOOL rip=FALSE;
rswindell's avatar
rswindell committed
62
	long l,length,savcon=console;
63 64
	FILE *stream;

65 66 67
	SAFECOPY(fpath, fname);
	fexistcase(fpath);
	p=getfext(fpath);
68
	if(p!=NULL) {
69
		if(stricmp(p,".rip")==0) {
rswindell's avatar
rswindell committed
70
			rip=TRUE;
rswindell's avatar
rswindell committed
71
			mode|=P_NOPAUSE;
72 73
		} else if(stricmp(p, ".seq") == 0) {
			mode |= P_PETSCII;
74 75
		} else if(stricmp(p, ".utf8") == 0) {
			mode |= P_UTF8;
rswindell's avatar
rswindell committed
76
		}
77
	}
78

79
	if(mode&P_NOABORT || rip) {
80 81
		if(online==ON_REMOTE && console&CON_R_ECHO) {
			rioctl(IOCM|ABORT);
rswindell's avatar
rswindell committed
82 83 84 85
			rioctl(IOCS|ABORT); 
		}
		sys_status&=~SS_ABORT; 
	}
86

rswindell's avatar
rswindell committed
87 88
	if(!(mode&P_NOCRLF) && row > 0 && !rip) {
		newline();
89
	}
90

91
	if((stream=fnopen(&file,fpath,O_RDONLY|O_DENYNONE))==NULL) {
92 93
		if(!(mode&P_NOERROR)) {
			lprintf(LOG_NOTICE,"!Error %d (%s) opening: %s"
94
				,errno,strerror(errno),fpath);
95
			bputs(text[FileNotFound]);
96
			if(SYSOP) bputs(fpath);
97 98 99
			CRLF;
		}
		return false; 
rswindell's avatar
rswindell committed
100
	}
101

102
	length=(long)filelength(file);
103
	if(length < 1) {
rswindell's avatar
rswindell committed
104
		fclose(stream);
105 106 107 108 109
		if(length < 0) {
			errormsg(WHERE,ERR_CHK,fpath,length);
			return false;
		}
		return true;
rswindell's avatar
rswindell committed
110
	}
111

112
	if((mode&P_OPENCLOSE) && length <= PRINTFILE_MAX_FILE_LEN) {
113 114 115 116 117 118 119 120 121 122 123 124 125
		if((buf=(char*)malloc(length+1L))==NULL) {
			fclose(stream);
			errormsg(WHERE,ERR_ALLOC,fpath,length+1L);
			return false; 
		}
		l=lread(file,buf,length);
		fclose(stream);
		if(l!=length)
			errormsg(WHERE,ERR_READ,fpath,length);
		else {
			buf[l]=0;
			if((mode&P_UTF8) && !term_supports(UTF8))
				utf8_normalize_str(buf);
126
			putmsg(buf,mode,org_cols, obj);
127 128 129
		}
		free(buf);
	} else {	// Line-at-a-time mode
130 131
		ulong sys_status_sav = sys_status;
		enum output_rate output_rate = cur_output_rate;
132
		uint tmpatr = curatr;
133 134
		ulong orgcon = console;
		attr_sp = 0;	/* clear any saved attributes */
135 136
		if(!(mode&P_SAVEATR))
			attr(LIGHTGRAY);
137 138
		if(mode&P_NOPAUSE)
			sys_status |= SS_PAUSEOFF;
139 140 141 142 143 144 145 146
		if(length > PRINTFILE_MAX_LINE_LEN)
			length = PRINTFILE_MAX_LINE_LEN;
		if((buf=(char*)malloc(length+1L))==NULL) {
			fclose(stream);
			errormsg(WHERE,ERR_ALLOC,fpath,length+1L);
			return false; 
		}
		while(!feof(stream) && !msgabort()) {
147
			if(fgets(buf, length + 1, stream) == NULL)
148 149 150
				break;
			if((mode&P_UTF8) && !term_supports(UTF8))
				utf8_normalize_str(buf);
151
			if(putmsgfrag(buf, &mode, org_cols, obj) != '\0') // early-EOF?
152
				break;
153 154
		}
		free(buf);
rswindell's avatar
rswindell committed
155
		fclose(stream);
156 157
		if(!(mode&P_SAVEATR)) {
			console = orgcon;
158
			attr(tmpatr);
159 160 161 162 163 164 165 166 167 168
		}
		if(!(mode&P_NOATCODES) && cur_output_rate != output_rate)
			set_output_rate(output_rate);
		if(mode&P_PETSCII)
			outcom(PETSCII_UPPERLOWER);
		attr_sp=0;	/* clear any saved attributes */

		/* Restore original settings of Forced Pause On/Off */
		sys_status &= ~(SS_PAUSEOFF|SS_PAUSEON);
		sys_status |= (sys_status_sav&(SS_PAUSEOFF|SS_PAUSEON));
rswindell's avatar
rswindell committed
169
	}
170

171
	if((mode&P_NOABORT || rip) && online==ON_REMOTE) {
172
		SYNC;
rswindell's avatar
rswindell committed
173 174
		rioctl(IOSM|ABORT); 
	}
175 176 177
	if(rip)
		ansi_getlines();
	console=savcon;
178
	return true;
rswindell's avatar
rswindell committed
179
}
180

181
bool sbbs_t::printtail(const char* fname, int lines, long mode, long org_cols, JSObject* obj)
182
{
rswindell's avatar
rswindell committed
183
	char*	buf;
184
	char	fpath[MAX_PATH+1];
rswindell's avatar
rswindell committed
185
	char*	p;
186
	FILE*	fp;
rswindell's avatar
rswindell committed
187 188
	int		file,cur=0;
	long	length,l;
189

190 191
	SAFECOPY(fpath, fname);
	fexistcase(fpath);
192 193 194
	if(mode&P_NOABORT) {
		if(online==ON_REMOTE) {
			rioctl(IOCM|ABORT);
rswindell's avatar
rswindell committed
195 196 197 198
			rioctl(IOCS|ABORT); 
		}
		sys_status&=~SS_ABORT; 
	}
rswindell's avatar
rswindell committed
199 200
	if(!(mode&P_NOCRLF) && row > 0) {
		newline();
rswindell's avatar
rswindell committed
201
	}
202
	if((fp=fnopen(&file,fpath,O_RDONLY|O_DENYNONE))==NULL) {
203 204
		if(!(mode&P_NOERROR)) {
			lprintf(LOG_NOTICE,"!Error %d (%s) opening: %s"
205
				,errno,strerror(errno),fpath);
206
			bputs(text[FileNotFound]);
207
			if(SYSOP) bputs(fpath);
208 209 210
			CRLF;
		}
		return false; 
rswindell's avatar
rswindell committed
211
	}
212
	length=(long)filelength(file);
rswindell's avatar
rswindell committed
213
	if(length<0) {
214
		fclose(fp);
215
		errormsg(WHERE,ERR_CHK,fpath,length);
216
		return false;
rswindell's avatar
rswindell committed
217
	}
218 219 220 221
	if(length > lines * PRINTFILE_MAX_LINE_LEN) {
		length = lines * PRINTFILE_MAX_LINE_LEN; 
		fseek(fp, -length, SEEK_END);
	}
deuce's avatar
deuce committed
222
	if((buf=(char*)malloc(length+1L))==NULL) {
223
		fclose(fp);
224
		errormsg(WHERE,ERR_ALLOC,fpath,length+1L);
225
		return false; 
rswindell's avatar
rswindell committed
226
	}
227 228
	l=fread(buf, sizeof(char), length, fp);
	fclose(fp);
rswindell's avatar
rswindell committed
229
	if(l!=length)
230
		errormsg(WHERE,ERR_READ,fpath,length);
rswindell's avatar
rswindell committed
231 232 233 234 235 236 237 238 239 240 241 242
	else {
		buf[l]=0;
		p=(buf+l)-1;
		if(*p==LF) p--;
		while(*p && p>buf) {
			if(*p==LF)
				cur++;
			if(cur>=lines) {
				p++;
				break; 
			}
			p--; 
rswindell's avatar
rswindell committed
243
		}
244
		putmsg(p,mode,org_cols, obj);
rswindell's avatar
rswindell committed
245
	}
246 247
	if(mode&P_NOABORT && online==ON_REMOTE) {
		SYNC;
rswindell's avatar
rswindell committed
248 249
		rioctl(IOSM|ABORT); 
	}
deuce's avatar
deuce committed
250
	free(buf);
251
	return true;
252 253 254
}

/****************************************************************************/
255
/* Displays a menu file (e.g. from the text/menu directory)                 */
256 257
/* Pass a code including wildcards (* or ?) to display a randomly-chosen	*/
/* file matching the pattern in 'code'										*/
258
/****************************************************************************/
259
bool sbbs_t::menu(const char *code, long mode, JSObject* obj)
260
{
261 262 263
    char path[MAX_PATH+1];
	const char *next= "msg";
	const char *last = "asc";
264

265 266 267
	if(strcspn(code, "*?") != strlen(code))
		return random_menu(code, mode, obj);

268 269
	sys_status&=~SS_ABORT;
	if(menu_file[0])
270
		SAFECOPY(path,menu_file);
271
	else {
rswindell's avatar
rswindell committed
272
		long term = term_supports();
273 274 275 276 277 278 279 280 281 282 283 284
		do {
			if((term&RIP) && menu_exists(code, "rip", path))
				break;
			if((term&(ANSI|COLOR)) == ANSI && menu_exists(code, "mon", path))
				break;
			if((term&ANSI) && menu_exists(code, "ans", path))
				break;
			if((term&PETSCII) && menu_exists(code, "seq", path))
				break;
			if(term&NO_EXASCII) {
				next = "asc";
				last = "msg";
rswindell's avatar
rswindell committed
285
			}
286 287 288 289
			if(menu_exists(code, next, path))
				break;
			menu_exists(code, last, path);
		} while(0);
290
	}
291

292
	mode |= P_OPENCLOSE | P_CPM_EOF;
293 294
	if(column == 0)
		mode |= P_NOCRLF;
295
	return printfile(path, mode, /* org_cols: */0, obj);
296 297
}

298
bool sbbs_t::menu_exists(const char *code, const char* ext, char* path)
299
{
300 301 302
	char pathbuf[MAX_PATH+1];
	if(path == NULL)
		path = pathbuf;
303

304 305 306 307 308 309 310 311 312
	if(menu_file[0]) {
		strncpy(path, menu_file, MAX_PATH);
		return fexistcase(path) ? true : false;
	}

	/* Either <menu>.asc or <menu>.msg is required */
	if(ext == NULL)
		return menu_exists(code, "asc", path)
			|| menu_exists(code, "msg", path);
313

314 315 316 317 318 319
	char prefix[MAX_PATH];
	if(isfullpath(code))
		SAFECOPY(prefix, code);
	else {
		backslash(menu_dir);
		SAFEPRINTF3(prefix, "%smenu/%s%s", cfg.text_dir, menu_dir, code);
320 321
		FULLPATH(path, prefix, MAX_PATH);
		SAFECOPY(prefix, path);
322
	}
323
	safe_snprintf(path, MAX_PATH, "%s.%lucol.%s", prefix, cols, ext);
rswindell's avatar
rswindell committed
324 325
	if(fexistcase(path))
		return true;
326
	safe_snprintf(path, MAX_PATH, "%s.%s", prefix, ext);
deuce's avatar
deuce committed
327 328
	return fexistcase(path) ? true : false;
}
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359

/****************************************************************************/
/* Displays a random menu file (e.g. from the text/menu directory)          */
/****************************************************************************/
bool sbbs_t::random_menu(const char *name, long mode, JSObject* obj)
{
	char path[MAX_PATH + 1];
	glob_t g = {0};
	str_list_t names = NULL;

	SAFEPRINTF2(path, "%smenu/%s", cfg.text_dir, name);
	if(glob(path, GLOB_NOESCAPE|GLOB_MARK, NULL, &g) != 0) {
		return false;
	}
	for(size_t i = 0; i < g.gl_pathc; i++) {
		char* ext = getfext(g.gl_pathv[i]);
		if(ext == NULL)
			continue;
		*ext = 0;
		strListPush(&names, g.gl_pathv[i]);
	}
	globfree(&g);
	strListDedupe(&names, /* case_sensitive: */true);
	bool result = false;
	size_t i = sbbs_random(strListCount(names));
	if(menu_exists(names[i], NULL, path)) {
		result = menu(names[i], mode, obj);
	}
	strListFree(&names);
	return result;
}