about summary refs log blame commit diff stats
path: root/src/LYPrint.c
blob: 9149956e04b0c3c24ea279eacb3c52ad675eae72 (plain) (tree)
1
2
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
  
                                                           



































































































































                                                                                   
                                             



                                                   
             
 
                                                                         







                                                              
                                          
                               
                                                           
                                                                 


                                                       
                                                             
                                             

         
                                                       









                      
                                             

















                                                                   
                                                              
                                                                        
                                          






















                                                                         
                                         






                                                        
                                                 





















                                                                          
                                         

























































                                                        

                             




                                 
 
        






                                           
     
 
               
                                                              










                                                      
                                                  





                                            
                                                













                                         
                                                                               

                 
                                




                                         
                                                                        









                                                           

                                                                   


































































                                                                                               
                                     





                                  


                                                         
                
                                              

            
                                      

     
                                  


                

                       

























                                                     
                                  














                                                                         
                                                             
                                                                  
                                  









































                                                                              



                                           

                       

                                                         
























































































                                                                               
                                       










                                                                            
                                       










                            

                                     




















































































                                                                              
                                                                              




















                                                                               
                                      



                           
                                                                 



                                                 
                            












                                                    
                            


















































                                                                               
                           


                                                        
                                              
                  
                                                                 











                                                          
                                                   










                                                                           
                                           

                                                 
                                       

                                                               
                                                               

                                                                        





                                                               
                                                                    

























                                                                               
                                 






















                                                          
                      







                                                   
                           






                                           
                          
                                                        
                                             

                            
 

                              

           
                                

                 















                                                                                   

          




                                                 
                







                                                                   
          
                                    
                
         
            
                              
      


                                

                                                   
                     








































































































































































































































































































































                                                                                     


                         










                                 
                             





                                                                              
                                                          





                                                        
                     








                                                                        


                                                       
                
                                            

            
                                    

     
                                




                                                   
                        
                                          
                     



                                             
                                            











                                            
                                    










                                                                             
       
                                    
 



                    
 
/*
 * $LynxId: LYPrint.c,v 1.105 2014/08/24 10:41:17 tom Exp $
 */
#include <HTUtils.h>
#include <HTAccess.h>
#include <HTList.h>
#include <HTAlert.h>
#include <HTFile.h>
#include <LYCurses.h>
#include <GridText.h>
#include <LYUtils.h>
#include <LYPrint.h>
#include <LYGlobalDefs.h>
#include <LYSignal.h>
#include <LYStrings.h>
#include <LYClean.h>
#include <LYGetFile.h>
#include <LYHistory.h>
#include <LYList.h>
#include <LYCharSets.h>		/* To get current charset for mail header. */

#include <LYLeaks.h>

#define CancelPrint(msg) HTInfoMsg(msg); goto done
#define CannotPrint(msg) HTAlert(msg); goto done

/*
 * printfile prints out the current file minus the links and targets to a
 * variety of places
 */

/* it parses an incoming link that looks like
 *
 * LYNXPRINT://LOCAL_FILE/lines=##
 * LYNXPRINT://MAIL_FILE/lines=##
 * LYNXPRINT://TO_SCREEN/lines=##
 * LYNXPRINT://LPANSI/lines=##
 * LYNXPRINT://PRINTER/lines=##/number=#
 */

#define TO_FILE   1
#define TO_SCREEN 2
/*
 * "lpansi.c"
 * Original author: Gary Day (gday@comp.uark.edu), 11/30/93
 * Current version: 2.1 by Noel Hunter (noel@wfu.edu), 10/20/94
 *
 * Basic structure based on print -- format files for printing from
 * _Practical_C_Programming by Steve Oualline, O'Reilly & Associates
 *
 * adapted from the README for lpansi.c v2.1, dated 10/20/1994:
 *		    Print to ANSI printer on local terminal
 *     The VT100 standard defines printer on and off escape sequences,
 *     esc[5i is printer on, and esc[4i is printer off.
 *
 * incorporate the idea of "lpansi" directly into LYPrint.c - HN
 */
#define LPANSI	  3
#define MAIL	  4
#define PRINTER   5

#if USE_VMS_MAILER
static int remove_quotes(char *string);
#endif /* USE_VMS_MAILER */

static char *subject_translate8bit(char *source);

#define LYNX_PRINT_TITLE   0
#define LYNX_PRINT_URL     1
#define LYNX_PRINT_DATE    2
#define LYNX_PRINT_LASTMOD 3

#define MAX_PUTENV 4

static void set_environ(int name,
			const char *value,
			const char *no_value)
{
    static const char *names[MAX_PUTENV] =
    {
	"LYNX_PRINT_TITLE",
	"LYNX_PRINT_URL",
	"LYNX_PRINT_DATE",
	"LYNX_PRINT_LASTMOD",
    };
    static char *pointers[MAX_PUTENV];
    char *envbuffer = 0;

#ifdef VMS
#define SET_ENVIRON(name, value, no_value) set_environ(name, value, no_value)
    char temp[80];

    StrAllocCopy(envbuffer, value);
    if (isEmpty(envbuffer))
	StrAllocCopy(envbuffer, no_value);
    Define_VMSLogical(strcpy(temp, names[name]), envbuffer);
    FREE(envbuffer);
#else
#define SET_ENVIRON(name, value, no_value) set_environ(name, value, "")
    /*
     * Once we've given a string to 'putenv()', we must not free it until we
     * give it a string to replace it.
     */
    StrAllocCopy(envbuffer, names[name]);
    StrAllocCat(envbuffer, "=");
    StrAllocCat(envbuffer, value ? value : no_value);
    putenv(envbuffer);
    FREE(pointers[name]);
    pointers[name] = envbuffer;
#endif
}

static char *suggested_filename(DocInfo *newdoc)
{
    char *sug_filename = 0;
    int rootlen;

    /*
     * Load the suggested filename string.  - FM
     */
    if (HText_getSugFname() != 0)
	StrAllocCopy(sug_filename, HText_getSugFname());	/* must be freed */
    else
	StrAllocCopy(sug_filename, newdoc->address);	/* must be freed */
    /*
     * Strip suffix for compressed-files, if present.
     */
    if (HTCompressFileType(sug_filename, ".", &rootlen) != cftNone)
	sug_filename[rootlen] = '\0';

    CTRACE((tfp, "suggest %s\n", sug_filename));
    return sug_filename;
}

static void SetupFilename(bstring **filename,
			  const char *sug_filename)
{
    HTFormat format;
    HTAtom *encoding;
    char *cp;

    BStrCopy0(*filename, sug_filename);		/* add suggestion info */
    /*
     * FIXME: the history-recall still uses fixed-size buffers
     */
    if ((*filename)->len >= LY_MAXPATH) {
	(*filename)->str[LY_MAXPATH - 1] = '\0';
    } else {
	BStrAlloc(*filename, LY_MAXPATH);
    }
    change_sug_filename((*filename)->str);
    if (!(HTisDocumentSource())
	&& (cp = strrchr((*filename)->str, '.')) != NULL) {
	format = HTFileFormat((*filename)->str, &encoding, NULL);
	CTRACE((tfp, "... format %s\n", format->name));
	if (!strcasecomp(format->name, "text/html") ||
	    !IsUnityEnc(encoding)) {
	    (*filename)->len = (int) (cp - (*filename)->str);
	    BStrCat0(*filename, TEXT_SUFFIX);
	}
    }
    CTRACE((tfp, "... result %s\n", (*filename)->str));
}

#define FN_INIT 0
#define FN_READ 1
#define FN_DONE 2
#define FN_QUIT 3

#define PRINT_FLAG   0
#define GENERIC_FLAG 1

static int RecallFilename(bstring **filename,
			  BOOLEAN *first,
			  int *now,
			  int *total,
			  int flag)
{
    int ch;
    char *cp;
    RecallType recall;

    /*
     * Set up the sug_filenames recall buffer.
     */
    if (*now < 0) {
	*total = (sug_filenames ? HTList_count(sug_filenames) : 0);
	*now = *total;
    }
    recall = ((*total >= 1) ? RECALL_URL : NORECALL);

    if ((ch = LYgetBString(filename, FALSE, 0, recall)) < 0 ||
	isBEmpty(*filename) || ch == UPARROW_KEY || ch == DNARROW_KEY) {
	if (recall && ch == UPARROW_KEY) {
	    if (*first) {
		*first = FALSE;
		/*
		 * Use the last Fname in the list.  - FM
		 */
		*now = 0;
	    } else {
		/*
		 * Go back to the previous Fname in the list.  - FM
		 */
		*now += 1;
	    }
	    if (*now >= *total) {
		/*
		 * Reset the *first flag, and use sug_file or a blank.  -
		 * FM
		 */
		*first = TRUE;
		*now = *total;
		_statusline(FILENAME_PROMPT);
		return FN_INIT;
	    } else if ((cp = (char *) HTList_objectAt(sug_filenames,
						      *now)) != NULL) {
		BStrCopy0(*filename, cp);
		if (*total == 1) {
		    _statusline(EDIT_THE_PREV_FILENAME);
		} else {
		    _statusline(EDIT_A_PREV_FILENAME);
		}
		return FN_READ;
	    }
	} else if (recall && ch == DNARROW_KEY) {
	    if (*first) {
		*first = FALSE;
		/*
		 * Use the first Fname in the list. - FM
		 */
		*now = *total - 1;
	    } else {
		/*
		 * Advance to the next Fname in the list. - FM
		 */
		*now -= 1;
	    }
	    if (*now < 0) {
		/*
		 * Set the *first flag, and use sug_file or a blank.  - FM
		 */
		*first = TRUE;
		*now = *total;
		_statusline(FILENAME_PROMPT);
		return FN_INIT;
	    } else if ((cp = (char *) HTList_objectAt(sug_filenames,
						      *now)) != NULL) {
		BStrCopy0(*filename, cp);
		if (*total == 1) {
		    _statusline(EDIT_THE_PREV_FILENAME);
		} else {
		    _statusline(EDIT_A_PREV_FILENAME);
		}
		return FN_READ;
	    }
	}

	/*
	 * Operation cancelled.
	 */
	if (flag == PRINT_FLAG)
	    HTInfoMsg(SAVE_REQUEST_CANCELLED);
	else if (flag == GENERIC_FLAG)
	    return FN_QUIT;

	return FN_QUIT;
    }
    return FN_DONE;
}

static BOOLEAN confirm_by_pages(const char *prompt,
				int lines_in_file,
				int lines_per_page)
{
    int pages = lines_in_file / (lines_per_page + 1);
    int c;

    /* count fractional pages ! */
    if ((lines_in_file % (LYlines + 1)) > 0)
	pages++;

    if (pages > 4) {
	char *msg = 0;

	HTSprintf0(&msg, prompt, pages);
	c = HTConfirmDefault(msg, YES);
	FREE(msg);

	if (c == YES) {
	    LYaddstr("   Ok...");
	} else {
	    HTInfoMsg(PRINT_REQUEST_CANCELLED);
	    return FALSE;
	}
    }
    return TRUE;
}

static void send_file_to_file(DocInfo *newdoc,
			      char *content_base,
			      char *sug_filename)
{
    BOOLEAN FirstRecall = TRUE;
    BOOLEAN use_cte;
    const char *disp_charset;
    FILE *outfile_fp;
    bstring *buffer = NULL;
    bstring *filename = NULL;
    int FnameNum = -1;
    int FnameTotal;
    int c = 0;

    _statusline(FILENAME_PROMPT);

  retry:
    SetupFilename(&filename, sug_filename);
    if (lynx_save_space) {
	BStrCopy0(buffer, lynx_save_space);
	BStrCat(buffer, filename);
	BStrCopy(filename, buffer);
    } else {
	BStrCopy0(buffer, "");
    }

  check_recall:
    switch (RecallFilename(&filename, &FirstRecall, &FnameNum,
			   &FnameTotal, PRINT_FLAG)) {
    case FN_INIT:
	goto retry;
    case FN_READ:
	goto check_recall;
    case FN_QUIT:
	goto done;
    default:
	break;
    }

    if (!LYValidateFilename(&buffer, &filename)) {
	CancelPrint(SAVE_REQUEST_CANCELLED);
    }

    /*
     * See if it already exists.
     */
    switch (c = LYValidateOutput(buffer->str)) {
    case 'Y':
	break;
    case 'N':
	_statusline(NEW_FILENAME_PROMPT);
	FirstRecall = TRUE;
	FnameNum = FnameTotal;
	goto retry;
    default:
	goto done;
    }

    /*
     * See if we can write to it.
     */
    CTRACE((tfp, "LYPrint: filename is %s, action is `%c'\n", buffer->str, c));

#ifdef HAVE_POPEN
    if (buffer->str[0] == '|') {
	if (no_shell) {
	    HTUserMsg(SPAWNING_DISABLED);
	    FirstRecall = TRUE;
	    FnameNum = FnameTotal;
	    goto retry;
	} else if ((outfile_fp = popen(buffer->str + 1, "w")) == NULL) {
	    CTRACE((tfp, "LYPrint: errno is %d\n", errno));
	    HTAlert(CANNOT_WRITE_TO_FILE);
	    _statusline(NEW_FILENAME_PROMPT);
	    FirstRecall = TRUE;
	    FnameNum = FnameTotal;
	    goto retry;
	}
    } else
#endif
	if ((outfile_fp = (TOUPPER(c) == 'A'
			   ? LYAppendToTxtFile(buffer->str)
			   : LYNewTxtFile(buffer->str))) == NULL) {
	CTRACE((tfp, "LYPrint: errno is %d\n", errno));
	HTAlert(CANNOT_WRITE_TO_FILE);
	_statusline(NEW_FILENAME_PROMPT);
	FirstRecall = TRUE;
	FnameNum = FnameTotal;
	goto retry;
    }

    if (LYPrependBaseToSource && HTisDocumentSource()) {
	/*
	 * Added the document's base as a BASE tag to the top of the file.  May
	 * create technically invalid HTML, but will help get any partial or
	 * relative URLs resolved properly if no BASE tag is present to replace
	 * it.  - FM
	 *
	 * Add timestamp (last reload).
	 */

	fprintf(outfile_fp,
		"<!-- X-URL: %s -->\n", newdoc->address);
	if (HText_getDate() != NULL) {
	    fprintf(outfile_fp,
		    "<!-- Date: %s -->\n", HText_getDate());
	    if (HText_getLastModified() != NULL
		&& strcmp(HText_getLastModified(), HText_getDate())
		&& strcmp(HText_getLastModified(),
			  "Thu, 01 Jan 1970 00:00:01 GMT")) {
		fprintf(outfile_fp,
			"<!-- Last-Modified: %s -->\n", HText_getLastModified());
	    }
	}

	fprintf(outfile_fp,
		"<BASE HREF=\"%s\">\n", content_base);
    }

    if (LYPrependCharsetToSource && HTisDocumentSource()) {
	/*
	 * Added the document's charset as a META CHARSET tag to the top of the
	 * file.  May create technically invalid HTML, but will help to resolve
	 * properly the document converted via chartrans:  printed document
	 * correspond to a display charset and we *should* override both
	 * assume_local_charset and original document's META CHARSET (if any).
	 *
	 * Currently, if several META CHARSETs are found Lynx uses the first
	 * only, and it is opposite to BASE where the original BASE in the
	 * <HEAD> overrides ones from the top.
	 *
	 * As in print-to-email we write charset only if the document has 8-bit
	 * characters, and we have no CJK or an unofficial "x-" charset.
	 */
	use_cte = HTLoadedDocumentEightbit();
	disp_charset = LYCharSet_UC[current_char_set].MIMEname;
	if (!use_cte || LYHaveCJKCharacterSet ||
	    strncasecomp(disp_charset, "x-", 2) == 0) {
	} else {
	    fprintf(outfile_fp,
		    "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=%s\">\n\n",
		    disp_charset);
	}
    }

    print_wwwfile_to_fd(outfile_fp, FALSE, FALSE);	/* FILE */
    if (keypad_mode)
	printlist(outfile_fp, FALSE);

#ifdef HAVE_POPEN
    if (LYIsPipeCommand(buffer->str))
	pclose(outfile_fp);
    else
#endif
	LYCloseOutput(outfile_fp);

#ifdef VMS
    if (0 == strncasecomp(buffer->str, "sys$disk:", 9)) {
	if (0 == StrNCmp((buffer->str + 9), "[]", 2)) {
	    HTAddSugFilename(buffer->str + 11);
	} else {
	    HTAddSugFilename(buffer->str + 9);
	}
    } else {
	HTAddSugFilename(buffer->str);
    }
#else
    HTAddSugFilename(buffer->str);
#endif /* VMS */

  done:
    BStrFree(buffer);
    BStrFree(filename);
    return;
}

static void send_file_to_mail(DocInfo *newdoc,
			      char *content_base,
			      char *content_location)
{
    static BOOLEAN first_mail_preparsed = TRUE;

#if USE_VMS_MAILER
    BOOLEAN isPMDF = LYMailPMDF();
    FILE *hfd;
    char hdrfile[LY_MAXPATH];
#endif
    BOOL use_mime;

#if !CAN_PIPE_TO_MAILER
    char my_temp[LY_MAXPATH];
#endif

    BOOL use_cte;
    BOOL use_type;
    const char *disp_charset;
    FILE *outfile_fp;
    char *buffer = NULL;
    char *subject = NULL;
    bstring *user_response = NULL;

    if (!LYSystemMail())
	return;

    if (LYPreparsedSource && first_mail_preparsed &&
	HTisDocumentSource()) {
	if (HTConfirmDefault(CONFIRM_MAIL_SOURCE_PREPARSED, NO) == YES) {
	    LYaddstr("   Ok...");
	    first_mail_preparsed = FALSE;
	} else {
	    CancelPrint(MAIL_REQUEST_CANCELLED);
	}
    }

    _statusline(MAIL_ADDRESS_PROMPT);
    BStrCopy0(user_response, NonNull(personal_mail_address));
    if (LYgetBString(&user_response, FALSE, 0, RECALL_MAIL) < 0 ||
	isBEmpty(user_response)) {
	CancelPrint(MAIL_REQUEST_CANCELLED);
    }

    /*
     * Determine which mail headers should be sent.  Use Content-Type and
     * MIME-Version headers only if needed.  We need them if we are mailing
     * HTML source, or if we have 8-bit characters and will be sending
     * Content-Transfer-Encoding to indicate this.  We will append a charset
     * parameter to the Content-Type if we do not have an "x-" charset, and we
     * will include the Content-Transfer-Encoding only if we are appending the
     * charset parameter, because indicating an 8-bit transfer without also
     * indicating the charset can cause problems with many mailers.  - FM & KW
     */
    disp_charset = LYCharSet_UC[current_char_set].MIMEname;
    use_cte = HTLoadedDocumentEightbit();
    if (!(use_cte && strncasecomp(disp_charset, "x-", 2))) {
	disp_charset = NULL;
#if USE_VMS_MAILER
	use_cte = FALSE;
#endif
    }
#if USE_VMS_MAILER
    use_type = (BOOL) (disp_charset || HTisDocumentSource());
#endif

    /*
     * Use newdoc->title as a subject instead of sug_filename:  MORE readable
     * and 8-bit letters shouldn't be a problem - LP
     */
    /* change_sug_filename(sug_filename); */
    subject = subject_translate8bit(newdoc->title);

    if (newdoc->isHEAD) {
	/*
	 * Special case for mailing HEAD responce:  this is rather technical
	 * information, show URL.
	 */
	FREE(subject);
	StrAllocCopy(subject, "HEAD  ");
	StrAllocCat(subject, newdoc->address);
    }
#if USE_VMS_MAILER
    if (StrChr(user_response->str, '@') &&
	!StrChr(user_response->str, ':') &&
	!StrChr(user_response->str, '%') &&
	!StrChr(user_response->str, '"')) {
	char *temp = 0;

	HTSprintf0(&temp, mail_adrs, user_response->str);
	BStrCopy0(user_response, temp);
	FREE(temp);
    }

    outfile_fp = LYOpenTemp(my_temp,
			    (HTisDocumentSource())
			    ? HTML_SUFFIX
			    : TEXT_SUFFIX,
			    "w");
    if (outfile_fp == NULL) {
	CannotPrint(UNABLE_TO_OPEN_TEMPFILE);
    }

    if (isPMDF) {
	if ((hfd = LYOpenTemp(hdrfile, TEXT_SUFFIX, "w")) == NULL) {
	    CannotPrint(UNABLE_TO_OPEN_TEMPFILE);
	}
	if (use_type) {
	    fprintf(hfd, "Mime-Version: 1.0\n");
	    if (use_cte) {
		fprintf(hfd, "Content-Transfer-Encoding: 8bit\n");
	    }
	}
	if (HTisDocumentSource()) {
	    /*
	     * Add Content-Type, Content-Location, and Content-Base headers for
	     * HTML source.  - FM
	     */
	    fprintf(hfd, "Content-Type: text/html");
	    if (disp_charset != NULL) {
		fprintf(hfd, "; charset=%s\n", disp_charset);
	    } else {
		fprintf(hfd, "\n");
	    }
	    fprintf(hfd, "Content-Base: %s\n", content_base);
	    fprintf(hfd, "Content-Location: %s\n", content_location);
	} else {
	    /*
	     * Add Content-Type:  text/plain if we have 8-bit characters and a
	     * valid charset for non-source documents.  - FM
	     */
	    if (disp_charset != NULL) {
		fprintf(hfd,
			"Content-Type: text/plain; charset=%s\n",
			disp_charset);
	    }
	}
	/*
	 * X-URL header.  - FM
	 */
	fprintf(hfd, "X-URL: %s\n", newdoc->address);
	/*
	 * For PMDF, put the subject in the header file and close it.  - FM
	 */
	fprintf(hfd, "Subject: %.70s\n\n", subject);
	LYCloseTempFP(hfd);
    }

    /*
     * Write the contents to a temp file.
     */
    if (LYPrependBaseToSource && HTisDocumentSource()) {
	/*
	 * Added the document's base as a BASE tag to the top of the message
	 * body.  May create technically invalid HTML, but will help get any
	 * partial or relative URLs resolved properly if no BASE tag is present
	 * to replace it.  - FM
	 */
	fprintf(outfile_fp,
		"<!-- X-URL: %s -->\n<BASE HREF=\"%s\">\n\n",
		newdoc->address, content_base);
    } else if (!isPMDF) {
	fprintf(outfile_fp, "X-URL: %s\n\n", newdoc->address);
    }
    print_wwwfile_to_fd(outfile_fp, TRUE, FALSE);	/* MAIL */
    if (keypad_mode)
	printlist(outfile_fp, FALSE);
    LYCloseTempFP(outfile_fp);

    buffer = NULL;
    if (isPMDF) {
	/*
	 * Now set up the command.  - FM
	 */
	HTSprintf0(&buffer,
		   "%s %s %s,%s %s",
		   system_mail,
		   system_mail_flags,
		   hdrfile,
		   my_temp,
		   user_response->str);
    } else {
	/*
	 * For "generic" VMS MAIL, include the subject in the command.  - FM
	 */
	remove_quotes(subject);
	HTSprintf0(&buffer,
		   "%s %s/subject=\"%.70s\" %s %s",
		   system_mail,
		   system_mail_flags,
		   subject,
		   my_temp,
		   user_response->str);
    }

    stop_curses();
    SetOutputMode(O_TEXT);
    printf(MAILING_FILE);
    LYSystem(buffer);
    LYSleepAlert();
    start_curses();
    SetOutputMode(O_BINARY);

    if (isPMDF)
	(void) LYRemoveTemp(hdrfile);
    (void) LYRemoveTemp(my_temp);
#else /* !VMS (Unix or DOS) */

#if CAN_PIPE_TO_MAILER
    outfile_fp = LYPipeToMailer();
#else
    outfile_fp = LYOpenTemp(my_temp, TEXT_SUFFIX, "w");
#endif
    if (outfile_fp == NULL) {
	CannotPrint(MAIL_REQUEST_FAILED);
    }

    /*
     * Determine which mail headers should be sent.  Use Content-Type and
     * MIME-Version headers only if needed.  We need them if we are mailing
     * HTML source, or if we have 8-bit characters and will be sending
     * Content-Transfer-Encoding to indicate this.
     *
     * Send Content-Transfer-Encoding only if the document has 8-bit
     * characters.  Send a charset parameter only if the document has 8-bit
     * characters and we seem to have a valid charset.  - kw
     */
    use_cte = HTLoadedDocumentEightbit();
    disp_charset = LYCharSet_UC[current_char_set].MIMEname;
    /*
     * Don't send a charset if we have a CJK character set selected, since it
     * may not be appropriate for mail...  Also don't use an unofficial "x-"
     * charset.  - kw
     */
    if (!use_cte || LYHaveCJKCharacterSet ||
	strncasecomp(disp_charset, "x-", 2) == 0) {
	disp_charset = NULL;
    }
#ifdef NOTDEFINED
    /* Enable this if indicating an 8-bit transfer without also indicating the
     * charset causes problems.  - kw */
    if (use_cte && !disp_charset)
	use_cte = FALSE;
#endif /* NOTDEFINED */
    use_type = (BOOL) (disp_charset || HTisDocumentSource());
    use_mime = (BOOL) (use_cte || use_type);

    if (use_mime) {
	fprintf(outfile_fp, "Mime-Version: 1.0\n");
	if (use_cte) {
	    fprintf(outfile_fp, "Content-Transfer-Encoding: 8bit\n");
	}
    }

    if (HTisDocumentSource()) {
	/*
	 * Add Content-Type, Content-Location, and Content-Base headers for
	 * HTML source.  - FM
	 */
	fprintf(outfile_fp, "Content-Type: text/html");
	if (disp_charset != NULL) {
	    fprintf(outfile_fp, "; charset=%s\n", disp_charset);
	} else {
	    fprintf(outfile_fp, "\n");
	}
    } else {
	/*
	 * Add Content-Type:  text/plain if we have 8-bit characters and a
	 * valid charset for non-source documents.  - KW
	 */
	if (disp_charset != NULL) {
	    fprintf(outfile_fp,
		    "Content-Type: text/plain; charset=%s\n",
		    disp_charset);
	}
    }
    /*
     * If we are using MIME headers, add content-base and content-location if
     * we have them.  This will always be the case if the document is source.
     * - kw
     */
    if (use_mime) {
	if (content_base)
	    fprintf(outfile_fp, "Content-Base: %s\n", content_base);
	if (content_location)
	    fprintf(outfile_fp, "Content-Location: %s\n", content_location);
    }

    /*
     * Add the To, Subject, and X-URL headers.  - FM
     */
    fprintf(outfile_fp, "To: %s\nSubject: %s\n", user_response->str, subject);
    fprintf(outfile_fp, "X-URL: %s\n\n", newdoc->address);

    if (LYPrependBaseToSource && HTisDocumentSource()) {
	/*
	 * Added the document's base as a BASE tag to the top of the message
	 * body.  May create technically invalid HTML, but will help get any
	 * partial or relative URLs resolved properly if no BASE tag is present
	 * to replace it.  - FM
	 */
	fprintf(outfile_fp,
		"<!-- X-URL: %s -->\n<BASE HREF=\"%s\">\n\n",
		newdoc->address, content_base);
    }
    print_wwwfile_to_fd(outfile_fp, TRUE, FALSE);	/* MAIL */
    if (keypad_mode)
	printlist(outfile_fp, FALSE);

#if CAN_PIPE_TO_MAILER
    pclose(outfile_fp);
#else
    LYCloseOutput(outfile_fp);
    LYSendMailFile(user_response->str,
		   my_temp,
		   subject,
		   "",
		   "");
    (void) LYRemoveTemp(my_temp);	/* Delete the tmpfile. */
#endif /* CAN_PIPE_TO_MAILER */
#endif /* USE_VMS_MAILER */

  done:			/* send_file_to_mail() */
    BStrFree(user_response);
    FREE(buffer);
    FREE(subject);
    return;
}

static void send_file_to_printer(DocInfo *newdoc,
				 char *content_base,
				 char *sug_filename,
				 int printer_number)
{
    BOOLEAN FirstRecall = TRUE;
    FILE *outfile_fp;
    char *the_command = 0;
    bstring *my_file = NULL;
    char my_temp[LY_MAXPATH];
    int FnameTotal, FnameNum = -1;
    lynx_list_item_type *cur_printer;

    outfile_fp = LYOpenTemp(my_temp,
			    (HTisDocumentSource())
			    ? HTML_SUFFIX
			    : TEXT_SUFFIX,
			    "w");
    if (outfile_fp == NULL) {
	CannotPrint(FILE_ALLOC_FAILED);
    }

    if (LYPrependBaseToSource && HTisDocumentSource()) {
	/*
	 * Added the document's base as a BASE tag to the top of the file.  May
	 * create technically invalid HTML, but will help get any partial or
	 * relative URLs resolved properly if no BASE tag is present to replace
	 * it.  - FM
	 */
	fprintf(outfile_fp,
		"<!-- X-URL: %s -->\n<BASE HREF=\"%s\">\n\n",
		newdoc->address, content_base);
    }
    print_wwwfile_to_fd(outfile_fp, FALSE, FALSE);	/* PRINTER */
    if (keypad_mode)
	printlist(outfile_fp, FALSE);

    LYCloseTempFP(outfile_fp);

    /* find the right printer number */
    {
	int count = 0;

	for (cur_printer = printers;
	     count < printer_number;
	     count++, cur_printer = cur_printer->next) ;	/* null body */
    }

    /*
     * Commands have the form "command %s [%s] [etc]" where %s is the filename
     * and the second optional %s is the suggested filename.
     */
    if (cur_printer->command == NULL) {
	CannotPrint(PRINTER_MISCONF_ERROR);
    }

    /*
     * Check for two '%s' and ask for the second filename argument if there
     * is.
     */
    BStrCopy0(my_file, "");
    if (HTCountCommandArgs(cur_printer->command) >= 2) {
	_statusline(FILENAME_PROMPT);
      again:
	SetupFilename(&my_file, sug_filename);
      check_again:
	switch (RecallFilename(&my_file, &FirstRecall, &FnameNum,
			       &FnameTotal, PRINT_FLAG)) {
	case FN_INIT:
	    goto again;
	case FN_READ:
	    goto check_again;
	case FN_QUIT:
	    goto done;
	default:
	    break;
	}

	if (no_dotfiles || !show_dotfiles) {
	    if (*LYPathLeaf(my_file->str) == '.') {
		HTAlert(FILENAME_CANNOT_BE_DOT);
		_statusline(NEW_FILENAME_PROMPT);
		FirstRecall = TRUE;
		FnameNum = FnameTotal;
		goto again;
	    }
	}
	/*
	 * Cancel if the user entered "/dev/null" on Unix, or an "nl:" path
	 * on VMS.  - FM
	 */
	if (LYIsNullDevice(my_file->str)) {
	    CancelPrint(PRINT_REQUEST_CANCELLED);
	}
	HTAddSugFilename(my_file->str);
    }
#ifdef SH_EX			/* 1999/01/04 (Mon) 09:37:03 */
    HTAddParam(&the_command, cur_printer->command, 1, my_temp);
    if (!isBEmpty(my_file)) {
	HTAddParam(&the_command, cur_printer->command, 2, my_file->str);
	HTEndParam(&the_command, cur_printer->command, 3);
    } else {
	HTEndParam(&the_command, cur_printer->command, 2);
    }
#else
    HTAddParam(&the_command, cur_printer->command, 1, my_temp);
    HTAddParam(&the_command, cur_printer->command, 2, my_file->str);
    HTEndParam(&the_command, cur_printer->command, 2);
#endif

    /*
     * Move the cursor to the top of the screen so that output from system'd
     * commands don't scroll up the screen.
     */
    LYmove(1, 1);

    stop_curses();
    CTRACE((tfp, "command: %s\n", the_command));
    SetOutputMode(O_TEXT);
    printf(PRINTING_FILE);
    /*
     * Set various bits of document information as environment variables, for
     * use by external print scripts/etc.  On UNIX, We assume there are values,
     * and leave NULL value checking up to the external PRINTER:  cmd/script -
     * KED
     */
    SET_ENVIRON(LYNX_PRINT_TITLE, HText_getTitle(), "No Title");
    SET_ENVIRON(LYNX_PRINT_URL, newdoc->address, "No URL");
    SET_ENVIRON(LYNX_PRINT_DATE, HText_getDate(), "No Date");
    SET_ENVIRON(LYNX_PRINT_LASTMOD, HText_getLastModified(), "No LastMod");

    LYSystem(the_command);
    FREE(the_command);
    (void) LYRemoveTemp(my_temp);

    /*
     * Remove the various LYNX_PRINT_xxxx logicals.  - KED
     * [could use unsetenv(), but it's not portable]
     */
    SET_ENVIRON(LYNX_PRINT_TITLE, "", "");
    SET_ENVIRON(LYNX_PRINT_URL, "", "");
    SET_ENVIRON(LYNX_PRINT_DATE, "", "");
    SET_ENVIRON(LYNX_PRINT_LASTMOD, "", "");

    fflush(stdout);
#ifndef VMS
    signal(SIGINT, cleanup_sig);
#endif /* !VMS */
#ifdef SH_EX
    fprintf(stdout, gettext(" Print job complete.\n"));
    fflush(stdout);
#endif
    SetOutputMode(O_BINARY);
    LYSleepMsg();
    start_curses();

  done:			/* send_file_to_printer() */
    BStrFree(my_file);
    return;
}

static void send_file_to_screen(DocInfo *newdoc,
				char *content_base,
				int Lpansi)
{
    FILE *outfile_fp;
    bstring *prompt = NULL;

    if (Lpansi) {
	_statusline(CHECK_PRINTER);
    } else {
	_statusline(PRESS_RETURN_TO_BEGIN);
    }

    BStrCopy0(prompt, "");
    if (LYgetBString(&prompt, FALSE, 0, NORECALL) < 0) {
	CancelPrint(PRINT_REQUEST_CANCELLED);
    } else {
	outfile_fp = stdout;

	stop_curses();
	SetOutputMode(O_TEXT);

#ifndef VMS
	signal(SIGINT, SIG_IGN);
#endif /* !VMS */

	if (LYPrependBaseToSource && HTisDocumentSource()) {
	    /*
	     * Added the document's base as a BASE tag to the top of the file.  May
	     * create technically invalid HTML, but will help get any partial or
	     * relative URLs resolved properly if no BASE tag is present to replace
	     * it.  - FM
	     */
	    fprintf(outfile_fp,
		    "<!-- X-URL: %s -->\n<BASE HREF=\"%s\">\n\n",
		    newdoc->address, content_base);
	}
	if (Lpansi)
	    printf("\033[5i");
	print_wwwfile_to_fd(outfile_fp, FALSE, FALSE);	/* SCREEN */
	if (keypad_mode)
	    printlist(outfile_fp, FALSE);

#ifdef VMS
	if (HadVMSInterrupt) {
	    HadVMSInterrupt = FALSE;
	    start_curses();
	    CancelPrint(PRINT_REQUEST_CANCELLED);
	}
#endif /* VMS */
	if (Lpansi) {
	    printf("\n\014");	/* Form feed */
	    printf("\033[4i");
	    fflush(stdout);	/* refresh to screen */
	} else {
	    fprintf(stdout, "\n\n%s", PRESS_RETURN_TO_FINISH);
	    fflush(stdout);	/* refresh to screen */
	    (void) LYgetch();	/* grab some user input to pause */
#ifdef VMS
	    HadVMSInterrupt = FALSE;
#endif /* VMS */
	}
#ifdef SH_EX
	fprintf(stdout, "\n");
#endif
	SetOutputMode(O_BINARY);
	start_curses();
    }

  done:			/* send_file_to_screen() */
    BStrFree(prompt);
    return;
}

int printfile(DocInfo *newdoc)
{
    BOOLEAN Lpansi = FALSE;
    DocAddress WWWDoc;
    char *content_base = NULL;
    char *content_location = NULL;
    char *cp = NULL;
    char *link_info = NULL;
    char *sug_filename = NULL;
    int lines_in_file = 0;
    int pagelen = 0;
    int printer_number = 0;
    int type = 0;

    /*
     * Extract useful info from URL.
     */
    StrAllocCopy(link_info, newdoc->address + 12);

    /*
     * Reload the file we want to print into memory.
     */
    LYpop(newdoc);
    WWWDoc.address = newdoc->address;
    WWWDoc.post_data = newdoc->post_data;
    WWWDoc.post_content_type = newdoc->post_content_type;
    WWWDoc.bookmark = newdoc->bookmark;
    WWWDoc.isHEAD = newdoc->isHEAD;
    WWWDoc.safe = newdoc->safe;
    if (!HTLoadAbsolute(&WWWDoc))
	return (NOT_FOUND);

    /*
     * If we have an explicit content-base, we may use it even if not in source
     * mode.  - kw
     */
    if (HText_getContentBase()) {
	StrAllocCopy(content_base, HText_getContentBase());
	LYRemoveBlanks(content_base);
	if (isEmpty(content_base)) {
	    FREE(content_base);
	}
    }
    /*
     * If document is source, load the content_base and content_location
     * strings.  - FM
     */
    if (HTisDocumentSource()) {
	if (HText_getContentLocation()) {
	    StrAllocCopy(content_location, HText_getContentLocation());
	    LYRemoveBlanks(content_location);
	    if (isEmpty(content_location)) {
		FREE(content_location);
	    }
	}
	if (!content_base) {
	    if ((content_location) && is_url(content_location)) {
		StrAllocCopy(content_base, content_location);
	    } else {
		StrAllocCopy(content_base, newdoc->address);
	    }
	}
	if (!content_location) {
	    StrAllocCopy(content_location, newdoc->address);
	}
    }

    sug_filename = suggested_filename(newdoc);

    /*
     * Get the number of lines in the file.
     */
    if ((cp = strstr(link_info, "lines=")) != NULL) {
	/*
	 * Terminate prev string here.
	 */
	*cp = '\0';
	/*
	 * Number of characters in "lines=".
	 */
	cp += 6;

	lines_in_file = atoi(cp);
    }

    /*
     * Determine the type.
     */
    if (strstr(link_info, "LOCAL_FILE")) {
	type = TO_FILE;
    } else if (strstr(link_info, "TO_SCREEN")) {
	type = TO_SCREEN;
    } else if (strstr(link_info, "LPANSI")) {
	Lpansi = TRUE;
	type = TO_SCREEN;
    } else if (strstr(link_info, "MAIL_FILE")) {
	type = MAIL;
    } else if (strstr(link_info, "PRINTER")) {
	type = PRINTER;

	if ((cp = strstr(link_info, "number=")) != NULL) {
	    /* number of characters in "number=" */
	    cp += 7;
	    printer_number = atoi(cp);
	}
	if ((cp = strstr(link_info, "pagelen=")) != NULL) {
	    /* number of characters in "pagelen=" */
	    cp += 8;
	    pagelen = atoi(cp);
	} else {
	    /* default to 66 lines */
	    pagelen = 66;
	}
    }

    /*
     * Act on the request.  - FM
     */
    switch (type) {

    case TO_FILE:
	send_file_to_file(newdoc, content_base, sug_filename);
	break;

    case MAIL:
	send_file_to_mail(newdoc, content_base, content_location);
	break;

    case TO_SCREEN:
	if (confirm_by_pages(CONFIRM_LONG_SCREEN_PRINT, lines_in_file, LYlines))
	    send_file_to_screen(newdoc, content_base, Lpansi);
	break;

    case PRINTER:
	if (confirm_by_pages(CONFIRM_LONG_PAGE_PRINT, lines_in_file, pagelen))
	    send_file_to_printer(newdoc, content_base, sug_filename, printer_number);
	break;

    }				/* end switch */

    FREE(link_info);
    FREE(sug_filename);
    FREE(content_base);
    FREE(content_location);
    return (NORMAL);
}

#if USE_VMS_MAILER
static int remove_quotes(char *string)
{
    int i;

    for (i = 0; string[i] != '\0'; i++)
	if (string[i] == '"')
	    string[i] = ' ';
	else if (string[i] == '&')
	    string[i] = ' ';
	else if (string[i] == '|')
	    string[i] = ' ';

    return (0);
}
#endif /* USE_VMS_MAILER */

/*
 * Mail subject may have 8-bit characters and they are in display charset.
 * There is no stable practice for 8-bit subject encodings:  MIME defines
 * "quoted-printable" which holds charset info but most mailers still don't
 * support it.  On the other hand many mailers send open 8-bit subjects without
 * charset info and use local assumption for certain countries.  Besides that,
 * obsolete SMTP software is not 8bit clean but still in use, it strips the
 * characters in 128-160 range from subjects which may be a fault outside
 * iso-8859-XX.
 *
 * We translate subject to "outgoing_mail_charset" (defined in lynx.cfg) it may
 * correspond to US-ASCII as the safest value or any other lynx character
 * handler, -1 for no translation (so display charset).
 *
 * Always returns a new allocated string which has to be freed.
 */
#include <LYCharUtils.h>
static char *subject_translate8bit(char *source)
{
    char *target = NULL;

    int charset_in, charset_out;

    int i = outgoing_mail_charset;	/* from lynx.cfg, -1 by default */

    StrAllocCopy(target, source);
    if (i < 0
	|| i == current_char_set
	|| LYCharSet_UC[current_char_set].enc == UCT_ENC_CJK
	|| LYCharSet_UC[i].enc == UCT_ENC_CJK) {
	return (target);	/* OK */
    } else {
	charset_out = i;
	charset_in = current_char_set;
    }

    LYUCTranslateBackHeaderText(&target, charset_in, charset_out, YES);

    return (target);
}

/*
 * print_options writes out the current printer choices to a file
 * so that the user can select printers in the same way that
 * they select all other links
 * printer links look like
 *
 * LYNXPRINT://LOCAL_FILE/lines=#	     print to a local file
 * LYNXPRINT://TO_SCREEN/lines=#	     print to the screen
 * LYNXPRINT://LPANSI/lines=#		     print to the local terminal
 * LYNXPRINT://MAIL_FILE/lines=#	     mail the file
 * LYNXPRINT://PRINTER/lines=#/number=#      print to printer number #
 */
int print_options(char **newfile,
		  const char *printed_url,
		  int lines_in_file)
{
    static char my_temp[LY_MAXPATH] = "\0";
    char *buffer = 0;
    int count;
    int pages;
    FILE *fp0;
    lynx_list_item_type *cur_printer;

    if ((fp0 = InternalPageFP(my_temp, TRUE)) == 0)
	return (-1);

    LYLocalFileToURL(newfile, my_temp);

    BeginInternalPage(fp0, PRINT_OPTIONS_TITLE, PRINT_OPTIONS_HELP);

    fprintf(fp0, "<pre>\n");

    /*  pages = lines_in_file/66 + 1; */
    pages = (lines_in_file + 65) / 66;
    HTSprintf0(&buffer,
	       "   <em>%s</em> %s\n   <em>%s</em> %d\n   <em>%s</em> %d %s %s\n",
	       gettext("Document:"), printed_url,
	       gettext("Number of lines:"), lines_in_file,
	       gettext("Number of pages:"), pages,
	       (pages > 1 ? gettext("pages") : gettext("page")),
	       gettext("(approximately)"));
    fputs(buffer, fp0);
    FREE(buffer);

    if (no_print || no_disk_save || no_mail)
	fprintf(fp0,
		"   <em>%s</em>\n",
		gettext("Some print functions have been disabled!"));

    fprintf(fp0, "\n%s\n",
	    (user_mode == NOVICE_MODE)
	    ? gettext("Standard print options:")
	    : gettext("Print options:"));

    if (no_disk_save == FALSE && no_print == FALSE) {
	fprintf(fp0,
		"   <a href=\"%s//LOCAL_FILE/lines=%d\">%s</a>\n",
		STR_LYNXPRINT,
		lines_in_file,
		gettext("Save to a local file"));
    } else {
	fprintf(fp0, "   <em>%s</em>\n", gettext("Save to disk disabled"));
    }
    if (no_mail == FALSE && local_host_only == FALSE)
	fprintf(fp0,
		"   <a href=\"%s//MAIL_FILE/lines=%d\">%s</a>\n",
		STR_LYNXPRINT,
		lines_in_file,
		gettext("Mail the file"));

#if defined(UNIX) || defined(VMS)
    fprintf(fp0,
	    "   <a href=\"%s//TO_SCREEN/lines=%d\">%s</a>\n",
	    STR_LYNXPRINT,
	    lines_in_file,
	    gettext("Print to the screen"));
    fprintf(fp0,
	    "   <a href=\"%s//LPANSI/lines=%d\">%s</a>\n",
	    STR_LYNXPRINT,
	    lines_in_file,
	    gettext("Print out on a printer attached to your vt100 terminal"));
#endif

    if (user_mode == NOVICE_MODE)
	fprintf(fp0, "\n%s\n", gettext("Local additions:"));

    for (count = 0, cur_printer = printers; cur_printer != NULL;
	 cur_printer = cur_printer->next, count++)
	if (no_print == FALSE || cur_printer->always_enabled) {
	    fprintf(fp0,
		    "   <a href=\"%s//PRINTER/number=%d/pagelen=%d/lines=%d\">",
		    STR_LYNXPRINT,
		    count, cur_printer->pagelen, lines_in_file);
	    fprintf(fp0, "%s", (cur_printer->name ?
				cur_printer->name : "No Name Given"));
	    fprintf(fp0, "</a>\n");
	}
    fprintf(fp0, "</pre>\n");
    EndInternalPage(fp0);
    LYCloseTempFP(fp0);

    LYforce_no_cache = TRUE;
    return (0);
}

/*
 * General purpose filename getter.
 *
 * Returns a pointer to an absolute filename string, if the input filename
 * exists, and is readable.  Returns NULL if the input was cancelled (^G, or CR
 * on empty input).
 *
 * The pointer to the filename string needs to be free()'d by the caller (when
 * non-NULL).
 *
 * --KED 02/21/99
 */
char *GetFileName(void)
{
    struct stat stat_info;

    bstring *fbuf = NULL;
    bstring *tbuf = NULL;
    char *result = NULL;

    BOOLEAN FirstRecall = TRUE;
    int FnameNum = -1;
    int FnameTotal;

    _statusline(FILENAME_PROMPT);

  retry:
    /*
     * No initial filename.
     */
    SetupFilename(&fbuf, "");

  check_recall:
    /*
     * Go get a filename (it would be nice to do TAB == filename-completion as
     * the name is entered, but we'll save doing that for another time.
     */
    switch (RecallFilename(&fbuf, &FirstRecall, &FnameNum,
			   &FnameTotal, GENERIC_FLAG)) {
    case FN_INIT:
	goto retry;
    case FN_READ:
	goto check_recall;
    case FN_QUIT:
	goto cleanup;
    default:
	break;
    }

    /*
     * Add raw input form to list ...  we may want to reuse/edit it on a
     * subsequent call, etc.
     */
#ifdef VMS
    if (0 == strncasecomp(fbuf->str, "sys$disk:", 9)) {
	if (0 == StrNCmp((fbuf->str + 9), "[]", 2)) {
	    HTAddSugFilename(fbuf->str + 11);
	} else {
	    HTAddSugFilename(fbuf->str + 9);
	}
    } else {
	HTAddSugFilename(fbuf->str);
    }
#else
    HTAddSugFilename(fbuf->str);
#endif /* VMS */

    /*
     * Expand tilde's, make filename absolute, etc.
     */
    BStrCopy0(tbuf, "");
    if (!LYValidateFilename(&tbuf, &fbuf))
	goto cleanup;

    /*
     * Check for file existence; readability.
     */
    if ((stat(tbuf->str, &stat_info) < 0) ||
	(!(S_ISREG(stat_info.st_mode)
#ifdef S_IFLNK
	   || S_ISLNK(stat_info.st_mode)
#endif /* S_IFLNK */
	 ))) {
	HTInfoMsg(FILE_DOES_NOT_EXIST);
	_statusline(FILE_DOES_NOT_EXIST_RE);
	FirstRecall = TRUE;
	FnameNum = FnameTotal;
	goto retry;
    }

    if (!LYCanReadFile(tbuf->str)) {
	HTInfoMsg(FILE_NOT_READABLE);
	_statusline(FILE_NOT_READABLE_RE);
	FirstRecall = TRUE;
	FnameNum = FnameTotal;
	goto retry;
    }

    /*
     * We have a valid filename, and readable file.  Return it to the caller.
     *
     * The returned pointer should be free()'d by the caller.
     */
    StrAllocCopy(result, tbuf->str);

  cleanup:
    BStrFree(fbuf);
    BStrFree(tbuf);
    return (result);
}