./PaxHeaders.22102/edbrowse-3.6.0.10000644000000000000000000000006212637565441013313 xustar0020 atime=1451158305 30 ctime=1451409178.157337163 edbrowse-3.6.0.1/0000755000000000000000000000000012637565441013504 5ustar00rootroot00000000000000edbrowse-3.6.0.1/PaxHeaders.22102/win320000644000000000000000000000006212637565441014120 xustar0020 atime=1451158305 30 ctime=1451409178.157337163 edbrowse-3.6.0.1/win32/0000755000000000000000000000000012637565441014446 5ustar00rootroot00000000000000edbrowse-3.6.0.1/win32/PaxHeaders.22102/vsprtf.h0000644000000000000000000000006212637565441015672 xustar0020 atime=1451158305 30 ctime=1451409178.157337163 edbrowse-3.6.0.1/win32/vsprtf.h0000644000000000000000000000047512637565441016151 0ustar00rootroot00000000000000/*\ * vsprtf.hxx * * Copyright (c) 2014 - Geoff R. McLane * Licence: GNU GPL version 2 * \*/ #ifndef _VSPRTF_HXX_ #define _VSPRTF_HXX_ extern int vasprintf (char **str, const char *fmt, va_list args); extern int asprintf (char **str, const char *fmt, ...); #endif // #ifndef _VSPRTF_HXX_ // eof - vsprtf.hxx edbrowse-3.6.0.1/win32/PaxHeaders.22102/vsprtf.c0000644000000000000000000000006212637565441015665 xustar0020 atime=1451158305 30 ctime=1451409178.157337163 edbrowse-3.6.0.1/win32/vsprtf.c0000644000000000000000000000267512637565441016150 0ustar00rootroot00000000000000/*\ * vsprtf.cxx - 20150920 * * Copyright (c) 2015 - Geoff R. McLane * Licence: GNU GPL version 2 * * from : https://github.com/littlstar/asprintf.c/blob/master/asprintf.c \*/ /** * `asprintf.c' - asprintf * * copyright (c) 2014 joseph werle */ #include #include #include #include #include "vsprtf.h" static const char *module = "vsprtf"; #ifdef _MSC_VER #define va_copy(dst,src) dst = src #endif // implementation int asprintf (char **str, const char *fmt, ...) { int size = 0; va_list args; // init variadic argumens va_start(args, fmt); // format and get size size = vasprintf(str, fmt, args); va_end(args); return size; } int vasprintf (char **str, const char *fmt, va_list args) { int size = 0; va_list tmpa; // copy va_copy(tmpa, args); // apply variadic arguments to // sprintf with format to get size size = vsnprintf(NULL, size, fmt, tmpa); // toss args va_end(tmpa); // return -1 to be compliant if // size is less than 0 if (size < 0) { return -1; } // alloc with size plus 1 for `\0' *str = (char *) malloc(size + 1); // return -1 to be compliant // if pointer is `NULL' if (NULL == *str) { return -1; } // format string with original // variadic arguments and set new size size = vsprintf(*str, fmt, args); return size; } // eof = vsprtf.cxx edbrowse-3.6.0.1/win32/PaxHeaders.22102/dirent.h0000644000000000000000000000006212637565441015633 xustar0020 atime=1451158305 30 ctime=1451409178.157337163 edbrowse-3.6.0.1/win32/dirent.h0000644000000000000000000000554212637565441016112 0ustar00rootroot00000000000000/* * DIRENT.H (formerly DIRLIB.H) * This file has no copyright assigned and is placed in the Public Domain. * This file is a part of the mingw-runtime package. * No warranty is given; refer to the file DISCLAIMER within the package. * */ #ifndef _DIRENT_H_ #define _DIRENT_H_ #include #include #ifndef RC_INVOKED #ifdef __cplusplus extern "C" { #endif struct dirent { long d_ino; /* Always zero. */ unsigned short d_reclen; /* Always zero. */ unsigned short d_namlen; /* Length of name in d_name. */ char d_name[FILENAME_MAX]; /* File name. */ }; #ifdef _WIN64 #define INTPTR __int64 #else #define INTPTR long #endif /* * This is an internal data structure. Good programmers will not use it * except as an argument to one of the functions below. * dd_stat field is now int (was short in older versions). */ typedef struct { /* disk transfer area for this dir */ struct _finddata_t dd_dta; /* dirent struct to return from dir (NOTE: this makes this thread * safe as long as only one thread uses a particular DIR struct at * a time) */ struct dirent dd_dir; /* _findnext handle */ INTPTR dd_handle; /* * Status of search: * 0 = not started yet (next entry to read is first entry) * -1 = off the end * positive = 0 based index of next entry */ int dd_stat; /* given path for dir with search pattern (struct is extended) */ char dd_name[1]; } DIR; DIR* __cdecl opendir (const char*); struct dirent* __cdecl readdir (DIR*); int __cdecl closedir (DIR*); void __cdecl rewinddir (DIR*); long __cdecl telldir (DIR*); void __cdecl seekdir (DIR*, long); /* wide char versions */ struct _wdirent { long d_ino; /* Always zero. */ unsigned short d_reclen; /* Always zero. */ unsigned short d_namlen; /* Length of name in d_name. */ wchar_t d_name[FILENAME_MAX]; /* File name. */ }; /* * This is an internal data structure. Good programmers will not use it * except as an argument to one of the functions below. */ typedef struct { /* disk transfer area for this dir */ struct _wfinddata_t dd_dta; /* dirent struct to return from dir (NOTE: this makes this thread * safe as long as only one thread uses a particular DIR struct at * a time) */ struct _wdirent dd_dir; /* _findnext handle */ INTPTR dd_handle; /* * Status of search: * 0 = not started yet (next entry to read is first entry) * -1 = off the end * positive = 0 based index of next entry */ int dd_stat; /* given path for dir with search pattern (struct is extended) */ wchar_t dd_name[1]; } _WDIR; _WDIR* __cdecl _wopendir (const wchar_t*); struct _wdirent* __cdecl _wreaddir (_WDIR*); int __cdecl _wclosedir (_WDIR*); void __cdecl _wrewinddir (_WDIR*); long __cdecl _wtelldir (_WDIR*); void __cdecl _wseekdir (_WDIR*, long); #ifdef __cplusplus } #endif #endif /* Not RC_INVOKED */ #endif /* Not _DIRENT_H_ */ edbrowse-3.6.0.1/win32/PaxHeaders.22102/dirent.c0000644000000000000000000000006212637565441015626 xustar0020 atime=1451158305 30 ctime=1451409178.157337163 edbrowse-3.6.0.1/win32/dirent.c0000644000000000000000000001564612637565441016113 0ustar00rootroot00000000000000/* * dirent.c * This file has no copyright assigned and is placed in the Public Domain. * This file is a part of the mingw-runtime package. * No warranty is given; refer to the file DISCLAIMER within the package. * * Derived from DIRLIB.C by Matt J. Weinstein * This note appears in the DIRLIB.H * DIRLIB.H by M. J. Weinstein Released to public domain 1-Jan-89 * * Updated by Jeremy Bettis * Significantly revised and rewinddir, seekdir and telldir added by Colin * Peters * */ #include #include #include #include #include #include "dirent.h" #define WIN32_LEAN_AND_MEAN #include /* for GetFileAttributes */ #include #ifdef _UNICODE #define _tdirent _wdirent #define _TDIR _WDIR #define _topendir _wopendir #define _tclosedir _wclosedir #define _treaddir _wreaddir #define _trewinddir _wrewinddir #define _ttelldir _wtelldir #define _tseekdir _wseekdir #else #define _tdirent dirent #define _TDIR DIR #define _topendir opendir #define _tclosedir closedir #define _treaddir readdir #define _trewinddir rewinddir #define _ttelldir telldir #define _tseekdir seekdir #endif #define SUFFIX _T("*") #define SLASH _T("\\") /* * opendir * * Returns a pointer to a DIR structure appropriately filled in to begin * searching a directory. */ _TDIR * _topendir (const _TCHAR *szPath) { _TDIR *nd; unsigned int rc; _TCHAR szFullPath[MAX_PATH]; errno = 0; if (!szPath) { errno = EFAULT; return (_TDIR *) 0; } if (szPath[0] == _T('\0')) { errno = ENOTDIR; return (_TDIR *) 0; } /* Attempt to determine if the given path really is a directory. */ rc = GetFileAttributes (szPath); if (rc == (unsigned int)-1) { /* call GetLastError for more error info */ errno = ENOENT; return (_TDIR *) 0; } if (!(rc & FILE_ATTRIBUTE_DIRECTORY)) { /* Error, entry exists but not a directory. */ errno = ENOTDIR; return (_TDIR *) 0; } /* Make an absolute pathname. */ _tfullpath (szFullPath, szPath, MAX_PATH); /* Allocate enough space to store DIR structure and the complete * directory path given. */ nd = (_TDIR *) malloc (sizeof (_TDIR) + (_tcslen(szFullPath) + _tcslen (SLASH) + _tcslen(SUFFIX) + 1) * sizeof(_TCHAR)); if (!nd) { /* Error, out of memory. */ errno = ENOMEM; return (_TDIR *) 0; } /* Create the search expression. */ _tcscpy (nd->dd_name, szFullPath); /* Add on a slash if the path does not end with one. */ if (nd->dd_name[0] != _T('\0') && nd->dd_name[_tcslen (nd->dd_name) - 1] != _T('/') && nd->dd_name[_tcslen (nd->dd_name) - 1] != _T('\\')) { _tcscat (nd->dd_name, SLASH); } /* Add on the search pattern */ _tcscat (nd->dd_name, SUFFIX); /* Initialize handle to -1 so that a premature closedir doesn't try * to call _findclose on it. */ nd->dd_handle = -1; /* Initialize the status. */ nd->dd_stat = 0; /* Initialize the dirent structure. ino and reclen are invalid under * Win32, and name simply points at the appropriate part of the * findfirst_t structure. */ nd->dd_dir.d_ino = 0; nd->dd_dir.d_reclen = 0; nd->dd_dir.d_namlen = 0; memset (nd->dd_dir.d_name, 0, FILENAME_MAX); return nd; } /* * readdir * * Return a pointer to a dirent structure filled with the information on the * next entry in the directory. */ struct _tdirent * _treaddir (_TDIR * dirp) { errno = 0; /* Check for valid DIR struct. */ if (!dirp) { errno = EFAULT; return (struct _tdirent *) 0; } if (dirp->dd_stat < 0) { /* We have already returned all files in the directory * (or the structure has an invalid dd_stat). */ return (struct _tdirent *) 0; } else if (dirp->dd_stat == 0) { /* We haven't started the search yet. */ /* Start the search */ dirp->dd_handle = _tfindfirst (dirp->dd_name, &(dirp->dd_dta)); if (dirp->dd_handle == -1) { /* Whoops! Seems there are no files in that * directory. */ dirp->dd_stat = -1; } else { dirp->dd_stat = 1; } } else { /* Get the next search entry. */ if (_tfindnext (dirp->dd_handle, &(dirp->dd_dta))) { /* We are off the end or otherwise error. _findnext sets errno to ENOENT if no more file Undo this. */ DWORD winerr = GetLastError(); if (winerr == ERROR_NO_MORE_FILES) errno = 0; _findclose (dirp->dd_handle); dirp->dd_handle = -1; dirp->dd_stat = -1; } else { /* Update the status to indicate the correct * number. */ dirp->dd_stat++; } } if (dirp->dd_stat > 0) { /* Successfully got an entry. Everything about the file is * already appropriately filled in except the length of the * file name. */ dirp->dd_dir.d_namlen = _tcslen (dirp->dd_dta.name); _tcscpy (dirp->dd_dir.d_name, dirp->dd_dta.name); return &dirp->dd_dir; } return (struct _tdirent *) 0; } /* * closedir * * Frees up resources allocated by opendir. */ int _tclosedir (_TDIR * dirp) { int rc; errno = 0; rc = 0; if (!dirp) { errno = EFAULT; return -1; } if (dirp->dd_handle != -1) { rc = _findclose (dirp->dd_handle); } /* Delete the dir structure. */ free (dirp); return rc; } /* * rewinddir * * Return to the beginning of the directory "stream". We simply call findclose * and then reset things like an opendir. */ void _trewinddir (_TDIR * dirp) { errno = 0; if (!dirp) { errno = EFAULT; return; } if (dirp->dd_handle != -1) { _findclose (dirp->dd_handle); } dirp->dd_handle = -1; dirp->dd_stat = 0; } /* * telldir * * Returns the "position" in the "directory stream" which can be used with * seekdir to go back to an old entry. We simply return the value in stat. */ long _ttelldir (_TDIR * dirp) { errno = 0; if (!dirp) { errno = EFAULT; return -1; } return dirp->dd_stat; } /* * seekdir * * Seek to an entry previously returned by telldir. We rewind the directory * and call readdir repeatedly until either dd_stat is the position number * or -1 (off the end). This is not perfect, in that the directory may * have changed while we weren't looking. But that is probably the case with * any such system. */ void _tseekdir (_TDIR * dirp, long lPos) { errno = 0; if (!dirp) { errno = EFAULT; return; } if (lPos < -1) { /* Seeking to an invalid position. */ errno = EINVAL; return; } else if (lPos == -1) { /* Seek past end. */ if (dirp->dd_handle != -1) { _findclose (dirp->dd_handle); } dirp->dd_handle = -1; dirp->dd_stat = -1; } else { /* Rewind and read forward to the appropriate index. */ _trewinddir (dirp); while ((dirp->dd_stat < lPos) && _treaddir (dirp)) ; } } edbrowse-3.6.0.1/PaxHeaders.22102/tools0000644000000000000000000000006212637565441014316 xustar0020 atime=1451158305 30 ctime=1451409178.157337163 edbrowse-3.6.0.1/tools/0000755000000000000000000000000012637565441014644 5ustar00rootroot00000000000000edbrowse-3.6.0.1/tools/PaxHeaders.22102/mkproto.c0000644000000000000000000000006212637565441016232 xustar0020 atime=1451158305 30 ctime=1451409178.157337163 edbrowse-3.6.0.1/tools/mkproto.c0000644000000000000000000001022212637565441016500 0ustar00rootroot00000000000000/* mkproto.c: make function prototype headers out of a sourcefile */ #include #include #include #include /* the symbol DOSLIKE is used to conditionally compile those constructs * that are common to DOS and NT, but not typical of Unix. */ #ifdef MSDOS #define DOSLIKE #endif #ifdef _WIN32 #define DOSLIKE #endif static char globalflag, staticflag; /* extract global or static prototypes. */ static void mkproto(char *fname); int main(int argc, char **argv) { if(argc > 1 && !strcmp(argv[1], "-g")) { ++argv, --argc; globalflag = 1; } if(argc > 1 && !strcmp(argv[1], "-s")) { ++argv, --argc; staticflag = 1; } if(argc == 1) { fprintf(stderr, "Usage: mkproto [-g|s] file1.c file2.c ...\n"); exit(1); } if(!staticflag) puts("/* This file is machine-generated, do not hand edit. */\n"); while(argc > 1) { ++argv, --argc; mkproto(*argv); } return 0; } /* main */ /* this function runs as a state machine, with the following states. 0 clear C text. 1 / received, starting a comment? 2 / * received, in comment. 3 * received in a comment, ending the comment? 4 in a #xxx preprocesssor line. 5 " received, starting a string. 6 \ received inside a string. 7 ' received, starting a char constant. 8 \ received inside a char constant. */ static void mkproto(char *fname) { int c; short comstate, nestlev, semstate; char lastchar, last_ns, spc; long offset, charcnt = 0; FILE *f = fopen(fname, "r"); char fword[10]; int fword_cnt; if(!f) { fprintf(stderr, "cannot open sourcefile %s\n", fname); return; } if(!staticflag) printf("/* sourcefile=%s */\n", fname); comstate = nestlev = 0; semstate = 1; last_ns = lastchar = 0; while((c = getc(f)) != EOF) { ++charcnt; #ifdef DOSLIKE if(c == '\n') ++charcnt; #endif if(comstate < 2 && c == '#' && lastchar == '\n') { comstate = 4; continue; } if(comstate == 1 && c == '/') { comstate = 4; continue; } if(c == '*' && comstate == 1) { comstate = 2; continue; } if(c == '*' && comstate == 2) { comstate = 3; continue; } if(c == '/' && comstate == 3) { comstate = 0; continue; } if(c == '/' && comstate == 0) { comstate = 1; continue; } if(comstate == 3 && c != '*') comstate = 2; if(comstate == 1) comstate = 0; /* not resolved into a comment */ if(c == '\n' && comstate == 4 && lastchar != '\\') { lastchar = c; comstate = 0; continue; } if(comstate == 5 || comstate == 7) { if(c == '\\') ++comstate; if(c == '"' && comstate == 5) comstate = 0; if(c == '\'' && comstate == 7) comstate = 0; continue; } if(comstate == 6 || comstate == 8) --comstate; if(!comstate && c == '"') comstate = 5; if(!comstate && c == '\'') comstate = 7; if(comstate) { lastchar = c; continue; } lastchar = c; if(c == '{') { ++nestlev; if(nestlev > 1) continue; if(last_ns != ')') continue; if(!strncmp(fword, "static", 6) || !strncmp(fword, "gstatic", 7)) { if(globalflag) continue; } else { if(staticflag) continue; } fseek(f, offset, 0); while(++offset < charcnt) { c = getc(f); #ifdef DOSLIKE if(c == '\n') ++offset; #endif if(c == '\n' && comstate == 4) { comstate = 0; continue; } if(c == '/') { char newstate[] = { 1, 4, 0, 0, 4 }; comstate = newstate[comstate]; continue; } if(comstate == 1) comstate = 2; if(comstate) continue; if(isspace(c)) { c = ' '; if(spc) continue; spc = 1; } else spc = 0; putchar(c); } printf(";\n"); getc(f); continue; } /* { read */ if(c == '}') { --nestlev; if(!nestlev) semstate = 1; continue; } if(nestlev) continue; if(c == ';') { semstate = 1; continue; } if(isspace(c)) continue; last_ns = c; if(!semstate) { if(fword_cnt < 10) fword[fword_cnt++] = c; continue; } offset = charcnt - 1; fword[0] = c; fword_cnt = 1; semstate = 0; } if(!staticflag) printf("\n"); fclose(f); } /* mkproto */ edbrowse-3.6.0.1/tools/PaxHeaders.22102/buildsourcestring.pl0000644000000000000000000000006212637565441020477 xustar0020 atime=1451158305 30 ctime=1451409178.157337163 edbrowse-3.6.0.1/tools/buildsourcestring.pl0000755000000000000000000000320512637565441020753 0ustar00rootroot00000000000000#!/usr/bin/perl -w # Turn a file into a C string containing the contents of that file. # Windows has a limit of 16380 single-byte characters. use strict; use warnings; sub prt($) { print shift; } my $max_chars = 16380; my $nargs = $#ARGV; if($nargs < 2) { prt "Usage: buildsourcestring.pl file1 string1 file2 string2 ... outfile\n"; exit 1; } my $outfile = $ARGV[$nargs]; my $outbase = $outfile; $outbase =~ s,.*/,,; if (! open OUTF, ">$outfile") { prt("Error: Unable to create $outfile file!\n"); exit(1); } print OUTF "/* $outbase: this file is machine generated; */\n\n"; # loop over input files for(my $j = 0; $j < $nargs; $j += 2) { my $infile = $ARGV[$j]; my $inbase = $infile; $inbase =~ s,.*/,,; my $stringname = $ARGV[$j+1]; if ( -f $infile ) { if (!open INF, "<$infile") { prt("Error: Unable to open $infile file!\n"); exit(1); } my @lines = ; close INF; print OUTF "/* source file $inbase */\n"; print OUTF "const char *$stringname = \"\\\n"; my ($line,$len,$total); $total = 0; foreach $line (@lines) { chomp $line; # in case \r is not removed on windows $line =~ s/\r*$//; $line =~ s/\\/\\\\/g; $line =~ s/"/\\"/g; $len = length($line) + 4; if (($total + $len) > $max_chars) { print OUTF "\"\n\""; $total = 0; } print OUTF "$line\\n\\\n"; $total += $len + 4; } print OUTF "\";\n"; print OUTF "\n"; prt("Content $infile written to $outfile\n"); } else { prt("Error: Unable to locate $infile file!\n"); exit(1); } } close OUTF; exit(0); # eof edbrowse-3.6.0.1/tools/PaxHeaders.22102/buildmsgstrings.pl0000644000000000000000000000006212637565441020150 xustar0020 atime=1451158305 30 ctime=1451409178.157337163 edbrowse-3.6.0.1/tools/buildmsgstrings.pl0000755000000000000000000000245412637565441020431 0ustar00rootroot00000000000000#!/usr/bin/perl -w # Build message strings for the languages that are supported. # This is much simpler than buildsourcestring.pl. # No interpretation or escaping, each line ia assumed to be a C string, # put quotes around it and that's it! # That means none of these lines can contain quotes, unless of course escaped, # as in \"hello\" use strict; use warnings; sub prt($) { print shift; } my @files = glob "lang/msg-*"; my $infile; my $outfile = "src/msg-strings.c"; my $outbase = $outfile; $outbase =~ s,.*/,,; if (! open OUTF, ">$outfile") { prt("Error: Unable to create $outfile!\n"); exit(1); } print OUTF "/* $outbase: this file is machine generated; */\n\n"; # loop over input files foreach $infile (@files) { my $inbase = $infile; $inbase =~ s,.*/,,; my $stringname = $inbase; $stringname =~ s/-/_/; if (!open INF, "<$infile") { prt("Error: Unable to open $infile!\n"); exit(1); } my @lines = ; my $line; close INF; print OUTF "/* source file $inbase */\n"; print OUTF "const char *$stringname" . "[] = {\n"; foreach $line (@lines) { chomp $line; # in case \r is not removed on windows $line =~ s/\r*$//; if($line =~ /^[0,\s]*$/) { print OUTF "\t0,\n"; } else { print OUTF "\t\"$line\",\n"; } } print OUTF "};\n\n"; } exit 0; edbrowse-3.6.0.1/tools/PaxHeaders.22102/buildebrcstring.pl0000644000000000000000000000006212637565441020112 xustar0020 atime=1451158305 30 ctime=1451409178.157337163 edbrowse-3.6.0.1/tools/buildebrcstring.pl0000755000000000000000000000062012637565441020364 0ustar00rootroot00000000000000#!/usr/bin/perl -w # Wrapper around buildsourcestring.pl, for the ebrc files in various languages. use strict; use warnings; my $s = join ' ', glob "lang/ebrc-*"; $s =~ s/ebrc-\w+/$& $&/g; # Some inconsistency has evolved here, # file name is hyphen, but C string has to be an underscore. $s =~ s/ ebrc-/ ebrc_/g; # Ready to go. system "perl -w tools/buildsourcestring.pl $s src/ebrc.c"; exit 0; edbrowse-3.6.0.1/tools/PaxHeaders.22102/Lindent0000644000000000000000000000006212637565441015713 xustar0020 atime=1451158305 30 ctime=1451409178.157337163 edbrowse-3.6.0.1/tools/Lindent0000755000000000000000000000071412637565441016171 0ustar00rootroot00000000000000#!/bin/sh PARAM="-npro -kr -i8 -ts8 -sob -l80 -ss -ncs -cp1" RES=`indent --version` V1=`echo $RES | cut -d' ' -f3 | cut -d'.' -f1` V2=`echo $RES | cut -d' ' -f3 | cut -d'.' -f2` V3=`echo $RES | cut -d' ' -f3 | cut -d'.' -f3` if [ $V1 -gt 2 ]; then PARAM="$PARAM -il0" elif [ $V1 -eq 2 ]; then if [ $V2 -gt 2 ]; then PARAM="$PARAM -il0"; elif [ $V2 -eq 2 ]; then if [ $V3 -ge 10 ]; then PARAM="$PARAM -il0" fi fi fi indent $PARAM "$@" edbrowse-3.6.0.1/PaxHeaders.22102/src0000644000000000000000000000006212637565441013745 xustar0020 atime=1451158305 30 ctime=1451409178.157337163 edbrowse-3.6.0.1/src/0000755000000000000000000000000012637565441014273 5ustar00rootroot00000000000000edbrowse-3.6.0.1/src/PaxHeaders.22102/url.c0000644000000000000000000000006212637565441014770 xustar0020 atime=1451158305 30 ctime=1451409178.157337163 edbrowse-3.6.0.1/src/url.c0000644000000000000000000006125512637565441015252 0ustar00rootroot00000000000000/* url.c * Separate a url into pieces, turn a relative file into an absolute url. * This file is part of the edbrowse project, released under GPL. */ #include "eb.h" struct PROTOCOL { const char prot[12]; int port; bool free_syntax; bool need_slashes; bool need_slash_after_host; } protocols[] = { { "file", 0, true, true, false}, { "http", 80, false, true, true}, { "https", 443, false, true, true}, { "pop3", 110, false, true, true}, { "pop3s", 995, false, true, true}, { "imap", 220, false, true, true}, { "imaps", 993, false, true, true}, { "smtp", 25, false, true, true}, { "submission", 587, false, true, true}, { "smtps", 465, false, true, true}, { "proxy", 3128, false, true, true}, { "ftp", 21, false, true, true}, { "sftp", 22, false, true, true}, { "scp", 22, false, true, true}, { "ftps", 990, false, true, true}, { "tftp", 69, false, true, true}, { "rtsp", 554, false, true, true}, { "pnm", 7070, false, true, true}, { "finger", 79, false, true, true}, { "smb", 139, false, true, true}, { "mailto", 0, false, false, false}, { "telnet", 23, false, false, false}, { "tn3270", 0, false, false, false}, { "data", 0, true, false, false}, { "javascript", 0, true, false, false}, { "git", 0, false, false, false}, { "svn", 0, false, false, false}, { "gopher", 70, false, false, false}, { "magnet", 0, false, false, false}, { "irc", 0, false, false, false}, { "", 0},}; static bool free_syntax; static int protocolByName(const char *p, int l) { int i; for (i = 0; protocols[i].prot[0]; i++) if (memEqualCI(protocols[i].prot, p, l)) return i; return -1; } /* protocolByName */ /* Unpercent the host component of a url, not the data component. */ void unpercentURL(char *url) { char c, *u, *w; int n; u = w = url; while (c = *u) { ++u; if (c == '+') c = ' '; if (c == '%' && isxdigit(u[0]) && isxdigit(u[1])) { c = fromHex(u[0], u[1]); u += 2; } if (!c) c = ' '; /* should never happen */ *w++ = c; if (strchr("?#\1", c)) break; if (c != '/') continue; n = w - url; if (n == 1 || n > 16) break; if (w[-2] != ':' && w[-2] != '/') break; } strmove(w, u); } /* unpercentURL */ /* Unpercent an entire string. */ void unpercentString(char *s) { char c, *u, *w; u = w = s; while (c = *u) { ++u; if (c == '+') c = ' '; if (c == '%' && isxdigit(u[0]) && isxdigit(u[1])) { c = fromHex(u[0], u[1]); u += 2; } if (!c) c = ' '; /* should never happen */ *w++ = c; } *w = 0; } /* unpercentString */ /* * Function: percentURL * Arguments: ** start: pointer to start of input string ** end: pointer to end of input string. * Return value: A new string or NULL if memory allocation failed. * This function copies its input to a dynamically-allocated buffer, * while performing the following transformation. Change backslash to slash, * and percent-escape some of the reserved characters as per RFC3986. * Some of the chars retain their reserved semantics and should not be changed. * This is a friggin guess! * All characters in the area between start and end, not including end, * are copied or transformed. * Get rid of :/ curl can't handle it. * This function is used to sanitize user-supplied URLs. */ /* these punctuations are percentable, anywhere in a url */ static const char percentable[] = "!*'()[]+$,"; static char hexdigits[] = "0123456789abcdef"; #define ESCAPED_CHAR_LENGTH 3 char *percentURL(const char *start, const char *end) { int bytes_to_alloc; char *new_copy; const char *in_pointer; char *out_pointer; const char *portloc; if (!end) end = start + strlen(start); bytes_to_alloc = end - start + 1; new_copy = NULL; in_pointer = NULL; out_pointer = NULL; portloc = NULL; for (in_pointer = start; in_pointer < end; in_pointer++) if (*in_pointer <= ' ' || strchr(percentable, *in_pointer)) bytes_to_alloc += (ESCAPED_CHAR_LENGTH - 1); new_copy = allocMem(bytes_to_alloc); if (new_copy) { char *frag, *params; out_pointer = new_copy; for (in_pointer = start; in_pointer < end; in_pointer++) { if (*in_pointer == '\\') *out_pointer++ = '/'; else if (*in_pointer <= ' ' || strchr(percentable, *in_pointer)) { *out_pointer++ = '%'; *out_pointer++ = hexdigits[(uchar) (*in_pointer & 0xf0) >> 4]; *out_pointer++ = hexdigits[(*in_pointer & 0x0f)]; } else *out_pointer++ = *in_pointer; } *out_pointer = '\0'; /* excise #hash, required by some web servers */ frag = findHash(new_copy); if (frag) *frag = 0; getPortLocURL(new_copy, &portloc, 0); if (portloc && !isdigit(portloc[1])) { const char *s = portloc + strcspn(portloc, "/?#\1"); strmove((char *)portloc, s); } } return new_copy; } /* percentURL */ /* escape & < > for display on a web page */ char *htmlEscape0(const char *s, bool do_and) { char *t; int l; if (!s) return 0; if (!*s) return emptyString; t = initString(&l); for (; *s; ++s) { if (*s == '&' && do_and) { stringAndString(&t, &l, "&"); continue; } if (*s == '<') { stringAndString(&t, &l, "<"); continue; } if (*s == '>') { stringAndString(&t, &l, ">"); continue; } stringAndChar(&t, &l, *s); } return t; } /* htmlEscape0 */ /* Decide if it looks like a web url. */ static bool httpDefault(const char *url) { static const char *const domainSuffix[] = { "com", "biz", "info", "net", "org", "gov", "edu", "us", "uk", "au", "ca", "de", "jp", "nz", 0 }; int n, len; const char *s, *lastdot, *end = url + strcspn(url, "/?#\1"); if (end - url > 7 && stringEqual(end - 7, ".browse")) end -= 7; s = strrchr(url, ':'); if (s && s < end) { const char *colon = s; ++s; while (isdigitByte(*s)) ++s; if (s == end) end = colon; } /* need at least two embedded dots */ n = 0; for (s = url + 1; s < end - 1; ++s) if (*s == '.' && s[-1] != '.' && s[1] != '.') ++n, lastdot = s; if (n < 2) return false; /* All digits, like an ip address, is ok. */ if (n == 3) { for (s = url; s < end; ++s) if (!isdigitByte(*s) && *s != '.') break; if (s == end) return true; } /* Look for standard domain suffix */ ++lastdot; len = end - lastdot; for (n = 0; domainSuffix[n]; ++n) if (memEqualCI(lastdot, domainSuffix[n], len) && !domainSuffix[n][len]) return true; /* www.anything.xx is ok */ if (len == 2 && memEqualCI(url, "www.", 4)) return true; return false; } /* httpDefault */ /********************************************************************* From wikipedia url scheme://domain:port/path?query_string#fragment_id but I allow, at the end of this, control a followed by post data, with the understanding that there should not be query_string and post data simultaneously. *********************************************************************/ static int parseURL(const char *url, const char **proto, int *prlen, const char **user, int *uslen, const char **pass, int *palen, /* ftp protocol */ const char **host, int *holen, const char **portloc, int *port, const char **data, int *dalen, const char **post) { const char *p, *q; int a; if (proto) *proto = NULL; if (prlen) *prlen = 0; if (user) *user = NULL; if (uslen) *uslen = 0; if (pass) *pass = NULL; if (palen) *palen = 0; if (host) *host = NULL; if (holen) *holen = 0; if (portloc) *portloc = 0; if (port) *port = 0; if (data) *data = NULL; if (dalen) *dalen = 0; if (post) *post = NULL; free_syntax = false; if (!url) return -1; /* Find the leading protocol:// */ a = -1; p = strchr(url, ':'); if (p) { /* You have to have something after the colon */ q = p + 1; if (*q == '/') ++q; if (*q == '/') ++q; skipWhite(&q); if (!*q) return false; a = protocolByName(url, p - url); } if (a >= 0) { if (proto) *proto = url; if (prlen) *prlen = p - url; if (p[1] != '/' || p[2] != '/') { if (protocols[a].need_slashes) { if (p[1] != '/') { setError(MSG_ProtExpected, protocols[a].prot); return -1; } /* We got one out of two slashes, I'm going to call it good */ ++p; } p -= 2; } p += 3; } else { /* nothing yet */ if (p && p - url < 12 && p[1] == '/') { for (q = url; q < p; ++q) if (!isalphaByte(*q)) break; if (q == p) { /* some protocol we don't know */ char qprot[12]; memcpy(qprot, url, p - url); qprot[p - url] = 0; setError(MSG_BadProt, qprot); return -1; } } if (httpDefault(url)) { static const char http[] = "http://"; if (proto) *proto = http; if (prlen) *prlen = 4; a = 1; p = url; } } if (a < 0) return false; if (free_syntax = protocols[a].free_syntax) { if (data) *data = p; if (dalen) *dalen = strlen(p); return true; } /* find the end of the domain */ q = p + strcspn(p, "@?#/\1"); if (*q == '@') { /* user:password@host */ const char *pp = strchr(p, ':'); if (!pp || pp > q) { /* no password */ if (user) *user = p; if (uslen) *uslen = q - p; } else { if (user) *user = p; if (uslen) *uslen = pp - p; if (pass) *pass = pp + 1; if (palen) *palen = q - pp - 1; } p = q + 1; } /* again, look for the end of the domain */ q = p + strcspn(p, ":?#/\1"); if (host) *host = p; if (holen) *holen = q - p; if (*q == ':') { /* port specified */ int n; const char *cc, *pp = q + strcspn(q, "/?#\1"); if (pp > q + 1) { n = strtol(q + 1, (char **)&cc, 10); if (cc != pp || !isdigitByte(q[1])) { setError(MSG_BadPort); return -1; } if (port) *port = n; } if (portloc) *portloc = q; q = pp; /* up to the slash */ } else { if (port) *port = protocols[a].port; } /* colon or not */ /* Skip past /, but not ? or # */ if (*q == '/') q++; p = q; /* post data is handled separately */ q = p + strcspn(p, "\1"); if (data) *data = p; if (dalen) *dalen = q - p; if (post) *post = *q ? q + 1 : NULL; return true; } /* parseURL */ bool isURL(const char *url) { int j = parseURL(url, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); if (j < 0) return false; return j; } /* isURL */ /* non-FTP URLs are always browsable. FTP URLs are browsable if they end with * a slash. */ bool isBrowseableURL(const char *url) { if (isURL(url)) return (!memEqualCI(url, "ftp://", 6)) || (url[strlen(url) - 1] == '/'); else return false; } /* isBrowseableURL */ bool isDataURI(const char *u) { return memEqualCI(u, "data:", 5); } /* isDataURI */ /* Helper functions to return pieces of the URL. * Makes a copy, so you can have your 0 on the end. * Return 0 for an error, and "" if that piece is missing. */ const char *getProtURL(const char *url) { static char buf[12]; int l; const char *s; int rc = parseURL(url, &s, &l, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); if (rc <= 0) return 0; memcpy(buf, s, l); buf[l] = 0; return buf; } /* getProtURL */ static char hostbuf[400]; const char *getHostURL(const char *url) { int l; const char *s; char *t; char c, d; int rc = parseURL(url, 0, 0, 0, 0, 0, 0, &s, &l, 0, 0, 0, 0, 0); if (rc <= 0) return 0; if (free_syntax) return 0; if (!s) return emptyString; if (l >= sizeof(hostbuf)) { setError(MSG_DomainLong); return 0; } memcpy(hostbuf, s, l); if (l && hostbuf[l - 1] == '.') --l; hostbuf[l] = 0; /* domain names must be ascii, with no spaces */ d = 0; for (s = t = hostbuf; (c = *s); ++s) { c &= 0x7f; if (c == ' ') continue; if (c == '.' && d == '.') continue; *t++ = d = c; } *t = 0; return hostbuf; } /* getHostURL */ const char *getHostPassURL(const char *url) { int hl; const char *h, *z, *u; char *t; int rc = parseURL(url, 0, 0, &u, 0, 0, 0, &h, &hl, 0, 0, 0, 0, 0); if (rc <= 0 || !h) return 0; if (free_syntax) return 0; z = h; t = hostbuf; if (u) z = u, hl += h - u, t += h - u; if (hl >= sizeof(hostbuf)) { setError(MSG_DomainLong); return 0; } memcpy(hostbuf, z, hl); hostbuf[hl] = 0; /* domain names must be ascii */ for (; *t; ++t) *t &= 0x7f; return hostbuf; } /* getHostPassURL */ const char *getUserURL(const char *url) { static char buf[MAXUSERPASS]; int l; const char *s; int rc = parseURL(url, 0, 0, &s, &l, 0, 0, 0, 0, 0, 0, 0, 0, 0); if (rc <= 0) return 0; if (free_syntax) return emptyString; if (!s) return emptyString; if (l >= sizeof(buf)) { setError(MSG_UserNameLong2); return 0; } memcpy(buf, s, l); buf[l] = 0; return buf; } /* getUserURL */ const char *getPassURL(const char *url) { static char buf[MAXUSERPASS]; int l; const char *s; int rc = parseURL(url, 0, 0, 0, 0, &s, &l, 0, 0, 0, 0, 0, 0, 0); if (rc <= 0) return 0; if (free_syntax) return emptyString; if (!s) return emptyString; if (l >= sizeof(buf)) { setError(MSG_PasswordLong2); return 0; } memcpy(buf, s, l); buf[l] = 0; return buf; } /* getPassURL */ const char *getDataURL(const char *url) { const char *s; int rc = parseURL(url, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, &s, 0, 0); if (rc <= 0) return 0; return s; } /* getDataURL */ void getDirURL(const char *url, const char **start_p, const char **end_p) { const char *dir = getDataURL(url); const char *end; static const char myslash[] = "/"; if (!dir || dir == url) goto slash; if (free_syntax) goto slash; if (!strchr("#?\1", *dir)) { if (*--dir != '/') i_printfExit(MSG_BadDirSlash, url); } if (*dir == '#') /* special case */ end = dir; else end = strpbrk(dir, "?\1"); if (!end) end = dir + strlen(dir); while (end > dir && end[-1] != '/') --end; if (end > dir) { *start_p = dir; *end_p = end; return; } slash: *start_p = myslash; *end_p = myslash + 1; } /* getDirURL */ /* #tag is only meaningfull after the last slash */ char *findHash(const char *s) { const char *t = strrchr(s, '/'); if (t) s = t; return (char *)strchr(s, '#'); } /* findHash */ /* extract the file piece of a pathname or url */ /* This is for debugPrint or w/, so could be chopped for convenience */ char *getFileURL(const char *url, bool chophash) { const char *s; const char *e; s = strrchr(url, '/'); if (s) ++s; else s = url; e = 0; if (isURL(url)) { chophash = true; e = strpbrk(s, "?\1"); } if (!e) e = s + strlen(s); if (chophash) { const char *h = findHash(s); if (h) e = h; } /* if slash at the end then back up to the prior slash */ if (e == s && s > url) { while (s > url && s[-1] == '/') --s; e = s; while (s > url && s[-1] != '/') --s; } /* don't retain the .browse suffix on a url */ if (e - s > 7 && stringEqual(e - 7, ".browse")) e -= 7; if (e - s > 64) e = s + 64; if (e == s) strcpy(hostbuf, "/"); else { strncpy(hostbuf, s, e - s); hostbuf[e - s] = 0; } return hostbuf; } /* getFileURL */ bool getPortLocURL(const char *url, const char **portloc, int *port) { int rc = parseURL(url, 0, 0, 0, 0, 0, 0, 0, 0, portloc, port, 0, 0, 0); if (rc <= 0) return false; if (free_syntax) return false; return true; } /* getPortLocURL */ int getPortURL(const char *url) { int port; int rc = parseURL(url, 0, 0, 0, 0, 0, 0, 0, 0, 0, &port, 0, 0, 0); if (rc <= 0) return 0; if (free_syntax) return 0; return port; } /* getPortURL */ bool isProxyURL(const char *url) { return ((url[0] | 0x20) == 'p'); } /* * copyPathSegment: copy everything from *src, starting with the leftmost * character (a slash), and ending with either the next slash (not included) * or the end of the string. * Advance *src to point to the character succeeding the copied text. */ static void copyPathSegment(char **src, char **dest, int *destlen) { int spanlen = strcspn(*src + 1, "/") + 1; stringAndBytes(dest, destlen, *src, spanlen); *src = *src + spanlen; } /* copyPathSegment */ /* * Remove the rightmost component of a path, * including the preceding slash, if any. */ static void snipLastSegment(char **path, int *pathLen) { char *rightmostSlash = strrchr(*path, '/'); if (rightmostSlash == NULL) rightmostSlash = *path; *rightmostSlash = '\0'; *pathLen = rightmostSlash - *path; } /* snipLastSegment */ static void squashDirectories(char *url) { char *dd = (char *)getDataURL(url); char *s, *t, *end; char *inPath = NULL; char *outPath; int outPathLen = 0; char *rest = NULL; outPath = initString(&outPathLen); if (memEqualCI(url, "javascript:", 11)) return; if (!dd || dd == url) return; if (!*dd) return; if (strchr("#?\1", *dd)) return; --dd; /* dd could point to : in bogus code such as */ /* crap: looks like a slashless protocol, perhaps unknown to us. */ if (*dd == ':') return; if (*dd != '/') i_printfExit(MSG_BadSlash, url); end = dd + strcspn(dd, "?\1"); rest = cloneString(end); inPath = pullString1(dd, end); s = inPath; /* The following algorithm is straight out of RFC 3986, section 5.2.4. */ /* We can ignore several steps because of a loop invariant: */ /* After the test, *s is always a slash. */ while (*s) { if (!strncmp(s, "/./", 3)) s += 2; /* Point s at 2nd slash */ else if (!strcmp(s, "/.")) { s[1] = '\0'; /* We'll copy the segment "/" on the next iteration. */ /* And that will be the final iteration of the loop. */ } else if (!strncmp(s, "/../", 4)) { s += 3; /* Point s at 2nd slash */ snipLastSegment(&outPath, &outPathLen); } else if (!strcmp(s, "/..")) { s[1] = '\0'; snipLastSegment(&outPath, &outPathLen); /* As above, copy "/" on the next and final iteration. */ } else copyPathSegment(&s, &outPath, &outPathLen); } *dd = '\0'; strcat(url, outPath); strcat(url, rest); nzFree(inPath); nzFree(outPath); nzFree(rest); } /* squashDirectories */ char *resolveURL(const char *base, const char *rel) { char *n; /* new url */ const char *s, *p; char *q; int l; if (memEqualCI(rel, "data:", 5)) return cloneString(rel); if (!base) base = emptyString; n = allocString(strlen(base) + strlen(rel) + 12); debugPrint(5, "resolve(%s|%s)", base, rel); if (rel[0] == '#' && !strchr(rel, '/')) { /* This is an anchor for the current document, don't resolve. */ /* I assume the base does not have a #fragment on the end; that is not part of the base. */ /* Thus I won't get url#foo#bar */ strcpy(n, rel); out_n: debugPrint(5, "= %s", n); return n; } if (rel[0] == '?' || rel[0] == '\1') { /* setting or changing get or post data */ strcpy(n, base); for (q = n; *q && *q != '\1' && *q != '?'; q++) ; strcpy(q, rel); goto out_n; } if (rel[0] == '/' && rel[1] == '/') { if (s = strstr(base, "//")) { strncpy(n, base, s - base); n[s - base] = 0; } else strcpy(n, "http:"); strcat(n, rel); goto squash; } if (parseURL(rel, &s, &l, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) > 0) { /* has a protocol */ n[0] = 0; if (s != rel) { /* It didn't have http in front of it before, put it on now. */ strncpy(n, s, l); strcpy(n + l, "://"); } strcat(n, rel); goto squash; } /* relative is already a url */ s = base; if (rel[0] == '/') { s = getDataURL(base); if (!s) { strcpy(n, rel); goto squash; } if (!*s) { if (s - base >= 7 && stringEqual(s - 7, ".browse")) s -= 7; if (s > base && s[-1] == '/') --s; } else if (!strchr("#?\1", *s)) { --s; } else if (s[-1] == '/') --s; l = s - base; strncpy(n, base, l); strcpy(n + l, rel); goto squash; } /* This is a relative change, paste it on after the last slash */ s = base; if (parseURL(base, 0, 0, 0, 0, 0, 0, &p, 0, 0, 0, 0, 0, 0) > 0 && p) s = p; for (p = 0; *s; ++s) { if (*s == '/') p = s; if (strchr("?\1", *s)) break; } if (!p) { if (isURL(base)) p = s; else p = base; } l = p - base; if (l) { strncpy(n, base, l); n[l++] = '/'; } strcpy(n + l, rel); squash: squashDirectories(n); goto out_n; } /* resolveURL */ /* This routine could be, should be, more sophisticated */ bool sameURL(const char *s, const char *t) { const char *u, *p, *q; int l; if (!s || !t) return false; /* check for post data at the end */ p = strchr(s, '\1'); if (!p) p = s + strlen(s); q = strchr(t, '\1'); if (!q) q = t + strlen(t); if (!stringEqual(p, q)) return false; /* lop off hash */ if (u = findHash(s)) p = u; if (u = findHash(t)) q = u; /* It's ok if one says http and the other implies it. */ if (memEqualCI(s, "http://", 7)) s += 7; if (memEqualCI(t, "http://", 7)) t += 7; if (p - s >= 7 && stringEqual(p - 7, ".browse")) p -= 7; if (q - t >= 7 && stringEqual(q - 7, ".browse")) q -= 7; l = p - s; if (l != q - t) return false; return !memcmp(s, t, l); } /* sameURL */ /* Find some helpful text to print, in place of an image. * Not sure why we would need more than 1000 chars for this, * so return a static buffer. */ char *altText(const char *base) { static char buf[1000]; int len, n; int recount = 0; char *s; debugPrint(6, "altText(%s)", base); if (!base) return 0; if (stringEqual(base, "#")) return 0; if (memEqualCI(base, "javascript", 10)) return 0; retry: if (recount >= 2) return 0; strncpy(buf, base, sizeof(buf) - 1); spaceCrunch(buf, true, false); len = strlen(buf); if (len && !isalnumByte(buf[len - 1])) buf[--len] = 0; while (len && !isalnumByte(buf[0])) strmove(buf, buf + 1), --len; if (len > 10) { /* see whether it's a phrase/sentence or a pathname/url */ /* Do this by counting spaces */ for (n = 0, s = buf; *s; ++s) if (*s == ' ') ++n; if (n * 8 >= len) return buf; /* looks like words */ /* Ok, now we believe it's a pathname or url */ /* get rid of post or get data */ s = strpbrk(buf, "?\1"); if (s) *s = 0; /* get rid of common suffix */ s = strrchr(buf, '.'); if (s) { /* get rid of trailing .html */ static const char *const suffix[] = { "html", "htm", "shtml", "shtm", "php", "asp", "cgi", "rm", "ram", "gif", "jpg", "bmp", 0 }; n = stringInListCI(suffix, s + 1); if (n >= 0 || s[1] == 0) *s = 0; } /* Get rid of everything up to the last slash, leaving the file name */ s = strrchr(buf, '/'); if (s && recount) { char *ss; *s = 0; ss = strrchr(buf, '/'); if (!ss) return 0; if (ss > buf && ss[-1] == '/') return 0; *s = '/'; s = ss; } if (s) strmove(buf, s + 1); } /* more than ten characters */ ++recount; /* If we don't have enough letters, forget it */ len = strlen(buf); if (len < 3) goto retry; for (n = 0, s = buf; *s; ++s) if (isalphaByte(*s)) ++n; if (n * 2 <= len) return 0; /* not enough letters */ return buf; } /* altText */ /* get post data ready for a url. */ char *encodePostData(const char *s) { char *post, c; int l; char buf[4]; if (!s) return 0; if (s == emptyString) return emptyString; post = initString(&l); while (c = *s++) { if (isalnumByte(c)) goto putc; if (strchr("-._~*()!", c)) goto putc; sprintf(buf, "%%%02X", (uchar) c); stringAndString(&post, &l, buf); continue; putc: stringAndChar(&post, &l, c); } return post; } /* encodePostData */ static char dohex(char c, const char **sp) { const char *s = *sp; char d, e; if (c == '+') return ' '; if (c != '%') return c; d = *s++; e = *s++; if (!isxdigit(d) || !isxdigit(e)) return c; /* should never happen */ d = fromHex(d, e); if (!d) d = ' '; /* don't allow nulls */ *sp = s; return d; } /* dohex */ char *decodePostData(const char *data, const char *name, int seqno) { const char *s, *n, *t; char *ns = 0, *w = 0; int j = 0; char c; if (!seqno && !name) i_printfExit(MSG_DecodePost); for (s = data; *s; s = (*t ? t + 1 : t)) { n = 0; t = strchr(s, '&'); if (!t) t = s + strlen(s); /* select attribute by number */ ++j; if (j == seqno) w = ns = allocString(t - s + 1); if (seqno && !w) continue; if (name) n = name; while (s < t && (c = *s) != '=') { ++s; c = dohex(c, &s); if (n) { /* I don't know if this is suppose to be case insensitive all the time, * though there are situations when it must be, as in * mailto:address?Subject=blah-blah */ if (isalphaByte(c)) { if (!((c ^ *n) & 0xdf)) ++n; else n = 0; } else if (c == *n) ++n; else n = 0; } if (w) *w++ = c; } if (s == t) { /* no equals, just a string */ if (name) continue; *w = 0; return ns; } if (w) *w++ = c; ++s; /* skip past equals */ if (name) { if (!n) continue; if (*n) continue; w = ns = allocString(t - s + 1); } /* At this point we have a match */ while (s < t) { c = *s++; c = dohex(c, &s); *w++ = c; } *w = 0; return ns; } return 0; } /* decodePostData */ void decodeMailURL(const char *url, char **addr_p, char **subj_p, char **body_p) { const char *s; if (memEqualCI(url, "mailto:", 7)) url += 7; s = url + strcspn(url, "/?"); if (addr_p) *addr_p = pullString1(url, s); if (subj_p) *subj_p = 0; if (body_p) *body_p = 0; s = strchr(url, '?'); if (!s) return; url = s + 1; if (subj_p) *subj_p = decodePostData(url, "subject", 0); if (body_p) *body_p = decodePostData(url, "body", 0); } /* decodeMailURL */ edbrowse-3.6.0.1/src/PaxHeaders.22102/stringfile.c0000644000000000000000000000006112637565441016333 xustar0020 atime=1451158305 29 ctime=1451409178.15333719 edbrowse-3.6.0.1/src/stringfile.c0000644000000000000000000010012312637565441016602 0ustar00rootroot00000000000000/* stringfile.c: manage strings, files, and directories for edbrowse. * This file is part of the edbrowse project, released under GPL. */ #include "eb.h" #include #include #ifdef DOSLIKE #include #else #include #include #include #include #include #endif char emptyString[] = ""; bool showHiddenFiles, isInteractive; int debugLevel = 1; char *downDir, *home; /********************************************************************* Allocate and manage memory. Allocate and copy strings. If we're out of memory, the program aborts. No error legs. Soooooo much easier! With 32gb of RAM, we shouldn't run out. *********************************************************************/ void *allocMem(size_t n) { void *s; if (!n) return emptyString; if (!(s = malloc(n))) i_printfExit(MSG_MemAllocError, n); return s; } /* allocMem */ void *allocZeroMem(size_t n) { void *s; if (!n) return emptyString; if (!(s = calloc(n, 1))) i_printfExit(MSG_MemCallocError, n); return s; } /* allocZeroMem */ void *reallocMem(void *p, size_t n) { void *s; if (!n) i_printfExit(MSG_ReallocP); /* small check against allocated strings getting huge */ if (n < 0) i_printfExit(MSG_MemAllocError, n); if (!p) i_printfExit(MSG_Realloc0, n); if (p == emptyString) return allocMem(n); if (!(s = realloc(p, n))) i_printfExit(MSG_ErrorRealloc, n); return s; } /* reallocMem */ /* When you know the allocated thing is a string. */ char *allocString(size_t n) { return (char *)allocMem(n); } /* allocString */ char *allocZeroString(size_t n) { return (char *)allocZeroMem(n); } /* allocZeroString */ char *reallocString(void *p, size_t n) { return (char *)reallocMem(p, n); } /* reallocString */ void nzFree(void *s) { if (s && s != emptyString) free(s); } /* nzFree */ /* some compilers care whether it's void * or const void * */ void cnzFree(const void *v) { nzFree((void *)v); } /* cnzFree */ uchar fromHex(char d, char e) { d |= 0x20, e |= 0x20; if (d >= 'a') d -= ('a' - '9' - 1); if (e >= 'a') e -= ('a' - '9' - 1); d -= '0', e -= '0'; return ((((uchar) d) << 4) | (uchar) e); } /* fromHex */ char *appendString(char *s, const char *p) { int slen = strlen(s); int plen = strlen(p); s = reallocString(s, slen + plen + 1); strcpy(s + slen, p); return s; } /* appendstring */ char *prependString(char *s, const char *p) { int slen = strlen(s); int plen = strlen(p); char *t = allocString(slen + plen + 1); strcpy(t, p); strcpy(t + plen, s); nzFree(s); return t; } /* prependstring */ void skipWhite(const char **s) { const char *t = *s; while (isspaceByte(*t)) ++t; *s = t; } /* skipWhite */ void trimWhite(char *s) { int l; if (!s) return; l = strlen(s); while (l && isspaceByte(s[l - 1])) --l; s[l] = 0; } /* trimWhite */ void stripWhite(char *s) { const char *t = s; skipWhite(&t); if (t > s) strmove(s, t); trimWhite(s); } /* stripWhite */ /* compress white space */ void spaceCrunch(char *s, bool onespace, bool unprint) { int i, j; char c; bool space = true; for (i = j = 0; c = s[i]; ++i) { if (isspaceByte(c)) { if (!onespace) continue; if (!space) s[j++] = ' ', space = true; continue; } if (unprint && !isprintByte(c)) continue; s[j++] = c, space = false; } if (space && j) --j; /* drop trailing space */ s[j] = 0; } /* spaceCrunch */ /* Like strcpy, but able to cope with overlapping strings. */ char *strmove(char *dest, const char *src) { return (char *)memmove(dest, src, strlen(src) + 1); } /* strmove */ /* OO has a lot of unnecessary overhead, and a few inconveniences, * but I really miss it right now. The following * routines make up for the lack of simple string concatenation in C. * The string space allocated is always a power of 2 - 1, starting with 1. * Each of these routines puts an extra 0 on the end of the "string". */ char *initString(int *l) { *l = 0; return emptyString; } /* String management routines realloc to one less than a power of 2 */ void stringAndString(char **s, int *l, const char *t) { char *p = *s; int oldlen, newlen, x; oldlen = *l; newlen = oldlen + strlen(t); *l = newlen; ++newlen; /* room for the 0 */ x = oldlen ^ newlen; if (x > oldlen) { /* must realloc */ newlen |= (newlen >> 1); newlen |= (newlen >> 2); newlen |= (newlen >> 4); newlen |= (newlen >> 8); newlen |= (newlen >> 16); p = reallocString(p, newlen); *s = p; } strcpy(p + oldlen, t); } /* stringAndString */ void stringAndBytes(char **s, int *l, const char *t, int cnt) { char *p = *s; int oldlen, newlen, x; oldlen = *l; newlen = oldlen + cnt; *l = newlen; ++newlen; x = oldlen ^ newlen; if (x > oldlen) { /* must realloc */ newlen |= (newlen >> 1); newlen |= (newlen >> 2); newlen |= (newlen >> 4); newlen |= (newlen >> 8); newlen |= (newlen >> 16); p = reallocString(p, newlen); *s = p; } memcpy(p + oldlen, t, cnt); p[oldlen + cnt] = 0; } /* stringAndBytes */ void stringAndChar(char **s, int *l, char c) { char *p = *s; int oldlen, newlen, x; oldlen = *l; newlen = oldlen + 1; *l = newlen; ++newlen; x = oldlen ^ newlen; if (x > oldlen) { /* must realloc */ newlen |= (newlen >> 1); newlen |= (newlen >> 2); newlen |= (newlen >> 4); newlen |= (newlen >> 8); newlen |= (newlen >> 16); p = reallocString(p, newlen); *s = p; } p[oldlen] = c; p[oldlen + 1] = 0; } /* stringAndChar */ void stringAndNum(char **s, int *l, int n) { char a[16]; sprintf(a, "%d", n); stringAndString(s, l, a); } /* stringAndNum */ /* 64M 16K etc */ void stringAndKnum(char **s, int *l, int n) { char a[16]; if (n && n / (1024 * 1024) * (1024 * 1024) == n) sprintf(a, "%dM", n / (1024 * 1024)); else if (n && n / 1024 * 1024 == n) sprintf(a, "%dK", n / 1024); else sprintf(a, "%d", n); stringAndString(s, l, a); } /* stringAndKnum */ char *cloneString(const char *s) { char *t; unsigned len; if (!s) return 0; if (!*s) return emptyString; len = strlen(s) + 1; t = allocString(len); strcpy(t, s); return t; } /* cloneString */ char *cloneMemory(const char *s, int n) { char *t = allocString(n); if (n) memcpy(t, s, n); return t; } /* cloneMemory */ void leftClipString(char *s) { char *t; if (!s) return; for (t = s; *t; ++t) if (!isspace(*t)) break; if (t > s) strmove(s, t); } /* leftClipString */ /* shift string one to the right */ void shiftRight(char *s, char first) { int l = strlen(s); for (; l >= 0; --l) s[l + 1] = s[l]; s[0] = first; } /* shiftRight */ char *Cify(const char *s, int n) { char *u; char *t = allocString(n + 1); if (n) memcpy(t, s, n); for (u = t; u < t + n; ++u) if (*u == 0) *u = ' '; *u = 0; return t; } /* Cify */ /* pull a substring out of a larger string, * and make it its own allocated string */ char *pullString(const char *s, int l) { char *t; if (!l) return emptyString; t = allocString(l + 1); memcpy(t, s, l); t[l] = 0; return t; } /* pullString */ char *pullString1(const char *s, const char *t) { return pullString(s, t - s); } /* return the number, if string is a number, else -1 */ int stringIsNum(const char *s) { int n; if (!isdigitByte(s[0])) return -1; n = strtol(s, (char **)&s, 10); if (*s) return -1; return n; } /* stringIsNum */ bool stringIsDate(const char *s) { if (!isdigit(*s)) return false; ++s; if (isdigit(*s)) ++s; if (*s++ != '/') return false; if (!isdigit(*s)) return false; ++s; if (isdigit(*s)) ++s; if (*s++ != '/') return false; if (!isdigit(*s)) return false; ++s; if (isdigit(*s)) ++s; if (isdigit(*s)) ++s; if (isdigit(*s)) ++s; if (*s) return false; return true; } /* stringIsDate */ bool stringIsFloat(const char *s, double *dp) { const char *t; *dp = strtod(s, (char **)&t); if (*t) return false; /* extra stuff at the end */ return true; } /* stringIsFloat */ bool memEqualCI(const char *s, const char *t, int len) { char c, d; if (s == t) return true; if (!s || !t) return false; while (len--) { c = *s, d = *t; if (islowerByte(c)) c = toupper(c); if (islowerByte(d)) d = toupper(d); if (c != d) return false; ++s, ++t; } return true; } /* memEqualCI */ char *strstrCI(const char *base, const char *search) { int l = strlen(search); while (*base) { if (memEqualCI(base, search, l)) return (char *)base; ++base; } return 0; } /* strstrCI */ bool stringEqual(const char *s, const char *t) { /* check equality of strings with handling of null pointers */ if (s == t) return true; if (!s || !t) return false; if (strcmp(s, t)) return false; return true; } /* stringEqual */ bool stringEqualCI(const char *s, const char *t) { char c, d; /* if two pointers are equal we can return */ if (s == t) return true; /* if one is NULL then the strings can't be equal */ if (!s || !t) return false; while ((c = *s) && (d = *t)) { if (islowerByte(c)) c = toupper(c); if (islowerByte(d)) d = toupper(d); if (c != d) return false; ++s, ++t; } if (*s) return false; if (*t) return false; return true; } /* stringEqualCI */ int stringInList(const char *const *list, const char *s) { int i = 0; if (!list) i_printfExit(MSG_NullStrList); if (s) while (*list) { if (stringEqual(s, *list)) return i; ++i; ++list; } return -1; } /* stringInList */ int stringInListCI(const char *const *list, const char *s) { int i = 0; if (!list) i_printfExit(MSG_NullStrListCI); if (s) while (*list) { if (stringEqualCI(s, *list)) return i; ++i; ++list; } return -1; } /* stringInListCI */ int charInList(const char *list, char c) { char *s; if (!list) i_printfExit(MSG_NullCharInList); s = (char *)strchr(list, c); if (!s) return -1; return s - list; } /* charInList */ /* In an empty list, next and prev point back to the list, not to 0. */ /* We also allow zero. */ bool listIsEmpty(const struct listHead * l) { return l->next == l || l->next == 0; } /* listIsEmpty */ void initList(struct listHead *l) { l->prev = l->next = l; } /* initList */ void delFromList(void *x) { struct listHead *xh = (struct listHead *)x; ((struct listHead *)xh->next)->prev = xh->prev; ((struct listHead *)xh->prev)->next = xh->next; } /* delFromList */ void addToListFront(struct listHead *l, void *x) { struct listHead *xh = (struct listHead *)x; xh->next = l->next; xh->prev = l; l->next = (struct listHead *)x; ((struct listHead *)xh->next)->prev = (struct listHead *)x; } /* addToListFront */ void addToListBack(struct listHead *l, void *x) { struct listHead *xh = (struct listHead *)x; xh->prev = l->prev; xh->next = l; l->prev = (struct listHead *)x; ((struct listHead *)xh->prev)->next = (struct listHead *)x; } /* addToListBack */ void addAtPosition(void *p, void *x) { struct listHead *xh = (struct listHead *)x; struct listHead *ph = (struct listHead *)p; xh->prev = p; xh->next = ph->next; ph->next = (struct listHead *)x; ((struct listHead *)xh->next)->prev = (struct listHead *)x; } /* addAtPosition */ void freeList(struct listHead *l) { while (!listIsEmpty(l)) { void *p = l->next; delFromList(p); nzFree(p); } } /* freeList */ /* like isalnumByte, but allows _ and - */ bool isA(char c) { if (isalnumByte(c)) return true; return (c == '_' || c == '-'); } /* isA */ bool isquote(char c) { return c == '"' || c == '\''; } /* isquote */ /* print an error message */ void errorPrint(const char *msg, ...) { char bailflag = 0; va_list p; va_start(p, msg); if (*msg == '@') { bailflag = 1; ++msg; /* I should internationalize this, but it's never suppose to happen! */ fprintf(stderr, "disaster, "); } else if (isdigitByte(*msg)) { bailflag = *msg - '0'; ++msg; } vfprintf(stderr, msg, p); fprintf(stderr, "\n"); va_end(p); if (bailflag) exit(bailflag); } /* errorPrint */ void debugPrint(int lev, const char *msg, ...) { va_list p; if (lev > debugLevel) return; va_start(p, msg); vprintf(msg, p); va_end(p); nl(); if (lev == 0 && !memcmp(msg, "warning", 7)) eeCheck(); } /* debugPrint */ void nl(void) { puts(""); } /* nl */ /* Turn perl string into C string, and complain about nulls. */ int perl2c(char *t) { int n = 0; while (*t != '\n') { if (*t == 0) ++n; ++t; } *t = 0; /* now it's a C string */ return n; /* number of nulls */ } /* perl2c */ /* The length of a perl string includes its terminating newline */ unsigned pstLength(pst s) { pst t; if (!s) i_printfExit(MSG_NullPtr); t = s; while (*t != '\n') ++t; return t + 1 - s; } /* pstLength */ pst clonePstring(pst s) { pst t; unsigned len; if (!s) return s; len = pstLength(s); t = (pst) allocMem(len); memcpy(t, s, len); return t; } /* clonePstring */ // Strings are assumed distinct, hence the use of memcpy. void copyPstring(pst s, const pst t) { int len = pstLength(t); memcpy(s, t, len); } /* copyPstring */ /* * fdIntoMemory reads data from a file descriptor, until EOF is reached. * It works even if we don't know the size beforehand. * We can now use it to read /proc files, pipes, and stdin. * This solves an outstanding issue, and it is needed for forthcoming * functionality, such as edpager. */ bool fdIntoMemory(int fd, char **data, int *len) { int length, n; const int blocksize = 8192; char *chunk, *buf; chunk = allocZeroString(blocksize); buf = initString(&length); n = 0; do { n = read(fd, chunk, blocksize); if (n < 0) { nzFree(buf); nzFree(chunk); *data = emptyString; *len = 0; setError(MSG_NoRead, "file descriptor"); return false; } if (n > 0) stringAndBytes(&buf, &length, chunk, n); } while (n != 0); nzFree(chunk); buf = reallocString(buf, length + 2); *data = buf; *len = length; return true; } /* fdIntoMemory */ bool fileIntoMemory(const char *filename, char **data, int *len) { int fh; char ftype = fileTypeByName(filename, false); bool ret; if (ftype && ftype != 'f') { setError(MSG_RegularFile, filename); return false; } fh = open(filename, O_RDONLY | O_BINARY); if (fh < 0) { setError(MSG_NoOpen, filename); return false; } ret = fdIntoMemory(fh, data, len); if (ret == false) setError(MSG_NoRead2, filename); close(fh); return ret; } /* fileIntoMemory */ /* inverse of the above */ bool memoryOutToFile(const char *filename, const char *data, int len, /* specify the error messages */ int msgcreate, int msgwrite) { int fh = open(filename, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0666); if (fh < 0) { setError(msgcreate, filename, errno); return false; } if (write(fh, data, len) < len) { setError(msgwrite, filename, errno); close(fh); return false; } close(fh); return true; } /* memoryOutToFile */ /* shift string to upper, lower, or mixed case */ /* action is u, l, or m. */ void caseShift(char *s, char action) { char c; int mc = 0; bool ws = true; for (; c = *s; ++s) { if (action == 'u') { if (isalphaByte(c)) *s = toupper(c); continue; } if (action == 'l') { if (isalphaByte(c)) *s = tolower(c); continue; } /* mixed case left */ if (isalphaByte(c)) { if (ws) c = toupper(c); else c = tolower(c); if (ws && c == 'M') mc = 1; else if (mc == 1 && c == 'c') mc = 2; else if (mc == 2) { c = toupper(c); mc = 0; } else mc = 0; *s = c; ws = false; continue; } ws = true, mc = 0; } /* loop */ } /* caseShift */ /********************************************************************* Manage files, directories, and terminal IO. You'll see some conditional compilation when this program is ported to other operating systems. *********************************************************************/ /* Return the type of a file. * Make it a capital letter if you are going through a link. * I think this will work on Windows, not sure. * But the link feature is Unix specific. */ char fileTypeByName(const char *name, bool showlink) { struct stat buf; bool islink = false; char c; int mode; #ifdef DOSLIKE if (stat(name, &buf)) { setError(MSG_NoAccess, name); return 0; } mode = buf.st_mode & S_IFMT; #else // !DOSLIKE if (lstat(name, &buf)) { setError(MSG_NoAccess, name); return 0; } mode = buf.st_mode & S_IFMT; if (mode == S_IFLNK) { /* symbolic link */ islink = true; /* If this fails, I'm guessing it's just a file. */ if (stat(name, &buf)) return (showlink ? 'F' : 0); mode = buf.st_mode & S_IFMT; } #endif // DOSLIKE y/n c = 'f'; if (mode == S_IFDIR) c = 'd'; #ifndef DOSLIKE /* I don't think these are Windows constructs. */ if (mode == S_IFBLK) c = 'b'; if (mode == S_IFCHR) c = 'c'; if (mode == S_IFIFO) c = 'p'; if (mode == S_IFSOCK) c = 's'; #endif if (islink & showlink) c = toupper(c); return c; } /* fileTypeByName */ char fileTypeByHandle(int fd) { struct stat buf; char c; int mode; if (fstat(fd, &buf)) { setError(MSG_NoAccess, "handle"); return 0; } mode = buf.st_mode & S_IFMT; c = 'f'; if (mode == S_IFDIR) c = 'd'; #ifndef DOSLIKE /* I don't think these are Windows constructs. */ if (mode == S_IFBLK) c = 'b'; if (mode == S_IFCHR) c = 'c'; if (mode == S_IFIFO) c = 'p'; if (mode == S_IFSOCK) c = 's'; #endif return c; } /* fileTypeByHandle */ int fileSizeByName(const char *name) { struct stat buf; if (stat(name, &buf)) { setError(MSG_NoAccess, name); return -1; } return buf.st_size; } /* fileSizeByName */ int fileSizeByHandle(int fd) { struct stat buf; if (fstat(fd, &buf)) { /* should never happen */ return -1; } return buf.st_size; } /* fileSizeByHandle */ time_t fileTimeByName(const char *name) { struct stat buf; if (stat(name, &buf)) { setError(MSG_NoAccess, name); return -1; } return buf.st_mtime; } /* fileTimeByName */ char *conciseSize(size_t n) { static char buf[32]; if (n >= (1 << 30)) sprintf(buf, "%luG", (unsigned long)n >> 30); else if (n >= (1 << 20)) sprintf(buf, "%luM", (unsigned long)n >> 20); else if (n >= (1 << 10)) sprintf(buf, "%luK", (unsigned long)n >> 10); else sprintf(buf, "%lu", (unsigned long)n); return buf; } /* conciseSize */ char *conciseTime(time_t t) { static char buffer[20]; static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; struct tm *tm = localtime(&t); sprintf(buffer, "%s %2d %d %02d:%02d", months[tm->tm_mon], tm->tm_mday, tm->tm_year + 1900, tm->tm_hour, tm->tm_min); return buffer; } /* conciseTime */ /* retain only characters l s t i k p m, for ls attributes */ bool lsattrChars(const char *buf, char *dest) { bool rc = true; const char *s; char c, *t; #ifdef DOSLIKE static const char ok_chars[] = "lst"; #else static const char ok_chars[] = "lstikpmy"; #endif char used[26]; memset(used, 0, sizeof(used)); t = dest; for (s = buf; (c = *s); ++s) { if (isspaceByte(c)) continue; if (!strchr(ok_chars, c)) { rc = false; continue; } if (used[c - 'a']) continue; used[c - 'a'] = 1; *t++ = c; } *t = 0; return rc; } /* lsattrChars */ /* expand the ls attributes for a file into a static string. */ /* This assumes user/group names will not be too long. */ char *lsattr(const char *path, const char *flags) { static char buf[200 + ABSPATH]; char p[40]; struct stat st, lst; struct passwd *pwbuf; struct group *grpbuf; char *s; int l, modebits; bool sympath = false; // user is requesting symlink path bool statyes = true; // stat call succeeds char newpath[ABSPATH]; buf[0] = 0; if (!path || !path[0] || !flags || !flags[0]) return buf; if (strchr(flags, 'y')) sympath = true; /* we already glommed onto this file through the directory listing; * it ought to be there, except for broken symlink. */ if (stat(path, &st)) { statyes = false; if (!sympath) return buf; } while (*flags) { if (buf[0]) strcat(buf, " "); /* exception when asking for information on a broken symlink */ if (!statyes && *flags != 'y') { strcat(buf, "?"); ++flags; continue; } switch (*flags) { case 't': strcat(buf, conciseTime(st.st_mtime)); break; case 'l': sprintf(p, "%lld", (long long)st.st_size); #ifndef DOSLIKE p: #endif // #ifndef DOSLIKE strcat(buf, p); break; case 's': strcat(buf, conciseSize(st.st_size)); break; #ifndef DOSLIKE /* not sure any of these work under windows */ case 'i': sprintf(p, "%lu", (unsigned long)st.st_ino); goto p; case 'k': sprintf(p, "%lu", (unsigned long)st.st_nlink); goto p; case 'm': strcpy(p, "-"); if (st.st_rdev) sprintf(p, "%d/%d", (int)(st.st_rdev >> 8), (int)(st.st_rdev & 0xff)); goto p; case 'p': s = buf + strlen(buf); pwbuf = getpwuid(st.st_uid); if (pwbuf) { l = strlen(pwbuf->pw_name); if (l > 20) l = 20; strncpy(s, pwbuf->pw_name, l); s[l] = 0; } else sprintf(s, "%d", st.st_uid); s += strlen(s); *s++ = ' '; grpbuf = getgrgid(st.st_gid); if (grpbuf) { l = strlen(grpbuf->gr_name); if (l > 20) l = 20; strncpy(s, grpbuf->gr_name, l); s[l] = 0; } else sprintf(s, "%d", st.st_gid); s += strlen(s); *s++ = ' '; modebits = st.st_mode; modebits &= 07777; if (modebits & 07000) *s++ = '0' + (modebits >> 9); modebits &= 0777; *s++ = '0' + (modebits >> 6); modebits &= 077; *s++ = '0' + (modebits >> 3); modebits &= 7; *s++ = '0' + modebits; *s = 0; break; case 'y': if (lstat(path, &lst)) { /* Wow, this call should always succeed */ strcat(buf, "?"); break; } if ((lst.st_mode & S_IFMT) != S_IFLNK) { strcat(buf, "-"); break; } /* yes it's a link, read the path */ l = readlink(path, newpath, sizeof(newpath)); if (l <= 0) strcat(buf, "?"); else { s = buf + strlen(buf); strncpy(s, newpath, l); s[l] = 0; } break; #endif } ++flags; } return buf; } /* lsattr */ #ifdef DOSLIKE void ttySaveSettings(void) { // TODO: Anything needed here for WIN32? isInteractive = _isatty(0); } #else // !#ifdef DOSLIKE static struct termios savettybuf; void ttySaveSettings(void) { isInteractive = isatty(0); if (isInteractive) { if (tcgetattr(0, &savettybuf)) i_printfExit(MSG_IoctlError); } } /* ttySaveSettings */ static void ttyRestoreSettings(void) { if (isInteractive) tcsetattr(0, TCSANOW, &savettybuf); } /* ttyRestoreSettings */ /* put the tty in raw mode. * Review your Unix manual on termio. * min>0 time>0: return min chars, or as many as you have received * when time/10 seconds have elapsed between characters. * min>0 time=0: block until min chars are received. * min=0 time>0: return 1 char, or 0 if the timer expires. * min=0 time=0: nonblocking, return whatever chars have been received. */ static void ttyRaw(int charcount, int timeout, bool isecho) { struct termios buf = savettybuf; /* structure copy */ buf.c_cc[VMIN] = charcount; buf.c_cc[VTIME] = timeout; buf.c_lflag &= ~(ICANON | ECHO); if (isecho) buf.c_lflag |= ECHO; tcsetattr(0, TCSANOW, &buf); } /* ttyRaw */ /* simulate MSDOS getche() system call */ int getche(void) { char c; fflush(stdout); ttyRaw(1, 0, true); read(0, &c, 1); ttyRestoreSettings(); return c; } /* getche */ int getch(void) { char c; fflush(stdout); ttyRaw(1, 0, false); read(0, &c, 1); ttyRestoreSettings(); return c; } /* getche */ #endif // #ifdef DOSLIKE y/n char getLetter(const char *s) { char c; while (true) { c = getch(); if (strchr(s, c)) break; printf("\a\b"); fflush(stdout); } printf("%c", c); return c; } /* getLetter */ /* Parameters: message, default file name, must this file be new, * and can we except an input of white space, * that being converted to a single space. */ char *getFileName(int msg, const char *defname, bool isnew, bool ws) { static char buf[ABSPATH]; static char spacename[] = " "; int l; char *p; bool allspace; while (true) { i_printf(msg); if (defname) printf("[%s] ", defname); fflush(stdout); if (!fgets(buf, sizeof(buf), stdin)) exit(0); allspace = false; for (p = buf; isspaceByte(*p); ++p) if (*p == ' ') allspace = true; l = strlen(p); while (l && isspaceByte(p[l - 1])) --l; p[l] = 0; if (!l) { if (ws & allspace) return spacename; if (!defname) continue; /* make a copy just to be safe */ l = strlen(defname); if (l >= ABSPATH) l = ABSPATH - 1; strncpy(buf, defname, l); buf[l] = 0; p = buf; } else defname = 0; if (isnew && fileTypeByName(p, false)) { i_printf(MSG_FileExists, p); defname = 0; continue; } return p; } } /* getFileName */ /* Protect a filename from expansion by the shell */ static const char shellmeta[] = "\\\n\t |&;<>(){}#'\"~$*?"; int shellProtectLength(const char *s) { int l = 0; while (*s) { if (strchr(shellmeta, *s)) ++l; ++l, ++s; } return l; } /* shellProtectLength */ void shellProtect(char *t, const char *s) { while (*s) { if (strchr(shellmeta, *s)) *t++ = '\\'; *t++ = *s++; } } /* shellProtect */ /* loop through the files in a directory */ const char *nextScanFile(const char *base) { static DIR *df; struct dirent *de; const char *s; if (!df) { if (!base) base = "."; df = opendir(base); /* directory could be unreadable */ if (!df) { i_puts(MSG_NoDirNoList); return 0; } } while (de = readdir(df)) { s = de->d_name; if (s[0] == '.') { if (stringEqual(s, ".")) continue; if (stringEqual(s, "..")) continue; if (!showHiddenFiles) continue; } return s; } /* end loop over files in directory */ closedir(df); df = 0; return 0; } /* nextScanFile */ /* compare routine for quicksort */ static int dircmp(const void *s, const void *t) { return strcoll((const char *)((const struct lineMap *)s)->text, (const char *)((const struct lineMap *)t)->text); } /* dircmp */ bool sortedDirList(const char *dir, struct lineMap **map_p, int *count_p) { const char *f; int linecount = 0, cap; struct lineMap *t, *map; cap = 128; map = t = (struct lineMap *)allocZeroMem(cap * LMSIZE); while (f = nextScanFile(dir)) { if (linecount == cap) { cap *= 2; map = (struct lineMap *)reallocMem(map, cap * LMSIZE); t = map + linecount; } /* leave room for @ / newline */ t->text = (pst) allocMem(strlen(f) + 3); strcpy((char *)t->text, f); t->ds1 = t->ds2 = 0; ++t, ++linecount; } *count_p = linecount; *map_p = map; if (!linecount) return true; /* sort the entries */ qsort(map, linecount, LMSIZE, dircmp); return true; } /* sortedDirList */ /* Expand environment variables, then wild cards. * But the result should be one and only one file. * Return the new expanded line. * Neither the original line nore the new line is allocated. * They are static char buffers that are just plain long enough. */ static bool envExpand(const char *line, const char **expanded) { const char *s; char *t; const char *v; /* result of getenv call */ bool inbrace; /* ${foo} */ struct passwd *pw; const char *udir; /* user directory */ int l; static char varline[ABSPATH]; char var1[40]; /* quick check */ if (line[0] != '~' && !strchr(line, '$')) { *expanded = line; return true; } /* ok, need to crunch along */ t = varline; s = line; if (line[0] != '~') goto dollars; l = 0; for (s = line + 1; isalnum(*s) || *s == '_'; ++s) ++l; if (l >= sizeof(var1) || isdigit(line[1]) || *s && *s != '/') { /* invalid syntax, put things back */ s = line; goto dollars; } udir = 0; strncpy(var1, line + 1, l); var1[l] = 0; #ifndef DOSLIKE if (l) { pw = getpwnam(var1); if (!pw) { setError(MSG_NoTilde, var1); return false; } if (pw->pw_dir && *pw->pw_dir) udir = pw->pw_dir; } else #endif udir = home; if (!udir) { s = line; goto dollars; } l = strlen(udir); if (l >= sizeof(varline)) goto longline; strcpy(varline, udir); t = varline + l; dollars: for (; *s; ++s) { if (t - varline == ABSPATH - 1) { longline: setError(MSG_ShellLineLong); return false; } if (*s == '\\' && s[1] == '$') { /* this $ is escaped */ ++s; appendchar: *t++ = *s; continue; } if (*s != '$') goto appendchar; /* this is $, see if it is $var or ${var} */ inbrace = false; v = s + 1; if (*v == '{') inbrace = true, ++v; if (!isalphaByte(*v) && *v != '_') goto appendchar; l = 0; while (isalnumByte(*v) || *v == '_') { if (l == sizeof(var1) - 1) goto longline; var1[l++] = *v++; } var1[l] = 0; if (inbrace) { if (*v != '}') goto appendchar; ++v; } s = v - 1; v = getenv(var1); if (!v) { setError(MSG_NoEnvVar, var1); return false; } l = strlen(v); if (t - varline + l >= ABSPATH) goto longline; strcpy(t, v); t += l; } *t = 0; *expanded = varline; return true; } /* envExpand */ bool envFile(const char *line, const char **expanded) { static char line2[ABSPATH]; const char *varline; const char *s; char *t; #ifndef DOSLIKE glob_t g; #endif // #ifndef DOSLIKE int rc, flags; /* ` disables this stuff */ /* but `` is a literal ` */ if (line[0] == '`') { if (line[1] != '`') { *expanded = line + 1; return true; } ++line; } if (!envExpand(line, &varline)) return false; #ifdef DOSLIKE *expanded = varline; return true; // TODO: WIN32: Expand like glob... #else // !#ifdef DOSLIKE /* expanded the environment variables, if any, now time to glob */ flags = GLOB_NOSORT; rc = glob(varline, flags, NULL, &g); if (rc == GLOB_NOMATCH) { /* unescape the metas */ t = line2; for (s = varline; *s; ++s) { if (*s == '\\' && s[1] && strchr("*?[", s[1])) ++s; *t++ = *s; } *t = 0; *expanded = line2; return true; } if (rc) { /* some other syntax error, whereup we can't expand. */ setError(MSG_ShellExpand); globfree(&g); return false; } if (g.gl_pathc != 1) { setError(MSG_ShellManyMatch); globfree(&g); return false; } /* looks good, if it isn't too long */ s = g.gl_pathv[0]; if (strlen(s) >= sizeof(line2)) { setError(MSG_ShellLineLong); globfree(&g); return false; } strcpy(line2, s); globfree(&g); *expanded = line2; return true; #endif // #ifdef DOSLIKE y/n } /* envFile */ /* Call the above routine if filename contains a slash, * or prepend the download directory if it does not. * If there is no download directory then always expand as above. */ bool envFileDown(const char *line, const char **expanded) { static char line2[MAXTTYLINE]; if (!downDir || strchr(line, '/')) /* we don't necessarily expect there to be a file here */ return envFile(line, expanded); if (strlen(downDir) + strlen(line) >= sizeof(line2) - 1) { setError(MSG_ShellLineLong); return false; } sprintf(line2, "%s/%s", downDir, line); *expanded = line2; return true; } /* envFileDown */ FILE *efopen(const char *name, const char *mode) { FILE *f; if (name[0] == '-' && name[1] == 0) { if (*mode == 'r') return stdin; if (*mode == 'w' || *mode == 'a') return stdout; } f = fopen(name, mode); if (f) return f; if (*mode == 'r') i_printfExit(MSG_OpenFail, name); else if (*mode == 'w' || *mode == 'a') i_printfExit(MSG_CreateFail, name); else i_printfExit(MSG_InvalidFopen, mode); return 0; } /* efopen */ int eopen(const char *name, int mode, int perms) { int fd; fd = open(name, mode, perms); if (fd >= 0) return fd; if (mode & O_WRONLY) i_printfExit(MSG_CreateFail, name); else i_printfExit(MSG_OpenFail, name); return -1; } /* eopen */ void appendFile(const char *fname, const char *message, ...) { FILE *f; va_list p; f = efopen(fname, "a"); va_start(p, message); vfprintf(f, message, p); va_end(p); fprintf(f, "\n"); fclose(f); } /* appendFile */ /* like the above, but no formatting */ void appendFileNF(const char *filename, const char *msg) { FILE *f = efopen(filename, "a"); fprintf(f, "%s\n", msg); fclose(f); } /* appendFileNF */ /* Wrapper around system(). */ int eb_system(const char *cmd, bool print_on_success) { int system_ret = 0; #ifdef DOSLIKE system_ret = system(cmd); #else /* Ignoring of SIGPIPE propagates across fork-exec. */ /* So stop ignoring it for the duration of system(). */ signal(SIGPIPE, SIG_DFL); system_ret = system(cmd); signal(SIGPIPE, SIG_IGN); #endif if (system_ret == 0) { if (print_on_success) i_puts(MSG_OK); } else { i_printf(MSG_SystemCmdFail, system_ret); nl(); } return system_ret; } /* eb_system */ edbrowse-3.6.0.1/src/PaxHeaders.22102/startwindow.js0000644000000000000000000000006112637565441016744 xustar0020 atime=1451158305 29 ctime=1451409178.15333719 edbrowse-3.6.0.1/src/startwindow.js0000644000000000000000000010607712637565441017231 0ustar00rootroot00000000000000/********************************************************************* This file contains support javascript functions used by a browser. They are much easier to write here, in javascript, then in C using the js api. And it is portable amongst all js engines. This file is converted into a C string and compiled and run at the start of each javascript window. Please take advantage of this machinery and put functions here, even prototypes and getter / setter support functions, whenever it makes sense to do so. The classes are created first, so that you can write meaningful prototypes here. *********************************************************************/ /* Some visual attributes of the window. * These are just guesses. * Better to have something than nothing at all. */ height = 768; width = 1024; status = 0; defaultStatus = 0; returnValue = true; menubar = true; scrollbars = true; toolbar = true; resizable = true; directories = false; name = "unspecifiedFrame"; document.bgcolor = "white"; document.readyState = "loading"; document.nodeType = 9; document.implementation = {}; screen = new Object; screen.height = 768; screen.width = 1024; screen.availHeight = 768; screen.availWidth = 1024; screen.availTop = 0; screen.availLeft = 0; /* some base arrays - lists of things we'll probably need */ document.heads = new Array; document.bases = new Array; document.links = new Array; document.metas = new Array; document.bodies = new Array; document.forms = new Array; document.elements = new Array; document.anchors = new Array; document.divs = new Array; document.scripts = new Array; document.paragraphs = new Array; document.tables = new Array; document.spans = new Array; document.images = new Array; document.areas = new Array; frames = new Array; document.getElementsByTagName = function(s) { s = s.toLowerCase(); return document.gebtn$(this, s); } document.gebtn$ = function(top, s) { var a = new Array; if(s === '*' || (top.nodeName && top.nodeName === s)) a.push(top); if(top.childNodes) { for(var i=0; i 0); } document.replaceChild = function(newc, oldc) { var lastentry; var l = this.childNodes.length; var nextinline; for(var i=0; i #define MAXRECAT 100 /* max number of recipients or attachments */ #define MAXMSLINE 1024 /* max mail server line */ char serverLine[MAXMSLINE]; static char spareLine[MAXMSLINE]; int mssock; /* mail server socket */ static bool doSignature; static bool ssl_on; static const char *mailhost; static char subjectLine[400]; static int mailAccount; static struct ALIAS { char name[16]; char email[64]; } *addressList; static int nads; /* number of addresses */ static time_t adbooktime; /* read and/or refresh the address book */ bool loadAddressBook(void) { char *buf, *bufend, *v, *last, *s, *t; bool cmt = false; char state = 0, c; int j, buflen, ln = 1; time_t mtime; if (!addressFile || (mtime = fileTimeByName(addressFile)) == -1 || mtime <= adbooktime) return true; debugPrint(3, "loading address book"); nzFree(addressList); addressList = 0; nads = 0; if (!fileIntoMemory(addressFile, &buf, &buflen)) return false; bufend = buf + buflen; for (s = t = last = buf; s < bufend; ++s) { c = *s; if (cmt) { if (c != '\n') continue; cmt = false; } if (c == ':') { /* delimiter */ if (state == 0) { setError(MSG_ABNoAlias, ln); freefail: nzFree(buf); nads = 0; return false; } while (t[-1] == ' ' || t[-1] == '\t') --t; if (state == 1) { *t++ = c; state = 2; continue; } c = '#'; /* extra fields are ignored */ } /* : */ if (c == '#') { cmt = true; continue; } if (c == '\n') { ++ln; if (state == 0) continue; if (state == 1) { setError(MSG_ABNoColon, ln - 1); goto freefail; } if (state == 3) { ++nads; while (isspaceByte(t[-1])) --t; *t = 0; v = strchr(last, ':'); if (v - last >= 16) { setError(MSG_ABAliasLong, ln - 1); goto freefail; } ++v; if (t - v >= 64) { setError(MSG_ABMailLong, ln - 1); goto freefail; } if (!strchr(v, '@')) { setError(MSG_ABNoAt, ln - 1); goto freefail; } if (strpbrk(v, " \t")) { setError(MSG_ABMailSpaces, ln - 1); goto freefail; } while (last < t) { if (!isprintByte(*last)) { setError(MSG_AbMailUnprintable, ln - 1); goto freefail; } ++last; } *t++ = c; } else t = last; /* back it up */ last = t; state = 0; continue; } /* nl */ if ((c == ' ' || c == '\t') && (state == 0 || state == 2)) continue; if (state == 0) state = 1; if (state == 2) state = 3; *t++ = c; } *t = 0; if (state) { setError(MSG_ABUnterminated); goto freefail; } if (nads) { addressList = allocMem(nads * sizeof(struct ALIAS)); j = 0; for (s = buf; *s; s = t + 1, ++j) { t = strchr(s, ':'); memcpy(addressList[j].name, s, t - s); addressList[j].name[t - s] = 0; s = t + 1; t = strchr(s, '\n'); memcpy(addressList[j].email, s, t - s); addressList[j].email[t - s] = 0; } } /* aliases are present */ nzFree(buf); adbooktime = mtime; return true; } /* loadAddressBook */ const char *reverseAlias(const char *reply) { int i; for (i = 0; i < nads; ++i) if (stringEqual(reply, addressList[i].email)) { const char *a = addressList[i].name; if (*a == '!') break; return a; } return 0; /* not found */ } /* reverseAlias */ static char *qpEncode(const char *line) { char *newbuf; int l; const char *s; char c; newbuf = initString(&l); for (s = line; (c = *s); ++s) { if (c < '\n' && c != '\t' || c == '=') { char expand[4]; sprintf(expand, "=%02X", (uchar) c); stringAndString(&newbuf, &l, expand); } else { stringAndChar(&newbuf, &l, c); } } return newbuf; } /* qpEncode */ /* Return 0 if there was no need to encode */ static char *isoEncode(char *start, char *end) { int nacount = 0, count = 0, len; char *s, *t; char c, code; for (s = start; s < end; ++s) { c = *s; if (c == 0) *s = ' '; if (isspaceByte(c)) *s = ' '; } for (s = start; s < end; ++s) { c = *s; ++count; if (!isprintByte(c) && c != ' ') ++nacount; } if (!nacount) return 0; if (nacount * 4 >= count && count > 8) { code = 'B'; s = base64Encode(start, end - start, false); goto package; } code = 'Q'; s = qpEncode(start); package: len = strlen(s); t = allocMem(len + 20); sprintf(t, "=?ISO-8859-1?%c?%s?=", code, s); nzFree(s); return t; } /* isoEncode */ /********************************************************************* Return a string that defines the charset of the outgoing mail. This just looks at your language setting - reaaly dumb. It should interrogate each file/attachment. Well, this will get us started. *********************************************************************/ static char *charsetString(const char *ct, const char *ce) { static char buf[24]; buf[0] = 0; if (!stringEqual(ce, "7bit") && (stringEqual(ct, "text/plain") || stringEqual(ct, "text/html"))) { if (cons_utf8) strcpy(buf, "; charset=utf-8"); else sprintf(buf, "; charset=iso-8859-%d", type8859); } return buf; } /* charsetString */ /* Read a file into memory, mime encode it, * and return the type of encoding and the encoded data. * Last three parameters are result parameters. * If ismail is nonzero, the file is the mail, not an attachment. * In fact ismail indicates the line that holds the subject. * If ismail is negative, then -ismail indicates the subject line, * and the string file is not the filename, but rather, the mail to send. */ bool encodeAttachment(const char *file, int ismail, bool webform, const char **type_p, const char **enc_p, char **data_p) { char *buf; char c; bool longline; char *s, *t, *v; char *ct, *ce; /* content type, content encoding */ int buflen, i, cx; int nacount, nullcount, nlcount; if (ismail < 0) { buf = cloneString(file); buflen = strlen(buf); ismail = -ismail; file = emptyString; } else { if (!ismc && (cx = stringIsNum(file)) >= 0) { static char newfilename[16]; if (!unfoldBuffer(cx, false, &buf, &buflen)) return false; if (!buflen) { if (webform) { empty: buf = emptyString; ct = "text/plain"; ce = "7bit"; goto success; } setError(MSG_BufferXEmpty, cx); goto freefail; } sprintf(newfilename, "", cx); file = newfilename; if (sessionList[cx].lw->fileName) file = sessionList[cx].lw->fileName; } else { if (!fileIntoMemory(file, &buf, &buflen)) return false; if (!buflen) { if (webform) goto empty; setError(MSG_FileXEmpty, file); goto freefail; } } } /* ismail negative or normal */ if (ismail) { /* Put newline at the end. Yes, the buffer is allocated * with space for newline and null. */ if (buf[buflen - 1] != '\n') buf[buflen++] = '\n'; /* check for subject: line */ s = buf; i = ismail; while (--i) { while (*s != '\n') ++s; ++s; } while (*s == ' ' || *s == '\t') ++s; if (!memEqualCI(s, "subject:", 8)) { setError(MSG_SubjectStart); goto freefail; } s += 8; while (*s == ' ' || *s == '\t') ++s; t = s; while (*s != '\n') ++s; v = s; while (s > t && isspaceByte(s[-1])) --s; if (s - t >= sizeof(subjectLine)) { setError(MSG_SubjectLong, sizeof(subjectLine) - 1); goto freefail; } if (s > t) memcpy(subjectLine, t, s - t); subjectLine[s - t] = 0; if (subjectLine[0]) { char *subjiso = isoEncode(subjectLine, subjectLine + strlen(subjectLine)); if (subjiso) { if (strlen(subjiso) >= sizeof(subjectLine)) { nzFree(subjiso); setError(MSG_SubjectLong, sizeof(subjectLine) - 1); goto freefail; } strcpy(subjectLine, subjiso); nzFree(subjiso); } } debugPrint(6, "subject = %s", subjectLine); /* Blank lines after subject are optional, and ignored. */ for (t = buf + buflen; v < t; ++v) if (*v != '\r' && *v != '\n') break; buflen -= (v - buf); if (buflen) memmove(buf, v, buflen); buf[buflen] = 0; if (doSignature) { /* Append .signature file. */ /* Try account specific .signature file, then fall back to .signature */ sprintf(sigFileEnd, "%d", mailAccount); c = fileTypeByName(sigFile, false); if (!c) { *sigFileEnd = 0; c = fileTypeByName(sigFile, false); } if (c != 0) { int fd, n; if (c != 'f') { setError(MSG_SigRegular); goto freefail; } n = fileSizeByName(sigFile); if (n > 0) { buf = reallocMem(buf, buflen + n + 1); fd = open(sigFile, O_RDONLY); if (fd < 0) { setError(MSG_SigAccess); goto freefail; } read(fd, buf + buflen, n); close(fd); buflen += n; buf[buflen] = 0; } } } /* .signature */ } /* Infer content type from the filename */ ct = 0; s = strrchr(file, '.'); if (s && s[1]) { ++s; if (stringEqualCI(s, "ps")) ct = "application/PostScript"; if (stringEqualCI(s, "jpeg")) ct = "image/jpeg"; if (stringEqualCI(s, "gif")) ct = "image/gif"; if (stringEqualCI(s, "wav")) ct = "audio/basic"; if (stringEqualCI(s, "mpeg")) ct = "video/mpeg"; if (stringEqualCI(s, "rtf")) ct = "text/richtext"; if (stringEqualCI(s, "htm") || stringEqualCI(s, "html") || stringEqualCI(s, "shtm") || stringEqualCI(s, "shtml") || stringEqualCI(s, "asp")) ct = "text/html"; } /* Count the nonascii characters */ nacount = nullcount = nlcount = 0; longline = false; s = 0; for (i = 0; i < buflen; ++i) { c = buf[i]; if (c == '\0') ++nullcount; if (c < 0) ++nacount; if (c != '\n') continue; ++nlcount; t = buf + i; if (s && t - s > 120) longline = true; if (!s && i > 120) longline = true; s = t; } t = buf + i; if (s && t - s > 120) longline = true; if (!s && i > 120) longline = true; debugPrint(6, "attaching %s length %d nonascii %d nulls %d longline %d", file, buflen, nacount, nullcount, longline); nacount += nullcount; /* Set the type of attachment */ if (buflen > 20 && nacount * 5 > buflen) { if (!ct) ct = "application/octet-stream"; /* default type for binary */ } if (!ct) ct = "text/plain"; /* Criteria for base64 encode. * files uploaded from a web form need not be encoded, unless they contain * nulls, which is a quirk of my slapped together software. */ if (!webform && (buflen > 20 && nacount * 5 > buflen) || webform && nullcount) { if (ismail) { setError(MSG_MailBinary, file); goto freefail; } s = base64Encode(buf, buflen, true); nzFree(buf); buf = s; ce = "base64"; goto success; } if (!webform) { /* Switch to unix newlines - we'll switch back to dos later. */ v = buf + buflen; for (s = t = buf; s < v; ++s) { c = *s; if (c == '\r' && s < v - 1 && s[1] == '\n') continue; *t++ = c; } buflen = t - buf; /* Do we need to use quoted-printable? */ /* Perhaps this hshould read (nacount > 0) */ if (nacount * 20 > buflen || nullcount || longline) { char *newbuf; int l, colno = 0, space = 0; newbuf = initString(&l); v = buf + buflen; for (s = buf; s < v; ++s) { c = *s; /* do we have to =expand this character? */ if (c < '\n' && c != '\t' || c == '=' || c == '\xff' || (c == ' ' || c == '\t') && s < v - 1 && s[1] == '\n') { char expand[4]; sprintf(expand, "=%02X", (uchar) c); stringAndString(&newbuf, &l, expand); colno += 3; } else { stringAndChar(&newbuf, &l, c); ++colno; } if (c == '\n') { colno = space = 0; continue; } if (c == ' ' || c == '\t') space = l; if (colno < 72) continue; if (s == v - 1) continue; /* If newline's coming up anyways, don't force another one. */ if (s[1] == '\n') continue; i = l; if (!space || space == i) { stringAndString(&newbuf, &l, "=\n"); colno = space = 0; continue; } colno = i - space; stringAndString(&newbuf, &l, "**"); /* make room */ while (i > space) { newbuf[i + 1] = newbuf[i - 1]; --i; } newbuf[space] = '='; newbuf[space + 1] = '\n'; space = 0; } /* loop over characters */ nzFree(buf); buf = newbuf; ce = "quoted-printable"; goto success; } } buf[buflen] = 0; ce = (nacount ? "8bit" : "7bit"); success: debugPrint(6, "encoded %s %s length %d", ct, ce, strlen(buf)); *enc_p = ce; *type_p = ct; *data_p = buf; return true; freefail: nzFree(buf); return false; } /* encodeAttachment */ static char *mailTimeString(void) { static char buf[48]; struct tm *cur_tm; time_t now; time(&now); cur_tm = localtime(&now); strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %z", cur_tm); return buf; } /* mailTimeString */ static char *messageTimeID(void) { static char buf[48]; struct tm *cur_tm; time_t now; time(&now); cur_tm = localtime(&now); sprintf(buf, "%04d%02d%02d%02d%02d%02d", cur_tm->tm_year + 1900, cur_tm->tm_mon, cur_tm->tm_mday, cur_tm->tm_hour, cur_tm->tm_min, cur_tm->tm_sec); return buf; } /* messageTimeID */ static void appendAttachment(const char *s, char **out, int *l) { const char *t; int n; while (*s) { /* another line */ t = strchr(s, '\n'); if (!t) t = s + strlen(s); n = t - s; if (t[-1] == '\r') --n; if (n) memcpy(serverLine, s, n); serverLine[n] = 0; strcat(serverLine, eol); stringAndString(out, l, serverLine); if (*t) ++t; s = t; } /* Small bug here - an attachment that is not base64 encoded, * and had no newline at the end, now has one. */ } /* appendAttachment */ char *makeBoundary(void) { static char boundary[24]; sprintf(boundary, "nextpart-eb-%06d", rand() % 1000000); return boundary; } /* makeBoundary */ struct smtp_upload { const char *data; /* These really need to be size_t, not int! * fixme when edbrowse uses size_t consistently */ int length; int pos; }; static size_t smtp_upload_callback(char *buffer_for_curl, size_t size, size_t nmem, struct smtp_upload *upload) { size_t out_buffer_size = size * nmem; size_t remaining = upload->length - upload->pos; size_t to_send, cur_pos; if (upload->pos >= upload->length) return 0; if (out_buffer_size < remaining) to_send = out_buffer_size; else to_send = remaining; memcpy(buffer_for_curl, upload->data + upload->pos, to_send); cur_pos = upload->pos + to_send; upload->pos = cur_pos; return to_send; } /* smtp_upload_callback */ static char *buildSMTPURL(const struct MACCOUNT *account) { char *url = NULL; const char *scheme; const char *smlogin = strchr(account->login, '\\'); if (smlogin) ++smlogin; else smlogin = account->login; if (account->outssl & 1) scheme = "smtps"; else scheme = "smtp"; if (asprintf (&url, "%s://%s:%d/%s", scheme, account->outurl, account->outport, smlogin) == -1) i_printfExit(MSG_NoMem); return url; } /* buildSMTPURL */ static struct curl_slist *buildRecipientSList(const char **recipients) { struct curl_slist *recipient_slist = NULL; const char **r; for (r = recipients; *r; r++) { recipient_slist = curl_slist_append(recipient_slist, *r); if (recipient_slist == NULL) i_printfExit(MSG_NoMem); } return recipient_slist; } /* buildRecipientSList */ static CURL *newSendmailHandle(const struct MACCOUNT *account, const char *outurl, const char *reply, struct curl_slist *recipients) { CURLcode res = CURLE_OK; CURL *handle = curl_easy_init(); if (!handle) { setError(MSG_LibcurlNoInit); return NULL; } curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, mailTimeout); res = setCurlURL(handle, outurl); if (res != CURLE_OK) { goto new_handle_cleanup; } if (debugLevel >= 4) curl_easy_setopt(handle, CURLOPT_VERBOSE, 1); curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, ebcurl_debug_handler); res = curl_easy_setopt(handle, CURLOPT_CAINFO, sslCerts); if (res != CURLE_OK) { goto new_handle_cleanup; } if (account->outssl == 2) curl_easy_setopt(handle, CURLOPT_USE_SSL, CURLUSESSL_ALL); if (account->outssl) { res = curl_easy_setopt(handle, CURLOPT_USERNAME, account->login); if (res != CURLE_OK) { goto new_handle_cleanup; } res = curl_easy_setopt(handle, CURLOPT_PASSWORD, account->password); if (res != CURLE_OK) { goto new_handle_cleanup; } } res = curl_easy_setopt(handle, CURLOPT_MAIL_FROM, reply); if (res != CURLE_OK) { goto new_handle_cleanup; } res = curl_easy_setopt(handle, CURLOPT_MAIL_RCPT, recipients); if (res != CURLE_OK) { goto new_handle_cleanup; } new_handle_cleanup: if (res != CURLE_OK) { ebcurl_setError(res, outurl); curl_easy_cleanup(handle); handle = NULL; } return handle; } /* newSendmailHandle */ typedef struct tagsmtp_upload { const char *data; size_t length; size_t pos; } smtp_upload; static bool sendMailSMTP(const struct MACCOUNT *account, const char *reply, const char **recipients, const char *message) { CURL *handle = 0; CURLcode res = CURLE_OK; bool smtp_success = false; char *smtp_url = buildSMTPURL(account); struct curl_slist *recipient_slist = buildRecipientSList(recipients); smtp_upload upload; upload.data = message; upload.length = strlen(message); upload.pos = 0; handle = newSendmailHandle(account, smtp_url, reply, recipient_slist); if (!handle) goto smtp_cleanup; curl_easy_setopt(handle, CURLOPT_READFUNCTION, smtp_upload_callback); curl_easy_setopt(handle, CURLOPT_READDATA, &upload); curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L); res = curl_easy_perform(handle); if (res == CURLE_OK) smtp_success = true; smtp_cleanup: if (res != CURLE_OK) ebcurl_setError(res, smtp_url); if (handle) curl_easy_cleanup(handle); curl_slist_free_all(recipient_slist); nzFree(smtp_url); return smtp_success; } /* sendMailSMTP */ /* Send mail to the smtp server. */ bool sendMail(int account, const char **recipients, const char *body, int subjat, const char **attachments, const char *refline, int nalt, bool dosig) { char *from, *fromiso, *reply, *login, *smlogin, *pass; const struct MACCOUNT *a, *ao, *localMail; const char *s, *boundary; char reccc[MAXRECAT]; char *t; int nat, cx, i, j; char *out = 0; bool sendmail_success = false; bool mustmime = false; bool firstgreet = true; bool firstrec; const char *ct, *ce; char *encoded = 0; if (!validAccount(account)) return false; mailAccount = account; localMail = accounts + localAccount - 1; a = accounts + account - 1; from = a->from; reply = a->reply; ao = a->outssl ? a : localMail; doSignature = dosig; nat = 0; /* number of attachments */ if (attachments) { while (attachments[nat]) ++nat; } if (nat) mustmime = true; if (nalt && nalt < nat) { setError(MSG_AttAlternate); return false; } if (!loadAddressBook()) return false; /* set copy flags */ for (j = 0; s = recipients[j]; ++j) { char cc = 0; if (*s == '^' || *s == '?') cc = *s++; if (j == MAXRECAT) { setError(MSG_RecipMany, MAXRECAT); return false; } recipients[j] = s; reccc[j] = cc; } /* Look up aliases in the address book */ for (j = 0; s = recipients[j]; ++j) { if (strchr(s, '@')) continue; t = 0; for (i = 0; i < nads; ++i) { const char *a = addressList[i].name; if (*a == '-' || *a == '!') ++a; if (!stringEqual(s, a)) continue; t = addressList[i].email; debugPrint(3, " %s becomes %s", s, t); break; } if (t) { recipients[j] = t; continue; } if (!addressFile) { setError(MSG_ABMissing); return false; } setError(MSG_ABNoAlias2, s); return false; } /* recipients */ if (!j) { setError(MSG_RecipNone); return false; } /* verify attachments are readable */ for (j = 0; s = attachments[j]; ++j) { if (!ismc && (cx = stringIsNum(s)) >= 0) { if (!cxCompare(cx) || !cxActive(cx)) return false; if (!sessionList[cx].lw->dol) { setError(MSG_AttSessionEmpty, cx); return false; } } else { char ftype = fileTypeByName(s, false); if (!ftype) { setError(MSG_AttAccess, s); return false; } if (ftype != 'f') { setError(MSG_AttRegular, s); return false; } if (!fileSizeByName(s)) { setError(MSG_AttEmpty2, s); return false; } } } /* loop over attachments */ if (!encodeAttachment(body, subjat, false, &ct, &ce, &encoded)) return false; if (ce[0] == 'q') mustmime = true; boundary = makeBoundary(); /* Build the outgoing mail, as one string. */ out = initString(&j); firstrec = true; for (i = 0; s = recipients[i]; ++i) { if (reccc[i]) continue; stringAndString(&out, &j, firstrec ? "To:" : ",\r\n "); stringAndString(&out, &j, s); firstrec = false; } if (!firstrec) stringAndString(&out, &j, eol); firstrec = true; for (i = 0; s = recipients[i]; ++i) { if (reccc[i] != '^') continue; stringAndString(&out, &j, firstrec ? "CC:" : ",\r\n "); stringAndString(&out, &j, s); firstrec = false; } if (!firstrec) stringAndString(&out, &j, eol); firstrec = true; for (i = 0; s = recipients[i]; ++i) { if (reccc[i] != '?') continue; stringAndString(&out, &j, firstrec ? "BCC:" : ",\r\n "); stringAndString(&out, &j, s); firstrec = false; } if (!firstrec) stringAndString(&out, &j, eol); fromiso = isoEncode(from, from + strlen(from)); if (!fromiso) fromiso = from; sprintf(serverLine, "From: %s <%s>%s", fromiso, reply, eol); stringAndString(&out, &j, serverLine); sprintf(serverLine, "Reply-to: %s <%s>%s", fromiso, reply, eol); stringAndString(&out, &j, serverLine); if (fromiso != from) nzFree(fromiso); if (refline) { s = strchr(refline, '\n'); if (!s) /* should never happen */ s = refline + strlen(refline); stringAndBytes(&out, &j, refline, s - refline); stringAndString(&out, &j, eol); } sprintf(serverLine, "User-Agent: %s%s", currentAgent, eol); stringAndString(&out, &j, serverLine); if (subjectLine[0]) { sprintf(serverLine, "Subject: %s%s", subjectLine, eol); stringAndString(&out, &j, serverLine); } sprintf(serverLine, "Date: %s%sMessage-ID: <%s.%s>%sMime-Version: 1.0%s", mailTimeString(), eol, messageTimeID(), reply, eol, eol); stringAndString(&out, &j, serverLine); if (!mustmime) { /* no mime components required, we can just send the mail. */ sprintf(serverLine, "Content-Type: %s%s%sContent-Transfer-Encoding: %s%s%s", ct, charsetString(ct, ce), eol, ce, eol, eol); stringAndString(&out, &j, serverLine); } else { sprintf(serverLine, "Content-Type: multipart/%s; boundary=%s%sContent-Transfer-Encoding: 7bit%s%s", nalt ? "alternative" : "mixed", boundary, eol, eol, eol); stringAndString(&out, &j, serverLine); stringAndString(&out, &j, "This message is in MIME format. Since your mail reader does not understand\r\n\ this format, some or all of this message may not be legible.\r\n\r\n--"); stringAndString(&out, &j, boundary); sprintf(serverLine, "%sContent-Type: %s%s%sContent-Transfer-Encoding: %s%s%s", eol, ct, charsetString(ct, ce), eol, ce, eol, eol); stringAndString(&out, &j, serverLine); } /* Now send the body, line by line. */ appendAttachment(encoded, &out, &j); nzFree(encoded); encoded = 0; if (mustmime) { for (i = 0; s = attachments[i]; ++i) { if (!encodeAttachment(s, 0, false, &ct, &ce, &encoded)) return false; sprintf(serverLine, "%s--%s%sContent-Type: %s%s", eol, boundary, eol, ct, charsetString(ct, ce)); stringAndString(&out, &j, serverLine); /* If the filename has a quote in it, forget it. */ /* Also, suppress filename if this is an alternate presentation. */ if (!nalt && !strchr(s, '"') && (ismc || stringIsNum(s) < 0)) { sprintf(serverLine, "; name=\"%s\"", s); stringAndString(&out, &j, serverLine); } sprintf(serverLine, "%sContent-Transfer-Encoding: %s%s%s", eol, ce, eol, eol); stringAndString(&out, &j, serverLine); appendAttachment(encoded, &out, &j); nzFree(encoded); encoded = 0; } /* loop over attachments */ /* The last boundary */ sprintf(serverLine, "%s--%s--%s", eol, boundary, eol); stringAndString(&out, &j, serverLine); } /* mime format */ sendmail_success = sendMailSMTP(ao, reply, recipients, out); nzFree(out); return sendmail_success; } /* sendMail */ bool validAccount(int n) { if (!maxAccount) { setError(MSG_MailAccountsNone); return false; } if (n <= 0 || n > maxAccount) { setError(MSG_MailAccountBad, n, maxAccount); return false; } return true; } /* validAccount */ bool sendMailCurrent(int sm_account, bool dosig) { const char *reclist[MAXRECAT + 1]; char *recmem; const char *atlist[MAXRECAT + 1]; char *atmem; char *s, *t; char cxbuf[4]; int lr, la, ln; char *refline = 0; int nrec = 0, nat = 0, nalt = 0; int account = localAccount; int j; bool rc = false; bool subj = false; if (cw->browseMode) { setError(MSG_MailBrowse); return false; } if (cw->sqlMode) { setError(MSG_MailDB); return false; } if (cw->dirMode) { setError(MSG_MailDir); return false; } if (cw->binMode) { setError(MSG_MailBinary2); return false; } if (!cw->dol) { setError(MSG_MailEmpty); return false; } if (!validAccount(account)) return false; recmem = initString(&lr); atmem = initString(&la); /* Gather recipients and attachments, until we reach subject: */ for (ln = 1; ln <= cw->dol; ++ln) { char *line = (char *)fetchLine(ln, -1); if (memEqualCI(line, "to:", 3) || memEqualCI(line, "mailto:", 7) || memEqualCI(line, "cc:", 3) || memEqualCI(line, "bcc:", 4) || memEqualCI(line, "reply to:", 9) || memEqualCI(line, "reply to ", 9)) { char cc = 0; if (toupper(line[0]) == 'C') cc = '^'; if (toupper(line[0]) == 'B') cc = '?'; if (toupper(line[0]) == 'R') line += 9; else line = strchr(line, ':') + 1; while (*line == ' ' || *line == '\t') ++line; if (*line == '\n') { setError(MSG_RecipNone2, ln); goto done; } if (nrec == MAXRECAT) { setError(MSG_RecipMany, MAXRECAT); goto done; } ++nrec; for (t = line; *t != '\n'; ++t) ; if (cc) { if (!lr) { setError(MSG_MailFirstCC); goto done; } stringAndChar(&recmem, &lr, cc); } stringAndBytes(&recmem, &lr, line, t + 1 - line); continue; } if (memEqualCI(line, "attach:", 7) || memEqualCI(line, "alt:", 4)) { if (toupper(line[1]) == 'T') line += 7; else line += 4, ++nalt; while (*line == ' ' || *line == '\t') ++line; if (*line == '\n') { setError(MSG_AttLineX, ln); goto done; } if (nat == MAXRECAT) { setError(MSG_RecipMany, MAXRECAT); goto done; } ++nat; for (t = line; *t != '\n'; ++t) ; stringAndBytes(&atmem, &la, line, t + 1 - line); continue; } if (memEqualCI(line, "account:", 8)) { line += 8; while (*line == ' ' || *line == '\t') ++line; if (!isdigitByte(*line) || (account = strtol(line, &line, 10)) == 0 || account > maxAccount || *line != '\n') { setError(MSG_MailAccountBadLineX, ln); goto done; } continue; } if (memEqualCI(line, "references:", 11)) { if (!refline) refline = line; continue; } if (memEqualCI(line, "subject:", 8)) { while (*line == ' ' || *line == '\t') ++line; subj = true; } break; } /* loop over lines */ if (sm_account) account = sm_account; if (!subj) { setError(((ln > cw->dol) + MSG_MailFirstLine), ln); goto done; } if (nrec == 0) { setError(MSG_RecipNone3); goto done; } for (s = recmem, j = 0; *s; s = t + 1, ++j) { t = strchr(s, '\n'); *t = 0; reclist[j] = s; } reclist[j] = 0; for (s = atmem, j = 0; *s; s = t + 1, ++j) { t = strchr(s, '\n'); *t = 0; atlist[j] = s; } atlist[j] = 0; sprintf(cxbuf, "%d", context); rc = sendMail(account, reclist, cxbuf, ln, atlist, refline, nalt, dosig); done: nzFree(recmem); nzFree(atmem); if (!rc && intFlag) setError(MSG_Interrupted); if (rc) i_puts(MSG_OK); return rc; } /* sendMailCurrent */ edbrowse-3.6.0.1/src/PaxHeaders.22102/plugin.c0000644000000000000000000000006112637565441015463 xustar0020 atime=1451158305 29 ctime=1451409178.15333719 edbrowse-3.6.0.1/src/plugin.c0000644000000000000000000002501512637565441015740 0ustar00rootroot00000000000000/********************************************************************* plugin.c: mime types and plugins. Run audio players, pdf converters, etc, based on suffix or content-type. *********************************************************************/ #include "eb.h" #include #ifdef DOSLIKE #include // for _getpid(),... #define getpid _getpid #endif /* create an input or an output file for edbrowse under /tmp. * Since an external program may act upon this file, a certain suffix * may be required. * Fails if /tmp/.edbrowse does not exist or cannot be created. */ static const char *tempbase; static char *tempin, *tempout; static char *suffixin; /* suffix of input file */ static char *suffixout; /* suffix of output file */ static bool makeTempFilename(const char *suffix, int idx, bool output) { char *filename; if (!tempbase) { /* has not yet been set */ #ifdef DOSLIKE int l; char *a; tempbase = getenv("TEMP"); if (!tempbase) { setError(MSG_NoEnvVar, "TEMP"); return false; } // put /edbrowse on the end l = strlen(tempbase); a = allocMem(l + 10); sprintf(a, "%s/edbrowse", tempbase); tempbase = a; #else tempbase = "/tmp/.edbrowse"; #endif } if (fileTypeByName(tempbase, false) != 'd') { /* no such directory, try to make it */ /* this temp edbrowse directory is used by everyone system wide */ if (mkdir(tempbase, 0777)) { setError(MSG_TempDir, tempbase); return false; } /* yes, we called mkdir with 777 above, but this call gets us past umask */ /* What does this do in Windows? */ chmod(tempbase, 0777); } if (asprintf(&filename, "%s/pf%d-%d.%s", tempbase, getpid(), idx, suffix) < 0) i_printfExit(MSG_MemAllocError, strlen(tempbase) + 24); if (output) { // free the last one, don't need it any more. nzFree(tempout); tempout = filename; } else { nzFree(tempin); tempin = filename; suffixin = tempin + strlen(tempin) - strlen(suffix); } return true; } /* makeTempFilename */ static int tempIndex; const struct MIMETYPE *findMimeBySuffix(const char *suffix) { int i; int len = strlen(suffix); const struct MIMETYPE *m = mimetypes; for (i = 0; i < maxMime; ++i, ++m) { const char *s = m->suffix, *t; if (!s) continue; while (*s) { t = strchr(s, ','); if (!t) t = s + strlen(s); if (t - s == len && memEqualCI(s, suffix, len)) return m; if (*t) ++t; s = t; } } return NULL; } /* findMimeBySuffix */ const struct MIMETYPE *findMimeByURL(const char *url) { char suffix[12]; const char *post, *s; post = url + strcspn(url, "?\1"); for (s = post - 1; s >= url && *s != '.' && *s != '/'; --s) ; if (*s != '.') return NULL; ++s; if (post >= s + sizeof(suffix)) return NULL; strncpy(suffix, s, post - s); suffix[post - s] = 0; return findMimeBySuffix(suffix); } /* findMimeByURL */ const struct MIMETYPE *findMimeByFile(const char *filename) { char suffix[12]; const char *post, *s; post = filename + strlen(filename); for (s = post - 1; s >= filename && *s != '.' && *s != '/'; --s) ; if (*s != '.') return NULL; ++s; if (post >= s + sizeof(suffix)) return NULL; strncpy(suffix, s, post - s); suffix[post - s] = 0; return findMimeBySuffix(suffix); } /* findMimeByFile */ const struct MIMETYPE *findMimeByContent(const char *content) { int i; int len = strlen(content); const struct MIMETYPE *m = mimetypes; for (i = 0; i < maxMime; ++i, ++m) { const char *s = m->content, *t; if (!s) continue; while (*s) { t = strchr(s, ','); if (!t) t = s + strlen(s); if (t - s == len && memEqualCI(s, content, len)) return m; if (*t) ++t; s = t; } } return NULL; } /* findMimeByContent */ const struct MIMETYPE *findMimeByProtocol(const char *prot) { int i; int len = strlen(prot); const struct MIMETYPE *m = mimetypes; for (i = 0; i < maxMime; ++i, ++m) { const char *s = m->prot, *t; if (!s) continue; while (*s) { t = strchr(s, ','); if (!t) t = s + strlen(s); if (t - s == len && memEqualCI(s, prot, len)) return m; if (*t) ++t; s = t; } } return NULL; } /* findMimeByProtocol */ /* The result is allocated */ char *pluginCommand(const struct MIMETYPE *m, const char *infile, const char *outfile, const char *suffix) { int len, inlen, outlen; const char *s; char *cmd, *t; if (!suffix) suffix = emptyString; if (!infile) infile = emptyString; if (!outfile) outfile = emptyString; len = 0; for (s = m->program; *s; ++s) { #if 0 if (*s == '*') { len += strlen(suffix); continue; } #endif if (*s == '%' && s[1] == 'i') { inlen = shellProtectLength(infile); len += inlen; ++s; continue; } if (*s == '%' && s[1] == 'o') { outlen = shellProtectLength(outfile); len += outlen; ++s; continue; } ++len; } ++len; cmd = allocMem(len); t = cmd; for (s = m->program; *s; ++s) { #if 0 if (*s == '*') { strcpy(t, suffix); t += strlen(suffix); continue; } #endif if (*s == '%' && s[1] == 'i') { shellProtect(t, infile); t += inlen; ++s; continue; } if (*s == '%' && s[1] == 'o') { shellProtect(t, outfile); t += outlen; ++s; continue; } *t++ = *s; } *t = 0; debugPrint(3, "plugin %s", cmd); return cmd; } /* pluginCommand */ /* play the contents of the current buffer, or otherwise * act upon it based on the program corresponding to its mine type. * This is called from twoLetter() in buffers.c, and should return: * 0 error, 1 success, 2 not a play buffer command */ int playBuffer(const char *line, const char *playfile) { const struct MIMETYPE *mt = cw->mt; static char sufbuf[12]; char *cmd; const char *suffix = NULL; char *buf; int buflen; char *infile; char c = line[2]; if (c && c != '.') return 2; if (!cw->dol) { setError(cw->dirMode ? MSG_EmptyBuffer : MSG_AudioEmpty); return 0; } if (cw->browseMode) { setError(MSG_AudioBrowse); return 0; } if (cw->sqlMode) { setError(MSG_AudioDB); return 0; } if (cw->dirMode && !playfile) { setError(MSG_AudioDir); return 0; } if (playfile) { /* play the file passed in */ suffix = strrchr(playfile, '.') + 1; strcpy(sufbuf, suffix); suffix = sufbuf; mt = findMimeBySuffix(suffix); if (!mt || mt->outtype | mt->stream) { setError(MSG_SuffixBad, suffix); return 0; } cmd = pluginCommand(mt, playfile, 0, suffix); if (!cmd) return 0; goto play_command; } if (!mt) { /* need to determine the mime type */ if (c == '.') { suffix = line + 3; } else { if (cw->fileName) { const char *endslash; suffix = strrchr(cw->fileName, '.'); endslash = strrchr(cw->fileName, '/'); if (suffix && endslash && endslash > suffix) suffix = NULL; } if (!suffix) { setError(MSG_NoSuffix); return 0; } ++suffix; } if (strlen(suffix) > 5) { setError(MSG_SuffixLong); return 0; } mt = findMimeBySuffix(suffix); if (!mt) { setError(MSG_SuffixBad, suffix); return 0; } cw->mt = mt; } if (!suffix) { suffix = mt->suffix; if (!suffix) suffix = "x"; else { int i; for (i = 0; i < sizeof(sufbuf) - 1; ++i) { if (mt->suffix[i] == ',' || mt->suffix[i] == 0) break; sufbuf[i] = mt->suffix[i]; } sufbuf[i] = 0; suffix = sufbuf; } } if (mt->outtype | mt->stream) { setError(MSG_SuffixBad, suffix); return 0; } ++tempIndex; if (!makeTempFilename(suffix, tempIndex, false)) return 0; infile = tempin; if (!isURL(cw->fileName) && !access(cw->fileName, 4) && !cw->changeMode) infile = cw->fileName; cmd = pluginCommand(mt, infile, 0, suffix); if (!cmd) return 0; if (infile == tempin) { if (!unfoldBuffer(context, false, &buf, &buflen)) { nzFree(cmd); return 0; } if (!memoryOutToFile(tempin, buf, buflen, MSG_TempNoCreate2, MSG_NoWrite2)) { unlink(tempin); nzFree(cmd); nzFree(buf); return 0; } nzFree(buf); } play_command: eb_system(cmd, true); if (!cw->dirMode && infile == tempin) unlink(tempin); nzFree(cmd); return 1; } /* playBuffer */ bool playServerData(void) { const struct MIMETYPE *mt = cw->mt; char *cmd; const char *suffix = mt->suffix; if (!suffix) suffix = "x"; else { static char sufbuf[12]; int i; for (i = 0; i < sizeof(sufbuf) - 1; ++i) { if (mt->suffix[i] == ',' || mt->suffix[i] == 0) break; sufbuf[i] = mt->suffix[i]; } sufbuf[i] = 0; suffix = sufbuf; } ++tempIndex; if (!makeTempFilename(suffix, tempIndex, false)) return false; cmd = pluginCommand(cw->mt, tempin, 0, suffix); if (!cmd) return false; if (!memoryOutToFile(tempin, serverData, serverDataLen, MSG_TempNoCreate2, MSG_NoWrite2)) { unlink(tempin); nzFree(cmd); return false; } eb_system(cmd, true); unlink(tempin); nzFree(cmd); return true; } /* playServerData */ /* return the name of the output file, or 0 upon failure */ /* Return "|" if output is in memory and not in a temp file. */ char *runPluginConverter(const char *buf, int buflen) { const struct MIMETYPE *mt = cw->mt; char *cmd; const char *suffix = mt->suffix; bool ispipe = !strstr(mt->program, "%o"); bool rc; char *infile; int system_ret = 0; if (!suffix) suffix = "x"; else { static char sufbuf[12]; int i; for (i = 0; i < sizeof(sufbuf) - 1; ++i) { if (mt->suffix[i] == ',' || mt->suffix[i] == 0) break; sufbuf[i] = mt->suffix[i]; } sufbuf[i] = 0; suffix = sufbuf; } ++tempIndex; if (!makeTempFilename(suffix, tempIndex, false)) return 0; suffixout = (cw->mt->outtype == 'h' ? "html" : "txt"); ++tempIndex; if (!makeTempFilename(suffixout, tempIndex, true)) return 0; infile = tempin; if (!isURL(cw->fileName) && !access(cw->fileName, 4) && !cw->changeMode) infile = cw->fileName; cmd = pluginCommand(cw->mt, infile, tempout, suffix); if (!cmd) return NULL; if (infile == tempin) { if (!memoryOutToFile(tempin, buf, buflen, MSG_TempNoCreate2, MSG_NoWrite2)) { unlink(tempin); nzFree(cmd); return NULL; } } #ifndef DOSLIKE /* no popen call in windows I guess */ if (ispipe) { FILE *p = popen(cmd, "r"); if (!p) { setError(MSG_NoSpawn, cmd, errno); if (infile == tempin) unlink(tempin); nzFree(cmd); return NULL; } /* borrow a global data array */ rc = fdIntoMemory(fileno(p), &serverData, &serverDataLen); fclose(p); if (infile == tempin) unlink(tempin); nzFree(cmd); if (rc) return "|"; nzFree(serverData); serverData = NULL; return NULL; } #endif // #ifndef DOSLIKE system_ret = eb_system(cmd, false); if (infile == tempin) unlink(tempin); nzFree(cmd); if (!system_ret) return tempout; else return NULL; } /* runPluginConverter */ edbrowse-3.6.0.1/src/PaxHeaders.22102/messages.h0000644000000000000000000000006112637565441016001 xustar0020 atime=1451158305 29 ctime=1451409178.15333719 edbrowse-3.6.0.1/src/messages.h0000644000000000000000000003141512637565441016257 0ustar00rootroot00000000000000/* messages.h * This file is part of the edbrowse project, released under GPL. */ /********************************************************************* Symbolic constants for message numbers. These become the indexes for the messages in lang/msg-en, or in lang/msg-xx for language xx. The externs below correspond to the languages supported. The array of messages is selected by $LANG. See selectLanguage() in messages.c. EdbrowseMessageCount is the number of edbrowse messages, including some blank spaces for messages no longer used. Each file lang/msg-* must have exactly this many lines. *********************************************************************/ #define EdbrowseMessageCount 659 // English extern const char *msg_en[], *ebrc_en; // French, Erwin Bliesenick, erwinb@no-log.org extern const char *msg_fr[], *ebrc_fr; // Brazilian Portuguese, Cleverson Casarin, clcaul@gmail.com extern const char *msg_pt_br[], *ebrc_pt_br; // German, Sebastian Humenda, shumenda@gmx.de extern const char *msg_de[], *ebrc_de; // Polish, Wojciech Gac, wojciech.s.gac@gmail.com extern const char *msg_pl[], *ebrc_pl; // Russian, Wojciech Gac, wojciech.s.gac@gmail.com extern const char *msg_ru[], *ebrc_ru; /********************************************************************* This file and lang/msg-en line up. The constant at line 101 of messages.h corresponds to the English string at line 1 of lang/msg-en. to keep this alignment, even if you run this file through indent, this comment block is just the right size. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *********************************************************************/ enum { MSG_EndFile, MSG_NoFile, MSG_Redirect, MSG_SubGlobal, MSG_SubLocal, MSG_CaseIns, MSG_CaseSen, MSG_DirReadonly, MSG_DirWritable, MSG_DirX, MSG_RedirectionOff, MSG_RedirectionOn, MSG_RefererOff, MSG_RefererOn, MSG_JavaOff, MSG_JavaOn, MSG_BinaryIgnore, MSG_BinaryDetect, MSG_PassiveMode, MSG_ActiveMode, MSG_JSCloseSessions, MSG_CertifyOff, MSG_CertifyOn, MSG_HiddenOff, MSG_HiddenOn, MSG_JSEngineFork, MSG_JSEngineExec, MSG_MarkOff, MSG_MarkList, MSG_MarkOn, MSG_NJNoAction, MSG_NJGoing, MSG_RedirectionInterrupted, MSG_Empty, MSG_SessionNew, MSG_NoTrailing, MSG_DirMode, MSG_BinaryData, MSG_OK, MSG_BinaryBrackets, MSG_NoTitle, MSG_NoDesc, MSG_NoKeywords, MSG_SessionX, MSG_MailHowto, MSG_LineUpdate1, MSG_ImapMessageHelp, MSG_ImapSearchHelp, MSG_NoMessages, MSG_EnterInterrupt, MSG_Ready, MSG_EnterNull, MSG_WebAuthorize, MSG_RedirectMany, MSG_Success, MSG_Directory, MSG_RedirectDelayed, MSG_UserName, MSG_UserNameLong, MSG_Password, MSG_PasswordLong, MSG_JSEnginePipe, MSG_NoDirNoList, MSG_ErrorMessageLong, MSG_LineX, MSG_NoDir, MSG_BufferUpdated, MSG_NoOptions, MSG_FormReset, MSG_FormSubmit, MSG_EBRC_Outtype, MSG_Many, MSG_Recommended, MSG_Close, MSG_NoOptionsMatch, MSG_MailSending, MSG_MailSent, MSG_Junk, MSG_Quit, MSG_Next, MSG_Delete, MSG_PluginsOff, MSG_PluginsOn, MSG_JSEngineRun, MSG_EndMessage, MSG_NYI, MSG_FileName, MSG_FileExists, MSG_Abbreviated, MSG_AttEmpty, MSG_Att, MSG_AttNoBuffer, MSG_AttNoCopy, MSG_AttNoSave, MSG_AttNoWrite, MSG_NoMail, MSG_MessagesX, MSG_JSEngineSync, MSG_LSBadChar, MSG_MailHelp, MSG_NoCreate, MSG_NoWrite, MSG_MailSaved, MSG_Appended, MSG_PageDone, MSG_TempDir, MSG_NoErrors, MSG_ProxyAuth, MSG_SessionInactive, MSG_Session0, MSG_SessionHigh, MSG_SessionCurrent, MSG_ExpectW, MSG_ExpectWX, MSG_LineLimit, MSG_PathNameLong, MSG_DirNoWrite, MSG_NoRecycle, MSG_NoRemove, MSG_NoMoveToTrash, MSG_DestInBlock, MSG_NoChange, MSG_Join1, MSG_DBOtherFile, MSG_DBOtherTable, MSG_MissingFileName, MSG_DomainEmpty, MSG_NoWriteURL, MSG_WriteDB, MSG_WriteEmpty, MSG_NoCreate2, MSG_NoWrite2, MSG_SessionBackground, MSG_OutOfRange, MSG_ShellNull, MSG_NoLabel, MSG_BadDelimit, MSG_NoSearchString, MSG_NoReplaceString, MSG_RexpLong, MSG_LineBackslash, MSG_UnexpectedRight, MSG_RexpDollar, MSG_RexpModifier, MSG_NoBracket, MSG_NoParen, MSG_EmptyBuffer, MSG_RexpError, MSG_RexpError2, MSG_NotFound, MSG_LineHigh, MSG_LineLow, MSG_RexpMissing, MSG_NoDelimit, MSG_NoMatchG, MSG_AllMatchV, MSG_NotModifiedG, MSG_Interrupted, MSG_NoMatch, MSG_JSEngineVars, MSG_NoAgent, MSG_CDGetError, MSG_CDSetError, MSG_CDInvalid, MSG_AudioEmpty, MSG_AudioBrowse, MSG_AudioDir, MSG_AudioDB, MSG_NoSuffix, MSG_SuffixLong, MSG_SuffixBad, MSG_NoRefresh, MSG_NoDB, MSG_NoBrowse, MSG_NoSlash, MSG_YesSlash, MSG_SMBadChar, MSG_BalanceChar, MSG_BalanceNoOpen, MSG_BalanceAmbig, MSG_BalanceNothing, MSG_Unbalanced, MSG_Personalize, MSG_SessionDir, MSG_NoFileName, MSG_EndBuffer, MSG_EndJoin, MSG_BadRange, MSG_BreakDir, MSG_BreakDB, MSG_BreakBrowse, MSG_UnknownCommand, MSG_DirCommand, MSG_DBCommand, MSG_BrowseCommand, MSG_AtLine0, MSG_NoSpaceAfter, MSG_GlobalCommand, MSG_BadDest, MSG_TextAfter, MSG_NoUndo, MSG_EnterKAZ, MSG_RangeLabel, MSG_Backup0, MSG_NoBufferExtraWindow, MSG_QAfter, MSG_DirRename, MSG_TableRename, MSG_BufferAppend, MSG_NoFileSpecified, MSG_NoDirWrite, MSG_NoDBWrite, MSG_ArrowAfter, MSG_NoPrevious, MSG_MAfter, MSG_NoDestSession, MSG_NoBackup, MSG_RangeG, MSG_DBG, MSG_RangeI, MSG_IG, MSG_BufferXEmpty, MSG_BufferXLines, MSG_NoOpen, MSG_NoRead, MSG_InputNull, MSG_InputCR, MSG_FirstLineLong, MSG_AlreadyInBuffer, MSG_BrowseBinary, MSG_BrowseEmpty, MSG_Unbrowsable, MSG_BrowseAlready, MSG_NoLable2, MSG_BrowseI, MSG_InsertFunction, MSG_JSEngineRW, MSG_CNYI, MSG_XOutOfRange, MSG_OptMatchNone, MSG_OptMatchMany, MSG_IsButton, MSG_SubmitButton, MSG_ResetButton, MSG_Textarea, MSG_Readonly, MSG_InputNewline, MSG_InputLong, MSG_InputRadio, MSG_ClearRadio, MSG_FileAccess, MSG_NumberExpected, MSG_SessionNull, MSG_FilePost, MSG_NoButton, MSG_NotInForm, MSG_ButtonNoJS, MSG_FormNoURL, MSG_FormBadURL, MSG_NJNoForm, MSG_BecameInsecure, MSG_SubmitProtBad, MSG_InputRange, MSG_InputRange2, MSG_ManyEmptyStrings, MSG_DownAbort, MSG_RexpMissing2, MSG_SubNumbersMany, MSG_SubSuffixBad, MSG_SubNumberG, MSG_BreakLong, MSG_ReplaceNewline, MSG_ReplaceNull, MSG_DirNameBad, MSG_DestFileExists, MSG_NoRename, MSG_InputNull2, MSG_InputNewline2, MSG_NoInputFields, MSG_NoLinks, MSG_NoButtons, MSG_ManyInputFields, MSG_ManyLinks, MSG_ManyButtons, MSG_WebRead, MSG_BadURL, MSG_DownSuccess, MSG_IdentifyHost, MSG_WebProtBad, MSG_BufferPreload, MSG_DownProgress, MSG_WebConnect, MSG_Down, MSG_NoCertify, MSG_Inaccess, MSG_ImapReadHelp, MSG_Authorize2, MSG_LoginAbort, MSG_SearchQuote, MSG_EndFolder, MSG_DownForeground, MSG_DownBackground, MSG_Complete, MSG_Failed, MSG_FTPConnect, MSG_FTPTransfer, MSG_NoFolders, MSG_SelectFolder, MSG_InProgress, MSG_Stop, MSG_FetchN, MSG_NoAttachments, MSG_EBRC_KeyInFunc, MSG_ShowLast, MSG_FTPSession, MSG_Push, MSG_DBNotCompiled, MSG_ABNoAlias, MSG_ABNoColon, MSG_ABAliasLong, MSG_ABMailLong, MSG_ABNoAt, MSG_ABMailSpaces, MSG_AbMailUnprintable, MSG_ABUnterminated, MSG_NoFolderMatch, MSG_ManyFolderMatch, MSG_Search, MSG_Limit, MSG_MoveTo, MSG_SectionIgnored, MSG_FileXEmpty, MSG_SubjectStart, MSG_notused334, MSG_SubjectLong, MSG_notused336, MSG_SigRegular, MSG_SigAccess, MSG_MailBinary, MSG_AttAlternate, MSG_RecipMany, MSG_ABMissing, MSG_ABNoAlias2, MSG_RecipNone, MSG_AttSessionEmpty, MSG_AttAccess, MSG_AttRegular, MSG_AttEmpty2, MSG_LineDelete1, MSG_LineDelete2, MSG_LineAdd1, MSG_LineAdd2, MSG_notused353, MSG_MailAccountsNone, MSG_MailAccountBad, MSG_MailBrowse, MSG_MailDB, MSG_MailDir, MSG_MailBinary2, MSG_MailEmpty, MSG_RecipNone2, MSG_MailFirstCC, MSG_AttLineX, MSG_MailAccountBadLineX, MSG_notused365, MSG_MailFirstLine, MSG_notused367, MSG_RecipNone3, MSG_ProtExpected, MSG_BadProt, MSG_BadPort, MSG_DomainLong, MSG_UserNameLong2, MSG_PasswordLong2, MSG_LineUpdate2, MSG_LineUpdate3, MSG_NoFunction, MSG_BadFunctionName, MSG_NoSuchFunction, MSG_ManyArgs, MSG_NoArgument, MSG_NoSpawn, MSG_TempNoCreate2, MSG_ManyTables, MSG_RegularFile, MSG_notused386, MSG_NoRead2, MSG_NoAccess, MSG_NoEnvVar, MSG_NoTilde, MSG_Submit, MSG_notused392, MSG_Secure, MSG_Bymail, MSG_Reset, MSG_ShellExpand, MSG_notused397, MSG_ShellManyMatch, MSG_ShellLineLong, MSG_DBUnspecified, MSG_DBConnect, MSG_DBUnexpected, MSG_DBNoKey, MSG_DBColumnLong, MSG_DBSyntax, MSG_DBColRange, MSG_DBManyColumns, MSG_DBNoColumn, MSG_DBNoTable, MSG_DBBadColumn, MSG_DBManyBlobs, MSG_DBPipes, MSG_DBNewline, MSG_DBAddField, MSG_DBLostField, MSG_DBNoKeyCol, MSG_DBMisc, MSG_DBMassDelete, MSG_DBChangeKey, MSG_DBChangeBlob, MSG_DBChangeText, MSG_DBDeleteCount, MSG_DBInsertCount, MSG_DBUpdateCount, MSG_DBRefInt, MSG_DBLocked, MSG_DBPerms, MSG_DBDeadlock, MSG_DBNotNull, MSG_DBCheck, MSG_DBTimeout, MSG_DBView, MSG_notused433, MSG_notused434, MSG_notused435, MSG_notused436, MSG_notused437, MSG_notused438, MSG_notused439, MSG_notused440, MSG_notused441, MSG_notused442, MSG_notused443, MSG_notused444, MSG_notused445, MSG_notused446, MSG_notused447, MSG_notused448, MSG_notused449, MSG_notused450, MSG_notused451, MSG_notused452, MSG_notused453, MSG_notused454, MSG_notused455, MSG_notused456, MSG_notused457, MSG_notused458, MSG_notused459, MSG_notused460, MSG_AttAfterChars, MSG_AttBad64, MSG_notused463, MSG_Doubleclick, MSG_OptionSync, MSG_notused466, MSG_notused467, MSG_GetLocalJS, MSG_GetJS, MSG_GetJS2, MSG_NJNoOnclick, MSG_NJNoOnchange, MSG_NJNoReset, MSG_NJNoSubmit, MSG_LostTag, MSG_notused476, MSG_GarbledRefresh, MSG_notused478, MSG_RedirectNoURL, MSG_HTTPError, MSG_notused481, MSG_notused482, MSG_HelpOn, MSG_FTPDownload, MSG_NoCertFile, MSG_SCPDownload, MSG_EBRC_Nulls, MSG_EBRC_NoFnName, MSG_EBRC_FnDigit, MSG_EBRC_FnTooLong, MSG_EBRC_SyntaxErr, MSG_EBRC_NoCondFile, MSG_EBRC_NoMatchStr, MSG_EBRC_MatchNowh, MSG_EBRC_Filters, MSG_EBRC_BadKeyword, MSG_EBRC_MailAttrOut, MSG_EBRC_MimeAttrOut, MSG_EBRC_TableAttrOut, MSG_EBRC_MailAttrIn, MSG_EBRC_MimeAttrIn, MSG_EBRC_TableAttrIn, MSG_EBRC_NoAttr, MSG_EBRC_ManyCols, MSG_EBRC_KeyNotNb, MSG_EBRC_KeyOutRange, MSG_EBRC_AbNotFile, MSG_notused508, MSG_EBRC_NotDir, MSG_EBRC_ManyAgents, MSG_EBRC_JarNotFile, MSG_EBRC_JarNoWrite, MSG_EBRC_NoJS, MSG_EBRC_DomainDot, MSG_notused515, MSG_EBRC_SSLNoFile, MSG_EBRC_SSLNoRead, MSG_EBRC_KeywordNYI, MSG_EBRC_SevDefaults, MSG_EBRC_NoInserver, MSG_EBRC_NoOutserver, MSG_EBRC_NoLogin, MSG_EBRC_NPasswd, MSG_EBRC_NoFrom, MSG_EBRC_NoReply, MSG_EBRC_NoType, MSG_EBRC_NDesc, MSG_EBRC_NoSuffix, MSG_EBRC_NoProgram, MSG_EBRC_NoTblName, MSG_EBRC_NoShortName, MSG_EBRC_NColumns, MSG_EBRC_UnexpBrace, MSG_EBRC_UnexElse, MSG_EBRC_GarblText, MSG_EBRC_FnNotStart, MSG_EBRC_StatNotInFn, MSG_EBRC_ManyAcc, MSG_EBRC_ManyTypes, MSG_EBRC_ManyTables, MSG_EBRC_ManyFn, MSG_EBRC_TooDeeply, MSG_EBRC_FnNotClosed, MSG_EBRC_MNotClosed, MSG_NotHome, MSG_NotDir, MSG_NoMailAcc, MSG_BadAccNb, MSG_Usage, MSG_MinOneRec, MSG_MinOneRecBefAtt, MSG_ManyOpen, MSG_InvalidSession, MSG_InvalidLineNb, MSG_EBRC_NoPROXY, MSG_SessionOutRange, MSG_DoubleInit, MSG_QuitNoActive, MSG_EmptyPiece, MSG_NoNlOnDir, MSG_NoClosingLine, MSG_NoTagFound, MSG_NoRebCookie, MSG_notused564, MSG_FetchNotBackgnd, MSG_NoMailDir, MSG_NoDirChange, MSG_notused568, MSG_LogPass, MSG_notused570, MSG_notused571, MSG_notused572, MSG_notused573, MSG_notused574, MSG_BadTagCode, MSG_notused576, MSG_HtmlNotreentrant, MSG_UnexSubmitForm, MSG_NullListInform, MSG_notused580, MSG_JavaMemError, MSG_JavaContextError, MSG_JavaWindowError, MSG_notused584, MSG_JavaObjError, MSG_notused586, MSG_notused587, MSG_MemAllocError, MSG_MemCallocError, MSG_ReallocP, MSG_Realloc0, MSG_ErrorRealloc, MSG_NullStrList, MSG_NullStrListCI, MSG_NullCharInList, MSG_NullPtr, MSG_IoctlError, MSG_OpenFail, MSG_CreateFail, MSG_InvalidFopen, MSG_BadDirSlash, MSG_BadSlash, MSG_DecodePost, MSG_notused604, MSG_notused605, MSG_notused606, MSG_ReDir, MSG_ReDB, MSG_ReEmpty, MSG_ReBinary, MSG_ReSubjectReply, MSG_ReNoID, MSG_ReNoInfo, MSG_ListControl, MSG_ListNA, MSG_PcreUtf8, MSG_Conv8859, MSG_ConvUtf8, MSG_IUConvertOff, MSG_IUConvertOn, MSG_CopyMoveDir, MSG_NoDirDelete, MSG_LibcurlNoInit, MSG_FTPEmptyDir, MSG_ReAscii, MSG_ReUtf8, MSG_BadUtf8String, MSG_DBManyKeyCol, MSG_DBNextSerial, MSG_ShowTables, MSG_DBNoSource, MSG_DBConnecting, MSG_Row, MSG_Rows, MSG_Selected, MSG_Inserted, MSG_Updated, MSG_Deleted, MSG_ProcExec, MSG_Table, MSG_UnterminatedSelect, MSG_DBNoWhere, MSG_RemoteAccessDenied, MSG_AuthRequired, MSG_NoMem, MSG_CurlVersion, MSG_Timeout, MSG_CurlSendData, MSG_CurlCatchAll, MSG_Fkeys, MSG_FetchBlobOff, MSG_FetchBlobOn, MSG_SSLConnectError, MSG_InputTTY, MSG_InputReadLine, MSG_notused656, MSG_JSSessionFail, MSG_SystemCmdFail, MSG_notused659, }; edbrowse-3.6.0.1/src/PaxHeaders.22102/messages.c0000644000000000000000000000006112637565441015774 xustar0020 atime=1451158305 29 ctime=1451409178.15333719 edbrowse-3.6.0.1/src/messages.c0000644000000000000000000003036712637565441016257 0ustar00rootroot00000000000000/* messages.c * Error, warning, and info messages in your host language, * as determined by the variable $LANG. * Messages can be generated in iso-8859-1, but utf8 is recommended. * This file is part of the edbrowse project, released under GPL. */ #include "eb.h" #include /* English by default */ static const char **messageArray = msg_en; int eb_lang = 1; /* startup .ebrc files in various languages */ const char *ebrc_string; bool cons_utf8, iuConvert = true; char type8859 = 1; bool helpMessagesOn; bool errorExit; /********************************************************************* Convert a string from iso 8859 to utf8, or vice versa. In each case a new string is allocated. Don't forget to free it when you're done. *********************************************************************/ /* only 8859-1 and 8859-2 so far */ static const int iso_unicodes[2][128] = { {0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff}, {0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0x104, 0x2d8, 0x141, 0xa4, 0x13d, 0x15a, 0xa7, 0xa8, 0x160, 0x15e, 0x164, 0x179, 0xad, 0x17d, 0x17b, 0xb0, 0x105, 0x2db, 0x142, 0xb4, 0x13e, 0x15b, 0x2c7, 0xb8, 0x161, 0x15f, 0x165, 0x17a, 0x2dd, 0x17e, 0x17c, 0x154, 0xc1, 0xc2, 0x102, 0xc4, 0x139, 0x106, 0xc7, 0x10c, 0xc9, 0x118, 0xcb, 0x11a, 0xcd, 0xce, 0x10e, 0x110, 0x143, 0x147, 0xd3, 0xd4, 0x150, 0xd6, 0xd7, 0x158, 0x16e, 0xda, 0x170, 0xdc, 0xdd, 0x162, 0xdf, 0x155, 0xe1, 0xe2, 0x103, 0xe4, 0x13a, 0x107, 0xe7, 0x10d, 0xe9, 0x119, 0xeb, 0x11b, 0xed, 0xee, 0x10f, 0x111, 0x144, 0x148, 0xf3, 0xf4, 0x151, 0xf6, 0xf7, 0x159, 0x16f, 0xfa, 0x171, 0xfc, 0xfd, 0x163, 0x2d9}, }; void iso2utf(const char *inbuf, int inbuflen, char **outbuf_p, int *outbuflen_p) { int i, j; int nacount = 0; char c; char *outbuf; const int *isoarray = iso_unicodes[type8859 - 1]; int ucode; if (!inbuflen) { *outbuf_p = emptyString; *outbuflen_p = 0; return; } /* count chars, so we can allocate */ for (i = 0; i < inbuflen; ++i) { c = inbuf[i]; if (c < 0) ++nacount; } outbuf = allocString(inbuflen + nacount + 1); for (i = j = 0; i < inbuflen; ++i) { c = inbuf[i]; if (c >= 0) { outbuf[j++] = c; continue; } ucode = isoarray[c & 0x7f]; outbuf[j++] = (ucode >> 6) | 0xc0; outbuf[j++] = (ucode & 0x3f) | 0x80; } outbuf[j] = 0; /* just for fun */ *outbuf_p = outbuf; *outbuflen_p = j; } /* iso2utf */ void utf2iso(const char *inbuf, int inbuflen, char **outbuf_p, int *outbuflen_p) { int i, j, k; char c; char *outbuf; const int *isoarray = iso_unicodes[type8859 - 1]; int ucode; if (!inbuflen) { *outbuf_p = emptyString; *outbuflen_p = 0; return; } outbuf = allocString(inbuflen + 1); for (i = j = 0; i < inbuflen; ++i) { c = inbuf[i]; /* regular chars and nonascii chars that aren't utf8 pass through. */ /* There shouldn't be any of the latter */ if (((uchar) c & 0xc0) != 0xc0) { outbuf[j++] = c; continue; } /* Convertable into 11 bit */ if (((uchar) c & 0xe0) == 0xc0 && ((uchar) inbuf[i + 1] & 0xc0) == 0x80) { ucode = c & 0x1f; ucode <<= 6; ucode |= (inbuf[i + 1] & 0x3f); for (k = 0; k < 128; ++k) if (isoarray[k] == ucode) break; if (k < 128) { outbuf[j++] = k | 0x80; ++i; continue; } } /* unicodes not found in our iso class are converted into stars */ c <<= 1; ++i; for (++i; c < 0; ++i, c <<= 1) { if (((uchar) outbuf[i] & 0xc0) != 0x80) break; } outbuf[j++] = '*'; --i; } outbuf[j] = 0; /* just for fun */ *outbuf_p = outbuf; *outbuflen_p = j; } /* utf2iso */ void selectLanguage(void) { char buf[8]; char *s = getenv("LANG"); // This is likely to fail in windows ebrc_string = ebrc_en; #ifndef DOSLIKE if (!s) return; if (!*s) return; if (strstrCI(s, "utf8") || strstrCI(s, "utf-8")) cons_utf8 = true; /* We roll our own international messages in this file, so you wouldn't think * we need setlocale, but pcre needs the locale for expressions like \w, * and for ranges like [A-z], * and to convert to upper or lower case etc. * So I set LC_ALL, which covers both LC_CTYPE and LC_COLLATE. * By calling strcoll, the directory scan is in the same order as ls. * See dircmp() in stringfile.c */ setlocale(LC_ALL, ""); #else // DOSLIKE /* I'm going to assume Windows runs utf8 */ cons_utf8 = true; if (!s) s = setlocale(LC_ALL, ""); if (!s) return; if (!*s) return; #endif // DOSLIKE y/n strncpy(buf, s, 7); buf[7] = 0; caseShift(buf, 'l'); if (!strncmp(buf, "en", 2)) return; /* english is default */ if (!strncmp(buf, "fr", 2)) { eb_lang = 2; messageArray = msg_fr; ebrc_string = ebrc_fr; return; } if (!strncmp(buf, "pt_br", 5)) { eb_lang = 3; messageArray = msg_pt_br; ebrc_string = ebrc_pt_br; return; } if (!strncmp(buf, "pl", 2)) { eb_lang = 4; messageArray = msg_pl; type8859 = 2; return; } if (!strncmp(buf, "de", 2)) { eb_lang = 5; messageArray = msg_de; ebrc_string = ebrc_de; type8859 = 1; return; } if (!strncmp(buf, "ru", 2)) { eb_lang = 6; messageArray = msg_ru; type8859 = 5; return; } /* This error is really annoying if it pops up every time you invoke edbrowse. fprintf(stderr, "Sorry, language %s is not implemented\n", buf); */ } /* selectLanguage */ const char *i_getString(int msg) { const char **a = messageArray; const char *s; char *t; int t_len; static char utfbuf[1000]; if (msg >= EdbrowseMessageCount) s = emptyString; else s = a[msg]; if (!s) s = msg_en[msg]; if (!s) s = "spurious message"; if (cons_utf8) return s; /* We have to convert it. */ utf2iso(s, strlen(s), &t, &t_len); strcpy(utfbuf, t); nzFree(t); return utfbuf; } /* i_getString */ /********************************************************************* Internationalize the standard puts and printf. These are simple informational messages, where you don't need to error out, or check the debug level, or store the error in a buffer. The i_ prefix means international. *********************************************************************/ void i_puts(int msg) { eb_puts(i_getString(msg)); } /* i_puts */ void i_printf(int msg, ...) { const char *realmsg = i_getString(msg); va_list p; va_start(p, msg); eb_vprintf(realmsg, p); va_end(p); } /* i_printf */ /* Print and exit. This puts newline on, like puts. */ void i_printfExit(int msg, ...) { const char *realmsg = i_getString(msg); va_list p; va_start(p, msg); eb_vprintf(realmsg, p); nl(); va_end(p); ebClose(99); } /* i_printfExit */ /* i_stringAndMessage: concatenate a message to an existing string. */ void i_stringAndMessage(char **s, int *l, int messageNum) { const char *messageText = i_getString(messageNum); stringAndString(s, l, messageText); } /* i_stringAndMessage */ /********************************************************************* The following error display functions are specific to edbrowse, rather than extended versions of the standard unix print functions. Thus I don't need the i_ prefix. *********************************************************************/ char errorMsg[4000]; /* Show the error message, not just the question mark, after these commands. */ static const char showerror_cmd[] = "AbefMqrw^"; /* Set the error message. Type h to see the message. */ void setError(int msg, ...) { va_list p; if (msg < 0) { errorMsg[0] = 0; return; } va_start(p, msg); vsprintf(errorMsg, i_getString(msg), p); va_end(p); /* sanity check */ if (strlen(errorMsg) >= sizeof(errorMsg)) { i_printf(MSG_ErrorMessageLong, strlen(errorMsg)); puts(errorMsg); exit(1); } } /* setError */ void showError(void) { if (errorMsg[0]) eb_puts(errorMsg); else i_puts(MSG_NoErrors); } /* showError */ void showErrorConditional(char cmd) { if (helpMessagesOn || strchr(showerror_cmd, cmd)) showError(); else eb_puts("?"); } /* showErrorConditional */ void showErrorAbort(void) { showError(); ebClose(99); } /* showErrorAbort */ /* error exit check function */ void eeCheck(void) { if (errorExit) ebClose(1); } /********************************************************************* Now for the international version of caseShift. This converts anything that might reasonably be a letter in your locale. But it isn't ready for prime time. I'd have to handle utf8 or not, and then understand upper and lower case letters per language. So this is commented out. It was just a preliminary effort anyways, based on iso8859-1. *********************************************************************/ #if 0 static const char upperMore[] = ""; static const char lowerMore[] = ""; static const char letterMore[] = ""; static bool i_isalphaByte(unsigned char c) { if (isalphaByte(c)) return true; if (c == false) return 0; if (strchr(letterMore, c)) return true; return false; } /* i_isalphaByte */ /* assumes the arg is a letter */ static unsigned char i_tolower(unsigned char c) { char *s; if (isalphaByte(c)) return tolower(c); s = strchr(upperMore, c); if (s) c = lowerMore[s - upperMore]; return c; } /* i_tolower */ static unsigned char i_toupper(unsigned char c) { char *s; if (isalphaByte(c)) return toupper(c); s = strchr(lowerMore, c); if (s) c = upperMore[s - lowerMore]; return c; } /* i_toupper */ /* This is a variation on the original routine, found in stringfile.c */ void i_caseShift(unsigned char *s, char action) { unsigned char c; /* The McDonalds conversion is very English - should we do it in all languages? */ int mc = 0; bool ws = true; for (; c = *s; ++s) { if (action == 'u') { if (i_isalphaByte(c)) *s = i_toupper(c); continue; } if (action == 'l') { if (i_isalphaByte(c)) *s = i_tolower(c); continue; } /* mixed case left */ if (i_isalphaByte(c)) { if (ws) c = i_toupper(c); else c = i_tolower(c); if (ws && c == 'M') mc = 1; else if (mc == 1 && c == 'c') mc = 2; else if (mc == 2) { c = i_toupper(c); mc = 0; } else mc = 0; *s = c; ws = false; continue; } ws = true, mc = 0; } /* loop */ } /* caseShift */ #endif void eb_puts(const char *s) { #ifdef DOSLIKE wchar_t *chars = NULL; DWORD written, mode; HANDLE output_handle; int needed; output_handle = GetStdHandle(STD_OUTPUT_HANDLE); if (GetConsoleMode(output_handle, &mode) == 0) { puts(s); return; } needed = MultiByteToWideChar(CP_UTF8, 0, s, -1, NULL, 0); if (needed == 0) { return; } //Add space for the newline chars = (wchar_t *) allocMem(sizeof(wchar_t) * (needed + 2)); MultiByteToWideChar(CP_UTF8, 0, s, -1, chars, needed); chars[needed - 1] = L'\r'; chars[needed] = L'\n'; chars[needed + 1] = L'\0'; WriteConsoleW(output_handle, (void *)chars, needed + 1, &written, NULL); free(chars); #else puts(s); #endif } /* eb_puts */ void eb_vprintf(const char *fmt, va_list args) { #ifdef DOSLIKE wchar_t *chars = NULL; DWORD written, mode; HANDLE output_handle; int needed; char *a; // result of vasprintf output_handle = GetStdHandle(STD_OUTPUT_HANDLE); if (GetConsoleMode(output_handle, &mode) == 0) { vprintf(fmt, args); return; } if (vasprintf(&a, fmt, args) < 0) return; needed = MultiByteToWideChar(CP_UTF8, 0, a, -1, NULL, 0); if (needed == 0) { free(a); return; } chars = (wchar_t *) allocMem(sizeof(wchar_t) * needed); MultiByteToWideChar(CP_UTF8, 0, a, -1, chars, needed); WriteConsoleW(output_handle, (void *)chars, needed - 1, &written, NULL); free(chars); free(a); #else vprintf(fmt, args); #endif } /* eb_printf */ edbrowse-3.6.0.1/src/PaxHeaders.22102/makefile.bsd0000644000000000000000000000006112637565441016270 xustar0020 atime=1451158305 29 ctime=1451409178.15333719 edbrowse-3.6.0.1/src/makefile.bsd0000644000000000000000000000362412637565441016547 0ustar00rootroot00000000000000# This is the makefile for edbrowse under BSD Unix. # Flags for gcc compilation. JS_CXXFLAGS =-I/usr/include/mozjs-24 # we need to only use the js flags when building with c++, so use CXXFLAGS CXXFLAGS += $(JS_CXXFLAGS) # Tell the dynamic linker to look in the pkg area. LFLAGS = -s -Wl,--rpath -Wl,/usr/pkg/lib # Override JSLIB on the command-line, if your distro uses a different name. # E.G., make JSLIB=-lmozjs JSLIB = -lmozjs-24 # Libraries for edbrowse. LDLIBS = -lpcre -lcurl -lreadline -lncurses -ltidy # Make the dynamically linked executable program by default. all: edbrowse edbrowse-js # edbrowse objects EBOBJS = main.o buffers.o auth.o http.o sendmail.o fetchmail.o \ html.o format.o cookies.o ebjs.o plugin.o ebrc.o ifeq ($BUILD_EDBR_ODBC = on) EBOBJS += dbodbc.o dbinfx.o dbops.o LDLIBS += -lodbc else EBOBJS += dbstubs.o endif # common objects COMOBJS = messages.o url.o stringfile.o html-tidy.o decorate.o msg-strings.o # Header file dependencies. $(EBOBJS) $(COMOBJS) jseng-moz.o : eb.h ebprot.h messages.h ebjs.h dbodbc.o dbinfx.o dbops.o : dbapi.h startwindow.c: startwindow.js cd .. ; ./tools/buildsourcestring.pl src/startwindow.js startWindowJS src/startwindow.c ebrc.c: ../lang/ebrc-* cd .. ; ./tools/buildebrcstring.pl msg-strings.c: ../lang/msg-* cd .. ; ./tools/buildmsgstrings.pl # library of common objects among all edbrowse processes common.a : $(COMOBJS) ar rs common.a $? # The implicit linking rule isn't good enough, because we don't have an # edbrowse.o object, and it expects one. edbrowse: $(EBOBJS) common.a startwindow.o cc $(LFLAGS) -o edbrowse startwindow.o dbstubs.o $(EBOBJS) common.a $(LIBS) clean: rm -f *.[oa] edbrowse edbrowse-js \ startwindow.c ebrc.c msg-strings.c jseng-moz.o: jseng-moz.cpp $(CXX) -c $(CXXFLAGS) $(JS_CXXFLAGS) jseng-moz.cpp -o $@ edbrowse-js : jseng-moz.o common.a cc jseng-moz.o common.a $(LOADLIBES) $(JSLIB) -ltidy -lstdc++ -o $@ edbrowse-3.6.0.1/src/PaxHeaders.22102/makefile0000644000000000000000000000006112637565441015521 xustar0020 atime=1451158305 29 ctime=1451409178.15333719 edbrowse-3.6.0.1/src/makefile0000644000000000000000000000621112637565441015773 0ustar00rootroot00000000000000# This is the makefile for edbrowse. prefix = /usr/local bindir = $(prefix)/bin PLATFORM := $(shell uname) ifeq ($(PLATFORM),Linux) CFLAGS += -DEDBROWSE_ON_LINUX endif # Flags for gcc compilation. # This assumes js and js-devel have been installed. # These are available packages on most distros. # You also need pcre[-devel] and curl[-devel] JS_CXXFLAGS =-I/usr/include/mozjs-24 # By default, we strip the executables. # Override this behavior on the command line, by setting STRIP to the # empty string. E.G., in order to build executables with debugging symbols, # CFLAGS='-g -ggdb' make STRIP='' STRIP=-s # Normal load flags LDFLAGS += $(STRIP) # ESQL C load flags #ESQLDFLAGS = $(STRIP) -Xlinker -rpath -Xlinker $(INFORMIXDIR)/lib:$(INFORMIXDIR)/lib/esql # but it's better to put those two directories into /etc/ld.so.conf and then run ldconfig ESQLDFLAGS = $(STRIP) # Libraries for edbrowse. # Override JSLIB on the command-line, if your distro uses a different name. # E.G., make JSLIB=-lmozjs JSLIB = -lmozjs-24 LDLIBS = -lpcre -lcurl -lreadline -lncurses -ltidy # Make the dynamically linked executable program by default. all: edbrowse edbrowse-js # edbrowse objects EBOBJS = main.o buffers.o auth.o http.o sendmail.o fetchmail.o \ html.o format.o cookies.o ebjs.o plugin.o ebrc.o ifeq ($(BUILD_EDBR_ODBC),on) EBOBJS += dbodbc.o dbops.o LDLIBS += -lodbc else EBOBJS += dbstubs.o endif # common objects COMOBJS = messages.o url.o stringfile.o html-tidy.o decorate.o msg-strings.o # Header file dependencies. $(EBOBJS) $(COMOBJS) jseng-moz.o : eb.h ebprot.h messages.h ebjs.h dbodbc.o dbinfx.o dbops.o : dbapi.h startwindow.c: startwindow.js cd .. ; ./tools/buildsourcestring.pl src/startwindow.js startWindowJS src/startwindow.c ebrc.c: ../lang/ebrc-* cd .. ; ./tools/buildebrcstring.pl msg-strings.c: ../lang/msg-* cd .. ; ./tools/buildmsgstrings.pl # library of common objects among all edbrowse processes common.a : $(COMOBJS) ar rs common.a $? # The implicit linking rule isn't good enough, because we don't have an # edbrowse.o object, and it expects one. edbrowse: $(EBOBJS) common.a startwindow.o $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@ # You probably need to be root to do this. install: install -Dm755 edbrowse $(DESTDIR)$(bindir)/edbrowse install -Dm755 edbrowse-js $(DESTDIR)$(bindir)/edbrowse-js # native Informix library for database access. # Others could be built, e.g. Oracle, but odbc is the most general. dbinfx.o : dbinfx.ec esql -c dbinfx.ec # Informix executable edbrowse-infx: $(EBOBJS) common.a startwindow.o dbops.o dbinfx.o esql $(ESQLDFLAGS) -o edbrowse-infx $(EBOBJS) common.a startwindow.o dbops.o dbinfx.o $(LDLIBS) clean: rm -f *.[oa] edbrowse edbrowse-js \ startwindow.c ebrc.c msg-strings.c jseng-moz.o: jseng-moz.cpp $(CXX) -c $(CXXFLAGS) $(JS_CXXFLAGS) jseng-moz.cpp -o $@ edbrowse-js : jseng-moz.o common.a $(LINK.o) $^ $(LOADLIBES) $(JSLIB) -ltidy -lstdc++ -o $@ # some hello world targets, for testing and debugging js_hello_moz : js_hello_moz.cpp gcc $(JS_CXXFLAGS) js_hello_moz.cpp $(JSLIB) -lstdc++ -o js_hello_moz js_hello_v8 : js_hello_v8.cpp gcc js_hello_v8.cpp -lv8 -lstdc++ -o js_hello_v8 edbrowse-3.6.0.1/src/PaxHeaders.22102/main.c0000644000000000000000000000006112637565441015111 xustar0020 atime=1451158305 29 ctime=1451409178.15333719 edbrowse-3.6.0.1/src/main.c0000644000000000000000000010444312637565441015371 0ustar00rootroot00000000000000/* main.c * Entry point, arguments and options. * This file is part of the edbrowse project, released under GPL. */ #include "eb.h" #include #include /* Define the globals that are declared in eb.h. */ /* See eb.h for descriptive comments. */ const char *version = "3.6.0.1"; const char *progname; char *userAgents[10], *currentAgent, *currentReferrer; const char eol[] = "\r\n"; int jsPool = 32; int webTimeout = 20, mailTimeout = 0; char *sslCerts; int verifyCertificates = 1; bool ismc, isimap, passMail; bool inInput, listNA; volatile bool intFlag; int fileSize; int localAccount, maxAccount; struct MACCOUNT accounts[MAXACCOUNT]; int maxMime; struct MIMETYPE mimetypes[MAXMIME]; int maxproxy; struct PXENT proxyEntries[MAXPROXY]; static int maxTables; static struct DBTABLE dbtables[MAXDBT]; char *dbarea, *dblogin, *dbpw; /* to log into the database */ bool fetchBlobColumns; bool caseInsensitive, searchStringsAll; bool allowRedirection = true, allowJS = true, sendReferrer = true; bool binaryDetect = true, pluginsOn = true; bool inputReadLine; int context = 1; pst linePending; char *changeFileName, *mailDir; char *mailUnread, *mailStash, *mailReply; char *addressFile; char *recycleBin, *configFile, *sigFile, *sigFileEnd; char *cookieFile; struct ebSession sessionList[MAXSESSION], *cs; /* Edbrowse functions, defined in the config file */ #define MAXEBSCRIPT 500 #define MAXNEST 20 static char *ebScript[MAXEBSCRIPT + 1]; static char *ebScriptName[MAXEBSCRIPT + 1]; #define MAXNOJS 500 static const char *javaDis[MAXNOJS]; static int javaDisCount; #define MAXFILTER 500 struct FILTERDESC { const char *match; const char *redirect; char type; long expire; }; static struct FILTERDESC filters[MAXFILTER]; static int n_filters; static int subjstart = 0; static char *cfgcopy; static int cfglen; static long nowday; #ifdef _MSC_VER #endif // _MSC_VER static void setNowDay(void) { time_t now; time(&now); now /= (60 * 60 * 24); /* convert to days */ now -= 30 * 365; now -= 7; /* leap years */ nowday = now; } /* setNowDay */ /* This routine succeeds, or aborts via i_printfExit */ static void readConfigFile(void) { char *buf, *s, *t, *v, *q; int buflen, n; char c, ftype; bool cmt = false; bool startline = true; uchar mailblock = 0; bool mimeblock = false, tabblock = false; int nest, ln, j; int sn = 0; /* script number */ char stack[MAXNEST]; char last[24]; int lidx = 0; struct MACCOUNT *act; struct PXENT *px; struct MIMETYPE *mt; struct DBTABLE *td; /* Order is important here: mail{}, mime{}, table{}, then global keywords */ #define MAILWORDS 0 #define MIMEWORDS 8 #define TABLEWORDS 15 #define GLOBALWORDS 19 static const char *const keywords[] = { "inserver", "outserver", "login", "password", "from", "reply", "inport", "outport", "type", "desc", "suffix", "protocol", "program", "content", "outtype", "tname", "tshort", "cols", "keycol", "adbook", "downdir", "maildir", "agent", "jar", "nojs", "xyz@xyz", "webtimer", "mailtimer", "certfile", "datasource", "proxy", "linelength", "localizeweb", "jspool", "novs", 0 }; if (!fileTypeByName(configFile, false)) return; /* config file not present */ if (!fileIntoMemory(configFile, &buf, &buflen)) showErrorAbort(); /* An extra newline won't hurt. */ if (buflen && buf[buflen - 1] != '\n') buf[buflen++] = '\n'; /* Undos, uncomment, watch for nulls */ /* Encode mail{ as hex 81 m, and other encodings. */ ln = 1; for (s = t = v = buf; s < buf + buflen; ++s) { c = *s; if (c == '\0') i_printfExit(MSG_EBRC_Nulls, ln); if (c == '\r' && s[1] == '\n') continue; if (cmt) { if (c != '\n') continue; cmt = false; } if (c == '#' && startline) { cmt = true; goto putc; } if (c == '\n') { last[lidx] = 0; lidx = 0; if (stringEqual(last, "}")) { *v = '\x82'; t = v + 1; } if (stringEqual(last, "}else{")) { *v = '\x83'; t = v + 1; } if (stringEqual(last, "mail{")) { *v = '\x81'; v[1] = 'm'; t = v + 2; } if (stringEqual(last, "plugin{") || stringEqual(last, "mime{")) { *v = '\x81'; v[1] = 'e'; t = v + 2; } if (stringEqual(last, "table{")) { *v = '\x81'; v[1] = 'b'; t = v + 2; } if (stringEqual(last, "fromfilter{")) { *v = '\x81'; v[1] = 'r'; t = v + 2; } if (stringEqual(last, "tofilter{")) { *v = '\x81'; v[1] = 't'; t = v + 2; } if (stringEqual(last, "subjfilter{")) { *v = '\x81'; v[1] = 's'; t = v + 2; } if (stringEqual(last, "if(*){")) { *v = '\x81'; v[1] = 'I'; t = v + 2; } if (stringEqual(last, "if(?){")) { *v = '\x81'; v[1] = 'i'; t = v + 2; } if (stringEqual(last, "while(*){")) { *v = '\x81'; v[1] = 'W'; t = v + 2; } if (stringEqual(last, "while(?){")) { *v = '\x81'; v[1] = 'w'; t = v + 2; } if (stringEqual(last, "until(*){")) { *v = '\x81'; v[1] = 'U'; t = v + 2; } if (stringEqual(last, "until(?){")) { *v = '\x81'; v[1] = 'u'; t = v + 2; } if (!strncmp(last, "loop(", 5) && isdigitByte(last[5])) { q = last + 6; while (isdigitByte(*q)) ++q; if (stringEqual(q, "){")) { *q = 0; last[4] = 'l'; last[3] = '\x81'; strcpy(v, last + 3); t = v + strlen(v); } } if (!strncmp(last, "function", 8) && (last[8] == '+' || last[8] == ':')) { q = last + 9; if (*q == 0 || *q == '{' || *q == '(') i_printfExit(MSG_EBRC_NoFnName, ln); if (isdigitByte(*q)) i_printfExit(MSG_EBRC_FnDigit, ln); while (isalnumByte(*q)) ++q; if (q - last - 9 > 10) i_printfExit(MSG_EBRC_FnTooLong, ln); if (*q != '{' || q[1]) i_printfExit(MSG_EBRC_SyntaxErr, ln); last[7] = 'f'; last[6] = '\x81'; strcpy(v, last + 6); t = v + strlen(v); } *t++ = c; v = t; ++ln; startline = true; continue; } if (c == ' ' || c == '\t') { if (startline) continue; } else { if (lidx < sizeof(last) - 1) last[lidx++] = c; startline = false; } putc: *t++ = c; } *t = 0; /* now it's a string */ /* Go line by line */ ln = 1; nest = 0; stack[0] = ' '; for (s = buf; *s; s = t + 1, ++ln) { t = strchr(s, '\n'); if (t == s) continue; /* empty line */ if (t == s + 1 && *s == '#') continue; /* comment */ *t = 0; /* I'll put it back later */ /* Gather the filters in a mail filter block */ if (mailblock > 1 && !strchr("\x81\x82\x83", *s)) { v = strchr(s, '>'); if (!v) i_printfExit(MSG_EBRC_NoCondFile, ln); while (v > s && (v[-1] == ' ' || v[-1] == '\t')) --v; if (v == s) i_printfExit(MSG_EBRC_NoMatchStr, ln); c = *v, *v++ = 0; if (c != '>') { while (*v != '>') ++v; ++v; } while (*v == ' ' || *v == '\t') ++v; if (!*v) i_printfExit(MSG_EBRC_MatchNowh, ln, s); if (n_filters == MAXFILTER - 1) i_printfExit(MSG_EBRC_Filters, ln); filters[n_filters].match = s; filters[n_filters].redirect = v; filters[n_filters].type = mailblock; ++n_filters; continue; } v = strchr(s, '='); if (!v) goto nokeyword; while (v > s && (v[-1] == ' ' || v[-1] == '\t')) --v; if (v == s) goto nokeyword; c = *v, *v = 0; for (q = s; q < v; ++q) if (!isalphaByte(*q)) { *v = c; goto nokeyword; } n = stringInList(keywords, s); if (n < 0) { if (!nest) i_printfExit(MSG_EBRC_BadKeyword, s, ln); *v = c; /* put it back */ goto nokeyword; } if (nest) i_printfExit(MSG_EBRC_KeyInFunc, ln); if (n < MIMEWORDS && mailblock != 1) i_printfExit(MSG_EBRC_MailAttrOut, ln, s); if (n >= MIMEWORDS && n < TABLEWORDS && !mimeblock) i_printfExit(MSG_EBRC_MimeAttrOut, ln, s); if (n >= TABLEWORDS && n < GLOBALWORDS && !tabblock) i_printfExit(MSG_EBRC_TableAttrOut, ln, s); if (n >= MIMEWORDS && mailblock) i_printfExit(MSG_EBRC_MailAttrIn, ln, s); if ((n < MIMEWORDS || n >= TABLEWORDS) && mimeblock) i_printfExit(MSG_EBRC_MimeAttrIn, ln, s); if ((n < TABLEWORDS || n >= GLOBALWORDS) && tabblock) i_printfExit(MSG_EBRC_TableAttrIn, ln, s); /* act upon the keywords */ ++v; if (c != '=') { while (*v != '=') ++v; ++v; } while (*v == ' ' || *v == '\t') ++v; if (!*v) i_printfExit(MSG_EBRC_NoAttr, ln, s); switch (n) { case 0: /* inserver */ act->inurl = v; continue; case 1: /* outserver */ act->outurl = v; continue; case 2: /* login */ act->login = v; continue; case 3: /* password */ act->password = v; continue; case 4: /* from */ act->from = v; continue; case 5: /* reply */ act->reply = v; continue; case 6: /* inport */ if (*v == '*') act->inssl = 1, ++v; act->inport = atoi(v); continue; case 7: /* outport */ if (*v == '+') act->outssl = 4, ++v; if (*v == '^') act->outssl = 2, ++v; if (*v == '*') act->outssl = 1, ++v; act->outport = atoi(v); continue; case 8: /* type */ mt->type = v; continue; case 9: /* desc */ mt->desc = v; continue; case 10: /* suffix */ mt->suffix = v; continue; case 11: /* protocol */ mt->prot = v; mt->stream = true; continue; case 12: /* program */ mt->program = v; continue; case 13: /* content */ mt->content = v; continue; case 14: /* outtype */ c = tolower(*v); if (c != 'h' && c != 't') i_printfExit(MSG_EBRC_Outtype, ln); mt->outtype = c; continue; case 15: /* tname */ td->name = v; continue; case 16: /* tshort */ td->shortname = v; continue; case 17: /* cols */ while (*v) { if (td->ncols == MAXTCOLS) i_printfExit(MSG_EBRC_ManyCols, ln, MAXTCOLS); td->cols[td->ncols++] = v; q = strchr(v, ','); if (!q) break; *q = 0; v = q + 1; } continue; case 18: /* keycol */ if (!isdigitByte(*v)) i_printfExit(MSG_EBRC_KeyNotNb, ln); td->key1 = strtol(v, &v, 10); if (*v == ',' && isdigitByte(v[1])) td->key2 = strtol(v + 1, &v, 10); if (td->key1 > td->ncols || td->key2 > td->ncols) i_printfExit(MSG_EBRC_KeyOutRange, ln, td->ncols); continue; case 19: /* adbook */ addressFile = v; ftype = fileTypeByName(v, false); if (ftype && ftype != 'f') i_printfExit(MSG_EBRC_AbNotFile, v); continue; case 20: /* downdir */ downDir = v; if (fileTypeByName(v, false) != 'd') i_printfExit(MSG_EBRC_NotDir, v); continue; case 21: /* maildir */ mailDir = v; if (fileTypeByName(v, false) != 'd') i_printfExit(MSG_EBRC_NotDir, v); mailUnread = allocMem(strlen(v) + 20); sprintf(mailUnread, "%s/unread", v); /* We need the unread directory, else we can't fetch mail. */ /* Create it if it isn't there. */ if (fileTypeByName(mailUnread, false) != 'd') { if (mkdir(mailUnread, 0700)) i_printfExit(MSG_EBRC_NotDir, mailUnread); } mailReply = allocMem(strlen(v) + 20); sprintf(mailReply, "%s/.reply", v); continue; case 22: /* agent */ for (j = 0; j < 10; ++j) if (!userAgents[j]) break; if (j == 10) i_printfExit(MSG_EBRC_ManyAgents, ln); userAgents[j] = v; continue; case 23: /* jar */ cookieFile = v; ftype = fileTypeByName(v, false); if (ftype && ftype != 'f') i_printfExit(MSG_EBRC_JarNotFile, v); j = open(v, O_WRONLY | O_APPEND | O_CREAT, 0600); if (j < 0) i_printfExit(MSG_EBRC_JarNoWrite, v); close(j); continue; case 24: /* nojs */ if (javaDisCount == MAXNOJS) i_printfExit(MSG_EBRC_NoJS, MAXNOJS); if (*v == '.') ++v; q = strchr(v, '.'); if (!q || q[1] == 0) i_printfExit(MSG_EBRC_DomainDot, ln, v); javaDis[javaDisCount++] = v; continue; case 26: /* webtimer */ webTimeout = atoi(v); continue; case 27: /* mailtimer */ mailTimeout = atoi(v); continue; case 28: /* certfile */ sslCerts = v; ftype = fileTypeByName(v, false); if (ftype && ftype != 'f') i_printfExit(MSG_EBRC_SSLNoFile, v); j = open(v, O_RDONLY); if (j < 0) i_printfExit(MSG_EBRC_SSLNoRead, v); close(j); continue; case 29: /* datasource */ setDataSource(v); continue; case 30: /* proxy */ if (maxproxy == MAXPROXY) i_printfExit(MSG_EBRC_NoPROXY, MAXPROXY); px = proxyEntries + maxproxy; maxproxy++; spaceCrunch(v, true, true); q = strchr(v, ' '); if (q) { *q = 0; if (!stringEqual(v, "*")) px->prot = v; v = q + 1; q = strchr(v, ' '); if (q) { *q = 0; if (!stringEqual(v, "*")) px->domain = v; v = q + 1; } } if (!stringEqualCI(v, "direct")) px->proxy = v; continue; case 31: /* linelength */ displayLength = atoi(v); if (displayLength < 80) displayLength = 80; continue; case 32: /* localizeweb */ /* We should probably allow autodetection of language. */ /* E.G., the keyword auto indicates that you want autodetection. */ setHTTPLanguage(v); continue; case 33: /* jspool */ jsPool = atoi(v); if (jsPool < 2) jsPool = 2; if (jsPool > 1000) jsPool = 1000; continue; case 34: /* novs */ if (*v == '.') ++v; q = strchr(v, '.'); if (!q || q[1] == 0) i_printfExit(MSG_EBRC_DomainDot, ln, v); addNovsHost(v); continue; default: i_printfExit(MSG_EBRC_KeywordNYI, ln, s); } /* switch */ nokeyword: if (stringEqual(s, "default") && mailblock == 1) { if (localAccount == maxAccount + 1) continue; if (localAccount) i_printfExit(MSG_EBRC_SevDefaults); localAccount = maxAccount + 1; continue; } if (stringEqual(s, "nofetch") && mailblock == 1) { act->nofetch = true; continue; } if (stringEqual(s, "secure") && mailblock == 1) { act->secure = true; continue; } if (stringEqual(s, "imap") && mailblock == 1) { act->imap = act->nofetch = true; continue; } if (stringEqual(s, "download") && mimeblock == 1) { mt->download = true; continue; } if (stringEqual(s, "stream") && mimeblock == 1) { mt->stream = true; continue; } if (*s == '\x82' && s[1] == 0) { if (mailblock == 1) { ++maxAccount; mailblock = 0; if (!act->inurl) i_printfExit(MSG_EBRC_NoInserver, ln); if (!act->outurl) i_printfExit(MSG_EBRC_NoOutserver, ln); if (!act->login) i_printfExit(MSG_EBRC_NoLogin, ln); if (!act->password) i_printfExit(MSG_EBRC_NPasswd, ln); if (!act->from) i_printfExit(MSG_EBRC_NoFrom, ln); if (!act->reply) i_printfExit(MSG_EBRC_NoReply, ln); if (act->secure) act->inssl = act->outssl = 1; if (!act->inport) if (act->secure) act->inport = (act->imap ? 993 : 995); else act->inport = (act->imap ? 143 : 110); if (!act->outport) act->outport = (act->secure ? 465 : 25); continue; } if (mailblock) { mailblock = 0; continue; } if (mimeblock) { ++maxMime; mimeblock = false; if (!mt->type) i_printfExit(MSG_EBRC_NoType, ln); if (!mt->desc) i_printfExit(MSG_EBRC_NDesc, ln); if (!mt->suffix && !mt->prot) i_printfExit(MSG_EBRC_NoSuffix, ln); if (!mt->program) i_printfExit(MSG_EBRC_NoProgram, ln); continue; } if (tabblock) { ++maxTables; tabblock = false; if (!td->name) i_printfExit(MSG_EBRC_NoTblName, ln); if (!td->shortname) i_printfExit(MSG_EBRC_NoShortName, ln); if (!td->ncols) i_printfExit(MSG_EBRC_NColumns, ln); continue; } if (--nest < 0) i_printfExit(MSG_EBRC_UnexpBrace, ln); if (nest) goto putback; /* This ends the function */ *s = 0; /* null terminate the script */ ++sn; continue; } if (*s == '\x83' && s[1] == 0) { /* Does else make sense here? */ c = toupper(stack[nest]); if (c != 'I') i_printfExit(MSG_EBRC_UnexElse, ln); goto putback; } if (*s != '\x81') { if (!nest) i_printfExit(MSG_EBRC_GarblText, ln); goto putback; } /* Starting something */ c = s[1]; if ((nest || mailblock || mimeblock) && strchr("fmerts", c)) { const char *curblock = "another function"; if (mailblock) curblock = "a mail descriptor"; if (mailblock > 1) curblock = "a filter block"; if (mimeblock) curblock = "a plugin descriptor"; i_printfExit(MSG_EBRC_FnNotStart, ln, curblock); } if (!strchr("fmertsb", c) && !nest) i_printfExit(MSG_EBRC_StatNotInFn, ln); if (c == 'm') { mailblock = 1; if (maxAccount == MAXACCOUNT) i_printfExit(MSG_EBRC_ManyAcc, MAXACCOUNT); act = accounts + maxAccount; continue; } if (c == 'e') { mimeblock = true; if (maxMime == MAXMIME) i_printfExit(MSG_EBRC_ManyTypes, MAXMIME); mt = mimetypes + maxMime; continue; } if (c == 'b') { tabblock = true; if (maxTables == MAXDBT) i_printfExit(MSG_EBRC_ManyTables, MAXDBT); td = dbtables + maxTables; continue; } if (c == 'r') { mailblock = 2; continue; } if (c == 't') { mailblock = 3; continue; } if (c == 's') { mailblock = 4; continue; } if (c == 'f') { stack[++nest] = c; if (sn == MAXEBSCRIPT) i_printfExit(MSG_EBRC_ManyFn, sn); ebScriptName[sn] = s + 2; t[-1] = 0; ebScript[sn] = t; goto putback; } if (++nest >= sizeof(stack)) i_printfExit(MSG_EBRC_TooDeeply, ln); stack[nest] = c; putback: *t = '\n'; } /* loop over lines */ if (nest) i_printfExit(MSG_EBRC_FnNotClosed, ebScriptName[sn]); if (mailblock | mimeblock) i_printfExit(MSG_EBRC_MNotClosed); } /* readConfigFile */ /********************************************************************* Redirect the incoming mail into a file, based on the subject or the sender. Along with the filters in .ebrc, this routine dips into your addressbook, to see if the sender (by email) is one of your established aliases. If it is, we save it in a file of the same name. This is saved formatted, unless you put a minus sign at the start of the alias in your address book. This is the same convention as the from filters in .ebrc. If you don't want an alias to act as a redirect filter, put a ! at the beginning of the alias name. *********************************************************************/ const char *mailRedirect(const char *to, const char *from, const char *reply, const char *subj) { int rlen = strlen(reply); int slen = strlen(subj); int tlen = strlen(to); struct FILTERDESC *f; const char *r; for (f = filters; f->match; ++f) { const char *m = f->match; int mlen = strlen(m); int j, k; r = f->redirect; switch (f->type) { case 2: if (stringEqualCI(m, from)) return r; if (stringEqualCI(m, reply)) return r; if (*m == '@' && mlen < rlen && stringEqualCI(m, reply + rlen - mlen)) return r; break; case 3: if (stringEqualCI(m, to)) return r; if (*m == '@' && mlen < tlen && stringEqualCI(m, to + tlen - mlen)) return r; break; case 4: if (stringEqualCI(m, subj)) return r; /* a prefix match is ok */ if (slen < 16 || mlen < 16) break; /* too short */ j = k = 0; while (true) { char c = subj[j]; char d = m[k]; if (isupperByte(c)) c = tolower(c); if (isupperByte(d)) d = tolower(d); if (!c || !d) break; if (c != d) break; for (++j; c == subj[j]; ++j) ; for (++k; d == m[k]; ++k) ; } /* must match at least 2/3 of either string */ if (k > j) j = k; if (j >= 2 * mlen / 3 || j >= 2 * slen / 3) { return r; } break; } /* switch */ } /* loop */ r = reverseAlias(reply); return r; } /* mailRedirect */ /********************************************************************* Are we ok to parse and execute javascript? *********************************************************************/ bool javaOK(const char *url) { int j, hl, dl; const char *h, *d, *q, *path; if (!allowJS) return false; if (!url) return true; if (isDataURI(url)) return true; h = getHostURL(url); if (!h) return true; hl = strlen(h); path = getDataURL(url); for (j = 0; j < javaDisCount; ++j) { d = javaDis[j]; q = strchr(d, '/'); if (!q) q = d + strlen(d); dl = q - d; if (dl > hl) continue; if (!memEqualCI(d, h + hl - dl, dl)) continue; if (*q == '/') { ++q; if (hl != dl) continue; if (!path) continue; if (strncmp(q, path, strlen(q))) continue; return false; } /* domain/path was specified */ if (hl == dl) return false; if (h[hl - dl - 1] == '.') return false; } return true; } /* javaOK */ /* Catch interrupt and react appropriately. */ static void catchSig(int n) { intFlag = true; if (inInput) i_puts(MSG_EnterInterrupt); /* If we were reading from a file, or socket, this signal should * cause the read to fail. Check for intFlag, so we know it was * interrupted, and not an io failure. * Then clean up appropriately. */ signal(SIGINT, catchSig); } /* catchSig */ void ebClose(int n) { bg_jobs(true); dbClose(); js_shutdown(); /* We should call curl_global_cleanup() here, for clarity and completeness, * but it can cause a seg fault when combined with older versions of open ssl, * and the process is going to exit anyways, so don't worry about it. */ exit(n); } /* ebClose */ bool isSQL(const char *s) { char c; const char *c1 = 0, *c2 = 0; c = *s; if (!sqlPresent) goto no; if (isURL(s)) goto no; if (!isalphaByte(c)) goto no; for (++s; c = *s; ++s) { if (c == '_') continue; if (isalnumByte(c)) continue; if (c == ':') { if (c1) goto no; c1 = s; continue; } if (c == ']') { c2 = s; goto yes; } } no: return false; yes: return true; } /* isSQL */ void setDataSource(char *v) { dbarea = dblogin = dbpw = 0; if (!v) return; if (!*v) return; dbarea = v; v = strchr(v, ','); if (!v) return; *v++ = 0; dblogin = v; v = strchr(v, ','); if (!v) return; *v++ = 0; dbpw = v; } /* setDataSource */ static void eb_curl_global_init(void) { const unsigned int major = 7; const unsigned int minor = 29; const unsigned int patch = 0; const unsigned int least_acceptable_version = (major << 16) | (minor << 8) | patch; curl_version_info_data *version_data = NULL; CURLcode curl_init_status = curl_global_init(CURL_GLOBAL_ALL); if (curl_init_status != 0) i_printfExit(MSG_LibcurlNoInit); version_data = curl_version_info(CURLVERSION_NOW); if (version_data->version_num < least_acceptable_version) i_printfExit(MSG_CurlVersion, major, minor, patch); } /* eb_curl_global_init */ /*\ MSVC Debug: May need to provide path to 3rdParty DLLs, like * set PATH=F:\Projects\software\bin;%PATH% ... \*/ /* I'm not going to expand wild card arguments here. * I don't need to on Unix, and on Windows there is a * setargv.obj, or something like that, that performs the expansion. * I'll assume you have folded that object into libc.lib. * So now you can edit *.c, on any operating system, * and it will do the right thing, with no work on my part. */ int main(int argc, char **argv) { int cx, account; bool rc, doConfig = true; bool dofetch = false, domail = false; #ifndef _MSC_VER // port setlinebuf(stdout);, if required... /* In case this is being piped over to a synthesizer, or whatever. */ if (fileTypeByHandle(fileno(stdout)) != 'f') setlinebuf(stdout); #endif // !_MSC_VER selectLanguage(); eb_curl_global_init(); ttySaveSettings(); initializeReadline(); progname = argv[0]; /* Let's everybody use my malloc and free routines */ pcre_malloc = allocMem; pcre_free = nzFree; /* Establish the home directory, and standard edbrowse files thereunder. */ home = getenv("HOME"); #ifdef _MSC_VER if (!home) { home = getenv("APPDATA"); if (home) { char *ebdata = (char *)allocMem(ABSPATH); sprintf(ebdata, "%s\\edbrowse", home); if (fileTypeByName(ebdata, false) != 'd') { FILE *fp; char *cfgfil; if (mkdir(ebdata, 0700)) { i_printfExit(MSG_NotHome); // TODO: more appropriate exit message... } cfgfil = (char *)allocMem(ABSPATH); sprintf(cfgfil, "%s\\.ebrc", ebdata); fp = fopen(cfgfil, "w"); if (fp) { fwrite(ebrc_string, 1, strlen(ebrc_string), fp); fclose(fp); } i_printfExit(MSG_Personalize, cfgfil); } home = ebdata; } } #endif // !_MSC_VER /* Empty is the same as missing. */ if (home && !*home) home = 0; /* I require this, though I'm not sure what this means for non-Unix OS's */ if (!home) i_printfExit(MSG_NotHome); if (fileTypeByName(home, false) != 'd') i_printfExit(MSG_NotDir, home); configFile = allocMem(strlen(home) + 7); sprintf(configFile, "%s/.ebrc", home); /* if not present then create it, as was done above */ if (fileTypeByName(configFile, false) == 0) { int fh = creat(configFile, 0600); if (fh >= 0) { write(fh, ebrc_string, strlen(ebrc_string)); close(fh); i_printfExit(MSG_Personalize, configFile); } } recycleBin = allocMem(strlen(home) + 8); sprintf(recycleBin, "%s/.Trash", home); if (fileTypeByName(recycleBin, false) != 'd') { if (mkdir(recycleBin, 0700)) { /* Don't want to abort here; we might be on a readonly filesystem. * Don't have a Trash directory and can't creat one; yet we should move on. */ free(recycleBin); recycleBin = 0; } } if (recycleBin) { mailStash = allocMem(strlen(recycleBin) + 12); sprintf(mailStash, "%s/rawmail", recycleBin); if (fileTypeByName(mailStash, false) != 'd') { if (mkdir(mailStash, 0700)) { free(mailStash); mailStash = 0; } } } sigFile = allocMem(strlen(home) + 20); sprintf(sigFile, "%s/.signature", home); sigFileEnd = sigFile + strlen(sigFile); { static char agent0[32] = "edbrowse/"; strcat(agent0, version); userAgents[0] = currentAgent = agent0; } setNowDay(); ++argv, --argc; if (argc && stringEqual(argv[0], "-c")) { if (argc == 1) *argv = configFile; else ++argv, --argc; doConfig = false; } else { readConfigFile(); if (maxAccount && !localAccount) localAccount = 1; } account = localAccount; for (; argc && argv[0][0] == '-'; ++argv, --argc) { char *s = *argv; ++s; if (stringEqual(s, "v")) { puts(version); exit(0); } if (stringEqual(s, "d")) { debugLevel = 4; continue; } if (*s == 'd' && isdigitByte(s[1]) && !s[2]) { debugLevel = s[1] - '0'; continue; } if (stringEqual(s, "e")) { errorExit = true; continue; } if (*s == 'p') ++s, passMail = true; if (*s == 'm' || *s == 'f') { if (!maxAccount) i_printfExit(MSG_NoMailAcc); if (*s == 'f') { account = 0; dofetch = true; ++s; if (*s == 'm') domail = true, ++s; } else { domail = true; ++s; } if (isdigitByte(*s)) { account = strtol(s, &s, 10); if (account == 0 || account > maxAccount) i_printfExit(MSG_BadAccNb, maxAccount); } if (!*s) { ismc = true; /* running as a mail client */ allowJS = false; /* no javascript in mail client */ ++argv, --argc; if (!argc || !dofetch) break; } } i_printfExit(MSG_Usage); } /* options */ if (!sslCerts) { verifyCertificates = 0; if (doConfig) if (debugLevel >= 1) i_puts(MSG_NoCertFile); } srand(time(0)); if (ismc) { char **reclist, **atlist; char *s, *body; int nat, nalt, nrec; if (!argc) { /* This is fetch / read mode */ if (dofetch) { int nfetch = 0; if (account) { isimap = accounts[account - 1].imap; if (isimap) domail = false; nfetch = fetchMail(account); } else { nfetch = fetchAllMail(); } if (!domail) { if (nfetch) i_printf(MSG_MessagesX, nfetch); else i_puts(MSG_NoMail); } } if (domail) { scanMail(); } exit(0); } /* now in sendmail mode */ if (argc == 1) i_printfExit(MSG_MinOneRec); /* I don't know that argv[argc] is 0, or that I can set it to 0, * so I back everything up by 1. */ reclist = argv - 1; for (nat = nalt = 0; nat < argc; ++nat) { s = argv[argc - 1 - nat]; if (*s != '+' && *s != '-') break; if (*s == '-') ++nalt; strmove(s, s + 1); } atlist = argv + argc - nat - 1; if (atlist <= argv) i_printfExit(MSG_MinOneRecBefAtt); body = *atlist; if (nat) memmove(atlist, atlist + 1, sizeof(char *) * nat); atlist[nat] = 0; nrec = atlist - argv; memmove(reclist, reclist + 1, sizeof(char *) * nrec); atlist[-1] = 0; if (sendMail(account, (const char **)reclist, body, 1, (const char **)atlist, 0, nalt, true)) exit(0); showError(); exit(1); } cookiesFromJar(); http_curl_init(); signal(SIGINT, catchSig); #ifndef _MSC_VER // port siginterrupt(SIGINT, 1); signal(SIGPIPE, SIG_IGN);, if required siginterrupt(SIGINT, 1); signal(SIGPIPE, SIG_IGN); #endif // !_MSC_VER cx = 0; while (argc) { char *file = *argv; ++cx; if (cx == MAXSESSION) i_printfExit(MSG_ManyOpen, MAXSESSION); cxSwitch(cx, false); if (cx == 1) runEbFunction("init"); changeFileName = 0; cw->fileName = cloneString(file); cw->firstURL = cloneString(file); if (isSQL(file)) cw->sqlMode = true; rc = readFileArgv(file); if (fileSize >= 0) debugPrint(1, "%d", fileSize); fileSize = -1; if (!rc) { showError(); } else if (changeFileName) { nzFree(cw->fileName); cw->fileName = changeFileName; changeFileName = 0; } cw->undoable = cw->changeMode = false; /* Browse the text if it's a url */ if (rc && isURL(cw->fileName) && (cw->mt && cw->mt->outtype || isBrowseableURL(cw->fileName))) { if (runCommand("b")) debugPrint(1, "%d", fileSize); else showError(); } ++argv, --argc; } /* loop over files */ if (!cx) { /* no files */ ++cx; cxSwitch(cx, false); runEbFunction("init"); i_puts(MSG_Ready); } if (cx > 1) cxSwitch(1, false); while (true) { pst p = inputLine(); pst save_p = clonePstring(p); if (perl2c((char *)p)) { i_puts(MSG_EnterNull); nzFree(save_p); } else { edbrowseCommand((char *)p, false); nzFree(linePending); linePending = save_p; } } /* infinite loop */ } /* main */ /* Find the balancing brace in an edbrowse function */ static const char *balance(const char *ip, int direction) { int nest = 0; uchar code; while (true) { if (direction > 0) { ip = strchr(ip, '\n') + 1; } else { for (ip -= 2; *ip != '\n'; --ip) ; ++ip; } code = *ip; if (code == 0x83) { if (nest) continue; break; } if (code == 0x81) nest += direction; if (code == 0x82) nest -= direction; if (nest < 0) break; } return ip; } /* balance */ /* Run an edbrowse function, as defined in the config file. */ bool runEbFunction(const char *line) { char *linecopy = cloneString(line); const char *args[10]; int argl[10]; /* lengths of args */ int argtl; /* total length */ const char *s; char *t, *new; int j, l, nest; const char *ip; /* think instruction pointer */ const char *endl; /* end of line to be processed */ bool nofail, ok; uchar code; char stack[MAXNEST]; int loopcnt[MAXNEST]; /* Separate function name and arguments */ spaceCrunch(linecopy, true, false); if (linecopy[0] == 0) { setError(MSG_NoFunction); goto fail; } memset(args, 0, sizeof(args)); memset(argl, 0, sizeof(argl)); argtl = 0; t = strchr(linecopy, ' '); if (t) *t = 0; for (s = linecopy; *s; ++s) if (!isalnumByte(*s)) { setError(MSG_BadFunctionName); goto fail; } for (j = 0; ebScript[j]; ++j) if (stringEqualCI(linecopy, ebScriptName[j] + 1)) break; if (!ebScript[j]) { setError(MSG_NoSuchFunction, linecopy); goto fail; } /* skip past the leading \n */ ip = ebScript[j] + 1; nofail = (ebScriptName[j][0] == '+'); nest = 0; ok = true; /* collect arguments */ j = 0; for (s = t; s; s = t) { if (++j >= 10) { setError(MSG_ManyArgs); goto fail; } args[j] = ++s; t = strchr(s, ' '); if (t) *t = 0; if (argtl) ++argtl; argtl += (argl[j] = strlen(s)); } argl[0] = argtl; while (code = *ip) { if (intFlag) { setError(MSG_Interrupted); goto fail; } endl = strchr(ip, '\n'); if (code == 0x83) { ip = balance(ip, 1) + 2; --nest; continue; } if (code == 0x82) { char control = stack[nest]; char ucontrol = toupper(control); const char *start = balance(ip, -1); start = strchr(start, '\n') + 1; if (ucontrol == 'L') { /* loop */ if (--loopcnt[nest]) ip = start; else ip = endl + 1, --nest; continue; } if (ucontrol == 'W' || ucontrol == 'U') { bool jump = ok; if (islowerByte(control)) jump ^= true; if (ucontrol == 'U') jump ^= true; ok = true; if (jump) ip = start; else ip = endl + 1, --nest; continue; } /* Apparently it's the close of an if or an else, just fall through */ goto nextline; } if (code == 0x81) { const char *skip = balance(ip, 1); bool jump; char control = ip[1]; char ucontrol = toupper(control); stack[++nest] = control; if (ucontrol == 'L') { loopcnt[nest] = j = atoi(ip + 2); if (j) goto nextline; ahead: if (*skip == (char)0x82) --nest; ip = skip + 2; continue; } if (ucontrol == 'U') goto nextline; /* if or while, test on ok */ jump = ok; if (isupperByte(control)) jump ^= true; ok = true; if (jump) goto ahead; goto nextline; } if (!ok && nofail) goto fail; /* compute length of line, then build the line */ l = endl - ip; for (s = ip; s < endl; ++s) if (*s == '~' && isdigitByte(s[1])) l += argl[s[1] - '0']; t = new = allocMem(l + 1); for (s = ip; s < endl; ++s) { if (*s == '~' && isdigitByte(s[1])) { j = *++s - '0'; if (j) { if (!args[j]) { setError(MSG_NoArgument, j); nzFree(new); goto fail; } strcpy(t, args[j]); t += argl[j]; continue; } /* ~0 is all args together */ for (j = 1; j <= 9 && args[j]; ++j) { if (j > 1) *t++ = ' '; strcpy(t, args[j]); t += argl[j]; } continue; } *t++ = *s; } *t = 0; /* Here we go! */ debugPrint(3, "< %s", new); ok = edbrowseCommand(new, true); free(new); nextline: ip = endl + 1; } if (!ok && nofail) goto fail; nzFree(linecopy); return true; fail: nzFree(linecopy); return false; } /* runEbFunction */ struct DBTABLE *findTableDescriptor(const char *sn) { int i; struct DBTABLE *td = dbtables; for (i = 0; i < maxTables; ++i, ++td) if (stringEqual(td->shortname, sn)) return td; return 0; } /* findTableDescriptor */ struct DBTABLE *newTableDescriptor(const char *name) { struct DBTABLE *td; if (maxTables == MAXDBT) { setError(MSG_ManyTables, MAXDBT); return 0; } td = dbtables + maxTables++; td->name = td->shortname = cloneString(name); td->ncols = 0; /* it's already 0 */ return td; } /* newTableDescriptor */ edbrowse-3.6.0.1/src/PaxHeaders.22102/jsrt0000644000000000000000000000006112637565441014726 xustar0020 atime=1451158305 29 ctime=1451409178.15333719 edbrowse-3.6.0.1/src/jsrt0000644000000000000000000005106212637565441015204 0ustar00rootroot00000000000000 Edbrowse js regression test ftp test y directory yecht package

State and zip:
Colors will change with the state, A through M or N through Z.
My favorite colors are:
Salary range: poverty gettin-by comfortable rich
Pets: dog cat bird rabbit
Amount of objects to create

Message Body:
Surface area is 4πr2.
edbrowse-3.6.0.1/src/PaxHeaders.22102/jseng-moz.cpp0000644000000000000000000000006112637565441016436 xustar0020 atime=1451158305 29 ctime=1451409178.15333719 edbrowse-3.6.0.1/src/jseng-moz.cpp0000644000000000000000000020223412637565441016713 0ustar00rootroot00000000000000/********************************************************************* This is the back-end process for javascript. This is the server, and edbrowse is the client. We receive interprocess messages from edbrowse, getting and setting properties for various DOM objects. This is the mozilla version. If you package this with the mozilla js libraries, you will need to include the MPL, mozilla public license, along with the GPL, general public license. The interface between this process and edbrowse is defined in ebjs.h. There should be no other local header files common to both. Exit codes are as follows: 0 terminate normally, as directed by edbrowse 1. bad arguments 2 cannot read or write to edbrowse 3 messages are out of sync 4 cannot create javascript runtime environmet 5 cannot read from stdin or write to stdout 6 unexpected message command from edbrowse 7 unexpected property type from edbrowse 8 unexpected class name from edbrowse 9 only arrays of objects are supported at this time 90 this program was never executed 99 memory allocation error or heap corruption *********************************************************************/ #include "eb.h" #include #include #include /* work around a bug where the standard UINT32_MAX isn't defined, I really hope this is correct */ #ifndef UINT32_MAX #define UINT32_MAX std::numeric_limits::max() #endif /* now we can include our jsapi */ #include #include using namespace std; /* This function closes edbrowse down, e.g. after malloc failure, * it is just a stub for exit here. */ void ebClose(int n) { exit(n); } /* ebclose */ /* stub, this function called by freeTags(), but never called in this context */ void freeEmptySideBuffer(int n) { } /* meta tags don't have side effects from within the js process. */ void htmlMetaHelper(struct htmlTag *t) { } /* textarea does not generate a side buffer here */ int sideBuffer(int cx, const char *text, int textlen, const char *bufname) { return 0; } /* ebrc strings don't mean anything here */ const char *ebrc_en = emptyString; const char *ebrc_fr = emptyString; const char *ebrc_pt_br = emptyString; const char *ebrc_de = emptyString; static void usage(void) { fprintf(stderr, "Usage: edbrowse-js pipe_in pipe_out jsHeapSize\n"); exit(1); } /* usage */ /* arguments, as indicated by the above */ static int pipe_in, pipe_out, enginePool; static void js_start(void); static void readMessage(void); static void processMessage(void); static void createContext(void); static void writeHeader(void); static JSContext *jcx; static JSObject *winobj; /* window object */ static JSObject *docobj; /* document object */ static void cwSetup(void) { in_js_cw.winobj = winobj; in_js_cw.docobj = docobj; in_js_cw.hbase = get_property_string(docobj, "base$href"); in_js_cw.baseset = true; } /* cwSetup */ static void cwBringdown(void) { freeTags(cw); nzFree(cw->ft); /* title could have been set by prerender */ cw->ft = 0; nzFree(cw->hbase); cw->hbase = 0; } /* cwBringdown */ static struct EJ_MSG head; static char *errorMessage; static char *effects; static int eff_l; #define effectString(s) stringAndString(&effects, &eff_l, (s)) #define effectChar(s) stringAndChar(&effects, &eff_l, (s)) #define endeffect() effectString("`~@}\n"); /* pack the decoration of a tree into the effects string */ static void packDecoration(void) { struct htmlTag *t; int j; if (!cw->tags) /* should never happen */ return; for (j = 0; j < cw->numTags; ++j) { char line[60]; t = tagList[j]; if (!t->jv) continue; sprintf(line, ",%d=%p", j, t->jv); effectString(line); } } /* packDecoration */ static char *membername; static char *propval; static enum ej_proptype proptype; static char *runscript; int main(int argc, char **argv) { /* do this first, in case usage some day is tailored to language */ selectLanguage(); if (argc != 4) usage(); pipe_in = stringIsNum(argv[1]); pipe_out = stringIsNum(argv[2]); enginePool = stringIsNum(argv[3]); if (pipe_in < 0 || pipe_out < 0 || enginePool < 0) usage(); if (enginePool < 2) enginePool = 2; js_start(); /* edbrowse catches interrupt, this process ignores it. */ /* Use quit to terminate, or kill from another console. */ signal(SIGINT, SIG_IGN); effects = initString(&eff_l); while (true) { readMessage(); head.highstat = EJ_HIGH_OK; head.lowstat = EJ_LOW_OK; head.side = head.msglen = 0; if (head.cmd == EJ_CMD_EXIT) exit(0); if (head.cmd == EJ_CMD_CREATE) { /* this one is special */ createContext(); if (!head.highstat) { head.jcx = jcx; head.winobj = winobj; head.docobj = docobj; } head.n = head.proplength = 0; writeHeader(); continue; } jcx = (JSContext *) head.jcx; winobj = (JSObject *) head.winobj; docobj = (JSObject *) head.docobj; if (head.cmd == EJ_CMD_DESTROY) { /* don't enter the compartment of a context you want to destroy */ JS_DestroyContext(jcx); head.n = head.proplength = 0; writeHeader(); continue; } /* this function will enter the compartment */ processMessage(); } } /* main */ /* read from and write to edbrowse */ static void readFromEb(void *data_p, int n) { int rc; if (n == 0) return; rc = read(pipe_in, data_p, n); if (rc == n) return; /* Oops - can't read from the process any more */ exit(2); } /* readFromEb */ static void writeToEb(const void *data_p, int n) { int rc; if (n == 0) return; rc = write(pipe_out, data_p, n); if (rc == n) return; /* Oops - can't write to the process any more */ fprintf(stderr, "js cannot communicate with edbrowse\n"); exit(2); } /* writeToEb */ static void writeHeader(void) { head.magic = EJ_MAGIC; head.side = eff_l; head.msglen = 0; if (errorMessage) head.msglen = strlen(errorMessage); writeToEb(&head, sizeof(head)); /* send out the error message and side effects, if present. */ /* Edbrowse will expect these before any returned values. */ if (head.side) { writeToEb(effects, head.side); nzFree(effects); effects = initString(&eff_l); } if (head.msglen) { writeToEb(errorMessage, head.msglen); nzFree(errorMessage); errorMessage = 0; } /* That's the header, you may still need to send a returned value */ } /* writeHeader */ static char *readString(int n) { char *s; if (!n) return 0; s = allocString(n + 1); readFromEb(s, n); s[n] = 0; return s; } /* readString */ /* Read the entire message, so we can process it and move on, * without any sync errors. This means we must read the property or run script * or anything else passed along. */ static void readMessage(void) { enum ej_cmd cmd; enum ej_proptype pt; readFromEb(&head, sizeof(head)); if (head.magic != EJ_MAGIC) { fprintf(stderr, "Messages between js and edbrowse are out of sync\n"); exit(3); } cmd = head.cmd; pt = head.proptype; if (cmd == EJ_CMD_SCRIPT) { if (head.proplength) runscript = readString(head.proplength); } if (cmd == EJ_CMD_HASPROP || cmd == EJ_CMD_GETPROP || cmd == EJ_CMD_CALL || cmd == EJ_CMD_SETPROP || cmd == EJ_CMD_DELPROP) { if (head.n) membername = readString(head.n); } /* property in function call is | separated list of object args */ if (cmd == EJ_CMD_SETPROP || cmd == EJ_CMD_SETAREL || cmd == EJ_CMD_CALL) { proptype = head.proptype; if (head.proplength) propval = readString(head.proplength); } /* and that's the whole message */ } /* readMessage */ static JSRuntime *jrt; /* our js runtime environment */ static const size_t gStackChunkSize = 8192; static void js_start(void) { jrt = JS_NewRuntime(enginePool * 1024L * 1024L, JS_NO_HELPER_THREADS); if (jrt) return; /* ok */ fprintf(stderr, "Cannot create javascript runtime environment\n"); /* send a message to edbrowse, so it can disable javascript, * so we don't get this same error on every browse. */ head.highstat = EJ_HIGH_PROC_FAIL; head.lowstat = EJ_LOW_RUNTIME; writeHeader(); exit(4); } /* js_start */ static void misconfigure(void) { /* there may already be a larger error */ if (head.highstat > EJ_HIGH_CX_FAIL) return; head.highstat = EJ_HIGH_CX_FAIL; head.lowstat = EJ_LOW_VARS; } /* misconfigure */ static void my_ErrorReporter(JSContext * cx, const char *message, JSErrorReport * report) { if (report && report->errorNumber == JSMSG_OUT_OF_MEMORY || message && strstr(message, "out of memory")) { head.highstat = EJ_HIGH_HEAP_FAIL; head.lowstat = EJ_LOW_MEMORY; } else if (errorMessage == 0 && head.highstat == EJ_HIGH_OK && message && *message) { if (report) head.lineno = report->lineno; errorMessage = cloneString(message); head.highstat = EJ_HIGH_STMT_FAIL; head.lowstat = EJ_LOW_SYNTAX; } if (report) report->flags = 0; } /* my_ErrorReporter */ /* see if a rooted js object is window or document. * Note what we have to do to get back to the actual object pointer. */ static bool iswindoc(JS::HandleObject obj) { JSObject *p = *obj.address(); return (p == winobj || p == docobj); } /* iswindoc */ /* is a rooted object window.location or document.location? */ static bool iswindocloc(JS::HandleObject obj) { JS::RootedObject w(jcx), p(jcx); js::RootedValue v(jcx); /* try window.location first */ w = winobj; if (JS_GetProperty(jcx, w, "location", v.address()) == JS_TRUE && v.isObject()) { p = JSVAL_TO_OBJECT(v); if (p == obj) return true; } w = docobj; if (JS_GetProperty(jcx, w, "location", v.address()) == JS_TRUE && v.isObject()) { p = JSVAL_TO_OBJECT(v); if (p == obj) return true; } return false; } /* iswindocloc */ /********************************************************************* Convert a JS string to a C string. This function is named JS_c_str to remind you of the C++ equivalent. However, this function allocates the result; you must free it. The converse is handled by JS_NewStringcopyZ, as provided by the library. *********************************************************************/ static char *JS_c_str(js::HandleString str) { size_t encodedLength = JS_GetStringEncodingLength(jcx, str); char *buffer = allocString(encodedLength + 1); buffer[encodedLength] = '\0'; size_t result = JS_EncodeStringToBuffer(jcx, str, buffer, encodedLength); if (result == (size_t) - 1) { misconfigure(); buffer[0] = 0; } return buffer; } /* JS_c_str */ /* represent an object pointer in ascii */ static const char *pointer2string(const JSObject * obj) { static char pbuf[32]; sprintf(pbuf, "%p", obj); return pbuf; } /* pointer2string */ static JSObject *string2pointer(const char *s) { JSObject *p; sscanf(s, "%p", &p); return p; } /* string2pointer */ /* like the function in ebjs.c, but a different name */ static const char *fakePropName(void) { static char fakebuf[24]; static int idx = 0; ++idx; sprintf(fakebuf, "cg$$%d", idx); return fakebuf; } /*fakePropName */ /********************************************************************* This returns the string equivalent of the js value, but use with care. It's only good til the next call to stringize, then it will be trashed. If you want the result longer than that, you better copy it. *********************************************************************/ static const char *stringize(js::HandleValue v) { static char buf[32]; static const char *dynamic; int n; double d; if (JSVAL_IS_STRING(v)) { if (dynamic) cnzFree(dynamic); js::RootedString str(jcx, JSVAL_TO_STRING(v)); dynamic = JS_c_str(str); return dynamic; } if (JSVAL_IS_INT(v)) { n = JSVAL_TO_INT(v); sprintf(buf, "%d", n); return buf; } if (JSVAL_IS_BOOLEAN(v)) { n = JSVAL_TO_BOOLEAN(v); sprintf(buf, "%d", n); return buf; } if (JSVAL_IS_DOUBLE(v)) { d = JSVAL_TO_DOUBLE(v); n = d; if (n == d) sprintf(buf, "%d", n); else sprintf(buf, "%lf", d); return buf; } /* don't know what it is */ return 0; } /* stringize */ #define PROP_STD (JSPROP_ENUMERATE) #define PROP_READONLY (JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY) /* The generic class and constructor */ #define generic_class(c, name) \ static JSClass c##_class = { \ #name, JSCLASS_HAS_PRIVATE, \ JS_PropertyStub, JS_DeletePropertyStub, \ JS_PropertyStub, JS_StrictPropertyStub, \ JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, \ NULL, JSCLASS_NO_OPTIONAL_MEMBERS \ }; #define generic_ctor(c) \ static JSBool c##_ctor(JSContext * cx, unsigned int argc, jsval * vp) \ { \ JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \ JSObject & callee = args.callee(); \ jsval callee_val = JS::ObjectValue(callee); \ JS::RootedObject newobj(cx, \ JS_NewObjectForConstructor(cx, &c##_class, &callee_val)); \ if (newobj == NULL) { \ misconfigure(); \ return JS_FALSE; \ } \ args.rval().set(OBJECT_TO_JSVAL(newobj)); \ return JS_TRUE; \ } #define generic_class_ctor(c, name) \ generic_class(c, name) \ generic_ctor(c) /* window class is diffferent, because the flags must be global */ static JSClass window_class = { "Window", JSCLASS_HAS_PRIVATE | JSCLASS_GLOBAL_FLAGS, JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, NULL, JSCLASS_NO_OPTIONAL_MEMBERS }; /* the window constructor can open a new window, a new edbrowse session. */ /* This is done by setting n{url} in the effects string */ static JSBool window_ctor(JSContext * cx, unsigned int argc, jsval * vp) { const char *newloc = 0; const char *winname = 0; JS::RootedString str(cx); js::RootedValue v(cx); JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JSObject & callee = args.callee(); jsval callee_val = JS::ObjectValue(callee); JS::RootedObject newwin(cx, JS_NewObjectForConstructor(cx, &window_class, &callee_val)); if (newwin == NULL) { misconfigure(); return JS_FALSE; } if (args.length() > 0 && (str = JS_ValueToString(cx, args[0]))) newloc = JS_c_str(str); if (args.length() > 1 && (str = JS_ValueToString(cx, args[1]))) winname = JS_c_str(str); /* third argument is attributes, like window size and location, * that we don't care about. * I only do something if opening a new web page. * If it's just a blank window, I don't know what to do with that. */ if (newloc && *newloc) { effectString("n{p"); // } effectString(newloc); effectChar('\n'); if (winname) effectString(winname); endeffect(); } cnzFree(newloc); cnzFree(winname); v = OBJECT_TO_JSVAL(winobj); JS_DefineProperty(cx, newwin, "opener", v, NULL, NULL, PROP_READONLY); args.rval().set(OBJECT_TO_JSVAL(newwin)); return JS_TRUE; } /* window_ctor */ /* All the other dom classes and constructors. * If a constructor is not in this list, it is coming later, * because it does something special. */ generic_class_ctor(document, Document) generic_class_ctor(head, Head) generic_class_ctor(meta, Meta) generic_class_ctor(link, Link) generic_class_ctor(body, Body) generic_class_ctor(base, Base) generic_class_ctor(form, Form) generic_class_ctor(element, Element) generic_class_ctor(image, Image) generic_class_ctor(frame, Frame) generic_class_ctor(anchor, Anchor) generic_class_ctor(lister, Lister) generic_class_ctor(listitem, Listitem) generic_class_ctor(tbody, Tbody) generic_class_ctor(table, Table) generic_class_ctor(div, Div) generic_class_ctor(area, Area) generic_class_ctor(span, Span) generic_class_ctor(trow, Trow) generic_class_ctor(cell, Cell) generic_class_ctor(para, P) generic_class(option, Option) /* constructor below */ generic_class_ctor(script, Script) generic_class(url, URL) /* constructor below */ generic_class(timer, Timer) /* instantiated through window.setTimout() */ generic_class(textnode, TextNode) /* constructor below */ /* Here are a couple nonstandard constructors. */ /* text and value can be passed as args to the option constructor */ static JSBool option_ctor(JSContext * cx, unsigned int argc, jsval * vp) { JS::RootedString str(cx); js::RootedValue v(cx); JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JSObject & callee = args.callee(); jsval callee_val = JS::ObjectValue(callee); JS::RootedObject newopt(cx, JS_NewObjectForConstructor(cx, &option_class, &callee_val)); if (newopt == NULL) { misconfigure(); return JS_FALSE; } if (args.length() > 0 && (str = JS_ValueToString(cx, args[0]))) { v = STRING_TO_JSVAL(str); JS_DefineProperty(cx, newopt, "text", v, NULL, NULL, PROP_STD); } if (args.length() > 1 && (str = JS_ValueToString(cx, args[1]))) { v = STRING_TO_JSVAL(str); JS_DefineProperty(cx, newopt, "value", v, NULL, NULL, PROP_STD); } str = JS_NewStringCopyZ(cx, "option"); v = STRING_TO_JSVAL(str); JS_DefineProperty(cx, newopt, "nodeName", v, NULL, NULL, PROP_STD); v = JSVAL_FALSE; JS_DefineProperty(cx, newopt, "selected", v, NULL, NULL, PROP_STD); JS_DefineProperty(cx, newopt, "defaultSelected", v, NULL, NULL, PROP_STD); args.rval().set(OBJECT_TO_JSVAL(newopt)); return JS_TRUE; } /* option_ctor */ /* text can be passed to the constructor */ static JSBool textnode_ctor(JSContext * cx, unsigned int argc, jsval * vp) { JS::RootedString str(cx); js::RootedValue v(cx); JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JSObject & callee = args.callee(); jsval callee_val = JS::ObjectValue(callee); JS::RootedObject newtext(cx, JS_NewObjectForConstructor(cx, &textnode_class, &callee_val)); if (newtext == NULL) { misconfigure(); return JS_FALSE; } if (args.length() > 0 && (str = JS_ValueToString(cx, args[0]))) v = STRING_TO_JSVAL(str); else v = JS_GetEmptyStringValue(jcx); JS_DefineProperty(cx, newtext, "data", v, NULL, NULL, PROP_STD); args.rval().set(OBJECT_TO_JSVAL(newtext)); return JS_TRUE; } /* textnode_ctor */ static JSBool setter_loc_hrefval(JSContext * cx, JS::HandleObject uo, JS::Handle < jsid > id, JSBool strict, JS::MutableHandle < JS::Value > vp); static JSBool url_ctor(JSContext * cx, unsigned int argc, jsval * vp) { const char *url = emptyString; const char *s; js::RootedValue v(cx); JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JSObject & callee = args.callee(); jsval callee_val = JS::ObjectValue(callee); JS::RootedObject uo(cx, JS_NewObjectForConstructor(cx, &url_class, &callee_val)); if (uo == NULL) { abort: misconfigure(); return JS_FALSE; } /* href$val has a setter, so define it here */ v = JS_GetEmptyStringValue(cx); if (JS_DefineProperty (cx, uo, "href$val", v, NULL, setter_loc_hrefval, PROP_STD) == JS_FALSE) goto abort; if (args.length() > 0 && JSVAL_IS_STRING(args[0])) { v = args[0]; s = stringize(v); if (s[0]) url = s; } /* string argument */ v = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, url)); /* This should invoke the js setter URL.href, I hope */ if (JS_SetProperty(cx, uo, "href", v.address()) == JS_FALSE) goto abort; args.rval().set(OBJECT_TO_JSVAL(uo)); return JS_TRUE; } /* url_ctor */ /********************************************************************* Setters perform special actions when values are assigned to js properties or even objects. These must be implemented as native C functions. Setters are effectively suspended by setting the following variable to true. This is usually done when the dom manipulates the javascript objects directly. When website javascript is running however, the setters should run as well. Realize that the javascript level setters in startwindow.js run all the time. There's no way to turn those off, so make sure you don't want to. *********************************************************************/ static bool setter_suspend; /* This setter is only for window.location or document.location. * It returns false to stop javascript; * the browser is redirected elsewhere and the current page replaced. */ static JSBool setter_loc(JSContext * cx, JS::HandleObject uo, JS::Handle < jsid > id, JSBool strict, JS::MutableHandle < JS::Value > vp) { const char *s = stringize(vp); if (!s) { JS_ReportError(jcx, "window.location is assigned something that I don't understand"); } else { effectString("n{r"); // } effectString(s); effectChar('\n'); endeffect(); } return JS_FALSE; } /* setter_loc */ /* this setter can open a new window, if the parent object * is window.location or document.location. * Otherwise it does nothing. */ static JSBool setter_loc_hrefval(JSContext * cx, JS::HandleObject uo, JS::Handle < jsid > id, JSBool strict, JS::MutableHandle < JS::Value > vp) { const char *url = 0; if (setter_suspend) return JS_TRUE; url = stringize(vp); if (!url) return JS_TRUE; if (iswindocloc(uo)) { effectString("n{r"); // } effectString(url); effectChar('\n'); endeffect(); return JS_FALSE; } return JS_TRUE; } /* setter_loc_hrefval */ static const char *emptyParms[] = { 0 }; static jsval emptyArgs[] = { jsval() }; /* determine js type from js value */ static ej_proptype val_proptype(JS::HandleValue v) { JS::RootedObject child(jcx); if (v.isNullOrUndefined()) return EJ_PROP_NONE; if (JSVAL_IS_STRING(v)) return EJ_PROP_STRING; if (JSVAL_IS_INT(v)) return EJ_PROP_INT; if (JSVAL_IS_DOUBLE(v)) return EJ_PROP_FLOAT; if (JSVAL_IS_BOOLEAN(v)) return EJ_PROP_BOOL; if (v.isObject()) { child = JSVAL_TO_OBJECT(v); if (JS_ObjectIsFunction(jcx, child)) return EJ_PROP_FUNCTION; if (JS_IsArrayObject(jcx, child)) return EJ_PROP_ARRAY; return EJ_PROP_OBJECT; } return EJ_PROP_NONE; /* don't know */ } /* val_proptype */ static ej_proptype find_proptype(JS::HandleObject parent, const char *name) { js::RootedValue v(jcx); if (JS_GetProperty(jcx, parent, name, v.address()) == JS_FALSE) return EJ_PROP_NONE; return val_proptype(v); } /* find_proptype */ enum ej_proptype has_property(jsobjtype parent, const char *name) { JS::RootedObject p(jcx, (JSObject *) parent); return find_proptype(p, name); } /* has_property */ static void delete_property1(JS::HandleObject parent, const char *name) { JS_DeleteProperty(jcx, parent, name); } /* delete_property1 */ void delete_property(jsobjtype parent, const char *name) { JS::RootedObject p(jcx, (JSObject *) parent); delete_property1(p, name); } /* delete_property */ static int get_arraylength1(JS::HandleObject a) { unsigned int length; if (JS_GetArrayLength(jcx, a, &length) == JS_FALSE) return -1; return length; } /* get_arraylength1 */ int get_arraylength(jsobjtype a) { JS::RootedObject p(jcx, (JSObject *) a); return get_arraylength1(p); } /* get_arraylength */ /* Use stringize() to return a property as a string, if it is * string compatible. The string is allocated, free it when done. */ static char *get_property_string1(JS::HandleObject parent, const char *name) { js::RootedValue v(jcx); const char *s; proptype = EJ_PROP_NONE; if (JS_GetProperty(jcx, parent, name, v.address()) == JS_FALSE) return NULL; proptype = val_proptype(v); if (proptype == EJ_PROP_NONE) return NULL; if (v.isObject()) { /* special code here to return the object pointer */ /* That's what edbrowse is going to want. */ s = pointer2string(JSVAL_TO_OBJECT(v)); } else s = stringize(v); return cloneString(s); } /* get_property_string1 */ char *get_property_string(jsobjtype parent, const char *name) { JS::RootedObject p(jcx, (JSObject *) parent); return get_property_string1(p, name); } /* get_property_string */ static JSObject *get_property_object1(JS::HandleObject parent, const char *name) { js::RootedValue v(jcx); JS::RootedObject child(jcx); if (JS_GetProperty(jcx, parent, name, v.address()) == JS_FALSE) return 0; if (!v.isObject()) return 0; child = JSVAL_TO_OBJECT(v); return child; } /* get_property_object1 */ jsobjtype get_property_object(jsobjtype parent, const char *name) { JS::RootedObject p(jcx, (JSObject *) parent); return get_property_object1(p, name); } /* get_property_object */ /* if js changes the value of an input field, this must be reflected * in the text in edbrowse. */ static JSBool setter_value(JSContext * cx, JS::HandleObject obj, JS::Handle < jsid > id, JSBool strict, JS::MutableHandle < JS::Value > vp) { const char *val; if (setter_suspend) return JS_TRUE; val = stringize(vp); if (!val) { JS_ReportError(jcx, "input.value is assigned something other than a string; this can cause problems when you submit the form."); } else { effectString("v{"); // } effectString(pointer2string(*obj.address())); effectChar('='); effectString(val); endeffect(); } return JS_TRUE; } /* setter_value */ static JSBool setter_innerHTML(JSContext * cx, JS::HandleObject obj, JS::Handle < jsid > id, JSBool strict, JS::MutableHandle < jsval > vp) { const char *s = stringize(vp); if (!s) return JS_TRUE; /* lop off the preexisting children */ JS::RootedObject children(jcx); children = get_property_object1(obj, "childNodes"); if (children) JS_SetArrayLength(jcx, children, 0); int begin; effectString("i{h"); // } effectString(pointer2string(obj)); begin = eff_l + 1; effectString("|\n"); effectString(s); if (*s && s[strlen(s) - 1] != '\n') effectChar('\n'); effectString(""); cwSetup(); jsobjtype innerParent = obj; html_from_setter(innerParent, effects + begin); effectChar('@'); packDecoration(); cwBringdown(); endeffect(); return JS_TRUE; } /* setter_innerHTML */ static JSBool setter_innerText(JSContext * cx, JS::HandleObject obj, JS::Handle < jsid > id, JSBool strict, JS::MutableHandle < jsval > vp) { const char *s = stringize(vp); if (!s) s = emptyString; effectString("i{t"); // } effectString(pointer2string(obj)); effectChar('|'); effectString(s); endeffect(); return JS_TRUE; } /* setter_innerText */ /********************************************************************* Maintain a copy of the cookie string that is relevant for this web page. Include a leading semicolon, thus the form ; foo=73838; bar=j_k_qqr; bas=21998999 This is the same as the string in document.cookie. But the setter folds a new cookie into this string, and also passes the cookie back to edbrowse to put in the cookie jar. *********************************************************************/ static char *cookieCopy; static int cook_l; static bool foldinCookie(const char *newcook) { char *nc, *loc, *loc2; int j; char *s; char save; /* make a copy with ; in front */ j = strlen(newcook); nc = allocString(j + 3); strcpy(nc, "; "); strcpy(nc + 2, newcook); /* cut off the extra attributes */ s = strpbrk(nc + 2, " \t;"); if (s) *s = 0; /* cookie has to look like keyword=value */ s = strchr(nc + 2, '='); if (!s || s == nc + 2) { nzFree(nc); return false; } /* pass back to edbrowse */ effectString("c{"); // } effectString(newcook); endeffect(); ++s; save = *s; *s = 0; /* I'll put it back later */ loc = strstr(cookieCopy, nc); *s = save; if (!loc) goto add; /* find next piece */ loc2 = strchr(loc + 2, ';'); if (!loc2) loc2 = loc + strlen(loc); /* excise the oold, put in the new */ j = loc2 - loc; strmove(loc, loc2); cook_l -= j; add: stringAndString(&cookieCopy, &cook_l, nc); nzFree(nc); return true; } /* foldinCookie */ static JSBool setter_cookie(JSContext * cx, JS::HandleObject obj, JS::Handle < jsid > id, JSBool strict, JS::MutableHandle < JS::Value > vp) { const char *newcook; char *original; if (setter_suspend) return JS_TRUE; /* grab the existing document.cookie string, is this reentrant, is this ok? */ original = get_property_string1(obj, "cookie"); if (!original) /* should never happen */ original = emptyString; cookieCopy = initString(&cook_l); if (original[0]) { stringAndString(&cookieCopy, &cook_l, "; "); stringAndString(&cookieCopy, &cook_l, original); } nzFree(original); newcook = stringize(vp); if (newcook) foldinCookie(newcook); if (cookieCopy[0]) { js::RootedValue v(cx); v = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, cookieCopy + 2)); vp.set(v); } nzFree(cookieCopy); return JS_TRUE; } /* setter_cookie */ /* this is a placeholder */ static JSBool setter_domain(JSContext * cx, JS::HandleObject obj, JS::Handle < jsid > id, JSBool strict, JS::MutableHandle < JS::Value > vp) { const char *val; if (setter_suspend) return JS_TRUE; val = stringize(vp); return JS_TRUE; } /* setter_domain */ static void set_property_string1(js::HandleObject parent, const char *name, const char *value) { js::RootedValue v(jcx); JSPropertyOp my_getter = NULL; JSStrictPropertyOp my_setter = NULL; JSBool found; if (value && *value) v = STRING_TO_JSVAL(JS_NewStringCopyZ(jcx, value)); else v = JS_GetEmptyStringValue(jcx); JS_HasProperty(jcx, parent, name, &found); if (found) { if (JS_SetProperty(jcx, parent, name, v.address()) == JS_FALSE) misconfigure(); return; } if (stringEqual(name, "value")) my_setter = setter_value; if (stringEqual(name, "innerHTML")) my_setter = setter_innerHTML; if (stringEqual(name, "innerText")) my_setter = setter_innerText; if (stringEqual(name, "domain") && *parent.address() == docobj) my_setter = setter_domain; if (stringEqual(name, "cookie") && *parent.address() == docobj) my_setter = setter_cookie; if (JS_DefineProperty (jcx, parent, name, v, my_getter, my_setter, PROP_STD) == JS_FALSE) misconfigure(); } /* set_property_string1 */ int set_property_string(jsobjtype parent, const char *name, const char *value) { JS::RootedObject p(jcx, (JSObject *) parent); set_property_string1(p, name, value); return 0; } /* set_property_string */ static void set_property_bool1(js::HandleObject parent, const char *name, bool n) { js::RootedValue v(jcx); JSBool found; v = (n ? JSVAL_TRUE : JSVAL_FALSE); JS_HasProperty(jcx, parent, name, &found); if (found) { if (JS_SetProperty(jcx, parent, name, v.address()) == JS_FALSE) misconfigure(); return; } if (JS_DefineProperty(jcx, parent, name, v, NULL, NULL, PROP_STD) == JS_FALSE) misconfigure(); } /* set_property_bool1 */ int set_property_bool(jsobjtype parent, const char *name, bool n) { JS::RootedObject p(jcx, (JSObject *) parent); set_property_bool1(p, name, n); return 0; } /* set_property_bool */ static void set_property_number1(js::HandleObject parent, const char *name, int n) { js::RootedValue v(jcx); JSBool found; v = INT_TO_JSVAL(n); JS_HasProperty(jcx, parent, name, &found); if (found) { if (JS_SetProperty(jcx, parent, name, v.address()) == JS_FALSE) misconfigure(); return; } if (JS_DefineProperty(jcx, parent, name, v, NULL, NULL, PROP_STD) == JS_FALSE) misconfigure(); } /* set_property_number1 */ int set_property_number(jsobjtype parent, const char *name, int n) { JS::RootedObject p(jcx, (JSObject *) parent); set_property_number1(p, name, n); return 0; } /* set_property_number */ static void set_property_float1(js::HandleObject parent, const char *name, double n) { js::RootedValue v(jcx); JSBool found; v = DOUBLE_TO_JSVAL(n); JS_HasProperty(jcx, parent, name, &found); if (found) { if (JS_SetProperty(jcx, parent, name, v.address()) == JS_FALSE) misconfigure(); return; } if (JS_DefineProperty(jcx, parent, name, v, NULL, NULL, PROP_STD) == JS_FALSE) misconfigure(); } /* set_property_float1 */ int set_property_float(jsobjtype parent, const char *name, double n) { JS::RootedObject p(jcx, (JSObject *) parent); set_property_float1(p, name, n); return 0; } /* set_property_float */ static void set_property_object1(js::HandleObject parent, const char *name, JS::HandleObject child) { js::RootedValue v(jcx); JSBool found; JSStrictPropertyOp my_setter = NULL; v = OBJECT_TO_JSVAL(child); JS_HasProperty(jcx, parent, name, &found); if (found) { if (JS_SetProperty(jcx, parent, name, v.address()) == JS_FALSE) misconfigure(); return; } if (stringEqual(name, "location") && iswindoc(parent)) my_setter = setter_loc; if (JS_DefineProperty(jcx, parent, name, v, NULL, my_setter, PROP_STD) == JS_FALSE) misconfigure(); } /* set_property_object1 */ int set_property_object(jsobjtype parent, const char *name, jsobjtype child) { JS::RootedObject p(jcx, (JSObject *) parent); JS::RootedObject c(jcx, (JSObject *) child); set_property_object1(p, name, c); return 0; } /* set_property_object */ /* for window.focus etc */ static JSBool nullFunction(JSContext * cx, unsigned int argc, jsval * vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); args.rval().set(JSVAL_VOID); return JS_TRUE; } /* nullFunction */ static JSBool falseFunction(JSContext * cx, unsigned int argc, jsval * vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); args.rval().set(JSVAL_FALSE); return JS_TRUE; } /* falseFunction */ static JSBool trueFunction(JSContext * cx, unsigned int argc, jsval * vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); args.rval().set(JSVAL_TRUE); return JS_TRUE; } /* trueFunction */ static void set_property_function1(js::HandleObject parent, const char *name, const char *body) { if (!body || !*body) { /* null or empty function, link to native null function */ JS_NewFunction(jcx, nullFunction, 0, 0, parent, name); } else { JS_CompileFunction(jcx, parent, name, 0, emptyParms, body, strlen(body), name, 1); } } /* set_property_function1 */ int set_property_function(jsobjtype parent, const char *name, const char *body) { JS::RootedObject p(jcx, (JSObject *) parent); set_property_function1(p, name, body); return 0; } /* set_property_function */ static void run_function_onearg1(js::HandleObject parent, const char *name, JS::HandleObject child) { js::RootedValue v(jcx); jsval argv[2]; argv[0] = OBJECT_TO_JSVAL(child); argv[1] = jsval(); JS_CallFunctionName(jcx, parent, name, 1, argv, v.address()); } /* run_function_onearg1 */ void run_function_onearg(jsobjtype parent, const char *name, jsobjtype child) { JS::RootedObject p(jcx, (JSObject *) parent); JS::RootedObject c(jcx, (JSObject *) child); run_function_onearg1(p, name, c); } /* run_function_onearg */ #if 0 /* Not clear that setAttribute needs any side effects, or needs to be native. */ static JSBool setAttribute(JSContext * cx, unsigned int argc, jsval * vp) { JS::RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp)); JS::CallArgs args = JS::CallArgsFromVp(argc, vp); js::RootedValue v1(cx), v2(cx); if (args.length() != 2 || !JSVAL_IS_STRING(args[0])) { JS_ReportError(cx, "unexpected arguments to setAttribute()"); } else { v1 = args[0]; v2 = args[1]; const char *prop = stringize(v1); JS_DefineProperty(cx, thisobj, prop, v2, NULL, NULL, PROP_STD); } args.rval().set(JSVAL_VOID); return JS_TRUE; } /* setAttribute */ #endif static void embedNodeName(JS::HandleObject obj) { const char *nodeName; js::RootedValue v(jcx); int length; if (JS_GetProperty(jcx, obj, "nodeName", v.address()) == JS_TRUE) { nodeName = stringize(v); if (nodeName) { length = strlen(nodeName); if (length >= MAXTAGNAME) length = MAXTAGNAME - 1; stringAndBytes(&effects, &eff_l, nodeName, length); } } } /* embedNodeName */ static JSBool appendChild0(bool side, JSContext * cx, unsigned int argc, jsval * vp) { unsigned i, length; js::RootedValue v(cx); JS::CallArgs args = JS::CallArgsFromVp(argc, vp); args.rval().set(JSVAL_VOID); /* we need an argument that is an object */ if (args.length() == 0 || !args[0].isObject()) return JS_TRUE; JS::RootedObject child(cx, JSVAL_TO_OBJECT(args[0])); JS::RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp)); if (JS_GetProperty(cx, thisobj, "childNodes", v.address()) == JS_FALSE) return JS_TRUE; /* no such array */ JS::RootedObject elar(cx, JSVAL_TO_OBJECT(v)); if (elar == NULL) { misconfigure(); return JS_FALSE; } if (JS_GetArrayLength(cx, elar, &length) == JS_FALSE) { misconfigure(); return JS_FALSE; } // see if it's already there. for (i = 0; i < length; ++i) { if (JS_GetElement(cx, elar, i, v.address()) == JS_FALSE) { misconfigure(); return JS_FALSE; } if (!v.isObject()) continue; if (JSVAL_TO_OBJECT(v) == child) { // child was already there, what should we do? args.rval().set(args[0]); return JS_TRUE; } } // add child to the end if (JS_DefineElement(cx, elar, length, args[0], NULL, NULL, PROP_STD) == JS_FALSE) { misconfigure(); return JS_FALSE; } JS_DefineProperty(cx, child, "parentNode", OBJECT_TO_JSVAL(thisobj), NULL, NULL, PROP_STD); args.rval().set(args[0]); if (!side) return JS_TRUE; /* pass this linkage information back to edbrowse, to update its dom tree */ char e[40]; sprintf(e, "l{a|%s,", pointer2string(thisobj)); effectString(e); embedNodeName(thisobj); effectChar(' '); effectString(pointer2string(child)); effectChar(','); embedNodeName(child); effectString(" 0x0, "); endeffect(); return JS_TRUE; } /* appendChild0 */ static JSBool appendChild(JSContext * cx, unsigned int argc, jsval * vp) { return appendChild0(true, cx, argc, vp); } /* appendChild */ static JSBool apch(JSContext * cx, unsigned int argc, jsval * vp) { return appendChild0(false, cx, argc, vp); } /* apch */ static JSBool insertBefore(JSContext * cx, unsigned int argc, jsval * vp) { unsigned i, mark, length; js::RootedValue v(cx); JS::CallArgs args = JS::CallArgsFromVp(argc, vp); args.rval().set(JSVAL_VOID); /* we need two objects */ if (args.length() < 2 || !args[0].isObject() || !args[1].isObject()) return JS_TRUE; JS::RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp)); JS::RootedObject child(cx, JSVAL_TO_OBJECT(args[0])); JS::RootedObject item(cx, JSVAL_TO_OBJECT(args[1])); if (JS_GetProperty(cx, thisobj, "childNodes", v.address()) == JS_FALSE) return JS_TRUE; /* no such array */ JS::RootedObject elar(cx, JSVAL_TO_OBJECT(v)); if (elar == NULL) { misconfigure(); return JS_FALSE; } if (JS_GetArrayLength(cx, elar, &length) == JS_FALSE) { misconfigure(); return JS_FALSE; } /* item better be somewhere in the array, and child should not be */ mark = -1; for (i = 0; i < length; ++i) { if (JS_GetElement(cx, elar, i, v.address()) == JS_FALSE) { misconfigure(); return JS_FALSE; } if (!v.isObject()) continue; if (JSVAL_TO_OBJECT(v) == item) mark = i; if (JSVAL_TO_OBJECT(v) == child) { // child was already there, what should we do? args.rval().set(args[0]); return JS_TRUE; } } if (mark < 0) return JS_TRUE; /* since the item to insert before was found, the call is going to */ /* succeed, so put the return value here */ args.rval().set(args[0]); /* push the other elements down */ for (i = length; i > mark; --i) { JS_GetElement(cx, elar, i - 1, v.address()); if (i == length) JS_DefineElement(cx, elar, i, v, NULL, NULL, PROP_STD); else JS_SetElement(cx, elar, i, v.address()); } /* and place the child */ v = args[0]; JS_SetElement(cx, elar, mark, v.address()); JS_DefineProperty(cx, child, "parentNode", OBJECT_TO_JSVAL(thisobj), NULL, NULL, PROP_STD); /* pass this linkage information back to edbrowse, to update its dom tree */ char e[40]; sprintf(e, "l{b|%s,", pointer2string(thisobj)); effectString(e); embedNodeName(thisobj); effectChar(' '); effectString(pointer2string(child)); effectChar(','); embedNodeName(child); effectChar(' '); effectString(pointer2string(item)); effectChar(','); embedNodeName(item); effectChar(' '); endeffect(); return JS_TRUE; } /* insertBefore */ static JSBool removeChild(JSContext * cx, unsigned int argc, jsval * vp) { unsigned i, mark, length; js::RootedValue v(cx); JS::CallArgs args = JS::CallArgsFromVp(argc, vp); args.rval().set(JSVAL_VOID); /* we need an object */ if (args.length() < 1 || !args[0].isObject()) return JS_TRUE; JS::RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp)); JS::RootedObject child(cx, JSVAL_TO_OBJECT(args[0])); if (JS_GetProperty(cx, thisobj, "childNodes", v.address()) == JS_FALSE) return JS_TRUE; /* no such array */ JS::RootedObject elar(cx, JSVAL_TO_OBJECT(v)); if (elar == NULL) { misconfigure(); return JS_FALSE; } if (JS_GetArrayLength(cx, elar, &length) == JS_FALSE) { misconfigure(); return JS_FALSE; } /* child better be somewhere in the array */ for (mark = 0; mark < length; ++mark) { if (JS_GetElement(cx, elar, mark, v.address()) == JS_FALSE) { misconfigure(); return JS_FALSE; } if (!v.isObject()) continue; if (JSVAL_TO_OBJECT(v) == child) goto found; } return JS_TRUE; found: /* we are now situated properly so set the return value, the node to remove*/ args.rval().set(args[0]); /* pull the others back */ for (i = mark; i < length - 1; ++i) { JS_GetElement(cx, elar, i + 1, v.address()); JS_SetElement(cx, elar, i, v.address()); } JS_SetArrayLength(cx, elar, length - 1); JS_DeleteProperty(cx, child, "parentNode"); /* pass this linkage information back to edbrowse, to update its dom tree */ char e[40]; sprintf(e, "l{r|%s,", pointer2string(thisobj)); effectString(e); embedNodeName(thisobj); effectChar(' '); effectString(pointer2string(child)); effectChar(','); embedNodeName(child); effectString(" 0x0, "); endeffect(); return JS_TRUE; } /* removeChild */ static void dwrite1(unsigned int argc, jsval * argv, bool newline) { int i, begin; const char *msg; JS::RootedString str(jcx); effectString("w{"); // } begin = eff_l; for (i = 0; i < (signed)argc; ++i) { if ((str = JS_ValueToString(jcx, argv[i])) && (msg = JS_c_str(str))) { effectString(msg); cnzFree(msg); } } if (newline) effectChar('\n'); endeffect(); } /* dwrite1 */ static JSBool doc_write(JSContext * cx, unsigned int argc, jsval * vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); dwrite1(args.length(), args.array(), false); args.rval().set(JSVAL_VOID); return JS_TRUE; } /* doc_write */ static JSBool doc_writeln(JSContext * cx, unsigned int argc, jsval * vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); dwrite1(args.length(), args.array(), true); args.rval().set(JSVAL_VOID); return JS_TRUE; } /* doc_writeln */ /* this has a native wrapper so we can set innerHTML with a setter */ static JSBool doc_createElement(JSContext * cx, unsigned int argc, jsval * vp) { char run[60]; const char *tagname = NULL, *s; JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedString str(cx); if (args.length() > 0 && (str = JS_ValueToString(cx, args[0]))) tagname = JS_c_str(str); if (!tagname || strlen(tagname) >= MAXTAGNAME) { fail: cnzFree(tagname); args.rval().set(JSVAL_NULL); return JS_TRUE; } for (s = tagname; *s; ++s) if (!isalnumByte(*s)) goto fail; /* let js do most of the work */ sprintf(run, "document.crel$$('%s')", tagname); JS::RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp)); js::RootedValue v(cx); if (JS_EvaluateScript(cx, thisobj, run, strlen(run), "create", 1, v.address()) == JS_FALSE) goto fail; if (!v.isObject()) goto fail; JS::RootedObject child(cx, JSVAL_TO_OBJECT(v)); /* and now the reason for the wrapper */ JS_DefineProperty(cx, child, "innerHTML", JS_GetEmptyStringValue(cx), NULL, setter_innerHTML, PROP_STD); /* But we can't set innerHTML unless the object exists in edbrowse */ sprintf(run, "l{c|%s,%s 0x0, 0x0, ", pointer2string(child), tagname); effectString(run); endeffect(); /* and return the created object */ args.rval().set(v); return JS_TRUE; } /* doc_createElement */ static JSFunctionSpec document_methods[] = { JS_FS("focus", nullFunction, 0, 0), JS_FS("blur", nullFunction, 0, 0), JS_FS("open", nullFunction, 0, 0), JS_FS("close", nullFunction, 0, 0), JS_FS("write", doc_write, 0, 0), JS_FS("writeln", doc_writeln, 0, 0), JS_FS("createElement", doc_createElement, 0, 0), JS_FS("appendChild", appendChild, 1, 0), JS_FS("apch$", apch, 1, 0), JS_FS("insertBefore", insertBefore, 2, 0), JS_FS("removeChild", removeChild, 1, 0), JS_FS_END }; static JSBool form_submit(JSContext * cx, unsigned int argc, jsval * vp) { JS::RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); effectString("f{s"); // } effectString(pointer2string(obj)); endeffect(); JS::CallArgs args = JS::CallArgsFromVp(argc, vp); args.rval().set(JSVAL_VOID); return JS_TRUE; } /* form_submit */ static JSBool form_reset(JSContext * cx, unsigned int argc, jsval * vp) { JS::RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); effectString("f{r"); // } effectString(pointer2string(obj)); endeffect(); JS::CallArgs args = JS::CallArgsFromVp(argc, vp); args.rval().set(JSVAL_VOID); return JS_TRUE; } /* form_reset */ static JSFunctionSpec form_methods[] = { JS_FS("submit", form_submit, 0, 0), JS_FS("reset", form_reset, 0, 0), JS_FS_END }; static JSBool win_close(JSContext * cx, unsigned int argc, jsval * vp) { if (head.highstat <= EJ_HIGH_CX_FAIL) { head.highstat = EJ_HIGH_CX_FAIL; head.lowstat = EJ_LOW_CLOSE; } JS::CallArgs args = JS::CallArgsFromVp(argc, vp); args.rval().set(JSVAL_VOID); return JS_TRUE; } /* win_close */ static JSBool win_puts(JSContext * cx, unsigned int argc, jsval * vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); const char *msg = NULL; JS::RootedString str(cx); if (args.length() > 0 && (str = JS_ValueToString(cx, args[0]))) msg = JS_c_str(str); if (msg && *msg) puts(msg); cnzFree(msg); args.rval().set(JSVAL_VOID); return JS_TRUE; } /* win_puts */ static JSBool win_prompt(JSContext * cx, unsigned int argc, jsval * vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); const char *msg = emptyString; const char *answer = emptyString; JS::RootedString str(cx); char inbuf[80]; char *s; char c; if (args.length() > 0 && (str = JS_ValueToString(cx, args[0]))) msg = JS_c_str(str); if (!msg) return JS_TRUE; if (!*msg) { cnzFree(msg); return JS_TRUE; } if (args.length() > 1 && (str = JS_ValueToString(cx, args[1]))) answer = JS_c_str(str); printf("%s", msg); /* If it doesn't end in space or question mark, print a colon */ c = msg[strlen(msg) - 1]; if (!isspace(c)) { if (!ispunct(c)) printf(":"); printf(" "); } if (answer) printf("[%s] ", answer); fflush(stdout); if (!fgets(inbuf, sizeof(inbuf), stdin)) exit(5); s = inbuf + strlen(inbuf); if (s > inbuf && s[-1] == '\n') *--s = 0; if (inbuf[0]) { cnzFree(answer); /* Don't need the default answer anymore. */ answer = inbuf; } args.rval().set(STRING_TO_JSVAL(JS_NewStringCopyZ(cx, answer))); if (answer != inbuf) cnzFree(answer); return JS_TRUE; } /* win_prompt */ static JSBool win_confirm(JSContext * cx, unsigned int argc, jsval * vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); const char *msg = emptyString; JS::RootedString str(cx); char inbuf[80]; char c; bool first = true; if (args.length() > 0 && (str = JS_ValueToString(cx, args[0]))) msg = JS_c_str(str); if (!msg) return JS_TRUE; if (!*msg) { cnzFree(msg); return JS_TRUE; } while (true) { printf("%s", msg); c = msg[strlen(msg) - 1]; if (!isspace(c)) { if (!ispunct(c)) printf(":"); printf(" "); } if (!first) printf("[y|n] "); first = false; fflush(stdout); if (!fgets(inbuf, sizeof(inbuf), stdin)) exit(5); c = *inbuf; if (c && strchr("nNyY", c)) break; } c = tolower(c); if (c == 'y') args.rval().set(JSVAL_TRUE); else args.rval().set(JSVAL_FALSE); cnzFree(msg); return JS_TRUE; } /* win_confirm */ /* Set a timer or an interval */ static JSObject *setTimeout(unsigned int argc, jsval * argv, bool isInterval) { JS::RootedValue v0(jcx), v1(jcx); // values of the 2 args // the function object, to execute, and the timer object. JS::RootedObject fo(jcx, 0), to(jcx); int n; /* number of milliseconds */ char nstring[20]; char fname[48]; /* function name */ const char *fstr; /* function string */ const char *methname = (isInterval ? "setInterval" : "setTimeout"); const char *allocatedName = NULL; const char *s = NULL; if (argc != 2 || !JSVAL_IS_INT(argv[1])) goto badarg; v0 = argv[0]; v1 = argv[1]; n = JSVAL_TO_INT(v1); if (JSVAL_IS_STRING(v0) || v0.isObject() && JS_ValueToObject(jcx, v0, fo.address()) && JS_ObjectIsFunction(jcx, fo)) { /* build the tag object and link it to window */ to = JS_NewObject(jcx, &timer_class, NULL, winobj); if (to == NULL) { abort: misconfigure(); return NULL; } // protect this timer from the garbage collector v1 = OBJECT_TO_JSVAL(to); if (JS_DefineProperty (jcx, winobj, fakePropName(), v1, NULL, NULL, PROP_STD) == JS_FALSE) goto abort; if (fo) { /* Extract the function name, which requires several steps */ js::RootedFunction f(jcx, JS_ValueToFunction(jcx, OBJECT_TO_JSVAL (fo))); JS::RootedString jss(jcx, JS_GetFunctionId(f)); if (jss) allocatedName = JS_c_str(jss); s = allocatedName; /* Remember that unnamed functions are named anonymous. */ if (!s || !*s || stringEqual(s, "anonymous")) s = "javascript"; int len = strlen(s); if (len > (signed)sizeof(fname) - 4) len = sizeof(fname) - 4; strncpy(fname, s, len); cnzFree(allocatedName); fname[len] = 0; strcat(fname, "()"); fstr = fname; set_property_object1(to, "onclick", fo); } else { /* compile the function from the string */ fstr = stringize(v0); if (JS_CompileFunction (jcx, to, "onclick", 0, emptyParms, fstr, strlen(fstr), "onclick", 1) == NULL) { JS_ReportError(jcx, "error compiling function in %s()", methname); return NULL; } strcpy(fname, "?"); s = fstr; skipWhite(&s); if (memEqualCI(s, "javascript:", 11)) s += 11; skipWhite(&s); if (isalpha(*s) || *s == '_') { char *j; for (j = fname; isalnum(*s) || *s == '_'; ++s) { if (j < fname + sizeof(fname) - 3) *j++ = *s; } strcpy(j, "()"); skipWhite(&s); if (*s != '(') strcpy(fname, "?"); } fstr = fname; } sprintf(nstring, "t{%d|", n); // } effectString(nstring); effectString(fstr); effectChar('|'); effectString(pointer2string(to)); effectChar('|'); effectChar((isInterval ? '1' : '0')); endeffect(); return to; } badarg: JS_ReportError(jcx, "invalid arguments to %s()", methname); return NULL; } /* setTimeout */ /* set timer and set interval */ static JSBool win_sto(JSContext * cx, unsigned int argc, jsval * vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject obj(jcx, setTimeout(args.length(), args.array(), false)); if (obj == NULL) return JS_FALSE; args.rval().set(OBJECT_TO_JSVAL(obj)); return JS_TRUE; } /* win_sto */ static JSBool win_intv(JSContext * cx, unsigned int argc, jsval * vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject obj(jcx, setTimeout(args.length(), args.array(), true)); if (obj == NULL) return JS_FALSE; args.rval().set(OBJECT_TO_JSVAL(obj)); return JS_TRUE; } /* win_intv */ /* Clear a timer or an interval */ static JSBool clearTimeout(JSContext * cx, unsigned int argc, jsval * vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); args.rval().set(JSVAL_VOID); if (argc == 0 || !args[0].isObject()) return JS_TRUE; JS::RootedObject obj(jcx, JSVAL_TO_OBJECT(args[0])); char nstring[80]; sprintf(nstring, "t{0|-|%s|0", pointer2string(obj)); // } effectString(nstring); endeffect(); return JS_TRUE; } /* clearTimeout */ static JSFunctionSpec window_methods[] = { JS_FS("$puts$", win_puts, 1, 0), JS_FS("prompt", win_prompt, 2, 0), JS_FS("confirm", win_confirm, 1, 0), JS_FS("setTimeout", win_sto, 2, 0), JS_FS("setInterval", win_intv, 2, 0), JS_FS("clearTimeout", clearTimeout, 1, 0), JS_FS("clearInterval", clearTimeout, 1, 0), JS_FS("close", win_close, 0, 0), JS_FS("focus", nullFunction, 0, 0), JS_FS("blur", nullFunction, 0, 0), JS_FS("scroll", nullFunction, 0, 0), JS_FS_END }; static struct { JSClass *obj_class; JSNative constructor; JSFunctionSpec *methods; int nargs; } const domClasses[] = { {&document_class, document_ctor, document_methods}, {&head_class, head_ctor}, {&meta_class, meta_ctor}, {&link_class, link_ctor}, {&body_class, body_ctor}, {&base_class, base_ctor}, {&form_class, form_ctor, form_methods}, {&element_class, element_ctor}, {&image_class, image_ctor, NULL, 1}, {&frame_class, frame_ctor}, {&anchor_class, anchor_ctor, NULL, 1}, {&lister_class, lister_ctor}, {&listitem_class, listitem_ctor}, {&table_class, table_ctor}, {&tbody_class, tbody_ctor}, {&trow_class, trow_ctor}, {&cell_class, cell_ctor}, {&div_class, div_ctor}, {&area_class, area_ctor}, {&span_class, span_ctor}, {&option_class, option_ctor, NULL, 2}, {&script_class, script_ctor}, {¶_class, para_ctor}, {&url_class, url_ctor}, {&textnode_class, textnode_ctor}, {0} }; static void createContext(void) { int i; jcx = JS_NewContext(jrt, gStackChunkSize); if (!jcx) { head.highstat = EJ_HIGH_HEAP_FAIL; head.lowstat = EJ_LOW_CX; return; } JSAutoRequest autoreq(jcx); JS_SetErrorReporter(jcx, my_ErrorReporter); JS_SetOptions(jcx, JSOPTION_VAROBJFIX); /* Create the Window object, which is the global object in DOM. */ winobj = JS_NewGlobalObject(jcx, &window_class, NULL); if (!winobj) { no_win: head.highstat = EJ_HIGH_HEAP_FAIL; head.lowstat = EJ_LOW_WIN; JS_DestroyContext(jcx); return; } /* enter the compartment for this object for the duration of this function */ JSAutoCompartment ac(jcx, winobj); /* now set the window object as global */ JS_SetGlobalObject(jcx, winobj); /* Math, Date, Number, String, etc */ if (!JS_InitStandardClasses(jcx, winobj)) { no_std: head.highstat = EJ_HIGH_HEAP_FAIL; head.lowstat = EJ_LOW_STD; JS_DestroyContext(jcx); return; } /* initialise the window class */ if (JS_InitClass (jcx, winobj, NULL, &window_class, window_ctor, 3, NULL, window_methods, NULL, NULL) == NULL) goto no_win; /* Ok, but the global object was created before the class, * so it doesn't have its methods yet. */ if (JS_DefineFunctions(jcx, winobj, window_methods) == JS_FALSE) goto no_win; /* Other classes that we'll need. */ for (i = 0; domClasses[i].obj_class; ++i) { if (JS_InitClass (jcx, winobj, 0, domClasses[i].obj_class, domClasses[i].constructor, domClasses[i].nargs, NULL, domClasses[i].methods, NULL, NULL) == NULL) goto no_std; } /* document under window */ JS::RootedObject d(jcx, JS_NewObject(jcx, &document_class, NULL, winobj)); if (!d) { no_doc: head.highstat = EJ_HIGH_HEAP_FAIL; head.lowstat = EJ_LOW_DOC; JS_DestroyContext(jcx); return; } docobj = *d.address(); js::RootedValue v(jcx, OBJECT_TO_JSVAL(d)); if (JS_DefineProperty (jcx, winobj, "document", v, NULL, NULL, PROP_READONLY) == JS_FALSE) goto no_doc; } /* createContext */ static JSClass *classByName(const char *classname) { JSClass *cp = 0; int i; if (!classname) return cp; /* generic object */ for (i = 0; cp = domClasses[i].obj_class; ++i) if (stringEqual(cp->name, classname)) break; if (!cp) { fprintf(stderr, "Unexpected class name %s from edbrowse\n", classname); exit(8); } return cp; } /* classByName */ /* based on propval and proptype */ static void set_property_generic(js::HandleObject parent, const char *name) { int n; double d; JSObject *child; JS::RootedObject childroot(jcx); JSClass *cp; switch (proptype) { case EJ_PROP_STRING: set_property_string1(parent, name, propval); break; case EJ_PROP_INT: n = atoi(propval); set_property_number1(parent, name, n); break; case EJ_PROP_BOOL: n = atoi(propval); set_property_bool1(parent, name, n); break; case EJ_PROP_FLOAT: d = atof(propval); set_property_float1(parent, name, d); break; case EJ_PROP_OBJECT: child = string2pointer(propval); childroot = child; set_property_object1(parent, name, childroot); break; case EJ_PROP_INSTANCE: cp = classByName(propval); nzFree(propval); propval = 0; childroot = JS_NewObject(jcx, cp, NULL, parent); if (!childroot) { misconfigure(); break; } if (cp == &url_class) { // the constructor didn't run, so create href$val with its native setter here js::RootedValue v(jcx); v = JS_GetEmptyStringValue(jcx); if (JS_DefineProperty (jcx, childroot, "href$val", v, NULL, setter_loc_hrefval, PROP_STD) == JS_FALSE) { misconfigure(); break; } } childreturn: set_property_object1(parent, name, childroot); propval = cloneString(pointer2string(*childroot.address())); break; case EJ_PROP_ARRAY: childroot = JS_NewArrayObject(jcx, 0, NULL); goto childreturn; case EJ_PROP_FUNCTION: set_property_function1(parent, name, propval); break; default: fprintf(stderr, "Unexpected property type %d from edbrowse\n", proptype); exit(7); } } /* set_property_generic */ static JSObject *instantiate_array1(js::HandleObject parent, const char *name) { js::RootedValue v(jcx); js::RootedObject a(jcx); JSBool found; JS_HasProperty(jcx, parent, name, &found); if (found) { if (v.isObject()) { a = JSVAL_TO_OBJECT(v); if (JS_IsArrayObject(jcx, a)) return a; } JS_DeleteProperty(jcx, parent, name); } a = JS_NewArrayObject(jcx, 0, NULL); v = OBJECT_TO_JSVAL(a); if (JS_DefineProperty(jcx, parent, name, v, NULL, NULL, PROP_STD) == JS_FALSE) { misconfigure(); return 0; } return a; } /* instantiate_array1 */ jsobjtype instantiate_array(jsobjtype parent, const char *name) { JS::RootedObject p(jcx, (JSObject *) parent); return instantiate_array1(p, name); } /* instantiate_array */ static JSObject *instantiate1(js::HandleObject parent, const char *name, const char *classname) { js::RootedValue v(jcx); js::RootedObject a(jcx); JSBool found; JS_HasProperty(jcx, parent, name, &found); if (found) { if (v.isObject()) { a = JSVAL_TO_OBJECT(v); /* I'm going to assume it is of the proper class */ return a; } JS_DeleteProperty(jcx, parent, name); } JSClass *cp = classByName(classname); a = JS_NewObject(jcx, cp, NULL, parent); v = OBJECT_TO_JSVAL(a); if (JS_DefineProperty(jcx, parent, name, v, NULL, NULL, PROP_STD) == JS_FALSE) { misconfigure(); return 0; } return a; } /* instantiate1 */ jsobjtype instantiate(jsobjtype parent, const char *name, const char *classname) { JS::RootedObject p(jcx, (JSObject *) parent); return instantiate1(p, name, classname); } /* instantiate */ static JSObject *instantiate_array_element1(js::HandleObject parent, int idx, const char *classname) { js::RootedObject a(jcx); JSClass *cp = classByName(classname); a = JS_NewObject(jcx, cp, NULL, parent); set_array_element_object(parent, idx, a); return a; } /* instantiate_array_element1 */ jsobjtype instantiate_array_element(jsobjtype parent, int idx, const char *classname) { JS::RootedObject p(jcx, (JSObject *) parent); return instantiate_array_element1(p, idx, classname); } /* instantiate_array_element */ static void set_array_element_object1(js::HandleObject parent, int idx, JS::HandleObject child) { js::RootedValue v(jcx); JSBool found; v = OBJECT_TO_JSVAL(child); JS_HasElement(jcx, parent, idx, &found); if (found) { if (JS_SetElement(jcx, parent, idx, v.address()) == JS_FALSE) misconfigure(); } else { if (JS_DefineElement(jcx, parent, idx, v, NULL, NULL, PROP_STD) == JS_FALSE) misconfigure(); } } /* set_array_element_object1 */ int set_array_element_object(jsobjtype parent, int idx, jsobjtype child) { JS::RootedObject p(jcx, (JSObject *) parent); JS::RootedObject c(jcx, (JSObject *) child); set_array_element_object1(p, idx, c); return 0; } /* set_array_element_object */ static JSObject *get_array_element_object1(JS::HandleObject parent, int idx) { js::RootedValue v(jcx); JS::RootedObject child(jcx); if (JS_GetElement(jcx, parent, idx, v.address()) == JS_FALSE) return 0; /* perhaps out of range */ if (!v.isObject()) { fprintf(stderr, "JS DOM arrays should contain only objects\n"); exit(9); } child = JSVAL_TO_OBJECT(v); return child; } /* get_array_element_object1 */ jsobjtype get_array_element_object(jsobjtype parent, int idx) { JS::RootedObject p(jcx, (JSObject *) parent); return get_array_element_object1(p, idx); } /* get_array_element_object */ /********************************************************************* ebjs.c allows for geting and setting array elements of all types, however the DOM only uses objects. Being lazy, I will simply implement objects. You can add other types later. *********************************************************************/ /********************************************************************* run a javascript function and return the result. If the result is an object then the pointer, as a string, is returned. The string is always allocated, you must free it. At entry, propval, if nonzero, is a | separated list of arguments to the function, assuming all args are objects. *********************************************************************/ static char *run_function(JS::HandleObject parent, const char *name) { js::RootedValue v(jcx); bool rc; JSBool found; const char *s; proptype = EJ_PROP_NONE; JS_HasProperty(jcx, parent, name, &found); if (!found) { nzFree(propval); propval = 0; return NULL; } if (!propval) { rc = JS_CallFunctionName(jcx, parent, name, 0, emptyArgs, v.address()); } else { int argc = 0; /* lazy, a hard limit of 20 arguments */ jsval argv[20 + 1]; const char *t; JSObject *o; for (s = propval; *s; s = t) { if (argc == 20) break; t = strchr(s, '|') + 1; o = string2pointer(s); argv[argc++] = OBJECT_TO_JSVAL(o); } argv[argc] = jsval(); nzFree(propval); propval = 0; rc = JS_CallFunctionName(jcx, parent, name, argc, argv, v.address()); } if (!rc) return NULL; proptype = val_proptype(v); if (proptype == EJ_PROP_NONE) return NULL; if (v.isObject()) s = pointer2string(JSVAL_TO_OBJECT(v)); else s = stringize(v); return cloneString(s); } /* run_function */ /* process each message from edbrowse and respond appropriately */ static void processMessage(void) { JSAutoRequest autoreq(jcx); JSAutoCompartment ac(jcx, winobj); /* head.obj should be a valid object or 0 */ JS::RootedObject parent(jcx, (JSObject *) head.obj); JS::RootedObject child(jcx); JSObject *chp; js::RootedValue v(jcx); const char *s; bool rc; /* return code */ bool setret; /* does setting a property produce a return? */ unsigned len; /* array length */ switch (head.cmd) { case EJ_CMD_SCRIPT: propval = 0; s = runscript; /* Sometimes Mac puts these three chars at the start of a text file. */ if (!strncmp(s, "\xef\xbb\xbf", 3)) s += 3; head.n = 0; head.proplength = 0; if (JS_EvaluateScript(jcx, parent, s, strlen(s), "foo", head.lineno, v.address())) { if (v != JSVAL_VOID) { s = 0; JS::RootedString str(jcx); str = JS_ValueToString(jcx, v); if (str) s = JS_c_str(str); if (s && !*s) { cnzFree(s); s = 0; } head.n = 1; if (s) head.proplength = strlen(s); } } nzFree(runscript); runscript = 0; writeHeader(); if (head.proplength) { writeToEb(s, head.proplength); cnzFree(s); } break; case EJ_CMD_HASPROP: head.proptype = find_proptype(parent, membername); nzFree(membername); membername = 0; head.n = head.proplength = 0; writeHeader(); break; case EJ_CMD_DELPROP: JS_DeleteProperty(jcx, parent, membername); nzFree(membername); membername = 0; head.n = head.proplength = 0; writeHeader(); break; case EJ_CMD_GETPROP: propval = get_property_string1(parent, membername); nzFree(membername); membername = 0; head.n = head.proplength = 0; head.proptype = EJ_PROP_NONE; if (propval) { head.proplength = strlen(propval); head.proptype = proptype; } writeHeader(); if (propval) writeToEb(propval, head.proplength); nzFree(propval); propval = 0; break; case EJ_CMD_SETPROP: /* does this set_property return something? */ setret = false; if (head.proptype == EJ_PROP_ARRAY || head.proptype == EJ_PROP_INSTANCE) setret = true; setter_suspend = true; set_property_generic(parent, membername); setter_suspend = false; nzFree(membername); membername = 0; propreturn: head.n = head.proplength = 0; if (setret) { if (propval) head.proplength = strlen(propval); } else { nzFree(propval); propval = 0; } writeHeader(); if (setret && propval) { writeToEb(propval, head.proplength); nzFree(propval); propval = 0; } break; case EJ_CMD_GETAREL: child = get_array_element_object1(parent, head.n); propval = 0; /* should already be 0 */ head.proplength = 0; if (child) { propval = cloneString(pointer2string(*child.address())); head.proplength = strlen(propval); head.proptype = EJ_PROP_OBJECT; } writeHeader(); if (propval) writeToEb(propval, head.proplength); nzFree(propval); propval = 0; break; case EJ_CMD_SETAREL: setret = false; if (head.proptype == EJ_PROP_INSTANCE) { JSClass *cp = classByName(propval); nzFree(propval); propval = 0; child = JS_NewObject(jcx, cp, NULL, parent); if (!child) misconfigure(); else set_array_element_object1(parent, head.n, child); setret = true; propval = cloneString(pointer2string(*child.address())); } if (head.proptype == EJ_PROP_OBJECT && propval) { chp = string2pointer(propval); child = chp; set_array_element_object1(parent, head.n, child); nzFree(propval); propval = 0; } goto propreturn; case EJ_CMD_ARLEN: if (JS_GetArrayLength(jcx, parent, &len) == JS_FALSE) head.n = -1; else head.n = len; head.proplength = 0; writeHeader(); break; case EJ_CMD_CALL: propval = run_function(parent, membername); nzFree(membername); membername = 0; head.proplength = head.n = 0; if (propval) head.proplength = strlen(propval); head.proptype = proptype; writeHeader(); if (propval) writeToEb(propval, head.proplength); nzFree(propval); propval = 0; break; default: fprintf(stderr, "Unexpected message command %d from edbrowse\n", head.cmd); exit(6); } } /* processMessage */ edbrowse-3.6.0.1/src/PaxHeaders.22102/js_hello_v8.cpp0000644000000000000000000000006112637565441016741 xustar0020 atime=1451158305 29 ctime=1451409178.15333719 edbrowse-3.6.0.1/src/js_hello_v8.cpp0000644000000000000000000000247012637565441017216 0ustar00rootroot00000000000000#include #include using namespace v8; static const char runCommand[] = "'hello world, the answer is ' + 6*7;"; int main(int argc, char* argv[]) { // Initialize V8. V8::InitializeICU(); Platform* platform = platform::CreateDefaultPlatform(); V8::InitializePlatform(platform); V8::Initialize(); // Create a new Isolate and make it the current one. Isolate* isolate = Isolate::New(); { Isolate::Scope isolate_scope(isolate); // Create a stack-allocated handle scope. HandleScope handle_scope(isolate); // Create a new context. Local context = Context::New(isolate); // Enter the context for compiling and running the hello world script. Context::Scope context_scope(context); // Create a string containing the JavaScript source code. Local source = String::NewFromUtf8(isolate, runCommand); // Compile the source code. Local and * * so make a guess towards the first form, knowing we could be off by 1. * Just leave it at t->js_ln */ if (cw->fileName && !t->scriptgen) js_file = cw->fileName; if (t->href) { /* fetch the javascript page */ if (javaOK(t->href)) { bool from_data = isDataURI(t->href); debugPrint(3, "java source %s", !from_data ? t->href : "data URI"); if (from_data) { char *mediatype; int data_l = 0; if (parseDataURI(t->href, &mediatype, &js_text, &data_l)) { prepareForBrowse(js_text, data_l); nzFree(mediatype); } else { debugPrint(3, "Unable to parse data URI containing JavaScript"); } } else if (browseLocal && !isURL(t->href)) { if (!fileIntoMemory (t->href, &serverData, &serverDataLen)) { if (debugLevel >= 1) i_printf(MSG_GetLocalJS, errorMsg); } else { js_text = serverData; prepareForBrowse(js_text, serverDataLen); } } else if (httpConnect(t->href, false, false)) { if (hcode == 200) { js_text = serverData; prepareForBrowse(js_text, serverDataLen); } else { nzFree(serverData); if (debugLevel >= 3) i_printf(MSG_GetJS, t->href, hcode); } } else { if (debugLevel >= 3) i_printf(MSG_GetJS2, errorMsg); } t->js_ln = 1; js_file = (!from_data ? t->href : "data_URI"); nzFree(changeFileName); changeFileName = NULL; } } else { js_text = t->textval; t->textval = 0; } if (!js_text) return; set_property_string(t->jv, "data", js_text); nzFree(js_text); filepart = getFileURL(js_file, true); t->js_file = cloneString(filepart); } /* prepareScript */ static void runScriptsPending(void) { struct htmlTag *t; struct inputChange *ic; int j, l; char *jtxt; const char *js_file; int ln; bool change; jsobjtype v; if (newlocation && newloc_r) return; /* if onclick code or some such does document write, where does that belong? * I don't know, I'll just put it at the end. * As you see below, document.write that comes from a specific javascript * appears inline where the script is. */ if (cw->dw) { stringAndString(&cw->dw, &cw->dw_l, "\n"); runGeneratedHtml(NULL, cw->dw, NULL); nzFree(cw->dw); cw->dw = 0; cw->dw_l = 0; } top: change = false; for (j = 0; j < cw->numTags; ++j) { t = tagList[j]; if (t->action != TAGACT_SCRIPT) continue; if (!t->jv) continue; if (t->step >= 3) continue; /* now running the script */ t->step = 3; change = true; prepareScript(t); jtxt = get_property_string(t->jv, "data"); if (!jtxt) continue; /* nothing there */ js_file = t->js_file; if (!js_file) js_file = "generated"; ln = t->js_ln; if (!ln) ln = 1; debugPrint(3, "execute %s at %d", js_file, ln); /* if script is in the html it usually begins on the next line, so increment, * and hope the error messages line up. */ if (ln > 1) ++ln; jsRunScript(cw->winobj, jtxt, js_file, ln); debugPrint(3, "execution complete"); nzFree(jtxt); if (newlocation && newloc_r) return; /* look for document.write from this script */ if (cw->dw) { stringAndString(&cw->dw, &cw->dw_l, "\n"); runGeneratedHtml(t, cw->dw, NULL); nzFree(cw->dw); cw->dw = 0; cw->dw_l = 0; } } if (change) goto top; /* look for an run innerHTML */ foreach(ic, inputChangesPending) { struct htmlTag *u, *v; char *h; if (ic->major != 'i' || ic->minor != 'h') continue; ic->major = 'x'; /* Cut all the children away from t */ t = ic->t; for (u = t->firstchild; u; u = v) { v = u->sibling; u->sibling = u->parent = 0; u->deleted = true; } t->firstchild = NULL; h = strstr(ic->value, "@"); if (h) { h += 7; *h++ = 0; } else h = emptyString; runGeneratedHtml(t, ic->value, h); change = true; } if (change) goto top; foreach(ic, inputChangesPending) { char *v; int side; if (ic->major != 'i' || ic->minor != 't') continue; ic->major = 'x'; t = ic->t; /* the tag should always be a textarea tag. */ /* Not sure what to do if it's not. */ if (t->action != TAGACT_INPUT || t->itype != INP_TA) { debugPrint(3, "innerText is applied to tag %d that is not a textarea.", t->seqno); continue; } /* 2 parts: innerText copies over to textarea->value * if js has not already done so, * and the text replaces what was in the side buffer. */ v = ic->value; set_property_string(t->jv, "value", v); side = t->lic; if (side <= 0 || side >= MAXSESSION || side == context) continue; if (sessionList[side].lw == NULL) continue; if (cw->browseMode) i_printf(MSG_BufferUpdated, side); sideBuffer(side, v, -1, 0); } foreach(ic, inputChangesPending) { if (ic->major != 'l') continue; ic->major = 'x'; javaSetsLinkage(true, ic->minor, ic->t, ic->value); } foreach(ic, inputChangesPending) { if (ic->major != 't') continue; ic->major = 'x'; javaSetsTimeout(ic->tagno, ic->value, ic->t, ic->minor - '0'); } freeList(&inputChangesPending); rebuildSelectors(); if (v = js_reset) { js_reset = 0; if (t = tagFromJavaVar(v)) formReset(t); } if (v = js_submit) { js_submit = 0; if (t = tagFromJavaVar(v)) { char *post; bool rc = infPush(t->seqno, &post); if (rc) gotoLocation(post, 0, false); else showError(); } } } /* runScriptsPending */ void preFormatCheck(int tagno, bool * pretag, bool * slash) { const struct htmlTag *t; *pretag = *slash = false; if (tagno >= 0 && tagno < cw->numTags) { t = tagList[tagno]; *pretag = (t->action == TAGACT_PRE); *slash = t->slash; } } /* preFormatCheck */ /* is there a doorway from html to js? */ static bool jsDoorway(void) { const struct htmlTag *t; int j; for (j = 0; j < cw->numTags; ++j) { t = tagList[j]; if (t->doorway) return true; } debugPrint(3, "no js doorway"); return false; } /* jsDoorway */ char *htmlParse(char *buf, int remote) { char *a, *newbuf; struct htmlTag *t; if (tagList) i_printfExit(MSG_HtmlNotreentrant); if (remote >= 0) browseLocal = !remote; initTagArray(); cw->baseset = false; cw->hbase = cloneString(cw->fileName); /* call the tidy parser to build the html nodes */ html2nodes(buf, true); nzFree(buf); htmlNodesIntoTree(0, NULL); prerender(0); /* if the html doesn't use javascript, then there's * no point in generating it. * This is typical of generated html, from pdf for instance, * or the html that is in email. */ if (cw->jcx && !jsDoorway()) freeJavaContext(cw); if (isJSAlive) { decorate(0); set_basehref(cw->hbase); runScriptsPending(); runOnload(); runScriptsPending(); } a = render(0); debugPrint(6, "|%s|\n", a); newbuf = htmlReformat(a); nzFree(a); set_property_string(cw->docobj, "readyState", "complete"); return newbuf; } /* htmlParse */ /* See if there are simple tags like

or */ bool htmlTest(void) { int j, ln; int cnt = 0; int fsize = 0; /* file size */ char look[12]; bool firstline = true; for (ln = 1; ln <= cw->dol; ++ln) { char *p = (char *)fetchLine(ln, -1); char c; int state = 0; while (isspaceByte(*p) && *p != '\n') ++p; if (*p == '\n') continue; /* skip blank line */ if (firstline && *p == '<') { /* check for 1 && (p[j] == '>' || isspaceByte(p[j]))) { /* something we recognize? */ const struct tagInfo *ti; for (ti = availableTags; ti->name[0]; ++ti) if (stringEqualCI(ti->name, look)) return true; } /* leading tag */ } /* leading < */ firstline = false; /* count tags through the buffer */ for (j = 0; (c = p[j]) != '\n'; ++j) { if (state == 0) { if (c == '<') state = 1; continue; } if (state == 1) { if (c == '/') state = 2; else if (isalphaByte(c)) state = 3; else state = 0; continue; } if (state == 2) { if (isalphaByte(c)) state = 3; else state = 0; continue; } if (isalphaByte(c)) continue; if (c == '>') ++cnt; state = 0; } fsize += j; } /* loop over lines */ /* we need at least one of these tags every 300 characters. * And we need at least 4 such tags. * Remember, you can always override by putting at the top. */ return (cnt >= 4 && cnt * 300 >= fsize); } /* htmlTest */ /* Show an input field */ void infShow(int tagno, const char *search) { const struct htmlTag *t = tagList[tagno], *v; const char *s; int i, cnt; bool show; s = inp_types[t->itype]; if (*s == ' ') ++s; printf("%s", s); if (t->multiple) i_printf(MSG_Many); if (t->itype >= INP_TEXT && t->itype <= INP_NUMBER && t->lic) printf("[%d]", t->lic); if (t->itype == INP_TA) { const char *rows = attribVal(t, "rows"); const char *cols = attribVal(t, "cols"); const char *wrap = attribVal(t, "wrap"); if (rows && cols) { printf("[%sx%s", rows, cols); if (wrap && stringEqualCI(wrap, "virtual")) i_printf(MSG_Recommended); i_printf(MSG_Close); } cnzFree(rows); cnzFree(cols); cnzFree(wrap); } /* text area */ if (t->name) printf(" %s", t->name); nl(); if (t->itype != INP_SELECT) return; /* display the options in a pick list */ /* If a search string is given, display the options containing that string. */ cnt = 0; show = false; for (i = 0; i < cw->numTags; ++i) { v = tagList[i]; if (v->controller != t) continue; if (!v->textval) continue; ++cnt; if (*search && !strstrCI(v->textval, search)) continue; show = true; printf("%3d %s\n", cnt, v->textval); } if (!show) { if (!search) i_puts(MSG_NoOptions); else i_printf(MSG_NoOptionsMatch, search); } } /* infShow */ /********************************************************************* Update an input field in the current edbrowse buffer. This can be done for one of two reasons. First, the user has interactively entered a value in the form, such as i=foobar In this case fromForm will be set to true. I need to find the tag in the current buffer. He just modified it, so it ought to be there. If it isn't there, print an error and do nothing. The second case: the value has been changed by form reset, either the user has pushed the reset button or javascript has called form.reset. Here fromForm is false. I'm not sure why js would reset a form before the page was even rendered; that's the only way the line should not be found, or perhaps if that section of the web page was deleted. notify = true causes the line to be printed after the change is made. Notify true and fromForm false is impossible. You don't need to be notified as each variable is changed during a reset. The new line replaces the old, and the old is freed. This works because undo is disabled in browse mode. *********************************************************************/ static void updateFieldInBuffer(int tagno, const char *newtext, bool notify, bool fromForm) { int ln, idx, n, plen; char *p, *s, *t, *new; if (locateTagInBuffer(tagno, &ln, &p, &s, &t)) { n = (plen = pstLength((pst) p)) + strlen(newtext) - (t - s); new = allocMem(n); memcpy(new, p, s - p); strcpy(new + (s - p), newtext); memcpy(new + strlen(new), t, plen - (t - p)); free(cw->map[ln].text); cw->map[ln].text = new; if (notify) displayLine(ln); return; } if (fromForm) i_printf(MSG_NoTagFound, tagno, newtext); } /* updateFieldInBuffer */ /* Update an input field. */ bool infReplace(int tagno, const char *newtext, bool notify) { const struct htmlTag *t = tagList[tagno], *v; const struct htmlTag *form = t->controller; char *display; int itype = t->itype; int newlen = strlen(newtext); int i; /* sanity checks on the input */ if (itype <= INP_SUBMIT) { int b = MSG_IsButton; if (itype == INP_SUBMIT || itype == INP_IMAGE) b = MSG_SubmitButton; if (itype == INP_RESET) b = MSG_ResetButton; setError(b); return false; } if (itype == INP_TA) { setError(MSG_Textarea, t->lic); return false; } if (t->rdonly) { setError(MSG_Readonly); return false; } if (strchr(newtext, '\n')) { setError(MSG_InputNewline); return false; } if (itype >= INP_TEXT && itype <= INP_NUMBER && t->lic && newlen > t->lic) { setError(MSG_InputLong, t->lic); return false; } if (itype >= INP_RADIO) { if (newtext[0] != '+' && newtext[0] != '-' || newtext[1]) { setError(MSG_InputRadio); return false; } if (itype == INP_RADIO && newtext[0] == '-') { setError(MSG_ClearRadio); return false; } } /* Two lines, clear the "other" radio button, and set this one. */ if (itype == INP_SELECT) { if (!locateOptions(t, newtext, 0, 0, false)) return false; locateOptions(t, newtext, &display, 0, false); updateFieldInBuffer(tagno, display, notify, true); nzFree(display); } if (itype == INP_FILE) { if (!envFile(newtext, &newtext)) return false; if (newtext[0] && access(newtext, 4)) { setError(MSG_FileAccess, newtext); return false; } } if (itype == INP_NUMBER) { if (*newtext && stringIsNum(newtext) < 0) { setError(MSG_NumberExpected); return false; } } if (itype == INP_RADIO && form && t->name && *newtext == '+') { /* clear the other radio button */ for (i = 0; i < cw->numTags; ++i) { v = tagList[i]; if (v->controller != form) continue; if (v->itype != INP_RADIO) continue; if (!v->name) continue; if (!stringEqual(v->name, t->name)) continue; if (fieldIsChecked(v->seqno) == true) updateFieldInBuffer(v->seqno, "-", false, true); } } if (itype != INP_SELECT) { updateFieldInBuffer(tagno, newtext, notify, true); } if (itype >= INP_RADIO && tagHandler(t->seqno, "onclick")) { if (!isJSAlive) runningError(MSG_NJNoOnclick); else { jSyncup(); run_function_bool(t->jv, "onclick"); jSideEffects(); if (js_redirects) return true; } } if (itype >= INP_TEXT && itype <= INP_SELECT && tagHandler(t->seqno, "onchange")) { if (!isJSAlive) runningError(MSG_NJNoOnchange); else { jSyncup(); run_function_bool(t->jv, "onchange"); jSideEffects(); if (js_redirects) return true; } } return true; } /* infReplace */ /********************************************************************* Reset or submit a form. This function could be called by javascript, as well as a human. It must therefore update the js variables and the text simultaneously. Most of this work is done by resetVar(). To reset a variable, copy its original value, in the html tag, back to the text buffer, and over to javascript. *********************************************************************/ static void resetVar(struct htmlTag *t) { int itype = t->itype; const char *w = t->rvalue; bool bval; /* This is a kludge - option looks like INP_SELECT */ if (t->action == TAGACT_OPTION) itype = INP_SELECT; if (itype <= INP_SUBMIT) return; if (itype >= INP_SELECT && itype != INP_TA) { bval = t->rchecked; t->checked = bval; w = bval ? "+" : "-"; } if (itype == INP_TA) { int cx = t->lic; if (cx) sideBuffer(cx, w, -1, 0); } else if (itype != INP_HIDDEN && itype != INP_SELECT) updateFieldInBuffer(t->seqno, w, false, false); if (itype >= INP_TEXT && itype <= INP_FILE || itype == INP_TA) { nzFree(t->value); t->value = cloneString(t->rvalue); } if (!t->jv) return; if (!isJSAlive) return; if (itype >= INP_RADIO) { set_property_bool(t->jv, "checked", bval); } else if (itype == INP_SELECT) { /* remember this means option */ set_property_bool(t->jv, "selected", bval); if (bval && !t->controller->multiple && t->controller->jv) set_property_number(t->controller->jv, "selectedIndex", t->lic); } else set_property_string(t->jv, "value", w); } /* resetVar */ static void formReset(const struct htmlTag *form) { struct htmlTag *t; int i, itype; char *display; for (i = 0; i < cw->numTags; ++i) { t = tagList[i]; if (t->action == TAGACT_OPTION) { resetVar(t); continue; } if (t->action != TAGACT_INPUT) continue; if (t->controller != form) continue; itype = t->itype; if (itype != INP_SELECT) { resetVar(t); continue; } if (t->jv && isJSAlive) set_property_number(t->jv, "selectedIndex", -1); } /* loop over tags */ /* loop again to look for select, now that options are set */ for (i = 0; i < cw->numTags; ++i) { t = tagList[i]; if (t->action != TAGACT_INPUT) continue; if (t->controller != form) continue; itype = t->itype; if (itype != INP_SELECT) continue; display = displayOptions(t); updateFieldInBuffer(t->seqno, display, false, false); nzFree(t->value); t->value = display; /* this should now be the same as t->rvalue, but I guess I'm * not going to check for that, or take advantage of it. */ } /* loop over tags */ i_puts(MSG_FormReset); } /* formReset */ /* Fetch a field value (from a form) to post. */ /* The result is allocated */ static char *fetchTextVar(const struct htmlTag *t) { char *v; // js must not muck with the value of a file field if (t->itype != INP_FILE) { if (t->jv && isJSAlive) return get_property_string(t->jv, "value"); } if (t->itype > INP_HIDDEN) { v = getFieldFromBuffer(t->seqno); if (v) return v; } /* Revert to the default value */ return cloneString(t->value); } /* fetchTextVar */ static bool fetchBoolVar(const struct htmlTag *t) { int checked; if (t->jv && isJSAlive) return get_property_bool(t->jv, (t->action == TAGACT_OPTION ? "selected" : "checked")); checked = fieldIsChecked(t->seqno); if (checked < 0) checked = t->rchecked; return checked; } /* fetchBoolVar */ /* Some information on posting forms can be found here. * http://www.w3.org/TR/REC-html40/interact/forms.html */ static char *pfs; /* post form string */ static int pfs_l; static const char *boundary; static void postDelimiter(char fsep) { char c = pfs[strlen(pfs) - 1]; if (c == '?' || c == '\1') return; if (fsep == '-') { stringAndString(&pfs, &pfs_l, "--"); stringAndString(&pfs, &pfs_l, boundary); stringAndChar(&pfs, &pfs_l, '\r'); fsep = '\n'; } stringAndChar(&pfs, &pfs_l, fsep); } /* postDelimiter */ static bool postNameVal(const char *name, const char *val, char fsep, uchar isfile) { char *enc; const char *ct, *ce; /* content type, content encoding */ if (!name) name = emptyString; if (!val) val = emptyString; if (!*name && !*val) return true; postDelimiter(fsep); switch (fsep) { case '&': enc = encodePostData(name); stringAndString(&pfs, &pfs_l, enc); stringAndChar(&pfs, &pfs_l, '='); nzFree(enc); break; case '\n': stringAndString(&pfs, &pfs_l, name); stringAndString(&pfs, &pfs_l, "=\r\n"); break; case '-': stringAndString(&pfs, &pfs_l, "Content-Disposition: form-data; name=\""); stringAndString(&pfs, &pfs_l, name); stringAndChar(&pfs, &pfs_l, '"'); /* I'm leaving nl off, in case we need ; filename */ break; } /* switch */ if (!*val && fsep == '&') return true; switch (fsep) { case '&': enc = encodePostData(val); stringAndString(&pfs, &pfs_l, enc); nzFree(enc); break; case '\n': stringAndString(&pfs, &pfs_l, val); stringAndString(&pfs, &pfs_l, eol); break; case '-': if (isfile) { if (isfile & 2) { stringAndString(&pfs, &pfs_l, "; filename=\""); stringAndString(&pfs, &pfs_l, val); stringAndChar(&pfs, &pfs_l, '"'); } if (!encodeAttachment(val, 0, true, &ct, &ce, &enc)) return false; val = enc; /* remember to free val in this case */ } else { const char *s; ct = "text/plain"; /* Anything nonascii makes it 8bit */ ce = "7bit"; for (s = val; *s; ++s) if (*s < 0) { ce = "8bit"; break; } } stringAndString(&pfs, &pfs_l, "\r\nContent-Type: "); stringAndString(&pfs, &pfs_l, ct); stringAndString(&pfs, &pfs_l, "\r\nContent-Transfer-Encoding: "); stringAndString(&pfs, &pfs_l, ce); stringAndString(&pfs, &pfs_l, "\r\n\r\n"); stringAndString(&pfs, &pfs_l, val); stringAndString(&pfs, &pfs_l, eol); if (isfile) nzFree(enc); break; } /* switch */ return true; } /* postNameVal */ static bool formSubmit(const struct htmlTag *form, const struct htmlTag *submit) { const struct htmlTag *t; int i, j, itype; char *name, *dynamicvalue = NULL; /* dynamicvalue needs to be freed with nzFree. */ const char *value; char fsep = '&'; /* field separator */ bool noname = false, rc; bool bval; if (form->bymail) fsep = '\n'; if (form->mime) { fsep = '-'; boundary = makeBoundary(); stringAndString(&pfs, &pfs_l, "`mfd~"); stringAndString(&pfs, &pfs_l, boundary); stringAndString(&pfs, &pfs_l, eol); } for (i = 0; i < cw->numTags; ++i) { t = tagList[i]; if (t->action != TAGACT_INPUT) continue; if (t->controller != form) continue; itype = t->itype; if (itype <= INP_SUBMIT && t != submit) continue; name = t->name; if (!name) name = t->id; if (t == submit) { /* the submit button you pushed */ int namelen; char *nx; if (!name) continue; value = t->value; if (!value || !*value) value = "Submit"; if (t->itype != INP_IMAGE) goto success; namelen = strlen(name); nx = (char *)allocMem(namelen + 3); strcpy(nx, name); strcpy(nx + namelen, ".x"); postNameVal(nx, "0", fsep, false); nx[namelen + 1] = 'y'; postNameVal(nx, "0", fsep, false); nzFree(nx); goto success; } if (itype >= INP_RADIO) { value = t->value; bval = fetchBoolVar(t); if (!bval) continue; if (!name) noname = true; if (value && !*value) value = 0; if (itype == INP_CHECKBOX && value == 0) value = "on"; goto success; } if (itype < INP_FILE) { /* Even a hidden variable can be adjusted by js. * fetchTextVar allows for this possibility. * I didn't allow for it in the above, the value of a radio button; * hope that's not a problem. */ dynamicvalue = fetchTextVar(t); postNameVal(name, dynamicvalue, fsep, false); nzFree(dynamicvalue); dynamicvalue = NULL; continue; } if (itype == INP_TA) { int cx = t->lic; char *cxbuf; int cxlen; if (!name) noname = true; if (cx) { if (fsep == '-') { char cxstring[12]; /* do this as an attachment */ sprintf(cxstring, "%d", cx); if (!postNameVal (name, cxstring, fsep, 1)) goto fail; continue; } /* attach */ if (!unfoldBuffer(cx, true, &cxbuf, &cxlen)) goto fail; for (j = 0; j < cxlen; ++j) if (cxbuf[j] == 0) { setError(MSG_SessionNull, cx); nzFree(cxbuf); goto fail; } if (j && cxbuf[j - 1] == '\n') --j; if (j && cxbuf[j - 1] == '\r') --j; cxbuf[j] = 0; rc = postNameVal(name, cxbuf, fsep, false); nzFree(cxbuf); if (rc) continue; goto fail; } postNameVal(name, 0, fsep, false); continue; } if (itype == INP_SELECT) { char *display = getFieldFromBuffer(t->seqno); char *s, *e; if (!display) { /* off the air */ struct htmlTag *v; int i2; /* revert back to reset state */ for (i2 = 0; i2 < cw->numTags; ++i2) { v = tagList[i2]; if (v->controller == t) v->checked = v->rchecked; } display = displayOptions(t); } rc = locateOptions(t, display, 0, &dynamicvalue, false); nzFree(display); if (!rc) goto fail; /* this should never happen */ /* option could have an empty value, usually the null choice, * before you have made a selection. */ if (!*dynamicvalue) { if (!t->multiple) postNameVal(name, dynamicvalue, fsep, false); continue; } /* Step through the options */ for (s = dynamicvalue; *s; s = e) { char more; e = 0; if (t->multiple) e = strchr(s, '\1'); if (!e) e = s + strlen(s); more = *e, *e = 0; postNameVal(name, s, fsep, false); if (more) ++e; } nzFree(dynamicvalue); dynamicvalue = NULL; continue; } if (itype == INP_FILE) { /* the only one left */ dynamicvalue = fetchTextVar(t); if (!dynamicvalue) continue; if (!*dynamicvalue) continue; if (!(form->post & form->mime)) { setError(MSG_FilePost); nzFree(dynamicvalue); goto fail; } rc = postNameVal(name, dynamicvalue, fsep, 3); nzFree(dynamicvalue); dynamicvalue = NULL; if (rc) continue; goto fail; } i_printfExit(MSG_UnexSubmitForm); success: postNameVal(name, value, fsep, false); } /* loop over tags */ if (form->mime) { /* the last boundary */ stringAndString(&pfs, &pfs_l, "--"); stringAndString(&pfs, &pfs_l, boundary); stringAndString(&pfs, &pfs_l, "--\r\n"); } i_puts(MSG_FormSubmit); return true; fail: return false; } /* formSubmit */ /********************************************************************* Push the reset or submit button. This routine must be reentrant. You push submit, which calls this routine, which runs the onsubmit code, which checks the fields and calls form.submit(), which calls this routine. Happens all the time. *********************************************************************/ bool infPush(int tagno, char **post_string) { struct htmlTag *t = tagList[tagno]; struct htmlTag *form; int itype; int actlen; const char *action = 0; char *section; const char *prot; bool rc; *post_string = 0; /* If the tag is actually a form, then infPush() was invoked * by form.submit(). * Revert t back to 0, since there may be multiple submit buttons * on the form, and we don't know which one was pushed. */ if (t->action == TAGACT_FORM) { form = t; t = 0; itype = INP_SUBMIT; } else { form = t->controller; itype = t->itype; } if (itype > INP_SUBMIT) { setError(MSG_NoButton); return false; } if (!form && itype != INP_BUTTON) { setError(MSG_NotInForm); return false; } if (t && tagHandler(t->seqno, "onclick")) { if (!isJSAlive) runningError(itype == INP_BUTTON ? MSG_NJNoAction : MSG_NJNoOnclick); else { if (t->jv) run_function_bool(t->jv, "onclick"); jSideEffects(); if (js_redirects) return true; } } if (itype == INP_BUTTON) { if (isJSAlive && t->jv && !handlerPresent(t->jv, "onclick")) { setError(MSG_ButtonNoJS); return false; } return true; } if (itype == INP_RESET) { /* Before we reset, run the onreset code */ if (t && tagHandler(form->seqno, "onreset")) { if (!isJSAlive) runningError(MSG_NJNoReset); else { rc = true; if (form->jv) rc = run_function_bool(form->jv, "onreset"); jSideEffects(); if (!rc) return true; if (js_redirects) return true; } } /* onreset */ formReset(form); return true; } /* Before we submit, run the onsubmit code */ if (t && tagHandler(form->seqno, "onsubmit")) { if (!isJSAlive) runningError(MSG_NJNoSubmit); else { rc = true; if (form->jv) rc = run_function_bool(form->jv, "onsubmit"); jSideEffects(); if (!rc) return true; if (js_redirects) return true; } } action = form->href; /* But we defer to the java variable */ if (form->jv && isJSAlive) { char *jh = get_property_url(form->jv, true); if (jh && (!action || !stringEqual(jh, action))) { /* Tie action to the form tag, to plug a small memory leak */ nzFree(form->href); form->href = resolveURL(cw->hbase, jh); action = form->href; } nzFree(jh); } /* if no action, or action is "#", the default is the current location */ if (!action || stringEqual(action, "#")) { action = cw->hbase; } if (!action) { setError(MSG_FormNoURL); return false; } debugPrint(2, "* %s", action); prot = getProtURL(action); if (!prot) { setError(MSG_FormBadURL); return false; } if (stringEqualCI(prot, "javascript")) { if (!isJSAlive) { setError(MSG_NJNoForm); return false; } jsRunScript(form->jv, action, 0, 0); jSideEffects(); return true; } form->bymail = false; if (stringEqualCI(prot, "mailto")) { if (!validAccount(localAccount)) return false; form->bymail = true; } else if (stringEqualCI(prot, "http")) { if (form->secure) { setError(MSG_BecameInsecure); return false; } } else if (!stringEqualCI(prot, "https")) { setError(MSG_SubmitProtBad, prot); return false; } pfs = initString(&pfs_l); stringAndString(&pfs, &pfs_l, action); section = findHash(pfs); if (section) { i_printf(MSG_SectionIgnored, section); *section = 0; pfs_l = section - pfs; } section = strpbrk(pfs, "?\1"); if (section && (*section == '\1' || !(form->bymail | form->post))) { debugPrint(3, "the url already specifies some data, which will be overwritten by the data in this form"); *section = 0; pfs_l = section - pfs; } stringAndChar(&pfs, &pfs_l, (form->post ? '\1' : '?')); actlen = strlen(pfs); if (!formSubmit(form, t)) { nzFree(pfs); return false; } debugPrint(3, "%s %s", form->post ? "post" : "get", pfs + actlen); /* Handle the mail method here and now. */ if (form->bymail) { char *addr, *subj, *q; const char *tolist[2], *atlist[2]; const char *name = form->name; int newlen = strlen(pfs) - actlen; /* the new string could be longer than post */ decodeMailURL(action, &addr, &subj, 0); tolist[0] = addr; tolist[1] = 0; atlist[0] = 0; newlen += 9; /* subject: \n */ if (subj) newlen += strlen(subj); else newlen += 11 + (name ? strlen(name) : 1); ++newlen; /* null */ ++newlen; /* encodeAttachment might append another nl */ q = (char *)allocMem(newlen); if (subj) sprintf(q, "subject:%s\n", subj); else sprintf(q, "subject:html form(%s)\n", name ? name : "?"); strcpy(q + strlen(q), pfs + actlen); nzFree(pfs); i_printf(MSG_MailSending, addr); SLEEP(1); rc = sendMail(localAccount, tolist, q, -1, atlist, 0, 0, false); if (rc) i_puts(MSG_MailSent); nzFree(addr); nzFree(subj); nzFree(q); *post_string = 0; return rc; } *post_string = pfs; return true; } /* infPush */ /* I don't have any reverse pointers, so I'm just going to scan the list */ /* This doesn't come up all that often. */ struct htmlTag *tagFromJavaVar(jsobjtype v) { struct htmlTag *t = 0; int i; if (!tagList) i_printfExit(MSG_NullListInform); for (i = 0; i < cw->numTags; ++i) { t = tagList[i]; if (t->jv == v) return t; } return 0; } /* tagFromJavaVar */ /* Like the above but create it if you can't find it. */ struct htmlTag *tagFromJavaVar2(jsobjtype v, const char *tagname) { struct htmlTag *t = tagFromJavaVar(v); if (t) return t; if (!tagname) return 0; t = newTag(tagname); if (!t) { debugPrint(3, "cannot create tag node %s", tagname); return 0; } t->jv = v; /* this node now has a js object, don't decorate it again. */ t->step = 2; /* and don't render it unless it is linked into the active tree */ t->deleted = true; return t; } /* tagFromJavaVar2 */ /* Return false to stop javascript, due to a url redirect */ void javaSubmitsForm(jsobjtype v, bool reset) { if (reset) js_reset = v; else js_submit = v; } /* javaSubmitsForm */ bool handlerGoBrowse(const struct htmlTag *t, const char *name) { if (!isJSAlive) return true; if (!t->jv) return true; return run_function_bool(t->jv, name); } /* handlerGoBrowse */ /* Javascript errors, we need to see these no matter what. */ void runningError(int msg, ...) { va_list p; if (ismc) return; if (debugLevel <= 2) return; va_start(p, msg); vprintf(i_getString(msg), p); va_end(p); nl(); } /* runningError */ /********************************************************************* Diff the old screen with the new rendered screen. This is a simple front back diff algorithm. Compare the two strings from the start, how many lines are the same. Compare the two strings from the back, how many lines are the same. That zeros in on the line that has changed. Most of the time one line has changed, or a couple of adjacent lines, or a couple of nearby lines. So this should do it. sameFront counts the lines from the top that are the same. We're here because the buffers are different, so sameFront will not equal $. Lines past sameBack1 and same back2 are the same to the bottom in the two buffers. *********************************************************************/ static int sameFront, sameBack1, sameBack2; static const char *newChunkStart, *newChunkEnd; static void frontBackDiff(const char *b1, const char *b2) { const char *f1, *f2, *s1, *s2, *e1, *e2; sameFront = 0; s1 = b1, s2 = b2; f1 = b1, f2 = b2; while (*s1 == *s2 && *s1) { if (*s1 == '\n') { f1 = s1 + 1, f2 = s2 + 1; ++sameFront; } ++s1, ++s2; } s1 = b1 + strlen(b1); s2 = b2 + strlen(b2); while (s1 > f1 && s2 > f2 && s1[-1] == s2[-1]) --s1, --s2; if (s1 == f1 && s2[-1] == '\n') goto mark_e; if (s2 == f2 && s1[-1] == '\n') goto mark_e; /* advance both pointers to newline or null */ while (*s1 && *s1 != '\n') ++s1, ++s2; /* these buffers should always end in nl, so the next if should always be true */ if (*s1 == '\n') ++s1, ++s2; mark_e: e1 = s1, e2 = s2; sameBack1 = sameFront; for (s1 = f1; s1 < e1; ++s1) if (*s1 == '\n') ++sameBack1; if (s1 > f1 && s1[-1] != '\n') // should never happen ++sameBack1; sameBack2 = sameFront; for (s2 = f2; s2 < e2; ++s2) if (*s2 == '\n') ++sameBack2; if (s2 > f2 && s2[-1] != '\n') // should never happen ++sameBack2; newChunkStart = f2; newChunkEnd = e2; } /* frontBackDiff */ static bool backgroundJS; /* Rerender the buffer and notify of any lines that have changed */ void rerender(bool rr_command) { char *a, *snap, *newbuf; int j; debugPrint(4, "rerender"); if (rr_command) { // You might have changed some input fields on the screen, then typed rr jSyncup(); } // screen snap, to compare with the new screen. if (!unfoldBufferW(cw, false, &snap, &j)) { snap = 0; puts("no screen snap available"); return; } /* and the new screen */ a = render(0); newbuf = htmlReformat(a); nzFree(a); /* the high runner case, most of the time nothing changes, * and we can check that efficiently with strcmp */ if (stringEqual(newbuf, snap)) { if (rr_command) i_puts(MSG_NoChange); nzFree(newbuf); nzFree(snap); return; } frontBackDiff(snap, newbuf); if (sameBack1 > sameFront) delText(sameFront + 1, sameBack1); if (sameBack2 > sameFront) addTextToBuffer((pst) newChunkStart, newChunkEnd - newChunkStart, sameFront, false); cw->undoable = false; if (!backgroundJS) { /* It's almost easier to do it than to report it. */ if (sameBack2 == sameFront) { /* delete */ if (sameBack1 == sameFront + 1) i_printf(MSG_LineDelete1, sameFront); else i_printf(MSG_LineDelete2, sameBack1 - sameFront, sameFront); } else if (sameBack1 == sameFront) { if (sameBack2 == sameFront + 1) i_printf(MSG_LineAdd1, sameFront + 1); else i_printf(MSG_LineAdd2, sameFront + 1, sameBack2); } else { if (sameBack1 == sameFront + 1 && sameBack2 == sameFront + 1) i_printf(MSG_LineUpdate1, sameFront + 1); else if (sameBack2 == sameFront + 1) i_printf(MSG_LineUpdate2, sameBack1 - sameFront, sameFront + 1); else i_printf(MSG_LineUpdate3, sameFront + 1, sameBack2); } } nzFree(newbuf); nzFree(snap); } /* rerender */ /* mark the tags on the deleted lines as deleted */ void delTags(int startRange, int endRange) { pst p; int j, tagno, action; struct htmlTag *t, *last_td; /* no javascript, no cause to ever rerender */ if (!cw->jcx) return; for (j = startRange; j <= endRange; ++j) { p = fetchLine(j, -1); last_td = 0; for (; *p != '\n'; ++p) { if (*p != InternalCodeChar) continue; tagno = strtol(p + 1, (char **)&p, 10); /* could be 0, but should never be negative */ if (tagno <= 0) continue; t = tagList[tagno]; /* Only mark certain tags as deleted. * If you mark

deleted, it could wipe out half the page. */ action = t->action; if (action == TAGACT_TEXT || action == TAGACT_HR || action == TAGACT_LI || action == TAGACT_IMAGE) t->deleted = true; #if 0 /* this seems to cause more trouble than it's worth */ if (action == TAGACT_TD) { printf("td%d\n", tagno); if (last_td) last_td->deleted = true; last_td = t; } #endif } } } /* delTags */ /* turn an onunload function into a clickable hyperlink */ static void unloadHyperlink(const char *js_function, const char *where) { dwStart(); stringAndString(&cw->dw, &cw->dw_l, "

Onclose "); stringAndString(&cw->dw, &cw->dw_l, where); stringAndString(&cw->dw, &cw->dw_l, "
"); } /* unloadHyperlink */ /* Run the various onload functions */ /* Turn the onunload functions into hyperlinks */ /* This runs after the page is parsed and before the various javascripts run, is that right? */ static void runOnload(void) { int i, action; int fn; /* form number */ struct htmlTag *t; if (!isJSAlive) return; /* window and document onload */ run_function_bool(cw->winobj, "onload"); run_function_bool(cw->docobj, "onload"); fn = -1; for (i = 0; i < cw->numTags; ++i) { t = tagList[i]; if (t->slash) continue; action = t->action; if (action == TAGACT_FORM) ++fn; if (!t->jv) continue; if (action == TAGACT_BODY && t->onload) run_function_bool(t->jv, "onload"); if (action == TAGACT_BODY && t->onunload) unloadHyperlink("document.body.onunload", "Body"); if (action == TAGACT_FORM && t->onload) run_function_bool(t->jv, "onload"); /* tidy5 says there is no form.onunload */ if (action == TAGACT_FORM && t->onunload) { char formfunction[48]; sprintf(formfunction, "document.forms[%d].onunload", fn); unloadHyperlink(formfunction, "Form"); } } } /* runOnload */ /********************************************************************* Manage js timers here. It's a simple list of timers, assuming there aren't too many. Store the seconds and milliseconds when the timer should fire, the code to execute, and the timer object, which becomes "this". *********************************************************************/ struct jsTimer { struct jsTimer *next, *prev; struct ebWindow *w; /* edbrowse window holding this timer */ time_t sec; int ms; bool isInterval; int jump_sec; /* for interval */ int jump_ms; jsobjtype timerObject; }; /* list of pending timers */ struct listHead timerList = { &timerList, &timerList }; static time_t now_sec; static int now_ms; static void currentTime(void) { struct timeval tv; gettimeofday(&tv, NULL); now_sec = tv.tv_sec; now_ms = tv.tv_usec / 1000; } /* currentTime */ static void javaSetsTimeout(int n, const char *jsrc, jsobjtype to, bool isInterval) { struct jsTimer *jt; if (jsrc[0] == 0) return; /* nothing to run */ if (stringEqual(jsrc, "-")) { // delete a timer foreach(jt, timerList) { if (jt->timerObject == to) { debugPrint(4, "timer delete"); delFromList(jt); nzFree(jt); return; } } // not found, just return. return; } jt = allocMem(sizeof(struct jsTimer)); jt->sec = n / 1000; jt->ms = n % 1000; jt->isInterval = isInterval; if (isInterval) jt->jump_sec = n / 1000, jt->jump_ms = n % 1000; currentTime(); jt->sec += now_sec; jt->ms += now_ms; if (jt->ms >= 1000) jt->ms -= 1000, ++jt->sec; jt->timerObject = to; jt->w = cw; addToListBack(&timerList, jt); debugPrint(4, "timer %d %s\n", n, jsrc); } /* javaSetsTimeout */ static struct jsTimer *soonest(void) { struct jsTimer *t, *best_t = 0; if (listIsEmpty(&timerList)) return 0; foreach(t, timerList) { if (!best_t || t->sec < best_t->sec || t->sec == best_t->sec && t->ms < best_t->ms) best_t = t; } return best_t; } /* soonest */ bool timerWait(int *delay_sec, int *delay_ms) { struct jsTimer *jt = soonest(); if (!jt) return false; currentTime(); if (now_sec > jt->sec || now_sec == jt->sec && now_ms >= jt->ms) *delay_sec = *delay_ms = 0; else { *delay_sec = jt->sec - now_sec; *delay_ms = (jt->ms - now_ms); if (*delay_ms < 0) *delay_ms += 1000, --*delay_sec; } return true; } /* timerWait */ void delTimers(struct ebWindow *w) { int delcount = 0; struct jsTimer *jt, *jnext; // if not browsing with javascript then there is nothing to do here. if (!w->jcx) return; for (jt = timerList.next; jt != (void *)&timerList; jt = jnext) { jnext = jt->next; if (jt->w == w) { ++delcount; delFromList(jt); nzFree(jt); } } debugPrint(4, "%d timers deleted", delcount); } /* delTimers */ void runTimers(void) { struct jsTimer *jt; struct ebWindow *save_cw = cw; currentTime(); while (jt = soonest()) { if (jt->sec > now_sec || jt->sec == now_sec && jt->ms > now_ms) break; cw = jt->w; backgroundJS = true; run_function_bool(jt->timerObject, "onclick"); if (!jt->isInterval) { delFromList(jt); nzFree(jt); jt = NULL; } else { jt->sec = now_sec + jt->jump_sec; jt->ms = now_ms + jt->jump_ms; if (jt->ms >= 1000) jt->ms -= 1000, ++jt->sec; } runScriptsPending(); if (cw != save_cw) { /* background window, go ahead and rerender, silently. */ rerender(false); } backgroundJS = false; } cw = save_cw; } /* runTimers */ void javaOpensWindow(const char *href, const char *name) { struct htmlTag *t; char *copy, *r; const char *a; bool replace = false; if (*href == 'r') replace = true; ++href; if (!*href) { debugPrint(3, "javascript is opening a blank window"); return; } copy = cloneString(href); unpercentURL(copy); r = resolveURL(cw->hbase, copy); nzFree(copy); if (replace || cw->browseMode && !backgroundJS) { gotoLocation(r, 0, replace); return; } /* Turn the new window into a hyperlink. */ /* just shovel this onto dw, as though it came from document.write() */ dwStart(); stringAndString(&cw->dw, &cw->dw_l, "

"); stringAndString(&cw->dw, &cw->dw_l, i_getString(MSG_Redirect)); stringAndString(&cw->dw, &cw->dw_l, ":
\n"); } /* javaOpensWindow */ /* Push an attribute onto an html tag. */ /* Value is already allocated, name is not. */ /* So far only used by javaSetsLinkage. */ static void setTagAttr(struct htmlTag *t, const char *name, char *val) { int nattr = 0; /* number of attributes */ int i = -1; if (!val) return; if (t->attributes) { for (nattr = 0; t->attributes[nattr]; ++nattr) if (stringEqualCI(name, t->attributes[nattr])) i = nattr; } if (i >= 0) { cnzFree(t->atvals[i]); t->atvals[i] = val; return; } /* push */ if (!nattr) { t->attributes = allocMem(sizeof(char *) * 2); t->atvals = allocMem(sizeof(char *) * 2); } else { t->attributes = reallocMem(t->attributes, sizeof(char *) * (nattr + 2)); t->atvals = reallocMem(t->atvals, sizeof(char *) * (nattr + 2)); } t->attributes[nattr] = cloneString(name); t->atvals[nattr] = val; ++nattr; t->attributes[nattr] = 0; t->atvals[nattr] = 0; } /* setTagAttr */ void javaSetsLinkage(bool after, char type, jsobjtype p_j, const char *rest) { struct htmlTag *parent, *add, *before, *c, *t; jsobjtype *a_j, *b_j; char p_name[MAXTAGNAME], a_name[MAXTAGNAME], b_name[MAXTAGNAME]; int action; char *jst; // java string // Postpone anything other than create until after js is finished, // so we can query js variables. if (!after && type != 'c') { struct inputChange *ic; ic = allocMem(sizeof(struct inputChange) + strlen(rest)); // Yeah I know, this isn't a pointer to htmlTag. ic->t = p_j; ic->tagno = 0; ic->major = 'l'; ic->minor = type; strcpy(ic->value, rest); addToListBack(&inputChangesPending, ic); return; } sscanf(rest, "%s %p,%s %p,%s ", p_name, &a_j, a_name, &b_j, b_name); /* options are relinked by rebuildSelectors, not here. */ if (stringEqual(p_name, "option")) return; parent = tagFromJavaVar2(p_j, p_name); if (type == 'c') /* create */ return; if (stringEqual(a_name, "option")) return; add = tagFromJavaVar2(a_j, a_name); if (!parent || !add) return; if (type == 'r') { /* add is a misnomer here, it's being removed */ add->deleted = true; add->parent = NULL; if (parent->firstchild == add) parent->firstchild = add->sibling; else { c = parent->firstchild; if (c) { for (; c->sibling; c = c->sibling) { if (c->sibling != add) continue; c->sibling = add->sibling; break; } } } return; } if (type == 'b') { /* insertBefore */ before = tagFromJavaVar2(b_j, b_name); if (!before) return; c = parent->firstchild; if (!c) return; if (c == before) { parent->firstchild = add; add->sibling = before; goto ab; } while (c->sibling && c->sibling != before) c = c->sibling; if (!c->sibling) return; c->sibling = add; add->sibling = before; goto ab; } /* type = a, appendchild */ if (!parent->firstchild) parent->firstchild = add; else { c = parent->firstchild; while (c->sibling) c = c->sibling; c->sibling = add; } ab: add->parent = parent; add->deleted = false; /* Bad news, we have to replicate some of the prerender logic here. */ /* This node is attached to the tree, just like an html tag would be. */ t = add; action = t->action; t->name = get_property_string(t->jv, "name"); t->id = get_property_string(t->jv, "id"); switch (action) { case TAGACT_INPUT: jst = get_property_string(t->jv, "type"); setTagAttr(t, "type", jst); t->value = get_property_string(t->jv, "value"); htmlInputHelper(t); break; case TAGACT_OPTION: if (!t->value) t->value = emptyString; if (!t->textval) t->textval = emptyString; break; case TAGACT_TA: t->action = TAGACT_INPUT; t->itype = INP_TA; t->value = get_property_string(t->jv, "value"); if (!t->value) t->value = emptyString; t->rvalue = emptyString; // Need to create the side buffer here. formControl(t, true); break; case TAGACT_SELECT: t->action = TAGACT_INPUT; t->itype = INP_SELECT; if (has_property(t->jv, "multiple")) t->multiple = true; formControl(t, true); break; case TAGACT_TR: t->controller = findOpenTag(t, TAGACT_TABLE); break; case TAGACT_TD: t->controller = findOpenTag(t, TAGACT_TR); break; } /* switch */ } /* javaSetsLinkage */ /* the new string, the result of the render operation */ static char *ns; static int ns_l; static bool invisible, tdfirst; static int listnest; /* count nested lists */ /* None of these tags nest, so it is reasonable to talk about * the current open tag. */ static struct htmlTag *currentForm, *currentA; static void tagInStream(int tagno) { char buf[32]; sprintf(buf, "%c%d*", InternalCodeChar, tagno); stringAndString(&ns, &ns_l, buf); } /* tagInStream */ /* see if a number or star is pending, waiting to be printed */ static void liCheck(struct htmlTag *t) { struct htmlTag *ltag; /* the list tag */ if (listnest && (ltag = findOpenList(t)) && ltag->post) { char olbuf[32]; if (ltag->ninp) tagInStream(ltag->ninp); if (ltag->action == TAGACT_OL) { int j = ++ltag->lic; sprintf(olbuf, "%d. ", j); } else { strcpy(olbuf, "* "); } if (!invisible) stringAndString(&ns, &ns_l, olbuf); ltag->post = false; } } /* liCheck */ static struct htmlTag *deltag; static void renderNode(struct htmlTag *t, bool opentag) { int tagno = t->seqno; char hnum[40]; /* hidden number */ #define ns_hnum() stringAndString(&ns, &ns_l, hnum) #define ns_ic() stringAndChar(&ns, &ns_l, InternalCodeChar) int j, l; int itype; /* input type */ const struct tagInfo *ti = t->info; int action = t->action; char c; bool retainTag; const char *a; /* usually an attribute */ char *u; struct htmlTag *ltag; /* list tag */ #if 0 printf("rend %c%s\n", (opentag ? ' ' : '/'), t->info->name); #endif if (deltag) { if (t == deltag && !opentag) deltag = 0; li_hide: /* we can skate past the li tag, but still need to increment the count */ if (action == TAGACT_LI && opentag && (ltag = findOpenList(t)) && ltag->action == TAGACT_OL) ++ltag->lic; return; } if (t->deleted) { deltag = t; goto li_hide; } if (!opentag && ti->bits & TAG_NOSLASH) return; retainTag = true; if (invisible) retainTag = false; if (ti->bits & TAG_INVISIBLE) { retainTag = false; invisible = opentag; /* special case for noscript with no js */ if (stringEqual(ti->name, "noscript") && !cw->jcx) invisible = false; } switch (action) { case TAGACT_TEXT: if (!t->textval && t->jv) { /* A text node from html should always contain a string. But if this node * is created by document.createTextNode(), the string is * down in the member "data". */ t->textval = get_property_string(t->jv, "data"); /* Unfortunately this does not reflect subsequent changes to TextNode.data. * either we query js every time, on every piece of text, * or we include a setter so that TextNode.data assignment has a side effect. */ } if (!t->textval) break; liCheck(t); if (!invisible) { tagInStream(tagno); stringAndString(&ns, &ns_l, t->textval); } break; case TAGACT_A: liCheck(t); currentA = (opentag ? t : 0); if (!retainTag) break; if (t->href) { if (opentag) sprintf(hnum, "%c%d{", InternalCodeChar, tagno); else sprintf(hnum, "%c0}", InternalCodeChar); } else { if (opentag) sprintf(hnum, "%c%d*", InternalCodeChar, tagno); else hnum[0] = 0; } ns_hnum(); break; case TAGACT_OL: case TAGACT_UL: t->lic = t->slic; t->post = false; if (opentag) ++listnest; else --listnest; case TAGACT_DL: case TAGACT_DT: case TAGACT_DD: case TAGACT_DIV: case TAGACT_BR: case TAGACT_P: case TAGACT_SPAN: case TAGACT_NOP: nop: if (invisible) break; j = ti->para; if (opentag) j &= 3; else j >>= 2; if (j) { c = '\f'; if (j == 1) { c = '\r'; if (action == TAGACT_BR) c = '\n'; } stringAndChar(&ns, &ns_l, c); } /* tags with id= have to be part of the screen, so you can jump to them */ if (t->id && opentag && action != TAGACT_LI) tagInStream(tagno); break; case TAGACT_PRE: if (!retainTag) break; /* one of those rare moments when I really need in the text stream */ j = (opentag ? tagno : t->balance->seqno); /* I need to manage the paragraph breaks here, rather than t->info->para, * which would rule if I simply redirected to nop. * But the order is wrong if I do that. */ if (opentag) stringAndChar(&ns, &ns_l, '\f'); sprintf(hnum, "%c%d*", InternalCodeChar, j); ns_hnum(); if (!opentag) stringAndChar(&ns, &ns_l, '\f'); break; case TAGACT_FORM: currentForm = (opentag ? t : 0); goto nop; case TAGACT_INPUT: if (!opentag) break; itype = t->itype; if (itype == INP_HIDDEN) break; if (!retainTag) break; liCheck(t); if (itype == INP_TA) { j = t->lic; if (j) sprintf(hnum, "%c%d", InternalCodeChar, t->seqno, j, InternalCodeChar); else strcpy(hnum, ""); ns_hnum(); break; } sprintf(hnum, "%c%d<", InternalCodeChar, tagno); ns_hnum(); if (itype < INP_RADIO) { if (t->value[0]) stringAndString(&ns, &ns_l, t->value); else if (itype == INP_SUBMIT || itype == INP_IMAGE) stringAndString(&ns, &ns_l, i_getString(MSG_Submit)); else if (itype == INP_RESET) stringAndString(&ns, &ns_l, i_getString(MSG_Reset)); else if (itype == INP_BUTTON) stringAndString(&ns, &ns_l, i_getString(MSG_Push)); } else stringAndChar(&ns, &ns_l, (t->checked ? '+' : '-')); if (currentForm && (itype == INP_SUBMIT || itype == INP_IMAGE)) { if (currentForm->secure) stringAndString(&ns, &ns_l, i_getString(MSG_Secure)); if (currentForm->bymail) stringAndString(&ns, &ns_l, i_getString(MSG_Bymail)); } ns_ic(); stringAndString(&ns, &ns_l, "0>"); break; case TAGACT_LI: if ((ltag = findOpenList(t))) { ltag->post = true; /* borrow ninp to store the tag number of

  • */ ltag->ninp = t->seqno; } goto nop; case TAGACT_HR: liCheck(t); if (retainTag) { tagInStream(tagno); stringAndString(&ns, &ns_l, "\r----------\r"); } break; case TAGACT_TR: if (opentag) tdfirst = true; case TAGACT_TABLE: goto nop; case TAGACT_TD: if (!retainTag) break; if (tdfirst) tdfirst = false; else { liCheck(t); j = ns_l; while (j && ns[j - 1] == ' ') --j; ns[j] = 0; ns_l = j; stringAndChar(&ns, &ns_l, TableCellChar); } tagInStream(tagno); break; /* This is strictly for rendering math pages written with my particular css. * becomes TAGACT_SUP, which means superscript. * sub is subscript and ovb is overbar. * Sorry to put my little quirks into this program, but hey, * it's my program. */ case TAGACT_SUP: case TAGACT_SUB: case TAGACT_OVB: if (!retainTag) break; if (action == TAGACT_SUB) j = 1; if (action == TAGACT_SUP) j = 2; if (action == TAGACT_OVB) j = 3; if (opentag) { static const char *openstring[] = { 0, "[", "^(", "`" }; t->lic = ns_l; liCheck(t); stringAndString(&ns, &ns_l, openstring[j]); break; } if (j == 3) { stringAndChar(&ns, &ns_l, '\''); break; } /* backup, and see if we can get rid of the parentheses or brackets */ l = t->lic + j; u = ns + l; /* skip past tag indicator */ if (*u == InternalCodeChar) { ++u; while (isdigit(*u)) ++u; ++u; } if (j == 2 && isalphaByte(u[0]) && !u[1]) goto unparen; if (j == 2 && (stringEqual(u, "th") || stringEqual(u, "rd") || stringEqual(u, "nd") || stringEqual(u, "st"))) { strmove(ns + l - 2, ns + l); ns_l -= 2; break; } while (isdigitByte(*u)) ++u; if (!*u) goto unparen; stringAndChar(&ns, &ns_l, (j == 2 ? ')' : ']')); break; unparen: /* ok, we can trash the original ( or [ */ l = t->lic + j; strmove(ns + l - 1, ns + l); --ns_l; if (j == 2) stringAndChar(&ns, &ns_l, ' '); break; case TAGACT_AREA: case TAGACT_FRAME: liCheck(t); if (!retainTag) break; stringAndString(&ns, &ns_l, (action == TAGACT_FRAME ? "\rFrame " : "\r")); a = 0; if (action == TAGACT_AREA) a = attribVal(t, "alt"); u = (char *)a; if (!u) { u = t->name; if (!u) u = altText(t->href); } if (!u) u = (action == TAGACT_FRAME ? "???" : "area"); if (t->href) { sprintf(hnum, "%c%d{", InternalCodeChar, tagno); ns_hnum(); } if (t->href || action == TAGACT_FRAME) stringAndString(&ns, &ns_l, u); if (t->href) { ns_ic(); stringAndString(&ns, &ns_l, "0}"); } stringAndChar(&ns, &ns_l, '\r'); break; case TAGACT_MUSIC: liCheck(t); if (!retainTag) break; if (!t->href) break; sprintf(hnum, "\r%c%d{", InternalCodeChar, tagno); ns_hnum(); stringAndString(&ns, &ns_l, (ti->name[0] == 'B' ? "Background Music" : "Audio passage")); sprintf(hnum, "%c0}\r", InternalCodeChar); ns_hnum(); break; case TAGACT_IMAGE: liCheck(t); tagInStream(tagno); if (!currentA) { if (a = attribVal(t, "alt")) { u = altText(a); a = NULL; if (u && !invisible) { stringAndChar(&ns, &ns_l, '['); stringAndString(&ns, &ns_l, u); stringAndChar(&ns, &ns_l, ']'); } } break; } /* image is part of a hyperlink */ if (!retainTag || !currentA->href || currentA->textin) break; u = 0; a = attribVal(t, "alt"); if (a) u = altText(a); if (!u) u = altText(t->name); if (!u) u = altText(currentA->href); if (!u) u = altText(t->href); if (!u) u = "image"; stringAndString(&ns, &ns_l, u); break; } /* switch */ } /* renderNode */ /* returns an allocated string */ char *render(int start) { ns = initString(&ns_l); invisible = false; listnest = 0; currentForm = currentA = NULL; traverse_callback = renderNode; traverseAll(start); return ns; } /* render */ edbrowse-3.6.0.1/src/PaxHeaders.22102/html-tidy.c0000644000000000000000000000006212637565441016101 xustar0020 atime=1451158305 30 ctime=1451409178.149337216 edbrowse-3.6.0.1/src/html-tidy.c0000644000000000000000000001773212637565441016364 0ustar00rootroot00000000000000/********************************************************************* html-tidy.c: use tidy5 to parse html tags. This is the only file that includes tidy.h, or accesses the tidy library. If you prefer a different parser in the future, write another file, html-foo.c, having the same connection to edbrowse, and change the makefile accordingly. If tidy5 changes its API, you only need edit this file. Note that tidy has Bool yes and no, which, fortunately, do not collide with edbrowse bool true false. *********************************************************************/ #include "eb.h" #include #include /********************************************************************* This routine preprocesses the html text to work around any shortcomings of tidy that are not worth fixing just for edbrowse, or have not been fixed yet. Other shortcomings are easier to manage after the fact, dealing with the tree of nodes - see nestedAnchors() in decorate.c. But some things must be managed prior to dity parse. Return null if there is no need to change the text. Otherwise return an allocated string. The only workaround here is the expansion of < > inside a textarea. *********************************************************************/ char *tidyPreprocess(const char *h) { char *ns; /* the new string */ int l; char *inside, *expanded; const char *lg, *s = strstrCI(h, "'); if (!s) break; ++s; stringAndBytes(&ns, &l, h, s - h); h = s; s = strstrCI(h, ""); /* lg is at most s */ if (lg == s) continue; inside = pullString1(h, s); expanded = htmlEscapeTextarea(inside); stringAndString(&ns, &l, expanded); nzFree(inside); nzFree(expanded); h = s; } stringAndString(&ns, &l, h); return ns; } /* tidyPreprocess */ /* the tidy structure corresponding to the html */ static TidyDoc tdoc; /* traverse the tidy tree with a callback function */ typedef void (*tidyNodeCallback) (TidyNode node, int level, bool opentag); static tidyNodeCallback traverse_tidycall; /* possible callback functions */ static void printNode(TidyNode node, int level, bool opentag); static void convertNode(TidyNode node, int level, bool opentag); static void traverseNode(TidyNode node, int level) { TidyNode child; /* first the callback function */ (*traverse_tidycall) (node, level, true); /* and now the children */ for (child = tidyGetChild(node); child; child = tidyGetNext(child)) traverseNode(child, level + 1); (*traverse_tidycall) (node, level, false); } /* traverseNode */ static void traverseTidy(void) { traverseNode(tidyGetRoot(tdoc), 0); } /* This is like the default tidy error reporter, except messages are suppressed * unless debugLevel >= 3, and in that case they are sent to stdout * rather than stderr, like most edbrowse messages. * Since these are debug messages, they are not internationalized. */ static Bool TIDY_CALL tidyErrorHandler(TidyDoc tdoc, TidyReportLevel lvl, uint line, uint col, ctmbstr mssg) { if (debugLevel >= 3) printf("line %d column %d: %s\n", line, col, mssg); return no; } /* tidyErrorHandler */ /* the entry point */ void html2nodes(const char *htmltext, bool startpage) { char *htmlfix = 0; tdoc = tidyCreate(); if (!startpage) tidyOptSetInt(tdoc, TidyBodyOnly, yes); tidySetReportFilter(tdoc, tidyErrorHandler); // tidySetReportFilter(tdoc, tidyReportFilter); tidySetCharEncoding(tdoc, (cons_utf8 ? "utf8" : "latin1")); htmlfix = tidyPreprocess(htmltext); if (htmlfix) { tidyParseString(tdoc, htmlfix); nzFree(htmlfix); } else tidyParseString(tdoc, htmltext); tidyCleanAndRepair(tdoc); if (debugLevel >= 5) { traverse_tidycall = printNode; traverseTidy(); } /* convert tidy nodes into edbrowse nodes */ traverse_tidycall = convertNode; traverseTidy(); tidyRelease(tdoc); } /* html2nodes */ /* this is strictly for debugging, level >= 5 */ static void printNode(TidyNode node, int level, bool opentag) { ctmbstr name; TidyAttr tattr; if (!opentag) { puts("}"); return; } switch (tidyNodeGetType(node)) { case TidyNode_Root: name = "Root"; break; case TidyNode_DocType: name = "DOCTYPE"; break; case TidyNode_Comment: name = "Comment"; break; case TidyNode_ProcIns: name = "Processing Instruction"; break; case TidyNode_Text: name = "Text"; break; case TidyNode_CDATA: name = "CDATA"; break; case TidyNode_Section: name = "XML Section"; break; case TidyNode_Asp: name = "ASP"; break; case TidyNode_Jste: name = "JSTE"; break; case TidyNode_Php: name = "PHP"; break; case TidyNode_XmlDecl: name = "XML Declaration"; break; case TidyNode_Start: case TidyNode_End: case TidyNode_StartEnd: default: name = tidyNodeGetName(node); break; } assert(name != NULL); printf("Node(%d): %s {\n", level, ((char *)name)); /* the ifs could be combined with && */ if (stringEqual(((char *)name), "Text")) { TidyBuffer tnv = { 0 }; /* text-node value */ tidyBufClear(&tnv); tidyNodeGetValue(tdoc, node, &tnv); printf("Text: %s\n", tnv.bp); if (tnv.size) tidyBufFree(&tnv); } /* Get the first attribute for the node */ tattr = tidyAttrFirst(node); while (tattr != NULL) { /* Print the node and its attribute */ printf("@%s = %s\n", tidyAttrName(tattr), tidyAttrValue(tattr)); /* Get the next attribute */ tattr = tidyAttrNext(tattr); } } /* printNode */ /* remove tags from start and end of a string, for innerHTML */ static void tagStrip(char *line) { char *s = line; if (*s != '<') { /* this shouldn't happen, don't know what to do. */ return; } s = strchr(line, '>'); if (!s) return; ++s; skipWhite2(&s); strmove(line, s); trimWhite(line); s = line + strlen(line); if (s == line || s[-1] != '>') return; /* back up over */ --s; while (s >= line) { if (*s == '<') { *s = 0; return; } --s; } } /* tagStrip */ static void convertNode(TidyNode node, int level, bool opentag) { ctmbstr name; TidyAttr tattr; struct htmlTag *t; int nattr; /* number of attributes */ int i; switch (tidyNodeGetType(node)) { case TidyNode_Text: name = "Text"; break; case TidyNode_Start: case TidyNode_End: case TidyNode_StartEnd: name = tidyNodeGetName(node); break; default: return; } t = newTag((char *)name); if (!t) return; if (!opentag) { t->slash = true; return; } /* if a js script, remember the line number for error messages */ if (t->action == TAGACT_SCRIPT) t->js_ln = tidyNodeLine(node); /* this is the open tag, set the attributes */ /* special case for text tag */ if (t->action == TAGACT_TEXT) { TidyBuffer tnv = { 0 }; /* text-node value */ tidyBufClear(&tnv); tidyNodeGetValue(tdoc, node, &tnv); if (tnv.size) { t->textval = cloneString(tnv.bp); tidyBufFree(&tnv); } } nattr = 0; tattr = tidyAttrFirst(node); while (tattr != NULL) { ++nattr; tattr = tidyAttrNext(tattr); } t->attributes = allocMem(sizeof(char *) * (nattr + 1)); t->atvals = allocMem(sizeof(char *) * (nattr + 1)); i = 0; tattr = tidyAttrFirst(node); while (tattr != NULL) { t->attributes[i] = cloneString(tidyAttrName(tattr)); t->atvals[i] = cloneString(tidyAttrValue(tattr)); ++i; tattr = tidyAttrNext(tattr); } t->attributes[i] = 0; t->atvals[i] = 0; /* innerHTML, only for certain tags */ if (t->info->bits & TAG_INNERHTML) { TidyBuffer tnv = { 0 }; /* text-node value */ tidyBufClear(&tnv); t->innerHTML = emptyString; tidyNodeGetText(tdoc, node, &tnv); if (tnv.size) { /* But it's not the original html, it has been sanitized. * Put a cap on size, else memory consumed could, theoretically, * grow as the size of the document squared. */ if (tnv.size <= 4096) t->innerHTML = cloneString(tnv.bp); tagStrip(t->innerHTML); tidyBufFree(&tnv); } } } /* convertNode */ edbrowse-3.6.0.1/src/PaxHeaders.22102/format.c0000644000000000000000000000006212637565441015456 xustar0020 atime=1451158305 30 ctime=1451409178.149337216 edbrowse-3.6.0.1/src/format.c0000644000000000000000000007403612637565441015741 0ustar00rootroot00000000000000/* format.c * Format text, establish line breaks, manage whitespace. * This file is part of the edbrowse project, released under GPL. */ #include "eb.h" /********************************************************************* Prepare html for text processing. Change nulls to spaces. Make sure it doesn't already contain my magic code, The one I use to indicate a tag. If it does, well, change them to something else. I can only hope this doesn't screw up some embedded javascript. *********************************************************************/ void prepareForBrowse(char *h, int h_len) { int i, j; for (i = j = 0; i < h_len; ++i) { if (h[i] == 0) h[i] = ' '; if (h[i] == '\b') { if (i && !strchr("\n\b<>'\"&", h[i - 1])) --j; continue; } if (h[i] == InternalCodeChar) h[i] = InternalCodeCharAlternate; h[j++] = h[i]; } h[j] = 0; /* now it's a string */ /* undos the file */ for (i = j = 0; h[i]; ++i) { if (h[i] == '\r' && h[i + 1] == '\n') continue; h[j++] = h[i]; } h[j] = 0; } /* prepareForBrowse */ /* An input field cannot contain newline, null, or the InternalCodeChar */ void prepareForField(char *h) { while (*h) { if (*h == 0 || *h == '\n') *h = ' '; if (*h == InternalCodeChar) *h = InternalCodeCharAlternate; ++h; } } /* prepareForField */ /********************************************************************* The primary goal of this routine is to turn Hey,{ click here } for more information into Hey, {click here} for more information But of course we won't do that if the section is preformatted. Nor can we muck with the whitespace that might be present in an input field <>. Also swap 32* whitespace, pushing invisible anchors forward. If a change is made, the procedure is run again, kinda like bubble sort. It has the potential to be terribly inefficient, but that doesn't seem to happen in practice. Use cnt to count the iterations, just for debugging. | is considered a whitespace character. Why is that? Html tables are mostly used for visual layout, but sometimes not. I use | to separate the cells of a table, but if there's nothing in them, or at least no text, then I get rid of the pipes. But every cell is going to have an invisible anchor from , so that js can, perhaps, set innerHTML inside this cell. So there's something there, but nothing there. I push these tags past pipes, so I can clear it all away. One web page in ten thousand will actually set html inside a cell, after the fact, and when that happens the text won't be in the right place, it won't have the pipes around it that it should. I'm willing to accept that for now. *********************************************************************/ static void cellDelimiters(char *buf) { char *lastcell = 0; int cellcount = 0; char *s; for (s = buf; *s; ++s) { if (*s == TableCellChar) { *s = '|'; lastcell = s; ++cellcount; continue; } if (!strchr("\f\r\n", *s)) continue; /* newline here, if just one cell delimiter then blank it out */ if (cellcount == 1) *lastcell = ' '; cellcount = 0; } } /* cellDelimiters */ static void anchorSwap(char *buf) { char c, d, *s, *ss, *w, *a; bool pretag; //
    	bool premode;		// inside 
     
    bool slash; // closing tag bool change; // made a swap somewhere bool strong; // strong whitespace, newline or paragraph int n, cnt; char tag[20]; static const char from[] = "\x1b\x95\x99\x9c\x9d\x91\x92\x93\x94\xa0\xad\x96\x97\x85"; static const char becomes[] = "_*'`'`'`' ----"; /* I use to convert a6 and c2 to hyphen space, not sure why */ /* Transliterate a few characters. One of them is 0xa0 to space, * so we need to do this now, before the anchors swap with whitespace. * Watch out for utf8 - don't translate the a0 in c3a0. That is a grave. * But a0 by itself is breakspace; turn it into space. * And c2a0 is a0 is breakspace. * Then get rid of hyperlinks with absolutely nothing to click on. */ for (s = w = buf; c = *s; ++s) { d = s[1]; /* utf8 test */ if ((c & 0xc0) == 0xc0 && (d & 0xc0) == 0x80) { unsigned int uni = 0; if ((c & 0x3c) == 0) { /* fits in 8 bits */ uni = ((uchar) c << 6) | (d & 0x3f); ss = strchr(from, (char)uni); if (ss) { c = becomes[ss - from]; ++s; goto put1; } } /* copy the utf8 sequence as is */ *w++ = c; ++s; c <<= 1; while ((c & 0x80) && ((d = *s) & 0xc0) == 0x80) { *w++ = d; ++s; } --s; continue; } /* Now assuming iso8859-1, which is practically depricated */ ss = strchr(from, c); if (ss) c = becomes[ss - from]; #if 0 // Should we modify empty anchors in any way? if (c != InternalCodeChar) goto put1; if (!isdigitByte(s[1])) goto put1; for (a = s + 2; isdigitByte(*a); ++a) ; if (*a != '{') goto put1; for (++a; *a == ' '; ++a) ; if (a[0] != InternalCodeChar || a[1] != '0' || a[2] != '}') goto put1; // do something with empty {} here. // Following code just skips it, but we likely shouldn't do that. s = a + 2; continue; #endif put1: *w++ = c; } *w = 0; /* anchor whitespace swap preserves the length of the string */ cnt = 0; change = true; while (change) { change = false; ++cnt; premode = false; /* w represents the state of whitespace */ w = NULL; /* a points to the prior anchor, which is swappable with following whitespace */ a = NULL; for (s = buf; c = *s; ++s) { if (isspaceByte(c) || c == '|') { if (c == '\t' && !premode) *s = ' '; if (!w) w = s; continue; } /* end of white space, should we swap it with prior tag? */ if (w && a) { memmove(a, w, s - w); memmove(a + (s - w), tag, n); change = true; w = NULL; } /* prior anchor has no significance */ a = NULL; if (c != InternalCodeChar) goto normalChar; /* some conditions that should never happen */ if (!isdigitByte(s[1])) goto normalChar; n = strtol(s + 1, &ss, 10); preFormatCheck(n, &pretag, &slash); d = *ss; if (!strchr("{}<>*", d)) goto normalChar; n = ss + 1 - s; memcpy(tag, s, n); tag[n] = 0; if (pretag) { w = 0; premode = !slash; s = ss; continue; } /* We have a tag, should we swap it with prior whitespace? */ if (w && !premode && d == '}') { memmove(w + n, w, s - w); memcpy(w, tag, n); change = true; w += n; s = ss; continue; } if ((d == '*' || d == '{') && !premode) a = s; s = ss; normalChar: w = 0; /* no more whitespace */ /* end of loop over the chars in the buffer */ } /* end of loop making changes */ } debugPrint(4, "anchorSwap %d", cnt); /* Framing characters like [] around an anchor are unnecessary here, * because we already frame it in braces. * Get rid of these characters, even in premode. */ for (s = w = buf; c = *s; ++s) { char open, close, linkchar; if (!strchr("{[(<", c)) goto putc; if (s[1] != InternalCodeChar) goto putc; if (!isdigitByte(s[2])) goto putc; for (a = s + 3; isdigitByte(*a); ++a) ; linkchar = 0; if (*a == '{') linkchar = '}'; if (*a == '<') linkchar = '>'; if (!linkchar) goto putc; open = c; close = 0; if (open == '{') close = '}'; if (open == '[') close = ']'; if (open == '(') close = ')'; if (open == '<') close = '>'; n = 1; while (n < 120) { d = a[n++]; if (!d) break; if (d != InternalCodeChar) continue; while (isdigitByte(a[n])) ++n; d = a[n++]; if (!d) break; /* should never happen */ if (strchr("{}<>", d)) break; } if (n >= 120) goto putc; if (d != linkchar) goto putc; a += n; if (*a != close) goto putc; ++s; memmove(w, s, a - s); w += a - s; s = a; continue; putc: *w++ = c; } /* loop over buffer */ *w = 0; debugPrint(4, "anchors unframed"); /* Now compress the implied linebreaks into one. */ premode = false; ss = 0; for (s = buf; c = *s; ++s) { if (c == InternalCodeChar && isdigitByte(s[1])) { n = strtol(s + 1, &s, 10); if (*s == '*') { preFormatCheck(n, &pretag, &slash); if (pretag) premode = !slash; } } if (!isspaceByte(c)) continue; strong = false; a = 0; for (w = s; isspaceByte(*w); ++w) { if (*w == '\n' || *w == '\f') strong = true; if (*w == '\r' && !a) a = w; } ss = s, s = w - 1; if (!a) continue; if (premode) continue; if (strong) { for (w = ss; w <= s; ++w) if (*w == '\r') *w = ' '; continue; } for (w = ss; w <= s; ++w) if (*w == '\r' && w != a) *w = ' '; } /* loop over buffer */ debugPrint(4, "whitespace combined"); /* Due to the anchor swap, the buffer could end in whitespace * followed by several anchors. Trim these off. */ s = buf + strlen(buf); while (s > buf + 1 && s[-1] == '*' && isdigitByte(s[-2])) { for (w = s - 3; w >= buf && isdigitByte(*w); --w) ; if (w < buf || *w != InternalCodeChar) break; s = w; } *s = 0; } /* anchorSwap */ /********************************************************************* Format text, and break lines at sentence/phrase boundaries. The prefix bl means breakline. *********************************************************************/ static char *bl_start, *bl_cursor, *bl_end; static bool bl_overflow; /* This is a virtual column number, extra spaces for tab, * and skipping over invisible anchors. */ static int colno; static const int optimalLine = 80; /* optimal line length */ static const int cutLineAfter = 36; /* cut sentence after this column */ static const int paraLine = 120; /* paragraph in a line */ static int longcut, pre_cr; static int lspace; /* last space value, 3 = paragraph */ /* Location of period comma rightparen or any word. * Question mark is equivalent to period etc. * Other things being equal, we break at period, rather than comma, etc. * First the column numbers, then the index into the string. */ static int lperiod, lcomma, lright, lany; static int idxperiod, idxcomma, idxright, idxany; static void debugChunk(const char *chunk, int len) { int i; if (debugLevel < 7) return; printf("chunk<"); for (i = 0; i < len; ++i) { char c = chunk[i]; if (c == '\t') { printf("\\t"); continue; } if (c == '\n') { printf("\\n"); continue; } if (c == '\f') { printf("\\f"); continue; } if (c == '\r') { printf("\\r"); continue; } if (c == '\0') { printf("\\0"); continue; } printf("%c", c); } printf(">%d.%d\n", colno, lspace); } /* debugChunk */ static void appendOneChar(char c) { if (bl_cursor == bl_end) bl_overflow = true; else *bl_cursor++ = c; } /* appendOneChar */ static bool spaceNotInInput(void) { char *t = bl_cursor; char c; for (--t; t >= bl_start; --t) { c = *t; if (c == '\n' || c == '\r') return true; if (c == '>' && t >= bl_start + 2 && t[-1] == '0' && t[-2] == InternalCodeChar) return true; if (c != '<') continue; while (t > bl_start && isdigitByte(t[-1])) --t; if (*t == '<') continue; if (t > bl_start && t[-1] == InternalCodeChar) return false; } return true; } /* spaceNotInInput */ static void appendSpaceChunk(const char *chunk, int len, bool premode) { int nlc = pre_cr; /* newline count */ int spc = 0; /* space count */ int i, j; char c, d, e; if (!len) return; for (i = 0; i < len; ++i) { c = chunk[i]; if (c == '\n' || c == '\r') { ++nlc, spc = 0; continue; } if (c == '\f') { nlc += 2, spc = 0; continue; } ++spc; } if (!premode && spaceNotInInput()) { int l = bl_cursor - bl_start; c = d = ' '; if (l) d = bl_cursor[-1]; if (l > 1) c = bl_cursor[-2]; e = d; if (strchr(")\"|}", d)) e = c; if (strchr(".?!:", e)) { bool ok = true; /* Check for Mr. Mrs. and others. */ if (e == '.' && bl_cursor - bl_start > 10) { static const char *const prefix[] = { "mr.", "mrs.", "sis.", "ms.", 0 }; char trailing[12]; for (i = 0; i < 6; ++i) { c = bl_cursor[i - 6]; if (isupperByte(c)) c = tolower(c); trailing[i] = c; } trailing[i] = 0; for (i = 0; prefix[i]; ++i) if (strstr(trailing, prefix[i])) ok = false; /* Check for John C. Calhoon */ if (isupperByte(bl_cursor[-2]) && isspaceByte(bl_cursor[-3])) ok = false; } if (ok) lperiod = colno, idxperiod = l; } e = d; if (strchr(")\"|", d)) e = c; if (strchr("-,;", e)) lcomma = colno, idxcomma = l; if (strchr(")\"|", d)) lright = colno, idxright = l; lany = colno, idxany = l; /* tack a short fragment onto the previous line. */ if (longcut && colno <= 15 && (nlc || lperiod == colno)) { bl_start[longcut] = ' '; if (!nlc) len = spc = 0, nlc = 1; } /* pasting small fragment onto previous line */ } /* allowing line breaks */ if (lspace == 3) nlc = 0; if (nlc) { if (lspace == 2) nlc = 1; appendOneChar('\n'); if (nlc > 1) appendOneChar('\n'); colno = 1; longcut = lperiod = lcomma = lright = lany = 0; if (lspace >= 2 || nlc > 1) lspace = 3; if (lspace < 2) lspace = 2; if (!premode) return; } if (!spc) return; if (!premode) { /* if the first char of the text to be reformatted is space, * then we will wind up here, with lspace = 3. */ if (lspace == 3) return; appendOneChar(' '); ++colno; lspace = 1; return; } j = -1; for (i = 0; i < len; ++i) { c = chunk[i]; if (c == '\n' || c == '\r' || c == '\f') j = i; } i = j + 1; if (i) colno = 1; for (; i < len; ++i) { c = chunk[i]; if (c == 0) c = ' '; appendOneChar(c); if (c == ' ') ++colno; if (c == '\t') colno += 4; } lspace = 1; } /* appendSpaceChunk */ static void appendPrintableChunk(const char *chunk, int len, bool premode) { int i, j; bool visible = true; for (i = 0; i < len; ++i) { char c = chunk[i]; appendOneChar(c); if (c == InternalCodeChar) { visible = false; continue; } if (visible) { ++colno; continue; } if (isdigitByte(c)) continue; /* end of the tag */ visible = true; if (c != '*') ++colno; } lspace = 0; if (premode) return; if (colno <= optimalLine) return; /* Oops, line is getting long. Let's see where we can cut it. */ i = j = 0; if (lperiod > cutLineAfter) i = lperiod, j = idxperiod; else if (lcomma > cutLineAfter) i = lcomma, j = idxcomma; else if (lright > cutLineAfter) i = lright, j = idxright; else if (lany > cutLineAfter) i = lany, j = idxany; if (!j) return; /* nothing we can do about it */ longcut = 0; if (i != lperiod) longcut = j; bl_start[j] = '\n'; colno -= i; lperiod -= i; lcomma -= i; lright -= i; lany -= i; } /* appendPrintableChunk */ /* Break up a line using the above routines. * The new lines are put in a fixed array. * Return false (fail) if we ran out of room. * This function is called from buffers.c, implementing the bl command, * and is only in this file because it shares the above routines and variables * with the html reformatting, which really has to be here. */ char *breakLineResult; #define REFORMAT_EXTRA 400 /* Count the formfeeds in a string. Each of these expands to \n\n, * making the string longer. */ static int formfeedCount(const char *buf, int len) { int i, ff = 0; for (i = 0; i < len; ++i) if (buf[i] == '\f') ++ff; return ff; } /* formfeedCount */ bool breakLine(const char *line, int len, int *newlen) { char c, state, newstate; int i, last, extra; pre_cr = 0; if (len && line[len - 1] == '\r') --len; if (lspace == 4) { /* special continuation code from the previous invokation */ lspace = 2; if (line[0]) ++pre_cr; } if (len > paraLine) ++pre_cr; if (lspace < 2) lspace = 2; /* should never happen */ if (!len + pre_cr) lspace = 3; nzFree(breakLineResult); extra = REFORMAT_EXTRA + formfeedCount(line, len); breakLineResult = allocMem(len + extra); bl_start = bl_cursor = breakLineResult; bl_end = breakLineResult + len + extra - 8; bl_overflow = false; colno = 1; longcut = lperiod = lcomma = lright = lany = 0; last = 0; state = 0; if (pre_cr) state = 1; for (i = 0; i < len; ++i) { c = line[i]; newstate = 2; if (!c || strchr(" \t\n\r\f", c)) newstate = 1; if (state == newstate) continue; if (!state) { state = newstate; continue; } /* state change here */ debugChunk(line + last, i - last); if (state == 1) appendSpaceChunk(line + last, i - last, false); else appendPrintableChunk(line + last, i - last, false); last = i; state = newstate; pre_cr = 0; } if (state) { /* last token */ debugChunk(line + last, len - last); if (state == 1) appendSpaceChunk(line + last, len - last, false); else appendPrintableChunk(line + last, len - last, false); } if (lspace < 2) { /* line didn't have a \r at the end */ appendSpaceChunk("\n", 1, false); } if (bl_cursor - bl_start > paraLine) lspace = 4; debugPrint(7, "chunk%d.%d", colno, lspace); *newlen = bl_cursor - bl_start; return !bl_overflow; } /* breakLine */ void breakLineSetup(void) { lspace = 3; } char *htmlReformat(char *buf) { const char *h, *nh, *s; char c; bool premode = false; bool pretag, slash; char *new; int l, tagno, extra; cellDelimiters(buf); anchorSwap(buf); longcut = lperiod = lcomma = lright = lany = 0; colno = 1; pre_cr = 0; lspace = 3; l = strlen(buf); /* Only a pathological web page gets longer after reformatting. * Those with paragraphs and nothing else to compress or remove. * Thus I allocate for the formfeeds, which correspond to paragraphs, * and are replaced with \n\n. * Plus some extra bytes for slop. * If you still overflow, even beyond the EXTRA, * it won't seg fault, you'll just lose some text. */ extra = REFORMAT_EXTRA + formfeedCount(buf, l); new = allocMem(l + extra); bl_start = bl_cursor = new; bl_end = new + l + extra - 20; bl_overflow = false; for (h = buf; (c = *h); h = nh) { if (isspaceByte(c)) { for (s = h + 1; isspaceByte(*s); ++s) ; nh = s; appendSpaceChunk(h, nh - h, premode); if (lspace == 3) { longcut = lperiod = lcomma = lright = lany = 0; colno = 1; } continue; } if (c != InternalCodeChar) { for (s = h + 1; *s; ++s) if (isspaceByte(*s) || *s == InternalCodeChar) break; nh = s; appendPrintableChunk(h, nh - h, premode); continue; } /* It's a tag */ tagno = strtol(h + 1, (char **)&nh, 10); c = *nh++; if (!c || !strchr("{}<>*", c)) i_printfExit(MSG_BadTagCode, tagno, c); appendPrintableChunk(h, nh - h, premode); preFormatCheck(tagno, &pretag, &slash); if (pretag) { premode = !slash; if (!premode) { /* This forces a new paragraph, so it last char was nl, erase it. */ char *w = bl_cursor - 1; while (*w != InternalCodeChar) --w; if (w > bl_start && w[-1] == '\n') { memmove(w - 1, w, bl_cursor - w); --bl_cursor; } } } } /* loop over text */ /* close off the last line */ if (lspace < 2) appendSpaceChunk("\n", 1, true); *bl_cursor = 0; l = bl_cursor - bl_start; /* Get rid of last space. */ if (l >= 2 && new[l - 1] == '\n' && new[l - 2] == ' ') new[l - 2] = '\n', new[--l] = 0; /* Don't need empty lines at the end. */ while (l > 1 && new[l - 1] == '\n' && new[l - 2] == '\n') --l; new[l] = 0; /* Don't allow an empty buffer */ if (!l) new[0] = '\n', new[1] = 0, l = 1; if (bl_overflow) { /* we should print a more helpful error message here */ strcpy(new + l, "\n???"); l += 4; } return new; } /* htmlReformat */ /********************************************************************* Convert a 31 bit unicode character into utf8. *********************************************************************/ static void uni2utf8(unsigned int unichar, unsigned char *outbuf) { int n = 0; if (unichar <= 0x7f) { outbuf[n++] = unichar; } else if (unichar <= 0x7ff) { outbuf[n++] = 0xc0 | ((unichar >> 6) & 0x1f); outbuf[n++] = 0x80 | (unichar & 0x3f); } else if (unichar <= 0xffff) { outbuf[n++] = 0xe0 | ((unichar >> 12) & 0xf); outbuf[n++] = 0x80 | ((unichar >> 6) & 0x3f); outbuf[n++] = 0x80 | (unichar & 0x3f); } else if (unichar <= 0x1fffff) { outbuf[n++] = 0xf0 | ((unichar >> 18) & 7); outbuf[n++] = 0x80 | ((unichar >> 12) & 0x3f); outbuf[n++] = 0x80 | ((unichar >> 6) & 0x3f); outbuf[n++] = 0x80 | (unichar & 0x3f); } else if (unichar <= 0x3ffffff) { outbuf[n++] = 0xf8 | ((unichar >> 24) & 3); outbuf[n++] = 0x80 | ((unichar >> 18) & 0x3f); outbuf[n++] = 0x80 | ((unichar >> 12) & 0x3f); outbuf[n++] = 0x80 | ((unichar >> 6) & 0x3f); outbuf[n++] = 0x80 | (unichar & 0x3f); } else if (unichar <= 0x7fffffff) { outbuf[n++] = 0xfc | ((unichar >> 30) & 1); outbuf[n++] = 0x80 | ((unichar >> 24) & 0x3f); outbuf[n++] = 0x80 | ((unichar >> 18) & 0x3f); outbuf[n++] = 0x80 | ((unichar >> 12) & 0x3f); outbuf[n++] = 0x80 | ((unichar >> 6) & 0x3f); outbuf[n++] = 0x80 | (unichar & 0x3f); } outbuf[n] = 0; } /* uni2utf8 */ /********************************************************************* Crunch a to-list or a copy-to-list down to its email addresses. Delimit them with newlines. "Smith, John" becomes jsmith@whatever.com *********************************************************************/ void extractEmailAddresses(char *line) { char *s, *t; char *mark; /* start of current entry */ char quote = 0, c; for (s = t = mark = line; c = *s; ++s) { if (c == ',' && !quote) { mark = t + 1; c = ' '; goto append; } if (c == '"') { if (!quote) quote = c; else if (quote == c) quote = 0; /* don't think you can quote in an email address */ continue; } if (c == '<') { if (!quote) { quote = c; t = mark; } continue; } if (c == '>') { if (quote == '<') quote = 0; continue; } if (quote == '"') continue; if (c < ' ') c = ' '; if (c == ' ' && quote == '<') c = '_'; append: *t++ = c; } *t = 0; spaceCrunch(line, true, false); for (s = line; c = *s; ++s) if (c == ' ') *s = ','; if (*line) strcat(line, ","); } /* extractEmailAddresses */ static void cutDuplicateEmail(char *line, const char *dup, int duplen) { char *s; while (*line) { s = strchr(line, ','); if (!s) return; /* should never happen */ if (duplen == s - line && memEqualCI(line, dup, duplen)) { ++s; strmove(line, s); continue; } line = s + 1; } } /* cutDuplicateEmail */ void cutDuplicateEmails(char *tolist, char *cclist, const char *reply) { int len; char *s, *t; len = strlen(reply); if (len) { cutDuplicateEmail(tolist, reply, len); cutDuplicateEmail(cclist, reply, len); } s = tolist; while (*s) { t = strchr(s, ','); if (!t) break; /* should never happen */ len = t - s; ++t; cutDuplicateEmail(t, s, len); cutDuplicateEmail(cclist, s, len); s = t; } s = cclist; while (*s) { t = strchr(s, ','); if (!t) break; /* should never happen */ len = t - s; ++t; cutDuplicateEmail(t, s, len); s = t; } /* If your email address is on the to or cc list, drop it. * But retain it if it is the reply, in case you sent mail to yourself. */ if (reply[0]) { struct MACCOUNT *m = accounts; int i; for (i = 0; i < maxAccount; ++i, ++m) { const char *r = m->reply; if (!r) continue; len = strlen(r); cutDuplicateEmail(tolist, r, len); cutDuplicateEmail(cclist, r, len); } } } /* cutDuplicateEmails */ /********************************************************************* We got some data, from a file or from the internet. Count the binary characters and decide if this is, on the whole, binary or text. I allow some nonascii chars, like you might see in Spanish or German, and still call it text, but if there's too many such chars, I call it binary. It's not an exact science. *********************************************************************/ bool looksBinary(const char *buf, int buflen) { int i, j, bincount = 0, charcount = 0; char c; uchar seed; for (i = 0; i < buflen; ++i, ++charcount) { c = buf[i]; // 0 is ascii, but not really text, and very common in binary files. if (c == 0) ++bincount; if (c >= 0) continue; // could represent a utf8 character seed = c; if ((seed & 0xfe) == 0xfe || (seed & 0xc0) == 0x80) { binchar: ++bincount; continue; } seed <<= 1; j = 1; while (seed & 0x80 && i + j < buflen && (buf[i + j] & 0xc0) == 0x80) seed <<= 1, ++j; if (seed & 0x80) goto binchar; // this is valid utf8 char, don't treat it as binary. i += j; } return (bincount * 8 - 16 >= charcount); } /* looksBinary */ void looks_8859_utf8(const char *buf, int buflen, bool * iso_p, bool * utf8_p) { int utfcount = 0, isocount = 0; int i, j, bothcount; for (i = 0; i < buflen; ++i) { char c = buf[i]; if (c >= 0) continue; /* This is the start of the nonascii sequence. */ /* No second bit, it has to be iso. */ if (!(c & 0x40)) { isogo: ++isocount; continue; } /* Next byte has to start with 10 to be utf8, else it's iso */ if (((uchar) buf[i + 1] & 0xc0) != 0x80) goto isogo; c <<= 2; for (j = i + 2; c < 0; ++j, c <<= 1) if (((uchar) buf[j] & 0xc0) != 0x80) goto isogo; ++utfcount; i = j - 1; } *iso_p = *utf8_p = false; bothcount = isocount + utfcount; if (!bothcount) return; /* ascii */ bothcount *= 6; if (utfcount * 7 >= bothcount) *utf8_p = true; if (isocount * 7 >= bothcount) *iso_p = true; } /* looks_8859_utf8 */ static char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /* * Encode some data in base64. * inbuf points to the data * inlen is the length of the data * lines is a boolean, indicating whether to add newlines to the output. * If true, newlines will be added after each group of 72 output bytes. * Returns: A freshly-allocated NUL-terminated string, containing the * base64 representation of the data. */ char *base64Encode(const char *inbuf, int inlen, bool lines) { char *out, *outstr; uchar *in = (uchar *) inbuf; int colno; int outlen = ((inlen / 3) + 1) * 4; ++outlen; /* zero on the end */ if (lines) outlen += (inlen / 54) + 1; outstr = out = allocMem(outlen); colno = 0; while (inlen >= 3) { *out++ = base64_chars[(int)(*in >> 2)]; *out++ = base64_chars[(int)((*in << 4 | *(in + 1) >> 4) & 63)]; *out++ = base64_chars[(int)((*(in + 1) << 2 | *(in + 2) >> 6) & 63)]; *out++ = base64_chars[(int)(*(in + 2) & 63)]; inlen -= 3; in += 3; if (!lines) continue; colno += 4; if (colno < 72) continue; *out++ = '\n'; colno = 0; } if (inlen == 1) { *out++ = base64_chars[(int)(*in >> 2)]; *out++ = base64_chars[(int)(*in << 4 & 63)]; *out++ = '='; *out++ = '='; colno += 4; } if (inlen == 2) { *out++ = base64_chars[(int)(*in >> 2)]; *out++ = base64_chars[(int)((*in << 4 | *(in + 1) >> 4) & 63)]; *out++ = base64_chars[(int)((*(in + 1) << 2) & 63)]; *out++ = '='; colno += 4; } /* finish the last line */ if (lines && colno) *out++ = '\n'; *out = 0; return outstr; } /* base64Encode */ uchar base64Bits(char c) { if (isupperByte(c)) return c - 'A'; if (islowerByte(c)) return c - ('a' - 26); if (isdigitByte(c)) return c - ('0' - 52); if (c == '+') return 62; if (c == '/') return 63; return 64; /* error */ } /* base64Bits */ /* * Decode some data in base64. * This function operates on the data in-line. It does not allocate a fresh * string to hold the decoded data. Since the data will be smaller than * the base64 encoded representation, this cannot overflow buffers. * If you need to preserve the input, copy it first. * * start points to the start of the input * *end initially points to the byte just after the end of the input * Returns: GOOD_BASE64_DECODE on success, BAD_BASE64_DECODE or * EXTRA_CHARS_BASE64_DECODE on error. * When the function returns success, *end points to the end of the decoded * data. On failure, end points to the just past the end of * what was successfully decoded. */ int base64Decode(char *start, char **end) { char *b64_end = *end; uchar val, leftover, mod; bool equals; int ret = GOOD_BASE64_DECODE; char c, *q, *r; /* Since this is a copy, and the unpacked version is always * smaller, just unpack it inline. */ mod = 0; equals = false; for (q = r = start; q < b64_end; ++q) { c = *q; if (isspaceByte(c)) continue; if (equals) { if (c == '=') continue; ret = EXTRA_CHARS_BASE64_DECODE; break; } if (c == '=') { equals = true; continue; } val = base64Bits(c); if (val & 64) { ret = BAD_BASE64_DECODE; break; } if (mod == 0) { leftover = val << 2; } else if (mod == 1) { *r++ = (leftover | (val >> 4)); leftover = val << 4; } else if (mod == 2) { *r++ = (leftover | (val >> 2)); leftover = val << 6; } else { *r++ = (leftover | val); } ++mod; mod &= 3; } *end = r; return ret; } /* base64Decode */ void iuReformat(const char *inbuf, int inbuflen, char **outbuf_p, int *outbuflen_p) { bool is8859, isutf8; *outbuf_p = 0; *outbuflen_p = 0; if (!iuConvert) return; looks_8859_utf8(inbuf, inbuflen, &is8859, &isutf8); if (cons_utf8 && is8859) { debugPrint(3, "converting to utf8"); iso2utf(inbuf, inbuflen, outbuf_p, outbuflen_p); } if (!cons_utf8 && isutf8) { debugPrint(3, "converting to iso8859"); utf2iso(inbuf, inbuflen, outbuf_p, outbuflen_p); } } /* iuReformat */ bool parseDataURI(const char *uri, char **mediatype, char **data, int *data_l) { bool base64 = false; const char *mediatype_start; const char *data_sep; const char *cp; size_t encoded_len; *data = *mediatype = emptyString; *data_l = 0; if (!isDataURI(uri)) return false; mediatype_start = uri + 5; data_sep = strchr(mediatype_start, ','); if (!data_sep) return false; for (cp = data_sep - 1; (cp >= mediatype_start && *cp != ';'); cp--) ; if (cp >= mediatype_start && memEqualCI(cp, ";base64,", 8)) { base64 = true; *mediatype = pullString1(mediatype_start, cp); } else { *mediatype = pullString1(mediatype_start, data_sep); } encoded_len = strlen(data_sep + 1); *data = pullString(data_sep + 1, encoded_len); unpercentString(*data); if (!base64) { *data_l = strlen(*data); } else { char *data_end = *data + strlen(*data); int unpack_ret = base64Decode(*data, &data_end); if (unpack_ret != GOOD_BASE64_DECODE) { nzFree(*mediatype); *mediatype = emptyString; nzFree(*data); *data = emptyString; return false; } *data_end = '\0'; *data_l = data_end - *data; } return true; } /* parseDataURI */ edbrowse-3.6.0.1/src/PaxHeaders.22102/fetchmail.c0000644000000000000000000000006212637565441016122 xustar0020 atime=1451158305 30 ctime=1451409178.149337216 edbrowse-3.6.0.1/src/fetchmail.c0000644000000000000000000017334112637565441016404 0ustar00rootroot00000000000000/* fetchmail.c * Get mail using the pop3 protocol. * Format the mail in ascii, or in html. * Unpack attachments. * This file is part of the edbrowse project, released under GPL. */ #include "eb.h" #ifdef _MSC_VER #include "vsprtf.h" #endif #define MHLINE 200 /* length of a mail header line */ /* headers and other information about an email */ struct MHINFO { struct MHINFO *next, *prev; struct listHead components; char *start, *end; char subject[MHLINE + 4]; char to[MHLINE]; char from[MHLINE]; char reply[MHLINE]; char date[MHLINE]; char boundary[MHLINE]; int boundlen; /* recipients and cc recipients */ char *tolist, *cclist; int tolen, cclen; char mid[MHLINE]; /* message id */ char ref[MHLINE]; /* references */ char cfn[MHLINE]; /* content file name */ uchar ct, ce; /* content type, content encoding */ bool andOthers; bool doAttach; bool atimage; bool pgp; uchar error64; }; static int nattach; /* number of attachments */ static int nimages; /* number of attached images */ static char *firstAttach; /* name of first file */ static bool mailIsHtml; static char *fm; /* formatted mail string */ static int fm_l; static struct MHINFO *lastMailInfo; static char *lastMailText; static void freeMailInfo(struct MHINFO *w) { struct MHINFO *v; while (!listIsEmpty(&w->components)) { v = w->components.next; delFromList(v); freeMailInfo(v); } nzFree(w->tolist); nzFree(w->cclist); nzFree(w); } /* freeMailInfo */ static bool ignoreImages; static void writeAttachment(struct MHINFO *w) { const char *atname; if ((ismc | ignoreImages) && w->atimage) return; /* image ignored */ if (w->pgp) return; /* Ignore PGP signatures. */ if (w->error64 == BAD_BASE64_DECODE) i_printf(MSG_Abbreviated); if (w->start == w->end) { i_printf(MSG_AttEmpty); if (w->cfn[0]) printf(" %s", w->cfn); nl(); atname = "x"; } else { i_printf(MSG_Att); atname = getFileName(MSG_FileName, (w->cfn[0] ? w->cfn : 0), true, false); /* X is like x, but deletes all future images */ if (stringEqual(atname, "X")) { atname = "x"; ignoreImages = true; } } if (!ismc && stringEqual(atname, "e")) { int cx, svcx = context; for (cx = 1; cx < MAXSESSION; ++cx) if (!sessionList[cx].lw) break; if (cx == MAXSESSION) { i_printf(MSG_AttNoBuffer); } else { cxSwitch(cx, false); i_printf(MSG_SessionX, cx); if (!addTextToBuffer ((pst) w->start, w->end - w->start, 0, false)) i_printf(MSG_AttNoCopy, cx); else if (w->cfn[0]) cw->fileName = cloneString(w->cfn); cxSwitch(svcx, false); /* back to where we were */ } } else if (!stringEqual(atname, "x")) { int fh = open(atname, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, 0666); if (fh < 0) { i_printf(MSG_AttNoSave, atname); if (ismc) exit(1); } else { int nb = w->end - w->start; if (write(fh, w->start, nb) < nb) { i_printf(MSG_AttNoWrite, atname); if (ismc) exit(1); } close(fh); } } } /* writeAttachment */ static void writeAttachments(struct MHINFO *w) { struct MHINFO *v; if (w->doAttach) { writeAttachment(w); } else { foreach(v, w->components) writeAttachments(v); } } /* writeAttachments */ /* string to hold the returned data from the mail server */ static char *mailstring; static int mailstring_l; static char *mailbox_url, *message_url; static int imapfetch = 100; static void setLimit(const char *t) { skipWhite2(&t); if (!isdigit(*t)) { i_puts(MSG_NumberExpected); return; } imapfetch = atoi(t); if (imapfetch < 10) imapfetch = 10; i_printf(MSG_FetchN, imapfetch); } /* setLimit */ /* mail message in a folder */ struct MIF { int uid; int seqno; int size; char *cbase; /* allocated string containing the following */ char *subject, *from, *reply; char *refer; time_t sent; bool seen; }; /* folders at the top of an imap system */ static struct FOLDER { const char *name; const char *path; bool children; int nmsgs; /* number of messages in this folder */ int nfetch; /* how many to fetch */ int unread; /* how many not yet seen */ int start; int uidnext; /* uid of next message */ struct MIF *mlist; /* allocated */ } *topfolders; static int n_folders; static char *tf_cbase; /* base of strings for folder names and paths */ /* This routine mucks with the passed in string, which was allocated * to receive data from the imap server. So leave it allocated. */ static void setFolders(void) { struct FOLDER *f; char *s, *t; char *child; char qc; /* quote character */ s = mailstring; while (t = strstr(s, "LIST (\\")) { s = t + 7; ++n_folders; } topfolders = allocZeroMem(n_folders * sizeof(struct FOLDER)); f = topfolders; s = mailstring; while (t = strstr(s, "LIST (\\")) { s = t + 6; child = strstr(s, "Children"); /* this should always be present */ if (!child) continue; /* HasChildren or HasNoChildren */ f->children = (child[-1] == 's'); t = child + 8; while (*t == ' ') ++t; while (*child != '\\') --child; if (child < s) /* should never happen */ child = s; strmove(child, t); if (*s == '\\') { /* there's a name */ ++s; t = strchr(s, ')'); if (!t) continue; while (t > s && t[-1] == ' ') --t; *t = 0; f->name = s; s = t + 1; /* the null folder at the top, like /, isn't really a folder. */ if (stringEqual(f->name, "Noselect")) continue; } else f->name = emptyString; /* now get the path */ t = strpbrk(s, "\r\n"); if (!t) continue; qc = ' '; s = t - 1; if (*s == '"') qc = '"', --s, --t; for (; s > child && *s != qc; --s) ; if (s == child) continue; f->path = ++s; *t = 0; if (s == t) continue; s = t + 1; /* successfully built this folder, move on to the next one */ ++f; } n_folders = f - topfolders; /* You don't dare free mailstring, because it's pieces * are now part of the folders, name and path etc. */ tf_cbase = mailstring; mailstring = 0; } /* setFolders */ static struct FOLDER *folderByName(char *line) { int i, j; struct FOLDER *f; int cnt = 0; trimWhite(line); if (!line[0]) return 0; i = stringIsNum(line); if (i > 0 && i <= n_folders) return topfolders + i - 1; f = topfolders; for (i = 0; i < n_folders; ++i, ++f) if (strstrCI(f->path, line)) ++cnt, j = i; if (cnt == 1) return topfolders + j; if (cnt) i_printf(MSG_ManyFolderMatch, line); else i_printf(MSG_NoFolderMatch, line); return 0; } /* folderByName */ /* data block for the curl ccallback write function in http.c */ static struct eb_curl_callback_data callback_data = { &mailstring, &mailstring_l }; /* imap emails come in through the headers, not the data. * No kidding! I don't understand it either. * This callback function doesn't use a data block, mailstring is assumed. */ static size_t imap_header_callback(char *i, size_t size, size_t nitems, void *data) { size_t b = nitems * size; int dots1, dots2; dots1 = mailstring_l / CHUNKSIZE; stringAndBytes(&mailstring, &mailstring_l, i, b); dots2 = mailstring_l / CHUNKSIZE; if (dots1 < dots2) { for (; dots1 < dots2; ++dots1) putchar('.'); fflush(stdout); } return b; } /* imap_header_callback */ /* after the email has been fetched via pop3 or imap */ static void undosOneMessage(void) { int j, k; if (mailstring_l >= CHUNKSIZE) nl(); /* We printed dots, so we terminate them with newline */ /* Remove DOS newlines. */ for (j = k = 0; j < mailstring_l; j++) { if (mailstring[j] == '\r' && j < mailstring_l - 1 && mailstring[j + 1] == '\n') continue; mailstring[k++] = mailstring[j]; } mailstring_l = k; mailstring[k] = 0; } /* undosOneMessage */ static char presentMail(void); static void envelopes(CURL * handle, struct FOLDER *f); static void cleanFolder(struct FOLDER *f) { int j; struct MIF *mif; if (!f->mlist) return; mif = f->mlist; for (j = 0; j < f->nfetch; ++j, ++mif) nzFree(mif->cbase); nzFree(f->mlist); f->mlist = NULL; f->nmsgs = f->nfetch = f->unread = 0; } /* cleanFolder */ /* search through imap server for a particular string */ /* Return true if the search ran successfully and found some messages. */ static bool imapSearch(CURL * handle, struct FOLDER *f, char *line) { char searchtype = 's'; char c; char *t, *u; CURLcode res; int cnt; struct MIF *mif; char cust_cmd[200]; if (*line && line[1] == ' ' && strchr("sfb", *line)) { searchtype = *line; line += 2; } else if (line[0] && !isspace(line[0]) && (isspace(line[1]) || !line[1])) { i_puts(MSG_ImapSearchHelp); return false; } skipWhite2(&line); trimWhite(line); if (!line[0]) { i_puts(MSG_Empty); return false; } if (strchr(line, '"')) { i_puts(MSG_SearchQuote); return false; } strcpy(cust_cmd, "SEARCH "); if (cons_utf8) strcat(cust_cmd, "CHARSET UTF-8 "); if (searchtype == 's') strcat(cust_cmd, "SUBJECT"); if (searchtype == 'f') strcat(cust_cmd, "FROM"); if (searchtype == 'b') strcat(cust_cmd, "BODY"); strcat(cust_cmd, " \""); strcat(cust_cmd, line); strcat(cust_cmd, "\""); curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, cust_cmd); mailstring = initString(&mailstring_l); res = curl_easy_perform(handle); if (res != CURLE_OK) { nzFree(mailstring); ebcurl_setError(res, mailbox_url); showError(); return false; } t = strstr(mailstring, "SEARCH "); if (!t) { none: nzFree(mailstring); i_puts(MSG_NoMatch); return false; } t += 6; cnt = 0; while (*t == ' ') ++t; u = t; while (*u) { if (!isdigit(*u)) break; ++cnt; while (isdigit(*u)) ++u; skipWhite2(&u); } if (!cnt) goto none; cleanFolder(f); f->nmsgs = f->nfetch = cnt; if (cnt > imapfetch) f->nfetch = imapfetch; f->mlist = allocZeroMem(sizeof(struct MIF) * f->nfetch); mif = f->mlist; u = t; while (*u) { int seqno = strtol(u, &u, 10); if (cnt <= f->nfetch) { mif->seqno = seqno; ++mif; } --cnt; skipWhite2(&u); } envelopes(handle, f); return true; } /* imapSearch */ /* scan through the messages in a folder */ static void scanFolder(CURL * handle, struct FOLDER *f, bool allmessages) { struct MIF *mif; int j; CURLcode res = CURLE_OK; char *t; char key; char cust_cmd[80]; char inputline[80]; bool yesdel = false, delflag; if (!f->nmsgs || !allmessages && !f->unread) { i_puts(MSG_NoMessages); return; } /* tell the server to descend into this folder */ if (asprintf(&t, "SELECT \"%s\"", f->path) == -1) i_printfExit(MSG_MemAllocError, strlen(f->path) + 12); curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, t); free(t); mailstring = initString(&mailstring_l); res = curl_easy_perform(handle); nzFree(mailstring); if (res != CURLE_OK) { abort: ebcurl_setError(res, mailbox_url); showError(); i_puts(MSG_EndFolder); return; } showmessages: mif = f->mlist; for (j = 0; j < f->nfetch; ++j, ++mif) { if (!allmessages && mif->seen) continue; if (!mif->seen) printf("*"); printf("%s: %s", mif->from, mif->subject); if (mif->sent) printf(" %s", conciseTime(mif->sent)); printf(" %s\n", conciseSize(mif->size)); action: delflag = false; printf("? "); fflush(stdout); key = getLetter("h?qdslmn /"); printf("\b\b\b"); fflush(stdout); if (key == '?' || key == 'h') { i_puts(MSG_ImapMessageHelp); goto action; } if (key == 'q') { i_puts(MSG_Quit); imap_done: curl_easy_cleanup(handle); exit(0); } if (key == '/') { i_printf(MSG_Search); fflush(stdout); if (!fgets(inputline, sizeof(inputline), stdin)) goto imap_done; if (!imapSearch(handle, f, inputline)) goto action; allmessages = true; if (f->nmsgs > f->nfetch) i_printf(MSG_ShowLast, f->nfetch, f->nmsgs); else i_printf(MSG_MessagesX, f->nmsgs); goto showmessages; } if (key == ' ') { /* download the email from the imap server */ sprintf(cust_cmd, "FETCH %d BODY[]", mif->seqno); curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, cust_cmd); mailstring = initString(&mailstring_l); /* I wanted to turn the write function off here, because we don't need it, * but turning it off and leaving HEADERFUNCTION on causes a seg fault, * I have no idea why. * curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, NULL); * curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); */ curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, imap_header_callback); res = curl_easy_perform(handle); /* and put things back */ curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, NULL); undosOneMessage(); if (res != CURLE_OK) { ebcurl_setError(res, mailbox_url); showError(); nzFree(mailstring); goto action; } /* have to strip 2 fetch BODY lines off the front, * and ) A018 OK off the end. */ t = strchr(mailstring, '\n'); if (t) t = strchr(t + 1, '\n'); if (t) { ++t; mailstring_l -= (t - mailstring); strmove(mailstring, t); } t = mailstring + mailstring_l; if (t > mailstring && t[-1] == '\n') t[-1] = 0, --mailstring_l; t = strrchr(mailstring, '\n'); if (t && t - 2 >= mailstring && !strncmp(t - 2, "\n)\nA", 4)) { --t; *t = 0; mailstring_l = t - mailstring; } key = presentMail(); /* presentMail has already freed mailstring */ } if (key == 's') { i_puts(MSG_Stop); break; } if (key == 'l') { i_printf(MSG_Limit); fflush(stdout); if (!fgets(inputline, sizeof(inputline), stdin)) goto imap_done; setLimit(inputline); goto action; } if (key == 'm') { struct FOLDER *g; int j2; struct MIF *mif2; i_printf(MSG_MoveTo); fflush(stdout); if (!fgets(inputline, sizeof(inputline), stdin)) goto imap_done; g = folderByName(inputline); if (g && g != f) { if (asprintf(&t, "MOVE %d \"%s\"", mif->seqno, g->path) == -1) i_printfExit(MSG_MemAllocError, 24); curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, t); free(t); mailstring = initString(&mailstring_l); res = curl_easy_perform(handle); nzFree(mailstring); if (res != CURLE_OK) goto abort; // The move command shifts all the sequence numbers down. j2 = j + 1; mif2 = mif + 1; while (j2 < f->nfetch) { --mif2->seqno; ++j2, ++mif2; } } } if (key == 'd') { delflag = true; i_puts(MSG_Delete); } if (!delflag) continue; sprintf(cust_cmd, "STORE %d +Flags \\Deleted", mif->seqno); curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, cust_cmd); mailstring = initString(&mailstring_l); res = curl_easy_perform(handle); nzFree(mailstring); if (res != CURLE_OK) goto abort; yesdel = true; } if (yesdel) { /* things deleted, time to expunge */ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "EXPUNGE"); mailstring = initString(&mailstring_l); res = curl_easy_perform(handle); nzFree(mailstring); if (res != CURLE_OK) goto abort; } puts("end of folder"); } /* scanFolder */ static void envelopes(CURL * handle, struct FOLDER *f) { int j; char *t, *u; CURLcode res; char cust_cmd[80]; for (j = 0; j < f->nfetch; ++j) { struct MIF *mif = f->mlist + j; #if 0 // nobody is using the uid, don't fetch it, save time. mailstring = initString(&mailstring_l); sprintf(cust_cmd, "FETCH %d UID", mif->seqno); curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, cust_cmd); res = curl_easy_perform(handle); if (res != CURLE_OK) goto abort; t = strstr(mailstring, "FETCH (UID "); if (t) { t += 11; while (*t == ' ') ++t; if (isdigit(*t)) mif->uid = atoi(t); } nzFree(mailstring); #endif mailstring = initString(&mailstring_l); sprintf(cust_cmd, "FETCH %d ALL", mif->seqno); curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, cust_cmd); res = curl_easy_perform(handle); if (res != CURLE_OK) { //abort: ebcurl_setError(res, mailbox_url); showErrorAbort(); } mif->cbase = mailstring; mif->subject = emptyString; mif->from = emptyString; mif->reply = emptyString; t = strstr(mailstring, "ENVELOPE ("); if (!t) { nzFree(mailstring); continue; } /* pull out subject, reply, etc */ /* Don't free mailstring, we're using pieces of it */ t += 10; while (*t == ' ') ++t; /* date first, and it must be quoted */ if (*t != '"') continue; ++t; u = strchr(t, '"'); if (!u) continue; *u = 0; mif->sent = parseHeaderDate(t); t = u + 1; /* subject next, I'll assume it is always quoted */ while (*t == ' ') ++t; if (*t != '"') continue; ++t; u = strchr(t, '"'); if (!u) continue; *u = 0; if (*t == '[' && u[-1] == ']') ++t, u[-1] = 0; mif->subject = t; t = u + 1; while (*t == ' ') ++t; if (strncmp(t, "((\"", 3)) goto doref; t += 3; u = strchr(t, '"'); if (!u) goto doref; *u = 0; mif->from = t; t = u + 1; while (*t == ' ') ++t; if (strncmp(t, "NIL", 3)) goto doref; t += 3; while (*t == ' ') ++t; /* again assuming each field is quoted */ if (*t != '"') goto doref; ++t; u = strchr(t, '"'); if (!u) goto doref; *u = '@'; ++u; while (*u == ' ') ++u; if (*u != '"') goto doref; ++u; strmove(strchr(t, '@') + 1, u); u = strchr(t, '"'); if (!u) goto doref; *u = 0; mif->reply = t; t = u + 1; doref: /* find the reference string, for replies */ u = strstr(t, " \"<"); if (!u) goto doflags; t = u + 2; u = strchr(t, '"'); if (!u) goto doflags; *u = 0; mif->refer = t; t = u + 1; doflags: /* flags, mostly looking for has this been read */ u = strstr(t, "FLAGS ("); if (!u) goto dosize; t = u + 7; if (strstr(t, "\\Seen")) mif->seen = true; else ++f->unread; dosize: u = strstr(t, "SIZE "); if (!u) continue; t = u + 5; if (!isdigit(*t)) continue; mif->size = atoi(t); } } /* envelopes */ /* examine the specified folder, gather message envelopes */ static void examineFolder(CURL * handle, struct FOLDER *f, bool dostats) { int i, j; char *t; CURLcode res; cleanFolder(f); /* interrogate folder */ if (asprintf(&t, "EXAMINE \"%s\"", f->path) == -1) i_printfExit(MSG_MemAllocError, strlen(f->path) + 12); curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, t); free(t); mailstring = initString(&mailstring_l); res = curl_easy_perform(handle); if (res != CURLE_OK) { ebcurl_setError(res, mailbox_url); showErrorAbort(); } /* look for message count */ t = strstr(mailstring, " EXISTS"); if (t) { while (*t == ' ') --t; if (isdigit(*t)) { while (isdigit(*t)) --t; ++t; f->nmsgs = atoi(t); } } f->nfetch = f->nmsgs; if (f->nfetch > imapfetch) f->nfetch = imapfetch; f->start = 1; if (f->nmsgs > f->nfetch) f->start += (f->nmsgs - f->nfetch); t = strstr(mailstring, "UIDNEXT "); if (t) { t += 8; while (*t == ' ') ++t; if (isdigit(*t)) f->uidnext = atoi(t); } nzFree(mailstring); if (dostats) { printf("%2lld %s", (unsigned long long)(f - topfolders + 1), f->path); /* if (f->children) printf(" with children"); */ printf(", "); i_printf(MSG_MessagesX, f->nmsgs); return; } if (!f->nmsgs) return; /* get some information on each message */ f->mlist = allocZeroMem(sizeof(struct MIF) * f->nfetch); for (j = 0; j < f->nfetch; ++j) { struct MIF *mif = f->mlist + j; mif->seqno = f->start + j; } envelopes(handle, f); if (f->nmsgs > f->nfetch) printf("showing last %d of %d messages\n", f->nfetch, f->nmsgs); else i_printf(MSG_MessagesX, f->nmsgs); } /* examineFolder */ /* find the last mail in the local unread directory */ static int unreadMax, unreadMin, unreadCount; static int unreadBase; /* find min larger than base */ static void unreadStats(void) { const char *f; int n; unreadMax = 0; unreadMin = 0; unreadCount = 0; while (f = nextScanFile(mailUnread)) { if (!stringIsNum(f)) continue; n = atoi(f); if (n > unreadMax) unreadMax = n; if (n > unreadBase) { if (!unreadMin || n < unreadMin) unreadMin = n; ++unreadCount; } } } /* unreadStats */ static char *umf; /* unread mail file */ static char *umf_end; static int umfd; /* file descriptor for the above */ /* convert mail message to/from utf8 if need be. */ /* This isn't really right, cause it should be done per mime component. */ static char *mailu8; static int mailu8_l; static CURL *newFetchmailHandle(const char *username, const char *password) { CURLcode res; CURL *handle = curl_easy_init(); if (!handle) i_printfExit(MSG_LibcurlNoInit); curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, mailTimeout); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, eb_curl_callback); curl_easy_setopt(handle, CURLOPT_WRITEDATA, &callback_data); if (debugLevel >= 4) curl_easy_setopt(handle, CURLOPT_VERBOSE, 1); curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, ebcurl_debug_handler); res = curl_easy_setopt(handle, CURLOPT_CAINFO, sslCerts); if (res != CURLE_OK) i_printfExit(MSG_LibcurlNoInit); res = curl_easy_setopt(handle, CURLOPT_USERNAME, username); if (res != CURLE_OK) { ebcurl_setError(res, mailbox_url); showErrorAbort(); } res = curl_easy_setopt(handle, CURLOPT_PASSWORD, password); if (res != CURLE_OK) { ebcurl_setError(res, mailbox_url); showErrorAbort(); } return handle; } /* newFetchmailHandle */ static void get_mailbox_url(const struct MACCOUNT *account) { const char *scheme = "pop3"; char *url = NULL; if (account->inssl) scheme = "pop3s"; if (isimap) scheme = (account->inssl ? "imaps" : "imap"); if (asprintf(&url, "%s://%s:%d/", scheme, account->inurl, account->inport) == -1) { /* The byte count is a little white lie / guess, we don't know * how much asprintf *really* requested. */ i_printfExit(MSG_MemAllocError, strlen(scheme) + strlen(account->inurl) + 8); } mailbox_url = url; } /* get_mailbox_url */ /* reads message into mailstring, it's up to you to free it */ static CURLcode fetchOneMessage(CURL * handle, int message_number) { CURLcode res = CURLE_OK; mailstring = initString(&mailstring_l); res = setCurlURL(handle, message_url); if (res != CURLE_OK) return res; res = curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, NULL); if (res != CURLE_OK) return res; res = curl_easy_setopt(handle, CURLOPT_NOBODY, 0L); if (res != CURLE_OK) return res; res = curl_easy_perform(handle); undosOneMessage(); if (res != CURLE_OK) return res; /* got the file, save it in unread */ sprintf(umf_end, "%d", unreadMax + message_number); umfd = open(umf, O_WRONLY | O_TEXT | O_CREAT, 0666); if (umfd < 0) i_printfExit(MSG_NoCreate, umf); if (write(umfd, mailstring, mailstring_l) < mailstring_l) i_printfExit(MSG_NoWrite, umf); close(umfd); return res; } /* fetchOneMessage */ static CURLcode deleteOneMessage(CURL * handle) { CURLcode res = curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "DELE"); if (res != CURLE_OK) return res; res = curl_easy_setopt(handle, CURLOPT_NOBODY, 1L); if (res != CURLE_OK) return res; res = curl_easy_perform(handle); return res; } /* deleteOneMessage */ static CURLcode count_messages(CURL * handle, int *message_count) { CURLcode res = setCurlURL(handle, mailbox_url); int i, num_messages = 0; bool last_nl = true; if (res != CURLE_OK) return res; res = curl_easy_perform(handle); if (res != CURLE_OK) return res; if (isimap) { struct FOLDER *f; char inputline[80]; char *t; bool allmessages; setFolders(); if (!n_folders) { i_puts(MSG_NoFolders); imap_done: curl_easy_cleanup(handle); exit(0); } f = topfolders; for (i = 0; i < n_folders; ++i, ++f) examineFolder(handle, f, true); input: i_puts(MSG_SelectFolder); if (!fgets(inputline, sizeof(inputline), stdin)) goto imap_done; t = inputline; if (*t == 'l' && isspace(t[1])) { setLimit(t + 1); goto input; } if (*t == 'q') { i_puts(MSG_Quit); goto imap_done; } allmessages = true; if (*t == '-') allmessages = false, ++t; f = folderByName(t); if (!f) goto input; examineFolder(handle, f, false); scanFolder(handle, f, allmessages); goto input; } for (i = 0; i < mailstring_l; i++) { if (mailstring[i] == '\n' || mailstring[i] == '\r') { last_nl = true; continue; } if (last_nl && isdigit(mailstring[i])) num_messages++; last_nl = false; } *message_count = num_messages; return CURLE_OK; } /* count_messages */ /* Returns number of messages fetched */ int fetchMail(int account) { CURL *mail_handle; const struct MACCOUNT *a = accounts + account - 1; const char *login = a->login; const char *pass = a->password; int nfetch = 0; /* number of messages actually fetched */ CURLcode res = CURLE_OK; const char *url_for_error; int message_count = 0, message_number; get_mailbox_url(a); url_for_error = mailbox_url; if (!mailDir) i_printfExit(MSG_NoMailDir); if (chdir(mailDir)) i_printfExit(MSG_NoDirChange, mailDir); if (!umf) { umf = allocMem(strlen(mailUnread) + 12); sprintf(umf, "%s/", mailUnread); umf_end = umf + strlen(umf); } unreadBase = 0; unreadStats(); mailstring = initString(&mailstring_l); mail_handle = newFetchmailHandle(login, pass); res = count_messages(mail_handle, &message_count); if (res != CURLE_OK) goto fetchmail_cleanup; for (message_number = 1; message_number <= message_count; message_number++) { if (asprintf(&message_url, "%s%u", mailbox_url, message_number) == -1) { /* Again, the byte count in the error message is a bit of a fib. */ i_printfExit(MSG_MemAllocError, strlen(mailbox_url) + 11); } nzFree(mailstring); res = fetchOneMessage(mail_handle, message_number); if (res != CURLE_OK) goto fetchmail_cleanup; nfetch++; res = deleteOneMessage(mail_handle); if (res != CURLE_OK) goto fetchmail_cleanup; nzFree(message_url); message_url = NULL; } fetchmail_cleanup: if (message_url) url_for_error = message_url; if (res != CURLE_OK) { ebcurl_setError(res, url_for_error); showError(); } curl_easy_cleanup(mail_handle); nzFree(message_url); nzFree(mailbox_url); nzFree(mailstring); mailstring = initString(&mailstring_l); return nfetch; } /* fetchMail */ /* fetch from all accounts except those with nofetch or imap set */ int fetchAllMail(void) { int i, j; const struct MACCOUNT *a, *b; int nfetch = 0; for (i = 1; i <= maxAccount; ++i) { a = accounts + i - 1; if (a->nofetch | a->imap) continue; /* don't fetch from an earlier account that has the same host an dlogin */ for (j = 1; j < i; ++j) { b = accounts + j - 1; if (!b->nofetch && stringEqual(a->inurl, b->inurl) && stringEqual(a->login, b->login)) break; } if (j < i) continue; debugPrint(3, "fetch from %d %s", i, a->inurl); nfetch += fetchMail(i); } return nfetch; } /* fetchAllMail */ static void readReplyInfo(void); static void writeReplyInfo(const char *addstring); void scanMail(void) { int nmsgs, m; if (!isInteractive) i_printfExit(MSG_FetchNotBackgnd); if (!mailDir) i_printfExit(MSG_NoMailDir); if (chdir(mailDir)) i_printfExit(MSG_NoDirChange, mailDir); if (!umf) { umf = allocMem(strlen(mailUnread) + 12); sprintf(umf, "%s/", mailUnread); umf_end = umf + strlen(umf); } /* How many mail messages? */ unreadBase = 0; unreadStats(); nmsgs = unreadCount; if (!nmsgs) { i_puts(MSG_NoMail); exit(0); } i_printf(MSG_MessagesX, nmsgs); loadAddressBook(); for (m = 1; m <= nmsgs; ++m) { nzFree(lastMailText); lastMailText = 0; /* Now grab the entire message */ unreadStats(); sprintf(umf_end, "%d", unreadMin); if (!fileIntoMemory(umf, &mailstring, &mailstring_l)) showErrorAbort(); unreadBase = unreadMin; if (presentMail() == 'd') unlink(umf); } /* loop over mail messages */ exit(0); } /* scanMail */ /* a mail message is in mailstring, present it to the user */ /* Return the key that was pressed. * stop is only meaningful for imap. */ static char presentMail(void) { int j, k; const char *redirect = NULL; /* send mail elsewhere */ char key = 0; const char *atname = NULL; /* name of file or attachment */ bool delflag = false; /* delete this mail */ bool scanat = false; /* scan for attachments */ int displine; int stashNumber = -1; char exists; int fsize; /* file size */ int fh; /* clear things out from the last message */ if (lastMailInfo) freeMailInfo(lastMailInfo); lastMailInfo = 0; if (sessionList[1].lw) cxQuit(1, 2); cs = 0; cxSwitch(1, false); iuReformat(mailstring, mailstring_l, &mailu8, &mailu8_l); if (mailu8) { if (!addTextToBuffer((pst) mailu8, mailu8_l, 0, false)) showErrorAbort(); } else { if (!addTextToBuffer((pst) mailstring, mailstring_l, 0, false)) showErrorAbort(); } browseCurrentBuffer(); if (!passMail) { redirect = mailRedirect(lastMailInfo->to, lastMailInfo->from, lastMailInfo->reply, lastMailInfo->subject); } if (redirect) { if (!isimap) { delflag = true; key = 'w'; if (*redirect == '-') ++redirect, key = 'u'; if (stringEqual(redirect, "x")) i_puts(MSG_Junk); else printf("> %s\n", redirect); } else { if (*redirect == '-') ++redirect; if (stringEqual(redirect, "x")) redirect = NULL; } } /* display the next page of mail and get a command from the keyboard */ displine = 1; paging: if (!delflag) { /* show next page */ if (displine <= cw->dol) { for (j = 0; j < 20 && displine <= cw->dol; ++j, ++displine) { char *showline = (char *)fetchLine(displine, 1); k = pstLength((pst) showline); showline[--k] = 0; printf("%s\n", showline); nzFree(showline); } } } /* get key command from user */ key_command: if (delflag) goto writeMail; /* interactive prompt depends on whether there is more text or not */ printf("%c ", displine > cw->dol ? '?' : '*'); fflush(stdout); key = getLetter((isimap ? "qh? nwWuUasdm" : "qh? nwud")); printf("\b\b\b"); fflush(stdout); switch (key) { case 'q': i_puts(MSG_Quit); exit(0); case 'n': i_puts(MSG_Next); goto afterinput; case 's': case 'm': goto afterinput; case 'd': if (!isimap) i_puts(MSG_Delete); delflag = true; goto afterinput; case ' ': if (displine > cw->dol) i_puts(MSG_EndMessage); goto paging; case '?': case 'h': i_puts(isimap ? MSG_ImapReadHelp : MSG_MailHelp); goto key_command; case 'a': key = 'w'; /* this will scan attachments */ scanat = true; case 'w': case 'W': case 'u': case 'U': break; default: i_puts(MSG_NYI); goto key_command; } /* switch */ /* At this point we're saving the mail somewhere. */ writeMail: if (!isimap || isupper(key)) delflag = true; atname = 0; if (!isimap) atname = redirect; if (scanat) goto attachOnly; saveMail: if (!atname) atname = getFileName(MSG_FileName, redirect, false, false); if (stringEqual(atname, "x")) goto afterinput; exists = fileTypeByName(atname, false); fh = open(atname, O_WRONLY | O_TEXT | O_CREAT | O_APPEND, 0666); if (fh < 0) { i_printf(MSG_NoCreate, atname); goto saveMail; } if (exists) write(fh, "======================================================================\n", 71); if (key == 'u') { if (write(fh, mailstring, mailstring_l) < mailstring_l) { badsave: i_printf(MSG_NoWrite, atname); close(fh); goto saveMail; } close(fh); fsize = mailstring_l; } else { /* key = w, write the file - if pop then save the original unformatted */ if (!isimap && mailStash) { char *rmf; /* raw mail file */ int rmfh; /* file handle to same */ /* I want a fairly easy filename, in case I want to go look at the original. * Not a 30 character message ID that I am forced to cut&paste. * 4 or 5 digits would be nice. * So the filename looks like /home/foo/.Trash/rawmail/36921 * I pick the digits randomly. * Please don't accumulate 100,000 emails before you empty your trash. * It's good to have a cron job empty the trash early Sunday morning. */ k = strlen(mailStash); rmf = allocMem(k + 12); /* Try 20 times, then give up. */ for (j = 0; j < 20; ++j) { int rn = rand() % 100000; /* random number */ sprintf(rmf, "%s/%05d", mailStash, rn); if (fileTypeByName(rmf, false)) continue; /* dump the original mail into the file */ rmfh = open(rmf, O_WRONLY | O_TEXT | O_CREAT | O_APPEND, 0666); if (rmfh < 0) break; if (write(rmfh, mailstring, mailstring_l) < mailstring_l) { close(rmfh); unlink(rmf); break; } close(rmfh); /* written successfully, remember the stash number */ stashNumber = rn; break; } } fsize = 0; for (j = 1; j <= cw->dol; ++j) { char *showline = (char *)fetchLine(j, 1); int len = pstLength((pst) showline); if (write(fh, showline, len) < len) goto badsave; nzFree(showline); fsize += len; } /* loop over lines */ if (stashNumber >= 0) { char addstash[60]; int minor = rand() % 100000; sprintf(addstash, "\nUnformatted %05d.%05d\n", stashNumber, minor); k = strlen(addstash); if (write(fh, addstash, k) < k) goto badsave; fsize += k; /* write the mailInfo data to the mail reply file */ addstash[k - 1] = ':'; writeReplyInfo(addstash + k - 12); } close(fh); attachOnly: if (nattach) writeAttachments(lastMailInfo); else if (scanat) i_puts(MSG_NoAttachments); } /* unformat or format */ if (scanat) goto afterinput; /* print "mail saved" message */ i_printf(MSG_MailSaved, fsize); if (exists) i_printf(MSG_Appended); nl(); afterinput: nzFree(mailstring); mailstring = 0; nzFree(mailu8); mailu8 = 0; if (delflag) return 'd'; if (key == 's' || key == 'm') return key; return 'n'; } /* presentMail */ /* Here are the common keywords for mail header lines. * These are in alphabetical order, so you can stick more in as you find them. * The more words we have, the more accurate the test. */ static const char *const mhwords[] = { "action:", "arrival-date:", "bcc:", "cc:", "content-transfer-encoding:", "content-type:", "date:", "delivered-to:", "errors-to:", "final-recipient:", "from:", "importance:", "last-attempt-date:", "list-id:", "mailing-list:", "message-id:", "mime-version:", "precedence:", "received:", "remote-mta:", "reply-to:", "reporting-mta:", "return-path:", "sender:", "sent:", "status:", "subject:", "to:", "user-agent:", "x-beenthere:", "x-comment:", "x-loop:", "x-mailer:", "x-mailman-version:", "x-mdaemon-deliver-to:", "x-mdremoteip:", "x-mimeole:", "x-ms-tnef-correlator:", "x-msmail-priority:", "x-originating-ip:", "x-priority:", "x-return-path:", "X-Spam-Checker-Version:", "x-spam-level:", "x-spam-msg-id:", "X-SPAM-Msg-Sniffer-Result:", "x-spam-processed:", "x-spam-status:", "x-uidl:", 0 }; /* Before we render a mail message, let's make sure it looks like email. * This is similar to htmlTest() in html.c. */ bool emailTest(void) { int i, j, k, n; /* This is a very simple test - hopefully not too simple. * The first 20 non-indented lines have to look like mail header lines, * with at least half the keywords recognized. */ for (i = 1, j = k = 0; i <= cw->dol && j < 20; ++i) { char *q; char *p = (char *)fetchLine(i, -1); char first = *p; if (first == '\n' || first == '\r' && p[1] == '\n') break; if (first == ' ' || first == '\t') continue; ++j; /* nonindented line */ for (q = p; isalnumByte(*q) || *q == '_' || *q == '-'; ++q) ; if (q == p) continue; if (*q++ != ':') continue; /* X-Whatever is a mail header word */ if (q - p >= 8 && p[1] == '-' && toupper(p[0]) == 'X') { ++k; } else { for (n = 0; mhwords[n]; ++n) if (memEqualCI(mhwords[n], p, q - p)) break; if (mhwords[n]) ++k; } if (k >= 4 && k * 2 >= j) return true; } /* loop over lines */ return false; } /* emailTest */ static void mail64Error(int err) { switch (err) { case BAD_BASE64_DECODE: runningError(MSG_AttBad64); break; case EXTRA_CHARS_BASE64_DECODE: runningError(MSG_AttAfterChars); break; } /* switch on error code */ } /* mail64Error */ static void unpackQP(struct MHINFO *w) { uchar val; char c, d, *q, *r; for (q = r = w->start; q < w->end; ++q) { c = *q; if (c != '=') { *r++ = c; continue; } c = *++q; if (c == '\n') continue; d = q[1]; if (isxdigit(c) && isxdigit(d)) { d = fromHex(c, d); if (d == 0) d = ' '; *r++ = d; ++q; continue; } --q; *r++ = '='; } w->end = r; *r = 0; } /* unpackQP */ void unpackUploadedFile(const char *post, const char *boundary, char **postb, int *postb_l) { static const char message64[] = "Content-Transfer-Encoding: base64"; const int boundlen = strlen(boundary); const int m64len = strlen(message64); char *post2; char *b1, *b2, *b3, *b4; /* boundary points */ int unpack_ret; *postb = 0; *postb_l = 0; if (!strstr(post, message64)) return; post2 = cloneString(post); b2 = strstr(post2, boundary); while (true) { b1 = b2 + boundlen; if (*b1 != '\r') break; b1 += 2; b1 = strstr(b1, "Content-Transfer"); b2 = strstr(b1, boundary); if (memcmp(b1, message64, m64len)) continue; b1 += m64len - 6; strcpy(b1, "8bit\r\n\r\n"); b1 += 8; b1[0] = b1[1] = ' '; b3 = b2 - 4; b4 = b3; unpack_ret = base64Decode(b1, &b4); if (unpack_ret != GOOD_BASE64_DECODE) mail64Error(unpack_ret); /* Should we *really* keep going at this point? */ strmove(b4, b3); b2 = b4 + 4; } b1 += strlen(b1); *postb = post2; *postb_l = b1 - post2; } /* unpackUploadedFile */ /* Look for the name of the attachment and boundary */ static void ctExtras(struct MHINFO *w, const char *s, const char *t) { char quote; const char *q, *al, *ar; if (w->ct < CT_MULTI) { quote = 0; for (q = s + 1; q < t; ++q) { if (isalnumByte(q[-1])) continue; /* could be name= or filename= */ if (memEqualCI(q, "file", 4)) q += 4; if (!memEqualCI(q, "name=", 5)) continue; q += 5; if (*q == '"') { quote = *q; ++q; } for (al = q; q < t; ++q) { if (*q == '"') break; if (quote) continue; if (strchr(",; \t", *q)) break; } ar = q; if (ar - al >= MHLINE) ar = al + MHLINE - 1; strncpy(w->cfn, al, ar - al); break; } } /* regular file */ if (w->ct >= CT_MULTI) { quote = 0; for (q = s + 1; q < t; ++q) { if (isalnumByte(q[-1])) continue; if (!memEqualCI(q, "boundary=", 9)) continue; q += 9; if (*q == '"') { quote = *q; ++q; } for (al = q; q < t; ++q) { if (*q == '"') break; if (quote) continue; if (strchr(",; \t", *q)) break; } ar = q; w->boundlen = ar - al; strncpy(w->boundary, al, ar - al); break; } } /* multi or alt */ } /* ctExtras */ static void isoDecode(char *vl, char **vrp) { char *vr = *vrp; char *start, *end; /* section being decoded */ char *s, *t, c, d, code; int len; uchar val, leftover, mod; start = vl; restart: start = strstr(start, "=?"); if (!start || start >= vr) goto finish; start += 2; if (!memEqualCI(start, "iso-", 4) && !memEqualCI(start, "us-ascii", 8) && !memEqualCI(start, "utf-", 4) && !memEqualCI(start, "cp1252", 6) && !memEqualCI(start, "gb", 2) && !memEqualCI(start, "windows-", 8)) goto restart; s = strchr(start, '?'); if (!s || s > vr - 5 || s[2] != '?') goto restart; code = s[1]; code = toupper(code); if (code != 'Q' && code != 'B') goto restart; s += 3; end = strstr(s, "?="); if (!end || end > vr - 2) goto restart; t = start - 2; if (code == 'Q') { while (s < end) { c = *s++; if (c == '=') { c = *s; d = s[1]; if (isxdigit(c) && isxdigit(d)) { d = fromHex(c, d); *t++ = d; s += 2; continue; } c = '='; } *t++ = c; } goto copy; } /* base64 */ mod = 0; for (; s < end; ++s) { c = *s; if (isspaceByte(c)) continue; if (c == '=') continue; val = base64Bits(c); if (val & 64) val = 0; /* ignore errors here */ if (mod == 0) { leftover = val << 2; } else if (mod == 1) { *t++ = (leftover | (val >> 4)); leftover = val << 4; } else if (mod == 2) { *t++ = (leftover | (val >> 2)); leftover = val << 6; } else { *t++ = (leftover | val); } ++mod; mod &= 3; } copy: s += 2; start = t; len = vr - s; if (len) memmove(t, s, len); vr = t + len; goto restart; finish: for (s = vl; s < vr; ++s) { c = *s; if (c == 0 || c == '\t') *s = ' '; } *vrp = vr; } /* isoDecode */ /* mail header reformat, to/from utf8 */ static void mhReformat(char *line) { char *tbuf; int tlen = strlen(line); iuReformat(line, tlen, &tbuf, &tlen); if (!tbuf) return; if (tlen >= MHLINE) tbuf[MHLINE - 1] = 0; strcpy(line, tbuf); nzFree(tbuf); } /* mhReformat */ static void extractLessGreater(char *s) { char *vl, *vr; vl = strchr(s, '<'); vr = strchr(s, '>'); if (vl && vr && vl < vr) { *vr = 0; strmove(s, vl + 1); } } /* extractLessGreater */ /* Now that we know it's mail, see what information we can * glean from the headers. * Returns a pointer to an allocated MHINFO structure. * This routine is recursive. */ static struct MHINFO *headerGlean(char *start, char *end) { char *s, *t, *q; char *vl, *vr; /* value left and value right */ struct MHINFO *w; int j, k, n; char linetype = 0; /* defaults */ w = allocZeroMem(sizeof(struct MHINFO)); initList(&w->components); w->ct = CT_OTHER; w->ce = CE_8BIT; w->andOthers = false; w->tolist = initString(&w->tolen); w->cclist = initString(&w->cclen); w->start = start, w->end = end; for (s = start; s < end; s = t + 1) { char quote; char first = *s; t = strchr(s, '\n'); if (!t) t = end - 1; /* should never happen */ if (t == s) break; /* empty line */ if (first == ' ' || first == '\t') { if (linetype == 'c') ctExtras(w, s, t); if (linetype == 't') stringAndBytes(&w->tolist, &w->tolen, s, t - s); if (linetype == 'y') stringAndBytes(&w->cclist, &w->cclen, s, t - s); continue; } /* find the lead word */ for (q = s; isalnumByte(*q) || *q == '_' || *q == '-'; ++q) ; if (q == s) continue; /* should never happen */ if (*q++ != ':') continue; /* should never happen */ for (vl = q; *vl == ' ' || *vl == '\t'; ++vl) ; for (vr = t; vr > vl && (vr[-1] == ' ' || vr[-1] == '\t'); --vr) ; if (vr == vl) continue; /* empty */ /* too long? */ if (vr - vl > MHLINE - 1) vr = vl + MHLINE - 1; /* This is sort of a switch statement on the word */ if (memEqualCI(s, "subject:", q - s)) { linetype = 's'; if (w->subject[0]) continue; /* get rid of forward/reply prefixes */ for (q = vl; q < vr; ++q) { static const char *const prefix[] = { "re", "sv", "fwd", 0 }; if (!isalphaByte(*q)) continue; if (q > vl && isalnumByte(q[-1])) continue; for (j = 0; prefix[j]; ++j) if (memEqualCI (q, prefix[j], strlen(prefix[j]))) break; if (!prefix[j]) continue; j = strlen(prefix[j]); if (!strchr(":-,;", q[j])) continue; ++j; while (q + j < vr && q[j] == ' ') ++j; memmove(q, q + j, vr - q - j); vr -= j; --q; /* try again */ } isoDecode(vl, &vr); strncpy(w->subject, vl, vr - vl); /* If the subject is really long, spreads onto the next line, * I'll just use ... */ if (t < end - 1 && (t[1] == ' ' || t[1] == '\t')) strcat(w->subject, "..."); mhReformat(w->subject); continue; } if (memEqualCI(s, "reply-to:", q - s)) { linetype = 'r'; if (!w->reply[0]) strncpy(w->reply, vl, vr - vl); continue; } if (memEqualCI(s, "message-id:", q - s)) { linetype = 'm'; if (!w->mid[0]) strncpy(w->mid, vl, vr - vl); continue; } if (memEqualCI(s, "references:", q - s)) { linetype = 'e'; if (!w->ref[0]) strncpy(w->ref, vl, vr - vl); continue; } if (memEqualCI(s, "from:", q - s)) { linetype = 'f'; if (w->from[0]) continue; isoDecode(vl, &vr); strncpy(w->from, vl, vr - vl); mhReformat(w->from); continue; } if (memEqualCI(s, "date:", q - s) || memEqualCI(s, "sent:", q - s)) { linetype = 'd'; if (w->date[0]) continue; /* don't need the weekday, seconds, or timezone */ if (vr - vl > 5 && isalphaByte(vl[0]) && isalphaByte(vl[1]) && isalphaByte(vl[2]) && vl[3] == ',' && vl[4] == ' ') vl += 5; strncpy(w->date, vl, vr - vl); q = strrchr(w->date, ':'); if (q) *q = 0; continue; } if (memEqualCI(s, "to:", q - s)) { linetype = 't'; if (w->tolen) stringAndChar(&w->tolist, &w->tolen, ','); stringAndBytes(&w->tolist, &w->tolen, q, vr - q); if (w->to[0]) continue; strncpy(w->to, vl, vr - vl); /* Only retain the first recipient */ quote = 0; for (q = w->to; *q; ++q) { if (*q == ',' && !quote) { w->andOthers = true; break; } if (*q == '"') { if (!quote) quote = *q; else if (quote == *q) quote = 0; continue; } if (*q == '<') { if (!quote) quote = *q; continue; } if (*q == '>') { if (quote == '<') quote = 0; continue; } } *q = 0; /* cut it off at the comma */ continue; } if (memEqualCI(s, "cc:", q - s)) { linetype = 'y'; if (w->cclen) stringAndChar(&w->cclist, &w->cclen, ','); stringAndBytes(&w->cclist, &w->cclen, q, vr - q); w->andOthers = true; continue; } if (memEqualCI(s, "content-type:", q - s)) { linetype = 'c'; if (memEqualCI(vl, "application/pgp-signature", 25)) w->pgp = true; if (memEqualCI(vl, "text", 4)) w->ct = CT_RICH; if (memEqualCI(vl, "text/html", 9)) w->ct = CT_HTML; if (memEqualCI(vl, "text/plain", 10)) w->ct = CT_TEXT; if (memEqualCI(vl, "application", 11)) w->ct = CT_APPLIC; if (memEqualCI(vl, "multipart", 9)) w->ct = CT_MULTI; if (memEqualCI(vl, "multipart/alternative", 21)) w->ct = CT_ALT; ctExtras(w, s, t); continue; } if (memEqualCI(s, "content-transfer-encoding:", q - s)) { linetype = 'e'; if (memEqualCI(vl, "quoted-printable", 16)) w->ce = CE_QP; if (memEqualCI(vl, "7bit", 4)) w->ce = CE_7BIT; if (memEqualCI(vl, "8bit", 4)) w->ce = CE_8BIT; if (memEqualCI(vl, "base64", 6)) w->ce = CE_64; continue; } linetype = 0; } /* loop over lines */ /* make sure there's room for a final nl */ stringAndChar(&w->tolist, &w->tolen, ' '); stringAndChar(&w->cclist, &w->cclen, ' '); extractEmailAddresses(w->tolist); extractEmailAddresses(w->cclist); w->start = start = s + 1; /* Fix up reply and from lines. * From should be the name, reply the address. */ if (!w->from[0]) strcpy(w->from, w->reply); if (!w->reply[0]) strcpy(w->reply, w->from); if (w->from[0] == '"') { strmove(w->from, w->from + 1); q = strchr(w->from, '"'); if (q) *q = 0; } vl = strchr(w->from, '<'); vr = strchr(w->from, '>'); if (vl && vr && vl < vr) { while (vl > w->from && vl[-1] == ' ') --vl; *vl = 0; } extractLessGreater(w->reply); /* get rid of (name) comment */ vl = strchr(w->reply, '('); vr = strchr(w->reply, ')'); if (vl && vr && vl < vr) { while (vl > w->reply && vl[-1] == ' ') --vl; *vl = 0; } /* no @ means it's not an email address */ if (!strchr(w->reply, '@')) w->reply[0] = 0; if (stringEqual(w->reply, w->from)) w->from[0] = 0; extractLessGreater(w->to); extractLessGreater(w->mid); extractLessGreater(w->ref); cutDuplicateEmails(w->tolist, w->cclist, w->reply); if (debugLevel >= 5) { puts("mail header analyzed"); printf("subject: %s\n", w->subject); printf("from: %s\n", w->from); printf("date: %s\n", w->date); printf("reply: %s\n", w->reply); printf("tolist: %s\n", w->tolist); printf("cclist: %s\n", w->cclist); printf("reference: %s\n", w->ref); printf("message: %s\n", w->mid); printf("boundary: %d|%s\n", w->boundlen, w->boundary); printf("filename: %s\n", w->cfn); printf("content %d/%d\n", w->ct, w->ce); } if (w->ce == CE_QP) unpackQP(w); if (w->ce == CE_64) { w->error64 = base64Decode(w->start, &w->end); if (w->error64 != GOOD_BASE64_DECODE) mail64Error(w->error64); } if (w->ce == CE_64 && w->ct == CT_OTHER || w->ct == CT_APPLIC || w->cfn[0]) { w->doAttach = true; ++nattach; q = w->cfn; if (*q) { /* name present */ if (stringEqual(q, "winmail.dat")) { w->atimage = true; ++nimages; } else if ((q = strrchr(q, '.'))) { static const char *const imagelist[] = { "gif", "jpg", "tif", "bmp", "asc", "png", 0 }; /* the asc isn't an image, it's a signature card. */ /* Similarly for the winmail.dat */ if (stringInListCI(imagelist, q + 1) >= 0) { w->atimage = true; ++nimages; } } if (!w->atimage && nattach == nimages + 1) firstAttach = w->cfn; } return w; } /* loop over the mime components */ if (w->ct == CT_MULTI || w->ct == CT_ALT) { char *lastbound = 0; bool endmode = false; struct MHINFO *child; /* We really need the -1 here, because sometimes the boundary will * be the very first thing in the message body. */ s = w->start - 1; while (!endmode && (t = strstr(s, "\n--")) && t < end) { if (memcmp(t + 3, w->boundary, w->boundlen)) { s = t + 3; continue; } q = t + 3 + w->boundlen; while (*q == '-') endmode = true, ++q; if (*q == '\n') ++q; debugPrint(5, "boundary found at offset %d", t - w->start); if (lastbound) { child = headerGlean(lastbound, t); addToListBack(&w->components, child); } s = lastbound = q; } w->start = w->end = 0; return w; } /* mime or alt */ /* Scan through, we might have a mail message included inline */ vl = 0; /* first mail header keyword line */ for (s = start; s < end; s = t + 1) { char first = *s; t = strchr(s, '\n'); if (!t) t = end - 1; /* should never happen */ if (t == s) { /* empty line */ if (!vl) continue; /* Do we have enough for a mail header? */ if (k >= 4 && k * 2 >= j) { struct MHINFO *child = headerGlean(vl, end); addToListBack(&w->components, child); w->end = end = vl; goto textonly; } /* found mail message inside */ vl = 0; } /* empty line */ if (first == ' ' || first == '\t') continue; /* indented */ for (q = s; isalnumByte(*q) || *q == '_' || *q == '-'; ++q) ; if (q == s || *q != ':') { vl = 0; continue; } /* looks like header: stuff */ if (!vl) { vl = s; j = k = 0; } ++j; for (n = 0; mhwords[n]; ++n) if (memEqualCI(mhwords[n], s, q - s)) break; if (mhwords[n]) ++k; } /* loop over lines */ /* Header could be at the very end */ if (vl && k >= 4 && k * 2 >= j) { struct MHINFO *child = headerGlean(vl, end); addToListBack(&w->components, child); w->end = end = vl; } textonly: /* Any additional processing of the text, from start to end, can go here. */ /* Remove leading blank lines or lines with useless words */ for (s = start; s < end; s = t + 1) { t = strchr(s, '\n'); if (!t) t = end - 1; /* should never happen */ vl = s, vr = t; if (vr - vl >= 4 && memEqualCI(vr - 4, "
    ", 4)) vr -= 4; while (vl < vr) { if (isalnumByte(*vl)) break; ++vl; } while (vl < vr) { if (isalnumByte(vr[-1])) break; --vr; } if (vl == vr) continue; /* empty */ if (memEqualCI(vl, "forwarded message", vr - vl)) continue; if (memEqualCI(vl, "original message", vr - vl)) continue; break; /* something real */ } w->start = start = s; return w; } /* headerGlean */ static char *headerShow(struct MHINFO *w, bool top) { static char buf[(MHLINE + 30) * 4]; static char lastsubject[MHLINE]; char *s; bool lines = false; buf[0] = 0; if (!(w->subject[0] | w->from[0] | w->reply[0])) return buf; if (!top) { strcpy(buf, "Message"); lines = true; if (w->from[0]) { strcat(buf, " from "); strcat(buf, w->from); } if (w->subject[0]) { if (stringEqual(w->subject, lastsubject)) { strcat(buf, " with the same subject"); } else { strcat(buf, " with subject: "); strcat(buf, w->subject); } } else strcat(buf, " with no subject"); if (mailIsHtml) { /* trash & < > */ for (s = buf; *s; ++s) { /* This is quick and stupid */ if (*s == '<') *s = '('; if (*s == '>') *s = ')'; if (*s == '&') *s = '*'; } } /* need a dot at the end? */ s = buf + strlen(buf); if (isalnumByte(s[-1])) *s++ = '.'; strcpy(s, mailIsHtml ? "\n
    " : "\n"); if (w->date[0]) { strcat(buf, "Sent "); strcat(buf, w->date); } if (w->reply) { if (!w->date[0]) strcat(buf, "From "); else strcat(buf, " from "); strcat(buf, w->reply); } if (w->date[0] | w->reply[0]) { /* second line */ strcat(buf, "\n"); } } else { /* This is at the top of the file */ if (w->subject[0]) { sprintf(buf, "Subject: %s\n", w->subject); lines = true; } if (nattach && ismc) { char atbuf[20]; if (lines & mailIsHtml) strcat(buf, "
    "); lines = true; if (nimages) { sprintf(atbuf, "%d images\n", nimages); if (nimages == 1) strcpy(atbuf, "1 image"); strcat(buf, atbuf); if (nattach > nimages) strcat(buf, " + "); } if (nattach == nimages + 1) { strcat(buf, "1 attachment"); if (firstAttach && firstAttach[0]) { strcat(buf, " "); strcat(buf, firstAttach); } } if (nattach > nimages + 1) { sprintf(atbuf, "%d attachments\n", nattach - nimages); strcat(buf, atbuf); } strcat(buf, "\n"); } /* attachments */ if (w->to[0] && !ismc) { if (lines & mailIsHtml) strcat(buf, "
    "); lines = true; strcat(buf, "To "); strcat(buf, w->to); if (w->andOthers) strcat(buf, " and others"); strcat(buf, "\n"); } if (w->from[0]) { if (lines & mailIsHtml) strcat(buf, "
    "); lines = true; strcat(buf, "From "); strcat(buf, w->from); strcat(buf, "\n"); } if (w->date[0] && !ismc) { if (lines & mailIsHtml) strcat(buf, "
    "); lines = true; strcat(buf, "Mail sent "); strcat(buf, w->date); strcat(buf, "\n"); } if (w->reply[0]) { if (lines & mailIsHtml) strcat(buf, "
    "); lines = true; strcat(buf, "Reply to "); strcat(buf, w->reply); strcat(buf, "\n"); } } if (lines) strcat(buf, mailIsHtml ? "

    \n" : "\n"); strcpy(lastsubject, w->subject); return buf; } /* headerShow */ /* Depth first block of text determines the type */ static int mailTextType(struct MHINFO *w) { struct MHINFO *v; int texttype = CT_OTHER, rc; if (w->doAttach) return CT_OTHER; /* jump right into the hard part, multi/alt */ if (w->ct >= CT_MULTI) { foreach(v, w->components) { rc = mailTextType(v); if (rc == CT_HTML) return rc; if (rc == CT_OTHER) continue; if (w->ct == CT_MULTI) return rc; texttype = rc; } return texttype; } /* multi */ /* If there is no text, return nothing */ if (w->start == w->end) return CT_OTHER; /* I don't know if this is right, but I override the type, * and make it html, if we start out with */ if (memEqualCI(w->start, "", 6)) return CT_HTML; return w->ct == CT_HTML ? CT_HTML : CT_TEXT; } /* mailTextType */ static void formatMail(struct MHINFO *w, bool top) { struct MHINFO *v; int ct = w->ct; int j, best; if (w->doAttach) return; debugPrint(5, "format headers for content %d subject %s", ct, w->subject); stringAndString(&fm, &fm_l, headerShow(w, top)); if (ct < CT_MULTI) { char *start = w->start; char *end = w->end; int newlen; /* If mail is not in html, reformat it */ if (start < end) { if (ct == CT_TEXT) { breakLineSetup(); if (breakLine(start, end - start, &newlen)) { start = breakLineResult; end = start + newlen; } } if (mailIsHtml && ct != CT_HTML) stringAndString(&fm, &fm_l, "

    ");
    			stringAndBytes(&fm, &fm_l, start, end - start);
    			if (mailIsHtml && ct != CT_HTML)
    				stringAndString(&fm, &fm_l, "
    \n"); } /* text present */ /* There could be a mail message inline */ foreach(v, w->components) { if (end > start) stringAndString(&fm, &fm_l, mailIsHtml ? "

    \n" : "\n"); formatMail(v, false); } return; } if (ct == CT_MULTI) { foreach(v, w->components) formatMail(v, false); return; } /* alternate presentations, pick the best one */ best = j = 0; foreach(v, w->components) { int subtype = mailTextType(v); ++j; if (subtype != CT_OTHER) best = j; if (mailIsHtml && subtype == CT_HTML || !mailIsHtml && subtype == CT_TEXT) break; } if (!best) best = 1; j = 0; foreach(v, w->components) { ++j; if (j != best) continue; formatMail(v, false); break; } } /* formatMail */ /* Browse the email file. */ char *emailParse(char *buf) { struct MHINFO *w, *v; nattach = nimages = 0; firstAttach = 0; mailIsHtml = ignoreImages = false; fm = initString(&fm_l); w = headerGlean(buf, buf + strlen(buf)); mailIsHtml = (mailTextType(w) == CT_HTML); if (mailIsHtml) stringAndString(&fm, &fm_l, "\n"); formatMail(w, true); /* Remember, we always need a nonzero buffer */ if (!fm_l || fm[fm_l - 1] != '\n') stringAndChar(&fm, &fm_l, '\n'); cw->mailInfo = allocMem(strlen(w->ref) + strlen(w->mid) + strlen(w->tolist) + strlen(w->cclist) + strlen(w->reply) + 6); sprintf(cw->mailInfo, "%s>%s>%s>%s>%s>", w->reply, w->tolist, w->cclist, w->ref, w->mid); if (!ismc) { writeAttachments(w); freeMailInfo(w); nzFree(buf); debugPrint(5, "mailInfo: %s", cw->mailInfo); } else { lastMailInfo = w; lastMailText = buf; } return fm; } /* emailParse */ /********************************************************************* Set up for a reply. This looks at the first 5 lines, which could contain subject to reply to from mail send in no particular order. Move replyt to the top and get rid of the others. Then, if you have browsed a mail file, grab the message id and reference it. Also, if mailing to all, stick in the other recipients. *********************************************************************/ bool setupReply(bool all) { int subln, repln; char linetype[8]; int j; char *out, *s, *t; bool rc; /* basic sanity */ if (cw->dirMode) { setError(MSG_ReDir); return false; } if (cw->sqlMode) { setError(MSG_ReDB); return false; } if (!cw->dol) { setError(MSG_ReEmpty); return false; } if (cw->binMode) { setError(MSG_ReBinary); return false; } subln = repln = 0; strcpy(linetype, " xxxxxx"); for (j = 1; j <= 6; ++j) { char *p; if (j > cw->dol) break; p = (char *)fetchLine(j, 1); if (memEqualCI(p, "subject:", 8)) { linetype[j] = 's'; subln = j; goto nextline; } if (memEqualCI(p, "to ", 3)) { linetype[j] = 't'; goto nextline; } if (memEqualCI(p, "from ", 5)) { linetype[j] = 'f'; goto nextline; } if (memEqualCI(p, "mail sent ", 10)) { linetype[j] = 'w'; goto nextline; } if (memEqualCI(p, "references:", 11)) { linetype[j] = 'v'; goto nextline; } if (memEqualCI(p, "reply to ", 9)) { linetype[j] = 'r'; repln = j; goto nextline; } /* This one has to be last. */ s = p; while (isdigitByte(*s)) ++s; if (memEqualCI(s, " attachment", 11) || memEqualCI(s, " image", 6)) { linetype[j] = 'a'; goto nextline; } /* line doesn't match anything we know */ nzFree(p); break; nextline: nzFree(p); } if (!subln || !repln) { setError(MSG_ReSubjectReply); return false; } /* delete the lines we don't need */ linetype[j] = 0; for (--j; j >= 1; --j) { if (strchr("srv", linetype[j])) continue; delText(j, j); strmove(linetype + j, linetype + j + 1); } /* move reply to 1, if it isn't already there */ repln = strchr(linetype, 'r') - linetype; subln = strchr(linetype, 's') - linetype; if (repln != 1) { struct lineMap *map = cw->map; struct lineMap swap; struct lineMap *q1 = map + 1; struct lineMap *q2 = map + repln; swap = *q1; *q1 = *q2; *q2 = swap; if (subln == 1) subln = repln; repln = 1; } j = strlen(linetype) - 1; if (j != subln) { struct lineMap *map = cw->map; struct lineMap swap; struct lineMap *q1 = map + j; struct lineMap *q2 = map + subln; swap = *q1; *q1 = *q2; *q2 = swap; } readReplyInfo(); if (!cw->mailInfo) { if (all) { setError(MSG_ReNoInfo); return false; } return true; /* that's all we can do */ } /* Build the header lines and put them in the buffer */ out = initString(&j); /* step through the to list */ s = strchr(cw->mailInfo, '>') + 1; while (*s != '>') { t = strchr(s, ','); if (all) { stringAndString(&out, &j, "to: "); stringAndBytes(&out, &j, s, t - s); stringAndChar(&out, &j, '\n'); } s = t + 1; } /* step through the cc list */ ++s; while (*s != '>') { t = strchr(s, ','); if (all) { stringAndString(&out, &j, "cc: "); stringAndBytes(&out, &j, s, t - s); stringAndChar(&out, &j, '\n'); } s = t + 1; } ++s; t = strchr(s, '>'); if (t[1] == '>') { i_puts(MSG_ReNoID); } else { stringAndString(&out, &j, "References: <"); if (*s != '>') { stringAndBytes(&out, &j, s, t - s); stringAndString(&out, &j, "> <"); } stringAndString(&out, &j, t + 1); stringAndChar(&out, &j, '\n'); } rc = true; if (j) rc = addTextToBuffer((unsigned char *)out, j, 1, false); nzFree(out); return rc; } /* setupReply */ static void writeReplyInfo(const char *addstring) { int rfh; /* reply file handle */ rfh = open(mailReply, O_WRONLY | O_APPEND | O_CREAT, 0666); if (rfh < 0) return; write(rfh, addstring, 12); write(rfh, cw->mailInfo, strlen(cw->mailInfo)); write(rfh, "\n", 1); close(rfh); } /* writeReplyInfo */ static void readReplyInfo(void) { int rfh; /* reply file handle */ char *p; int ln, major, minor; char prestring[20]; char *buf; int buflen; char *s, *t, *cut; if (cw->mailInfo) return; /* already there */ /* scan through the buffer looking for the Unformatted line, * but stop if you hit an email divider. */ for (ln = 1; ln <= cw->dol; ++ln) { p = fetchLine(ln, -1); if (!memcmp (p, "======================================================================\n", 71)) return; if (pstLength((pst) p) == 24 && sscanf(p, "Unformatted %d.%d", &major, &minor) == 2) goto found; } return; found: /* prestring is the key */ sprintf(prestring, "%05d.%05d:", major, minor); rfh = open(mailReply, O_RDONLY); if (rfh < 0) return; if (!fdIntoMemory(rfh, &buf, &buflen)) return; close(rfh); /* loop through lines looking for the key */ for (s = buf; *s; s = t + 1) { t = strchr(s, '\n'); if (!t) break; if (memcmp(s, prestring, 12)) continue; /* key match, put this string into mailInfo */ s += 12; cw->mailInfo = pullString(s, t - s); break; } nzFree(buf); } /* readReplyInfo */ edbrowse-3.6.0.1/src/PaxHeaders.22102/ebprot.h0000644000000000000000000000006212637565441015466 xustar0020 atime=1451158305 30 ctime=1451409178.149337216 edbrowse-3.6.0.1/src/ebprot.h0000644000000000000000000003476112637565441015752 0ustar00rootroot00000000000000/* Prototypes for edbrowse */ /* sourcefile=auth.c */ bool getUserPass(const char *url, char *creds, bool find_proxy) ; bool addWebAuthorization(const char *url, const char *credentials, bool proxy) ; /* sourcefile=buffers.c */ pst fetchLine(int n, int show) ; void displayLine(int n) ; void initializeReadline(void) ; pst inputLine(void) ; bool cxCompare(int cx) ; bool cxActive(int cx) ; bool cxQuit(int cx, int action) ; void cxSwitch(int cx, bool interactive) ; void gotoLocation(char *url, int delay, bool rf) ; bool addTextToBuffer(const pst inbuf, int length, int destl, bool onside) ; void delText(int start, int end) ; bool readFileArgv(const char *filename); bool unfoldBufferW(const struct ebWindow *w, bool cr, char **data, int *len) ; bool unfoldBuffer(int cx, bool cr, char **data, int *len) ; bool runCommand(const char *line) ; bool edbrowseCommand(const char *line, bool script) ; int sideBuffer(int cx, const char *text, int textlen, const char *bufname); void freeEmptySideBuffer(int n); bool browseCurrentBuffer(void) ; bool locateTagInBuffer(int tagno, int *ln_p, char **p_p, char **s_p, char **t_p); char *getFieldFromBuffer(int tagno); int fieldIsChecked(int tagno); /* sourcefile=cookies.c */ bool domainSecurityCheck(const char *server, const char *domain) ; bool receiveCookie(const char *url, const char *str) ; void cookiesFromJar(void) ; void sendCookies(char **s, int *l, const char *url, bool issecure) ; /* sourcefile=dbodbc.c (and others) */ bool fetchForeign(char *tname) ; /* sourcefile=dbops.c */ int findColByName(const char *name) ; /* sourcefile=dbstubs.c */ bool sqlReadRows(const char *filename, char **bufptr) ; void dbClose(void) ; void showColumns(void) ; void showForeign(void) ; bool showTables(void) ; bool sqlDelRows(int start, int end) ; bool sqlUpdateRow(pst source, int slen, pst dest, int dlen) ; bool sqlAddRows(int ln) ; bool ebConnect(void) ; int goSelect(int *startLine, char **rbuf) ; /* sourcefile=ebjs.c */ void dwStart(void); void createJavaContext(void) ; void freeJavaContext(struct ebWindow *w) ; void js_shutdown(void) ; void js_disconnect(void); char *jsRunScriptResult(jsobjtype obj, const char *str, const char *filename, int lineno) ; void jsRunScript(jsobjtype obj, const char *str, const char *filename, int lineno) ; enum ej_proptype has_property(jsobjtype obj, const char *name) ; void delete_property(jsobjtype obj, const char *name) ; char *get_property_string(jsobjtype obj, const char *name) ; int get_property_number(jsobjtype obj, const char *name) ; double get_property_float(jsobjtype obj, const char *name) ; bool get_property_bool(jsobjtype obj, const char *name) ; jsobjtype get_property_object(jsobjtype parent, const char *name) ; jsobjtype get_property_function(jsobjtype parent, const char *name); jsobjtype get_array_element_object(jsobjtype obj, int idx) ; int set_property_string(jsobjtype obj, const char *name, const char *value) ; int set_property_number(jsobjtype obj, const char *name, int n) ; int set_property_float(jsobjtype obj, const char *name, double n) ; int set_property_bool(jsobjtype obj, const char *name, bool n) ; int set_property_object(jsobjtype parent, const char *name, jsobjtype child) ; jsobjtype instantiate_array(jsobjtype parent, const char *name) ; int set_array_element_object(jsobjtype array, int idx, jsobjtype child) ; jsobjtype instantiate_array_element(jsobjtype array, int idx, const char *classname) ; jsobjtype instantiate(jsobjtype parent, const char *name, const char *classname) ; int set_property_function(jsobjtype parent, const char *name, const char *body) ; int get_arraylength(jsobjtype a); char *get_property_option(jsobjtype obj) ; void setupJavaDom(void) ; char *get_property_url(jsobjtype owner, bool action) ; void rebuildSelectors(void); jsobjtype run_function_object(jsobjtype obj, const char *name); bool run_function_bool(jsobjtype obj, const char *name); void run_function_objargs(jsobjtype obj, const char *name, int nargs, ...); void run_function_onearg(jsobjtype obj, const char *name, jsobjtype o); void set_basehref(const char *b); /* sourcefile=fetchmail.c */ int fetchMail(int account) ; int fetchAllMail(void) ; void scanMail(void) ; bool emailTest(void) ; void unpackUploadedFile(const char *post, const char *boundary, char **postb, int *postb_l) ; char *emailParse(char *buf) ; bool setupReply(bool all) ; /* sourcefile=format.c */ void prepareForBrowse(char *h, int h_len) ; void prepareForField(char *h); bool breakLine(const char *line, int len, int *newlen) ; void breakLineSetup(void) ; char *htmlReformat(char *buf) ; void extractEmailAddresses(char *line) ; void cutDuplicateEmails(char *tolist, char *cclist, const char *reply) ; bool looksBinary(const char *buf, int buflen) ; void looks_8859_utf8(const char *buf, int buflen, bool * iso_p, bool * utf8_p) ; uchar base64Bits(char c); char *base64Encode(const char *inbuf, int inlen, bool lines); int base64Decode(char *start, char **end); void iuReformat(const char *inbuf, int inbuflen, char **outbuf_p, int *outbuflen_p) ; bool parseDataURI(const char *uri, char **mediatype, char **data, int *data_l); /* sourcefile=html.c */ bool tagHandler(int seqno, const char *name) ; void jSideEffects(void) ; void jSyncup(void) ; void htmlMetaHelper(struct htmlTag *t); void preFormatCheck(int tagno, bool * pretag, bool * slash) ; char *htmlParse(char *buf, int remote) ; bool htmlTest(void) ; void infShow(int tagno, const char *search) ; bool infReplace(int tagno, const char *newtext, bool notify) ; bool infPush(int tagno, char **post_string) ; struct htmlTag *tagFromJavaVar(jsobjtype v); struct htmlTag *tagFromJavaVar2(jsobjtype v, const char *tagname); void javaSubmitsForm(jsobjtype v, bool reset) ; bool handlerGoBrowse(const struct htmlTag *t, const char *name) ; void runningError(int msg, ...) ; void rerender(bool notify); void delTags(int startRange, int endRange); bool timerWait(int *delay_sec, int *delay_ms); void delTimers(struct ebWindow *w); void runTimers(void); void javaOpensWindow(const char *href, const char *name) ; void javaSetsLinkage(bool after, char type, jsobjtype p, const char *rest); /* sourcefile=html-tidy.c */ void html2nodes(const char *htmltext, bool startpage); /* sourcefile=decorate.c */ void traverseAll(int start); const char *attribVal(const struct htmlTag *t, const char *name); struct htmlTag *findOpenTag(struct htmlTag *t, int action); struct htmlTag *findOpenList(struct htmlTag *t); void formControl(struct htmlTag *t, bool namecheck); void htmlInputHelper(struct htmlTag *t); char *displayOptions(const struct htmlTag *sel) ; void prerender(int start); jsobjtype instantiate_url(jsobjtype parent, const char *name, const char *url) ; char *render(int start); void decorate(int start); void freeTags(struct ebWindow *w) ; struct htmlTag *newTag(const char *tagname) ; void initTagArray(void); void htmlNodesIntoTree(int start, struct htmlTag *attach); void html_from_setter( jsobjtype innerParent, const char *h); /* sourcefile=http.c */ size_t eb_curl_callback(char *incoming, size_t size, size_t nitems, struct eb_curl_callback_data *data) ; char *extractHeaderParam(const char *str, const char *item) ; time_t parseHeaderDate(const char *date) ; bool parseRefresh(char *ref, int *delay_p) ; bool refreshDelay(int sec, const char *u) ; bool httpConnect(const char *url, bool down_ok, bool webpage); void ebcurl_setError(CURLcode curlret, const char *url) ; void setHTTPLanguage(const char *lang) ; void http_curl_init(void) ; int ebcurl_debug_handler(CURL * handle, curl_infotype info_desc, char *data, size_t size, void *unused) ; int bg_jobs(bool iponly); void addNovsHost(char *host) ; CURLcode setCurlURL(CURL * h, const char *url) ; const char *findProxyForURL(const char *url) ; /* sourcefile=main.c */ const char *mailRedirect(const char *to, const char *from, const char *reply, const char *subj) ; bool javaOK(const char *url) ; void ebClose(int n) ; bool isSQL(const char *s) ; void setDataSource(char *v) ; bool runEbFunction(const char *line) ; struct DBTABLE *findTableDescriptor(const char *sn) ; struct DBTABLE *newTableDescriptor(const char *name) ; /* sourcefile=plugin.c */ const struct MIMETYPE *findMimeBySuffix(const char *suffix) ; const struct MIMETYPE *findMimeByURL(const char *url) ; const struct MIMETYPE *findMimeByFile(const char *filename) ; const struct MIMETYPE *findMimeByContent(const char *content) ; const struct MIMETYPE *findMimeByProtocol(const char *prot) ; char *pluginCommand(const struct MIMETYPE *m, const char *infile, const char *outfile, const char *suffix); int playBuffer(const char *line, const char *playfile); bool playServerData(void); char *runPluginConverter(const char *buf, int buflen); /* sourcefile=sendmail.c */ bool loadAddressBook(void) ; const char *reverseAlias(const char *reply) ; bool encodeAttachment(const char *file, int ismail, bool webform, const char **type_p, const char **enc_p, char **data_p) ; char *makeBoundary(void) ; bool sendMail(int account, const char **recipients, const char *body, int subjat, const char **attachments, const char *refline, int nalt, bool dosig) ; bool validAccount(int n) ; bool sendMailCurrent(int sm_account, bool dosig) ; /* sourcefile=messages.c */ void iso2utf(const char *inbuf, int inbuflen, char **outbuf_p, int *outbuflen_p) ; void utf2iso(const char *inbuf, int inbuflen, char **outbuf_p, int *outbuflen_p) ; void selectLanguage(void) ; const char *i_getString(int msg); void i_puts(int msg) ; void i_printf(int msg, ...) ; void i_printfExit(int msg, ...) ; void i_stringAndMessage(char **s, int *l, int messageNum) ; void setError(int msg, ...) ; void showError(void) ; void showErrorConditional(char cmd) ; void showErrorAbort(void) ; #if 0 void i_caseShift(unsigned char *s, char action) ; #endif void eeCheck(void) ; void eb_puts(const char *s); void eb_vprintf (const char *fmt, va_list args) ; /* sourcefile=stringfile.c */ void *allocMem(size_t n) ; void *allocZeroMem(size_t n) ; void *reallocMem(void *p, size_t n) ; char *allocString(size_t n) ; char *allocZeroString(size_t n) ; char *reallocString(void *p, size_t n) ; void nzFree(void *s) ; void cnzFree(const void *s) ; uchar fromHex(char d, char e) ; char *appendString(char *s, const char *p) ; char *prependString(char *s, const char *p) ; void skipWhite(const char **s) ; #define skipWhite2(s) skipWhite((const char **)s) void trimWhite(char *s) ; void stripWhite(char *s) ; void spaceCrunch(char *s, bool onespace, bool unprint) ; char *strmove(char *dest, const char *src) ; char *initString(int *l) ; void stringAndString(char **s, int *l, const char *t) ; void stringAndBytes(char **s, int *l, const char *t, int cnt) ; void stringAndChar(char **s, int *l, char c) ; void stringAndNum(char **s, int *l, int n) ; void stringAndKnum(char **s, int *l, int n) ; char *cloneString(const char *s) ; char *cloneMemory(const char *s, int n) ; void leftClipString(char *s) ; void shiftRight(char *s, char first) ; char *Cify(const char *s, int n) ; char *pullString(const char *s, int l) ; char *pullString1(const char *s, const char *t) ; int stringIsNum(const char *s) ; bool stringIsDate(const char *s) ; bool stringIsFloat(const char *s, double *dp) ; bool memEqualCI(const char *s, const char *t, int len) ; char *strstrCI(const char *base, const char *search) ; bool stringEqual(const char *s, const char *t) ; bool stringEqualCI(const char *s, const char *t) ; int stringInList(const char *const *list, const char *s) ; int stringInListCI(const char *const *list, const char *s) ; int charInList(const char *list, char c) ; bool listIsEmpty(const struct listHead * l) ; void initList(struct listHead *l) ; void delFromList(void *x) ; void addToListFront(struct listHead *l, void *x) ; void addToListBack(struct listHead *l, void *x) ; void addAtPosition(void *p, void *x) ; void freeList(struct listHead *l) ; bool isA(char c) ; bool isquote(char c) ; void errorPrint(const char *msg, ...) ; void debugPrint(int lev, const char *msg, ...) ; void nl(void) ; int perl2c(char *t) ; unsigned pstLength(pst s) ; pst clonePstring(pst s) ; void copyPstring(pst s, const pst t) ; bool fdIntoMemory(int fd, char **data, int *len) ; bool fileIntoMemory(const char *filename, char **data, int *len) ; bool memoryOutToFile(const char *filename, const char *data, int len, int msgcreate, int msgwrite) ; void caseShift(char *s, char action) ; char fileTypeByName(const char *name, bool showlink) ; char fileTypeByHandle(int fd) ; int fileSizeByName(const char *name) ; int fileSizeByHandle(int fd) ; time_t fileTimeByName(const char *name) ; char *conciseSize(size_t n); char *conciseTime(time_t t); bool lsattrChars(const char *buf, char *dest); char *lsattr(const char *path, const char *flags); void ttySaveSettings(void) ; #ifndef _INC_CONIO int getche(void) ; int getch(void) ; #endif // #ifndef _INC_CONIO char getLetter(const char *s) ; char *getFileName(int msg, const char *defname, bool isnew, bool ws); int shellProtectLength(const char *s); void shellProtect(char *t, const char *s); const char *nextScanFile(const char *base) ; bool sortedDirList(const char *dir, struct lineMap **map_p, int *count_p) ; bool envFile(const char *line, const char **expanded); bool envFileDown(const char *line, const char **expanded) ; FILE *efopen(const char *name, const char *mode) ; int eopen(const char *name, int mode, int perms) ; void appendFile(const char *fname, const char *message, ...) ; void appendFileNF(const char *filename, const char *msg) ; int eb_system(const char *cmd, bool print_on_success); /* sourcefile=url.c */ void unpercentURL(char *url) ; void unpercentString(char *s) ; char *percentURL(const char *start, const char *end); char *htmlEscape0(const char *s, bool do_and); #define htmlEscape(s) htmlEscape0((s), true) #define htmlEscapeTextarea(s) htmlEscape0((s), false) bool isURL(const char *url) ; bool isBrowseableURL(const char *url) ; bool isDataURI(const char *u); const char *getProtURL(const char *url) ; const char *getHostURL(const char *url) ; const char *getHostPassURL(const char *url) ; const char *getUserURL(const char *url) ; const char *getPassURL(const char *url) ; const char *getDataURL(const char *url) ; void getDirURL(const char *url, const char **start_p, const char **end_p) ; char *findHash(const char *s); char *getFileURL(const char *url, bool chophash); bool getPortLocURL(const char *url, const char **portloc, int *port) ; int getPortURL(const char *url) ; bool isProxyURL(const char *url) ; char *resolveURL(const char *base, const char *rel) ; bool sameURL(const char *s, const char *t) ; char *altText(const char *base) ; char *encodePostData(const char *s) ; char *decodePostData(const char *data, const char *name, int seqno) ; void decodeMailURL(const char *url, char **addr_p, char **subj_p, char **body_p) ; edbrowse-3.6.0.1/src/PaxHeaders.22102/ebjs.h0000644000000000000000000000006212637565441015116 xustar0020 atime=1451158305 30 ctime=1451409178.149337216 edbrowse-3.6.0.1/src/ebjs.h0000644000000000000000000000415612637565441015375 0ustar00rootroot00000000000000/********************************************************************* ebjs.h: edbrowse javascript engine interface. Contains structures and values that are passed between the two processes. The prefix EJ in symbolic constants is short for edbrowse javascript. This is the only header file shared between edbrowse and the js process. It does not and should not include other edbrowse files. It also does not reference bool true false, which are defined constants in edbrowse, written in C, but are reserved words in the js process, written in C++. Since there are no function declarations here, we don't need the extern "C" {} *********************************************************************/ #ifndef EBJS_H #define EBJS_H 1 #define EJ_MAGIC 0xac97 enum ej_cmd { EJ_CMD_NONE, EJ_CMD_CREATE, EJ_CMD_DESTROY, EJ_CMD_EXIT, EJ_CMD_SCRIPT, EJ_CMD_GETPROP, EJ_CMD_SETPROP, EJ_CMD_DELPROP, EJ_CMD_HASPROP, EJ_CMD_GETAREL, EJ_CMD_SETAREL, EJ_CMD_ARLEN, EJ_CMD_CALL, }; enum ej_highstat { EJ_HIGH_OK, EJ_HIGH_STMT_FAIL, EJ_HIGH_CX_FAIL, EJ_HIGH_HEAP_FAIL, EJ_HIGH_PROC_FAIL, }; enum ej_lowstat { EJ_LOW_OK, EJ_LOW_SYNTAX, EJ_LOW_CLOSE, EJ_LOW_CX, EJ_LOW_WIN, EJ_LOW_DOC, EJ_LOW_STD, EJ_LOW_VARS, EJ_LOW_MEMORY, EJ_LOW_EXEC, EJ_LOW_RUNTIME, EJ_LOW_SYNC, }; enum ej_proptype { EJ_PROP_NONE, EJ_PROP_STRING, EJ_PROP_BOOL, EJ_PROP_INT, EJ_PROP_FLOAT, EJ_PROP_OBJECT, EJ_PROP_ARRAY, EJ_PROP_FUNCTION, EJ_PROP_INSTANCE, }; /* Opaque indicator of an object that can be shared * between the two processes. */ typedef void *jsobjtype; struct EJ_MSG { int magic; /* sanity check */ enum ej_cmd cmd; jsobjtype jcx; /* javascript context */ jsobjtype winobj; /* window object */ jsobjtype docobj; /* document object */ jsobjtype obj; /* an object somewhere in the window tree */ enum ej_highstat highstat; enum ej_lowstat lowstat; /* the property, as a string, follows this struct in the message */ int proplength; enum ej_proptype proptype; int n; /* an overloaded integer */ int side; /* length of side effects string */ int msglen; /* error message from JS */ int lineno; /* line number */ }; #endif edbrowse-3.6.0.1/src/PaxHeaders.22102/ebjs.c0000644000000000000000000000006212637565441015111 xustar0020 atime=1451158305 30 ctime=1451409178.149337216 edbrowse-3.6.0.1/src/ebjs.c0000644000000000000000000011417312637565441015371 0ustar00rootroot00000000000000/* ebjs.c: edbrowse javascript engine interface. * * Launch the js engine process and communicate with it to build * js objects and run js code. * Also provide some wrapper functions like get_property_string, * so that edbrowse can call functions to manipulate js objects, * thus hiding the details of sending messages to the js process * and receiving replies from same. */ #include "eb.h" #include #if defined(DOSLIKE) && defined(HAVE_PTHREAD_H) #include // for _execlp() #include // for pthreads... #endif /* defined(DOSLIKE) && defined(HAVE_PTHREAD_H) */ /* If connection is lost, mark all js sessions as dead. */ static void markAllDead(void) { int cx; /* edbrowse context */ struct ebWindow *w; bool killed = false; for (cx = 1; cx < MAXSESSION; ++cx) { w = sessionList[cx].lw; if (!w) continue; if (w->winobj) { w->winobj = 0; w->docobj = 0; w->jcx = 0; killed = true; } while (w != sessionList[cx].fw) { w = w->prev; if (w->winobj) { w->winobj = 0; w->docobj = 0; w->jcx = 0; killed = true; } } } if (killed) i_puts(MSG_JSCloseSessions); } /* markAllDead */ /* communication pipes with the js process */ static int pipe_in[2], pipe_out[2]; static char arg1[8], arg2[8], arg3[8]; static int js_pid; static struct EJ_MSG head; #ifdef DOSLIKE #define PIPE(a) _pipe(a,1024,_O_BINARY) #else // !DOSLIKE #define PIPE pipe #endif // DOSLIKE y/n #if defined(DOSLIKE) && defined(HAVE_PTHREAD_H) static pthread_t tid; static void *child_proc(void *vp) { /* child here, exec the back end js process */ size_t len; // change [d:\foo\bar\]edbrowse[.exe] to [d:\foo\bar\]edbrowse-js[.exe] char drive[_MAX_DRIVE]; char dir[_MAX_DIR]; char fname[_MAX_FNAME]; char ext[_MAX_EXT]; char pn[MAX_PATH]; // split it up _splitpath(progname, drive, dir, fname, ext); // put it back together pn[0] = 0; strcat(pn, drive); strcat(pn, dir); len = strlen(fname); if (len && (fname[len - 1] == 'd')) { fname[len - 1] = 0; strcat(pn, fname); strcat(pn, "-jsd"); // add to the debug file name } else { strcat(pn, fname); strcat(pn, "-js"); // add to the file name } strcat(pn, ext); //close(pipe_in[0]); //close(pipe_out[1]); sprintf(arg1, "%d", pipe_out[0]); sprintf(arg2, "%d", pipe_in[1]); sprintf(arg3, "%d", jsPool); debugPrint(5, "spawning '%s' %s %s %s", pn, arg1, arg2, arg3); len = _spawnl(_P_WAIT, pn, "edbrowse-js", arg1, arg2, arg3, 0); //_execlp(pn, "edbrowse-js", arg1, arg2, arg3, 0); // len = 1; if (len) { debugPrint(5, "spawning FAILED! %d\n", errno); /* oops, process did not exec */ /* write a message from this child, saying js would not exec */ /* The process just started; head is zero */ head.magic = EJ_MAGIC; head.highstat = EJ_HIGH_PROC_FAIL; head.lowstat = EJ_LOW_EXEC; write(pipe_in[1], &head, sizeof(head)); //exit(90); } return (void *)90; } #endif // defined(DOSLIKE) && defined(HAVE_PTHREAD_H) /* Start the js process. */ static void js_start(void) { int pid; char *jsprog; if (js_pid) return; /* already running */ #if defined(DOSLIKE) && !defined(HAVE_PTHREAD_H) debugPrint(5, "no pthread, so no communication channels for javascript"); allowJS = false; return; #endif // defined(DOSLIKE) && !defined(HAVE_PTHREAD_H) #ifndef DOSLIKE /* doesn't hurt to do this more than once */ signal(SIGPIPE, SIG_IGN); #endif // !DOSLIKE debugPrint(5, "setting of communication channels for javascript"); if (PIPE(pipe_in)) { i_puts(MSG_JSEnginePipe); allowJS = false; return; } if (PIPE(pipe_out)) { i_puts(MSG_JSEnginePipe); allowJS = false; close(pipe_in[0]); close(pipe_in[1]); return; } #if defined(DOSLIKE) #if defined(HAVE_PTHREAD_H) /* windows implementation of fork() using pthreads */ pid = pthread_create(&tid, NULL, child_proc, 0); #else // !HAVE_PTHREAD_h pid = 1; #endif // HAVE_PTHREAD_H y/n if (pid) { i_puts(MSG_JSEngineFork); allowJS = false; close(pipe_in[0]); close(pipe_in[1]); close(pipe_out[0]); close(pipe_out[1]); return; } js_pid = 1; #else // !(defined(DOSLIKE) && defined(HAVE_PTHREAD_H) pid = fork(); if (pid < 0) { i_puts(MSG_JSEngineFork); allowJS = false; close(pipe_in[0]); close(pipe_in[1]); close(pipe_out[0]); close(pipe_out[1]); return; } if (pid) { /* parent */ js_pid = pid; close(pipe_in[1]); close(pipe_out[0]); return; } /* child here, exec the back end js process */ close(pipe_in[0]); close(pipe_out[1]); sprintf(arg1, "%d", pipe_out[0]); sprintf(arg2, "%d", pipe_in[1]); sprintf(arg3, "%d", jsPool); debugPrint(5, "spawning edbrowse-js %s %s %s", arg1, arg2, arg3); if (!strchr(progname, '/')) { // no path specified, just the program, so assume edbrowse-js is also on $PATH, // hopefully in the same bin. execlp("edbrowse-js", "edbrowse-js", arg1, arg2, arg3, NULL); } else { // change /foo/bar/edbrowse to /foo/bar/edbrowse-js int l = strlen(progname); char *jspath = allocMem(l + 4); sprintf(jspath, "%s-js", progname); execl(jspath, "edbrowse-js", arg1, arg2, arg3, NULL); nzFree(jspath); } /* oops, process did not exec */ /* write a message from this child, saying js would not exec */ /* The process just started; head is zero */ head.magic = EJ_MAGIC; head.highstat = EJ_HIGH_PROC_FAIL; head.lowstat = EJ_LOW_EXEC; write(pipe_in[1], &head, sizeof(head)); exit(90); #endif // defined(DOSLIKE) && defined(HAVE_PTHREAD_H) y/n } /* js_start */ /* Shut down the js process, although if we got here, * it's probably dead anyways. */ static void js_kill(void) { if (!js_pid) return; close(pipe_in[0]); close(pipe_out[1]); #ifndef DOSLIKE kill(js_pid, SIGTERM); #endif // #ifndef DOSLIKE js_pid = 0; } /* js_kill */ /* String description of side effects, as a result of running js code. */ static char *effects; /* source file containing the js code */ static const char *jsSourceFile; /* queue of edbrowse buffer changes produced by running js - see eb.h */ struct listHead inputChangesPending = { &inputChangesPending, &inputChangesPending }; /* Javascript has changed an input field */ static void javaSetsTagVar(jsobjtype v, const char *newtext) { struct inputChange *ic; struct htmlTag *t = tagFromJavaVar(v); if (!t) return; if (t->itype == INP_HIDDEN || t->itype == INP_RADIO || t->itype == INP_FILE) return; if (t->itype == INP_TA) { debugPrint(3, "textarea.value is being updated"); return; } nzFree(t->value); t->value = cloneString(newtext); } /* javaSetsTagVar */ static void javaSetsInner(jsobjtype v, const char *newtext, char c) { struct inputChange *ic; struct htmlTag *t = tagFromJavaVar(v); if (!t) return; ic = allocMem(sizeof(struct inputChange) + strlen(newtext)); ic->t = t; ic->tagno = t->seqno; ic->major = 'i'; ic->minor = c; strcpy(ic->value, newtext); addToListBack(&inputChangesPending, ic); } /* javaSetsInner */ /* start a document.write */ void dwStart(void) { if (cw->dw) return; cw->dw = initString(&cw->dw_l); stringAndString(&cw->dw, &cw->dw_l, ""); } /* dwStart */ /********************************************************************* Process the side effects of running js. These are: w{ document.write() strings that fold back into html } n{ new window() that may open a new edbrowse buffer } t{ timer or interval calling a js function } v{ javascript changes the value of an input field } c{set cookie} i{ innnerHtml or innerText } f{ form submit or reset } l{ linking objects together in a tree } Any or all of these could be coded in the side effects string. *********************************************************************/ static void processEffects(void) { char *s, *t, *v; char c; jsobjtype p; int n; struct inputChange *ic; if (!effects) return; s = effects; while (c = *s) { /* another effect */ s += 2; v = strstr(s, "`~@}"); /* end marker */ /* There should always be an end marker - * unless there is a spurious null in the string. */ if (!v) break; *v = 0; switch (c) { case 'w': /* document.write */ dwStart(); stringAndString(&cw->dw, &cw->dw_l, s); break; case 'n': /* new window */ /* url on one line, name of window on next line */ t = strchr(s, '\n'); *t = 0; javaOpensWindow(s, t + 1); break; case 'v': /* value = "foo" */ t = strchr(s, '='); *t++ = 0; sscanf(s, "%p", &p); prepareForField(t); javaSetsTagVar(p, t); break; case 't': /* js timer */ n = strtol(s, &t, 10); s = t + 1; t = strchr(s, '|'); *t++ = 0; v[-2] = 0; sscanf(t, "%p", &p); ic = allocMem(sizeof(struct inputChange) + strlen(s)); // Yeah I know, this isn't a pointer to htmlTag. ic->t = p; ic->tagno = n; ic->major = 't'; ic->minor = v[-1]; strcpy(ic->value, s); addToListBack(&inputChangesPending, ic); break; case 'c': /* cookie */ /* Javascript does some modest syntax checking on the cookie before * passing it back to us, so I'm just going to assume it works. */ receiveCookie(cw->fileName, s); break; case 'f': c = *s++; sscanf(s, "%p", &p); javaSubmitsForm(p, (c == 'r')); break; case 'i': c = *s++; /* h = inner html, t = inner text */ t = strchr(s, '|'); *t++ = 0; sscanf(s, "%p", &p); javaSetsInner(p, t, c); break; case 'l': c = *s; s += 2; sscanf(s, "%p", &p); s = strchr(s, ',') + 1; javaSetsLinkage(false, c, p, s); break; } /* switch */ /* skip past end marker + newline */ s = v + 5; } /* loop over effects */ free(effects); effects = 0; } /* processEffects */ /* Read some data from the js process. * Close things down if there is any trouble from the read. * Returns 0 for ok or -1 for bad read. */ static int readFromJS(void *data_p, int n) { int rc; if (n == 0) return 0; rc = read(pipe_in[0], data_p, n); debugPrint(7, "js read %d", rc); if (rc == n) return 0; /* Oops - can't read from the process any more */ i_puts(MSG_JSEngineRW); js_kill(); markAllDead(); return -1; } /* readFromJS */ static int writeToJS(const void *data_p, int n) { int rc; if (n == 0) return 0; rc = write(pipe_out[1], data_p, n); if (rc == n) return 0; /* Oops - can't write to the process any more */ js_kill(); /* this call will print an error message for you */ markAllDead(); return -1; } /* writeToJS */ static char *propval; /* property value, allocated */ static enum ej_proptype proptype; /* Read the entire message from js, then take action. * Thus messages will remain in sync. */ static int readMessage(void) { int l; char *msg; /* error message from js */ if (readFromJS(&head, sizeof(head)) < 0) return -1; /* read failed */ if (head.magic != EJ_MAGIC) { /* this should never happen */ js_kill(); i_puts(MSG_JSEngineSync); markAllDead(); return -1; } if (head.highstat >= EJ_HIGH_HEAP_FAIL) { js_kill(); /* perhaps a helpful message, before we close down js sessions */ if (head.highstat == EJ_HIGH_PROC_FAIL) allowJS = false; if (head.lowstat == EJ_LOW_EXEC) i_puts(MSG_JSEngineExec); if (head.lowstat == EJ_LOW_MEMORY) i_puts(MSG_JavaMemError); if (head.lowstat == EJ_LOW_RUNTIME) i_puts(MSG_JSEngineRun); if (head.lowstat == EJ_LOW_SYNC) i_puts(MSG_JSEngineSync); markAllDead(); return -1; } if (head.side) { effects = allocMem(head.side + 1); if (readFromJS(effects, head.side) < 0) { free(effects); effects = 0; return -1; } effects[head.side] = 0; if (debugLevel >= 4) printf("< side effects\n%s", effects); processEffects(); } /* next grab the error message, if there is one */ l = head.msglen; if (l) { msg = allocMem(l + 1); if (readFromJS(msg, l)) { free(msg); return -1; } msg[l] = 0; if (debugLevel >= 3) { /* print message, this will be in English, and mostly for our debugging */ if (jsSourceFile) printf("%s line %d: ", jsSourceFile, head.lineno); printf("%s\n", msg); } free(msg); } /* Read in the requested property, if there is one. * The calling function must handle the property. */ l = head.proplength; proptype = head.proptype; if (l) { propval = allocMem(l + 1); if (readFromJS(propval, l)) { free(propval); propval = 0; return -1; } propval[l] = 0; } if (head.highstat == EJ_HIGH_CX_FAIL) { if (head.lowstat == EJ_LOW_VARS) i_puts(MSG_JSEngineVars); if (head.lowstat == EJ_LOW_CX) i_puts(MSG_JavaContextError); if (head.lowstat == EJ_LOW_WIN) i_puts(MSG_JavaWindowError); if (head.lowstat == EJ_LOW_DOC) i_puts(MSG_JavaObjError); if (head.lowstat == EJ_LOW_CLOSE) i_puts(MSG_PageDone); else i_puts(MSG_JSSessionFail); freeJavaContext(cw); /* should I free and zero the property at this point? */ } return 0; } /* readMessage */ static int writeHeader(void) { head.magic = EJ_MAGIC; head.jcx = cw->jcx; head.winobj = cw->winobj; head.docobj = cw->docobj; return writeToJS(&head, sizeof(head)); } /* writeHeader */ static const char *debugString(const char *v) { if (!v) return emptyString; if (strlen(v) > 100) return "long"; return v; } /* debugString */ /* If debug is at least 5, show a simple acknowledgement or error * from the js process. */ static void ack5(void) { if (debugLevel < 5) return; printf("<"); if (head.highstat) printf(" error %d|%d", head.highstat, head.lowstat); if (propval) printf(" %s\n", debugString(propval)); else if (head.cmd == EJ_CMD_HASPROP) printf(" %d\n", head.proptype); else puts(" ok"); } /* ack5 */ /* Create a js context for the current window. * The corresponding js context will be stored in cw->jcx. */ void createJavaContext(void) { if (!allowJS) return; js_start(); debugPrint(5, "> create context for session %d", context); memset(&head, 0, sizeof(head)); head.cmd = EJ_CMD_CREATE; if (writeHeader()) return; if (readMessage()) return; ack5(); if (head.highstat) return; /* Copy the context pointer back to edbrowse. */ cw->jcx = head.jcx; cw->winobj = head.winobj; cw->docobj = head.docobj; setupJavaDom(); } /* createJavaContext */ /********************************************************************* This is unique among all the wrappered calls in that it can be made for a window that is not the current window. You can free another window, or a whole stack of windows, by typeing q2 while in session 1. Thus I build the message header here, instead of using the standard writeHeader() function above, * whibuilds the message assuming cw. *********************************************************************/ void freeJavaContext(struct ebWindow *w) { if (!w->winobj) return; debugPrint(5, "> free context session %d", context); head.magic = EJ_MAGIC; head.cmd = EJ_CMD_DESTROY; head.jcx = w->jcx; head.winobj = w->winobj; head.docobj = w->docobj; if (writeToJS(&head, sizeof(head))) return; if (readMessage()) return; ack5(); w->jcx = w->winobj = 0; } /* freeJavaContext */ void js_shutdown(void) { if (!js_pid) /* js not running */ return; debugPrint(5, "> js shutdown"); head.magic = EJ_MAGIC; head.cmd = EJ_CMD_EXIT; head.jcx = 0; head.winobj = 0; head.docobj = 0; writeToJS(&head, sizeof(head)); } /* js_shutdown */ /* After fork, the child process does not need to talk to js */ void js_disconnect(void) { if (!js_pid) return; close(pipe_in[0]); close(pipe_out[1]); js_pid = 0; } /* js_disconnect */ /* Run some javascript code under the current window */ /* Pass the return value of the script back as a string. */ char *jsRunScriptResult(jsobjtype obj, const char *str, const char *filename, int lineno) { int rc; char *s; if (!allowJS || !cw->winobj || !obj) return 0; if (!str || !str[0]) return 0; debugPrint(5, "> script:"); debugPrint(6, "%s", str); head.cmd = EJ_CMD_SCRIPT; head.obj = obj; /* this, in js */ head.proplength = strlen(str); head.lineno = lineno; if (writeHeader()) return 0; /* and send the script to execute */ if (writeToJS(str, head.proplength)) return 0; jsSourceFile = filename; rc = readMessage(); jsSourceFile = 0; if (rc) return 0; ack5(); s = propval; propval = 0; if (head.n) { /* a real result */ if (!s) s = emptyString; } else { nzFree(s); s = 0; } return s; } /* jsRunScriptResult */ /* like the above but throw away the result */ void jsRunScript(jsobjtype obj, const char *str, const char *filename, int lineno) { char *s = jsRunScriptResult(obj, str, filename, lineno); nzFree(s); } /* jsRunScript */ /* does the member exist? */ enum ej_proptype has_property(jsobjtype obj, const char *name) { if (!allowJS || !cw->winobj || !obj) return EJ_PROP_NONE; debugPrint(5, "> has %s", name); head.cmd = EJ_CMD_HASPROP; head.n = strlen(name); head.obj = obj; if (writeHeader()) return EJ_PROP_NONE; if (writeToJS(name, head.n)) return EJ_PROP_NONE; if (readMessage()) return EJ_PROP_NONE; ack5(); return head.proptype; } /* has_property */ void delete_property(jsobjtype obj, const char *name) { if (!allowJS || !cw->winobj || !obj) return; debugPrint(5, "> delete %s", name); head.cmd = EJ_CMD_DELPROP; head.obj = obj; head.n = strlen(name); if (writeHeader()) return; if (writeToJS(name, head.n)) return; if (readMessage()) return; ack5(); } /* delete_property */ /* Get a property from an object, js will tell us the type. */ static int get_property(jsobjtype obj, const char *name) { propval = 0; /* should already be 0 */ if (!allowJS || !cw->winobj || !obj) return -1; debugPrint(5, "> get %s", name); head.cmd = EJ_CMD_GETPROP; head.n = strlen(name); head.obj = obj; if (writeHeader()) return -1; if (writeToJS(name, head.n)) return -1; if (readMessage()) return -1; ack5(); return 0; } /* get_property */ /* Some type specific wrappers around the above. * First is string; the caller must free it. */ char *get_property_string(jsobjtype obj, const char *name) { char *s; get_property(obj, name); s = propval; propval = 0; if (!s && proptype == EJ_PROP_STRING) s = emptyString; return s; } /* get_property_string */ int get_property_number(jsobjtype obj, const char *name) { int n = -1; get_property(obj, name); if (!propval) return n; n = atoi(propval); free(propval); propval = 0; return n; } /* get_property_number */ double get_property_float(jsobjtype obj, const char *name) { double n = 0.0, d; get_property(obj, name); if (!propval) return n; if (stringIsFloat(propval, &d)) n = d; free(propval); propval = 0; return n; } /* get_property_float */ bool get_property_bool(jsobjtype obj, const char *name) { bool n = false; get_property(obj, name); if (!propval) return n; if (stringEqual(propval, "true") || stringEqual(propval, "1")) n = true; free(propval); propval = 0; return n; } /* get_property_bool */ /* get a js object, as a member of another object */ jsobjtype get_property_object(jsobjtype parent, const char *name) { jsobjtype child = 0; get_property(parent, name); if (!propval) return child; if (proptype == EJ_PROP_OBJECT || proptype == EJ_PROP_ARRAY) sscanf(propval, "%p", &child); free(propval); propval = 0; return child; } /* get_property_object */ jsobjtype get_property_function(jsobjtype parent, const char *name) { jsobjtype child = 0; get_property(parent, name); if (!propval) return child; if (proptype == EJ_PROP_FUNCTION) sscanf(propval, "%p", &child); free(propval); propval = 0; return child; } /* get_property_function */ /* Get an element of an array, again a string representation. */ static int get_array_element(jsobjtype obj, int idx) { propval = 0; if (!allowJS || !cw->winobj || !obj) return -1; debugPrint(5, "> get [%d]", idx); head.cmd = EJ_CMD_GETAREL; head.n = idx; head.obj = obj; if (writeHeader()) return -1; if (readMessage()) return -1; ack5(); return 0; } /* get_array_element */ jsobjtype get_array_element_object(jsobjtype obj, int idx) { jsobjtype p = 0; get_array_element(obj, idx); if (!propval) return p; if (proptype == EJ_PROP_OBJECT || proptype == EJ_PROP_ARRAY) sscanf(propval, "%p", &p); free(propval); propval = 0; return p; } /* get_array_element_object */ static int set_property(jsobjtype obj, const char *name, const char *value, enum ej_proptype proptype) { int l; if (!allowJS || !cw->winobj || !obj) return -1; debugPrint(5, "> set %s=%s", name, debugString(value)); head.cmd = EJ_CMD_SETPROP; head.obj = obj; head.proptype = proptype; head.proplength = strlen(value); head.n = strlen(name); if (writeHeader()) return -1; if (writeToJS(name, head.n)) return -1; if (writeToJS(value, strlen(value))) return -1; if (proptype == EJ_PROP_FUNCTION) jsSourceFile = name; if (readMessage()) return -1; jsSourceFile = NULL; ack5(); return 0; } /* set_property */ int set_property_string(jsobjtype obj, const char *name, const char *value) { if (value == NULL) value = emptyString; return set_property(obj, name, value, EJ_PROP_STRING); } /* set_property_string */ int set_property_number(jsobjtype obj, const char *name, int n) { char buf[20]; sprintf(buf, "%d", n); return set_property(obj, name, buf, EJ_PROP_INT); } /* set_property_number */ int set_property_float(jsobjtype obj, const char *name, double n) { char buf[32]; sprintf(buf, "%lf", n); return set_property(obj, name, buf, EJ_PROP_FLOAT); } /* set_property_float */ int set_property_bool(jsobjtype obj, const char *name, bool n) { char buf[8]; strcpy(buf, (n ? "1" : "0")); return set_property(obj, name, buf, EJ_PROP_BOOL); } /* set_property_bool */ int set_property_object(jsobjtype parent, const char *name, jsobjtype child) { char buf[32]; sprintf(buf, "%p", child); return set_property(parent, name, buf, EJ_PROP_OBJECT); } /* set_property_object */ jsobjtype instantiate_array(jsobjtype parent, const char *name) { jsobjtype p = 0; if (!allowJS || !cw->winobj || !parent) return 0; debugPrint(5, "> new array %s", name); head.cmd = EJ_CMD_SETPROP; head.obj = parent; head.proptype = EJ_PROP_ARRAY; head.proplength = 0; head.n = strlen(name); if (writeHeader()) return 0; if (writeToJS(name, head.n)) return 0; if (readMessage()) return 0; ack5(); if (propval) { sscanf(propval, "%p", &p); free(propval); propval = 0; } return p; } /* instantiate_array */ static int set_array_element(jsobjtype array, int idx, const char *value, enum ej_proptype proptype) { int l; if (!allowJS || !cw->winobj || !array) return -1; debugPrint(5, "> set [%d]=%s", idx, debugString(value)); head.cmd = EJ_CMD_SETAREL; head.obj = array; head.proptype = proptype; head.proplength = 0; if (value) head.proplength = strlen(value); head.n = idx; if (writeHeader()) return -1; if (writeToJS(value, head.proplength)) return -1; if (readMessage()) return -1; ack5(); return 0; } /* set_array_element */ int set_array_element_object(jsobjtype array, int idx, jsobjtype child) { char buf[32]; sprintf(buf, "%p", child); return set_array_element(array, idx, buf, EJ_PROP_OBJECT); } /* set_array_element_object */ jsobjtype instantiate_array_element(jsobjtype array, int idx, const char *classname) { jsobjtype p = 0; set_array_element(array, idx, classname, EJ_PROP_INSTANCE); if (!propval) return p; sscanf(propval, "%p", &p); nzFree(propval); propval = 0; return p; } /* instantiate_array_element */ /* Instantiate a new object from a given class. * Return is NULL if there is a js disaster. * Set classname = NULL for a generic object. */ jsobjtype instantiate(jsobjtype parent, const char *name, const char *classname) { jsobjtype p = 0; if (!allowJS || !cw->winobj || !parent) return 0; debugPrint(5, "> instantiate %s %s", name, (classname ? classname : "object")); head.cmd = EJ_CMD_SETPROP; head.obj = parent; head.proptype = EJ_PROP_INSTANCE; if (!classname) classname = emptyString; head.proplength = strlen(classname); head.n = strlen(name); if (writeHeader()) return 0; if (writeToJS(name, head.n)) return 0; if (writeToJS(classname, head.proplength)) return 0; if (readMessage()) return 0; ack5(); if (propval) { sscanf(propval, "%p", &p); free(propval); propval = 0; } return p; } /* instantiate */ int set_property_function(jsobjtype parent, const char *name, const char *body) { if (!body) body = emptyString; return set_property(parent, name, body, EJ_PROP_FUNCTION); /* should this really return the function created, like instantiate()? */ } /* set_property_function */ /* call javascript function with arguments, but all args must be objects */ static int run_function(jsobjtype obj, const char *name, int argc, const jsobjtype * argv) { int rc; propval = 0; /* should already be 0 */ if (!allowJS || !cw->winobj || !obj) return -1; debugPrint(5, "> call %s(%d)", name, argc); if (argc) { int i, l; char oval[20]; propval = initString(&l); for (i = 0; i < argc; ++i) { sprintf(oval, "%p|", argv[i]); stringAndString(&propval, &l, oval); } } head.cmd = EJ_CMD_CALL; head.n = strlen(name); head.obj = obj; head.proplength = 0; if (propval) head.proplength = strlen(propval); if (writeHeader()) return -1; if (writeToJS(name, head.n)) return -1; if (propval) { rc = writeToJS(propval, head.proplength); nzFree(propval); propval = 0; if (rc) return -1; } if (readMessage()) return -1; ack5(); return 0; } /* run_function */ int get_arraylength(jsobjtype a) { head.cmd = EJ_CMD_ARLEN; head.obj = a; if (writeHeader()) return -1; if (readMessage()) return -1; ack5(); return head.n; } /* get_arraylength */ /********************************************************************* Everything beyond this point is, perhaps, part of a DOM support layer above what has come before. Still, these are library-like routines that are used repeatedly by other files, particularly html.c and decorate.c. *********************************************************************/ /* pass, to the js process, the filename, * or the , for relative url resolution on innerHTML. * This has to be retained per edbrowse buffer. */ void set_basehref(const char *h) { if (!h) h = emptyString; set_property_string(cw->docobj, "base$href$", h); } /* set_basehref */ /* The object is a select-one field in the form, and this function returns * object.options[selectedIndex].value */ char *get_property_option(jsobjtype obj) { int n; jsobjtype oa; /* option array */ jsobjtype oo; /* option object */ char *val; if (!allowJS || !cw->winobj || !obj) return 0; n = get_property_number(obj, "selectedIndex"); if (n < 0) return 0; oa = get_property_object(obj, "options"); if (!oa) return 0; oo = get_array_element_object(oa, n); if (!oo) return 0; return get_property_string(oo, "value"); } /* get_property_option */ /********************************************************************* When an element is created without a name, it is not linked to its owner (via that name), and could be cleared via garbage collection. This is a disaster! Create a fake name, so we can attach the element. *********************************************************************/ /* set document.cookie to the cookies relevant to this url */ static void docCookie(jsobjtype d) { int cook_l; char *cook = initString(&cook_l); const char *url = cw->fileName; bool secure = false; const char *proto; char *s; if (url) { proto = getProtURL(url); if (proto && stringEqualCI(proto, "https")) secure = true; sendCookies(&cook, &cook_l, url, secure); if (memEqualCI(cook, "cookie: ", 8)) { /* should often happen */ strmove(cook, cook + 8); } if (s = strstr(cook, "\r\n")) *s = 0; } set_property_string(d, "cookie", cook); nzFree(cook); } /* docCookie */ #ifdef DOSLIKE // port of uname(p), and struct utsname struct utsname { char sysname[32]; char machine[32]; }; int uname(struct utsname *pun) { memset(pun, 0, sizeof(struct utsname)); // TODO: WIN32: maybe fill in sysname, and machine... return 0; } #else // !DOSLIKE - // port of uname(p), and struct utsname #include #endif // DOSLIKE y/n // port of uname(p), and struct utsname /* After createJavaContext, set up the document object and other variables * and methods that are base for client side DOM. */ void setupJavaDom(void) { jsobjtype w = cw->winobj; // window object jsobjtype d = cw->docobj; // document object jsobjtype nav; // navigator object jsobjtype navpi; // navigator plugins jsobjtype navmt; // navigator mime types jsobjtype hist; // history object struct MIMETYPE *mt; struct utsname ubuf; int i; char save_c; static const char *languages[] = { 0, "english", "french", "portuguese", "polish", "german", "russian", }; extern const char *startWindowJS; /* self reference through several names */ set_property_object(w, "window", w); set_property_object(w, "self", w); set_property_object(w, "parent", w); set_property_object(w, "top", w); nav = instantiate(w, "navigator", 0); if (!nav) return; /* some of the navigator is in startwindow.js; the runtime properties are here. */ set_property_string(nav, "userLanguage", languages[eb_lang]); set_property_string(nav, "language", languages[eb_lang]); set_property_string(nav, "appVersion", version); set_property_string(nav, "vendorSub", version); set_property_string(nav, "userAgent", currentAgent); uname(&ubuf); set_property_string(nav, "oscpu", ubuf.sysname); set_property_string(nav, "platform", ubuf.machine); /* Build the array of mime types and plugins, * according to the entries in the config file. */ navpi = instantiate_array(nav, "plugins"); if (navpi == NULL) return; navmt = instantiate_array(nav, "mimeTypes"); if (navmt == NULL) return; mt = mimetypes; for (i = 0; i < maxMime; ++i, ++mt) { int len; /* po is the plugin object and mo is the mime object */ jsobjtype po = instantiate_array_element(navpi, i, 0); jsobjtype mo = instantiate_array_element(navmt, i, 0); if (po == NULL || mo == NULL) return; set_property_object(mo, "enabledPlugin", po); set_property_string(mo, "type", mt->type); set_property_object(navmt, mt->type, mo); set_property_string(mo, "description", mt->desc); set_property_string(mo, "suffixes", mt->suffix); /* I don't really have enough information from the config file to fill * in the attributes of the plugin object. * I'm just going to fake it. * Description will be the same as that of the mime type, * and the filename will be the program to run. * No idea if this is right or not. */ set_property_string(po, "description", mt->desc); set_property_string(po, "filename", mt->program); /* For the name, how about the program without its options? */ len = strcspn(mt->program, " \t"); save_c = mt->program[len]; mt->program[len] = 0; set_property_string(po, "name", mt->program); mt->program[len] = save_c; } hist = instantiate(w, "history", 0); if (hist == NULL) return; set_property_string(hist, "current", cw->fileName); /* Since there is no history in edbrowse, the rest is left to startwindow.js */ /* the js window/document setup script. * These are all the things that do not depend on the platform, * OS, configurations, etc. */ jsRunScript(w, startWindowJS, "StartWindow", 1); // Document properties that must be set after startwindow.js. // Most of these use the setters in the URL class. set_property_string(d, "referrer", cw->referrer); instantiate_url(d, "URL", cw->fileName); instantiate_url(d, "location", cw->fileName); instantiate_url(w, "location", cw->fileName); set_property_string(d, "domain", getHostURL(cw->fileName)); docCookie(d); } /* setupJavaDom */ /* Get the url from a url object, special wrapper. * Owner object is passed, look for obj.href, obj.src, or obj.action. * Return that if it's a string, or its member href if it is a url. * The result, coming from get_property_string, is allocated. */ char *get_property_url(jsobjtype owner, bool action) { enum ej_proptype mtype; /* member type */ jsobjtype uo = 0; /* url object */ if (action) { mtype = has_property(owner, "action"); if (mtype == EJ_PROP_STRING) return get_property_string(owner, "action"); if (mtype != EJ_PROP_OBJECT) return 0; uo = get_property_object(owner, "action"); if (has_property(uo, "actioncrash")) return 0; } else { mtype = has_property(owner, "href"); if (mtype == EJ_PROP_STRING) return get_property_string(owner, "href"); if (mtype == EJ_PROP_OBJECT) uo = get_property_object(owner, "href"); else if (mtype) return 0; if (!uo) { mtype = has_property(owner, "src"); if (mtype == EJ_PROP_STRING) return get_property_string(owner, "src"); if (mtype == EJ_PROP_OBJECT) uo = get_property_object(owner, "src"); } } if (uo == NULL) return 0; /* should this be href$val? */ return get_property_string(uo, "href"); } /* get_property_url */ /********************************************************************* Javascript sometimes builds or rebuilds a submenu, based upon your selection in a primary menu. These new options must map back to html tags, and then to the dropdown list as you interact with the form. This is tested in jsrt - select a state, whereupon the colors below, that you have to choose from, can change. Someday the entire page will be rerendered based upon the js tree, which could be modified in almost any way, but today I only look at changing menus, because that is the high runner case. Besides, it use to seg fault when I didn't watch for this. *********************************************************************/ static void rebuildSelector(struct htmlTag *sel, jsobjtype oa, int len2) { int i1, i2, len1; bool check2; char *s; const char *selname; bool changed = false; struct htmlTag *t; jsobjtype oo; /* option object */ len1 = cw->numTags; i1 = i2 = 0; selname = sel->name; if (!selname) selname = "?"; debugPrint(4, "testing selector %s %d %d", selname, len1, len2); sel->lic = (sel->multiple ? 0 : -1); while (i1 < len1 && i2 < len2) { /* there is more to both lists */ t = tagList[i1++]; if (t->action != TAGACT_OPTION) continue; if (t->controller != sel) continue; /* find the corresponding option object */ if ((oo = get_array_element_object(oa, i2)) == NULL) { /* Wow this shouldn't happen. */ /* Guess I'll just pretend the array stops here. */ len2 = i2; --i1; break; } t->jv = oo; /* should already equal oo */ t->rchecked = get_property_bool(oo, "defaultSelected"); check2 = get_property_bool(oo, "selected"); if (check2) { if (sel->multiple) ++sel->lic; else sel->lic = i2; } ++i2; if (t->checked != check2) changed = true; t->checked = check2; s = get_property_string(oo, "text"); if (s && !t->textval || !stringEqual(t->textval, s)) { nzFree(t->textval); t->textval = s; changed = true; } else nzFree(s); s = get_property_string(oo, "value"); if (s && !t->value || !stringEqual(t->value, s)) { nzFree(t->value); t->value = s; } else nzFree(s); } /* one list or the other or both has run to the end */ if (i2 == len2) { for (; i1 < len1; ++i1) { t = tagList[i1]; if (t->action != TAGACT_OPTION) continue; if (t->controller != sel) continue; /* option is gone in js, disconnect this option tag from its select */ t->jv = 0; t->controller = 0; t->action = TAGACT_NOP; changed = true; } } else if (i1 == len1) { for (; i2 < len2; ++i2) { if ((oo = get_array_element_object(oa, i2)) == NULL) break; t = newTag("option"); t->lic = i2; t->controller = sel; t->jv = oo; t->step = 2; // already decorated t->textval = get_property_string(oo, "text"); t->value = get_property_string(oo, "value"); t->checked = get_property_bool(oo, "selected"); if (t->checked) { if (sel->multiple) ++sel->lic; else sel->lic = i2; } t->rchecked = get_property_bool(oo, "defaultSelected"); changed = true; } } if (!changed) return; debugPrint(4, "selector %s has changed", selname); /* If js change the menu, it should have also changed select.value * according to the checked options, but did it? * Don't know, so I'm going to do it here. */ s = displayOptions(sel); if (!s) s = emptyString; set_property_string(sel->jv, "value", s); javaSetsTagVar(sel->jv, s); nzFree(s); if (!sel->multiple) set_property_number(sel->jv, "selectedIndex", sel->lic); } /* rebuildSelector */ void rebuildSelectors(void) { int i1; struct htmlTag *t; jsobjtype oa; /* option array */ int len; /* length of option array */ for (i1 = 0; i1 < cw->numTags; ++i1) { t = tagList[i1]; if (!t->jv) continue; if (t->action != TAGACT_INPUT) continue; if (t->itype != INP_SELECT) continue; /* there should always be an options array, if not then move on */ if ((oa = get_property_object(t->jv, "options")) == NULL) continue; if ((len = get_arraylength(oa)) < 0) continue; rebuildSelector(t, oa, len); } } /* rebuildSelectors */ /* run a function with no args that returns an object */ jsobjtype run_function_object(jsobjtype obj, const char *name) { run_function(obj, name, 0, NULL); if (!propval) return NULL; if (head.proptype == EJ_PROP_OBJECT || head.proptype == EJ_PROP_ARRAY) { jsobjtype p; sscanf(propval, "%p", &p); nzFree(propval); propval = 0; return p; } /* wrong type, just return NULL */ nzFree(propval); propval = 0; return NULL; } /* run_function_object */ /* run a function with no args that returns a boolean */ bool run_function_bool(jsobjtype obj, const char *name) { run_function(obj, name, 0, NULL); if (!propval) return true; if (head.proptype == EJ_PROP_BOOL) { bool rc = (propval[0] == '1'); nzFree(propval); propval = 0; return rc; } if (head.proptype == EJ_PROP_INT) { int n = atoi(propval); nzFree(propval); propval = 0; return (n != 0); } /* wrong type, but at least it's something, just return true */ nzFree(propval); propval = 0; return true; } /* run_function_bool */ void run_function_objargs(jsobjtype obj, const char *name, int nargs, ...) { /* lazy, limit of 20 args */ jsobjtype argv[20]; int i; va_list p; if (nargs > 20) { puts("more than 20 args to a javascript function"); return; } va_start(p, nargs); for (i = 0; i < nargs; ++i) argv[i] = va_arg(p, jsobjtype); va_end(p); run_function(obj, name, nargs, argv); /* return is thrown away; this is a void function */ nzFree(propval); propval = 0; } /* run_function_objargs */ void run_function_onearg(jsobjtype obj, const char *name, jsobjtype a) { run_function_objargs(obj, name, 1, a); } /* run_function_onearg */ edbrowse-3.6.0.1/src/PaxHeaders.22102/eb.h0000644000000000000000000000006212637565441014561 xustar0020 atime=1451158305 30 ctime=1451409178.149337216 edbrowse-3.6.0.1/src/eb.h0000644000000000000000000004355012637565441015041 0ustar00rootroot00000000000000/* eb.h * Copyright (c) Karl Dahlke, 2008 * This file is part of the edbrowse project, released under GPL. */ #ifndef EB_H #define EB_H 1 /* the symbol DOSLIKE is used to conditionally compile those constructs * that are common to DOS and NT, but not typical of Unix. */ #ifdef MSDOS #define DOSLIKE 1 #endif #ifdef _WIN32 #define DOSLIKE 1 #endif /* Define _GNU_SOURCE on Linux, so we don't have an implicit declaration * of asprintf, but only if we are not compiling C++. * Turns out that when compiling C++ on LInux, _GNU_SOURCE is helpfully * predefined, so defining it twice generates a nasty warning. */ #if defined(EDBROWSE_ON_LINUX) && !defined(__cplusplus) #define _GNU_SOURCE #endif /* seems like everybody needs these header files */ #include #include #include #include #include #include #include #include #include #include #include #ifdef DOSLIKE #include #include // for _mkdir, ... #include // for _kbhit, getch, getche #include // for UINT32_MAX #else #include #endif #ifndef O_BINARY #define O_BINARY 0 #endif #ifndef O_TEXT #define O_TEXT 0 #endif #ifndef O_SYNC #define O_SYNC 0 #endif /* WARNING: the following typedef is pseudo-standard in C. * Some systems will define ushort in sys/types.h, others will not. * Unfortunately there is no #define symbol to key on; * no way to conditionally compile the following statement. */ #ifdef DOSLIKE typedef unsigned short ushort; typedef unsigned long ulong; #endif /* sys/types.h defines unsigned char as unchar. I prefer uchar. * It is consistent with ushort uint and ulong, and doesn't remind * me of the uncola, a char that isn't really a char. */ typedef unsigned char uchar; /* We use unsigned char for boolean fields. */ #ifndef __cplusplus typedef uchar bool; #define false 0 #define true 1 #endif /* Some source files are shared between edbrowse, a C program, * and edbrowse-js, currently a C++ function program, thus the prototypes, * and some other structures, must be C protected. */ #ifdef __cplusplus // Because of this line, you can't meaningfully run this file through indent. extern "C" { #endif /********************************************************************* Include the header file that connects edbrowse to the js process. This is a series of enums, and the interprocess message structure, and most importantly, the type jsobjtype, which is an opaque number that corresponds to an object in the javascript world. Edbrowse uses this number to connect an html tag, such as , with its corresponding js object. These object numbers are passed back and forth to connect edbrowse and js. *********************************************************************/ #include "ebjs.h" /* ctype macros, when you're passing a byte, * and you don't want to worry about whether it's char or uchar. * Call the regular routines when c is an int, from fgetc etc. */ #define isspaceByte(c) isspace((uchar)c) #define isalphaByte(c) isalpha((uchar)c) #define isalnumByte(c) isalnum((uchar)c) #define islowerByte(c) islower((uchar)c) #define isupperByte(c) isupper((uchar)c) #define isdigitByte(c) isdigit((uchar)c) #define ispunctByte(c) ispunct((uchar)c) #define isprintByte(c) isprint((uchar)c) /* http encoding, content type, content transfer encoding */ enum { ENC_PLAIN, ENC_COMPRESS, ENC_GZIP, ENC_URL, ENC_MFD }; enum { CT_OTHER, CT_TEXT, CT_HTML, CT_RICH, CT_APPLIC, CT_MULTI, CT_ALT }; enum { CE_7BIT, CE_8BIT, CE_QP, CE_64 }; /* This program was originally written in perl. * So I got use to perl strings, which admit nulls. * In our case, they will be terminated by newline. */ typedef uchar *pst; /* perl string */ /* A specific nonascii char denotes an html tag * in the rendered html text. * See the comments in buffers.c for the rationale. */ #define InternalCodeChar '\2' #define InternalCodeCharAlternate '\1' #define TableCellChar '\3' /* How long can an absolute path be? */ #define ABSPATH 264 // max length of an absolute pathname, in windows or unix /* How long can a regular expression be? */ #define MAXRE 400 /* How long can an entered line be? */ #define MAXTTYLINE 256 /* The longest string, in certain contexts. */ #define MAXSTRLEN 1024 /* How about user login and password? */ #define MAXUSERPASS 40 /* Number of pop3 mail accounts */ #define MAXACCOUNT 100 /* Number of mime types */ #define MAXMIME 40 /* Number of proxy entries */ #define MAXPROXY 200 /* number of db tables */ #define MAXDBT 100 #define MAXTCOLS 40 /* How many sessions open concurrently */ #define MAXSESSION 1000 /* Allocation increment for a growing string, that we don't expect * to get too large. This must be a power of 2. */ #define ALLOC_GR 0x100 /* print a dot on download for each chunk of this size */ #define CHUNKSIZE 1000000 /* alignments */ #define AL_LEFT 0 #define AL_CENTER 1 #define AL_RIGHT 2 #define AL_BLOCK 3 #define AL_NO 4 /* left and top borders for dialog box, stubs. */ #define DIALOG_LB 1 #define DIALOG_TB 1 #define COLOR_DIALOG_TEXT 0 #define G_BFU_FONT_SIZE 0 extern const char *version; // the version of edbrowse extern const char *progname; // edbrowse, or the absolute path to edbrowse extern const char eol[]; /* internet end of line */ extern char emptyString[]; /* use this whenever you would use "" */ /* Here are the strings you can send out to identify this browser. * Most of the time we will send out the first string, edbrowse-2.15.3. * But sometimes we have to lie. * When you deal with clickbank, for instance, they won't let you in the * door unless you are one of three approved browsers. * Tell them you're Explorer, and walk right in. * Anyways, this array holds up to 10 user agent strings. */ extern char *userAgents[10], *currentAgent; extern char *newlocation; extern int newloc_d; /* delay */ extern bool newloc_r; /* location replaces this page */ extern const char *ebrc_string; /* default ebrc file */ struct eb_curl_callback_data { char **buffer; int *length; }; struct MACCOUNT { /* pop3 account */ char *login, *password, *from, *reply; char *inurl, *outurl; int inport, outport; uchar inssl, outssl; bool nofetch, imap, secure; }; extern struct MACCOUNT accounts[]; /* all the email accounts */ extern int maxAccount; /* how many email accounts specified */ struct PXENT { /* proxy entry */ char *prot; /* null means any */ char *domain; /* null means any */ char *proxy; /* null means direct */ }; extern struct PXENT proxyEntries[]; extern int maxproxy; struct MIMETYPE { char *type, *desc; char *suffix, *prot, *program; char *content; char outtype; bool stream, download; }; extern struct MIMETYPE mimetypes[]; extern int maxMime; /* how many mime types specified */ struct DBTABLE { char *name, *shortname; char *cols[MAXTCOLS]; int ncols; unsigned char key1, key2, key3, key4; char *types; char *nullable; }; /* various globals */ extern CURL *http_curl_handle; extern int debugLevel; /* 0 to 9 */ extern char *sslCerts; /* ssl certificates to validate the secure server */ extern int verifyCertificates; /* is a certificate required for the ssl connection? */ extern int displayLength; /* when printing a line */ extern int jsPool; /* size of js pool in megabytes */ extern int webTimeout, mailTimeout; extern uchar browseLocal; extern bool sqlPresent; /* Was edbrowse compiled with SQL built in? */ extern bool ismc; /* Is the program running as a mail client? */ extern bool isimap; /* Is the program running as an imap client? */ extern bool down_bg; /* download in background */ extern int eb_lang; /* edbrowse language, determined by $LANG */ extern bool cons_utf8; /* does the console expect utf8? */ extern bool iuConvert; /* perform iso utf8 conversions automatically */ extern char type8859; /* 1 through 15 */ extern bool js_redirects; /* window.location = new_url */ extern bool passMail; /* pass mail across the filters */ extern bool errorExit; /* exit on any error, for scripting purposes */ extern bool isInteractive; extern volatile bool intFlag; /* set this when interrupt signal is caught */ extern bool binaryDetect; extern bool inputReadLine; extern bool listNA; /* list nonascii chars */ extern bool inInput; /* reading line from standard in */ extern int fileSize; /* when reading/writing files */ extern int mssock; /* mail server socket */ extern long hcode; /* http code, like 404 file not found */ extern char errorMsg[]; /* generated error message */ extern char serverLine[]; /* lines to and from the mail server */ extern int localAccount; /* this is the smtp server for outgoing mail */ extern char *mailDir; /* move to this directory when fetching mail */ extern char *downDir; /* the download directory */ extern char *mailUnread; /* place to hold fetched but unread mail */ extern char *mailReply; /* file to hold reply info for each email */ /* Keep a copy of unformatted mail that you probably won't need again, * but you never know. Should probably live somewhere under .Trash */ extern char *mailStash; extern char *dbarea, *dblogin, *dbpw; /* to log into the database */ extern bool fetchBlobColumns; extern bool caseInsensitive, searchStringsAll; extern bool allowRedirection; /* from http code 301, or http refresh */ extern bool sendReferrer; /* in the http header */ extern bool allowJS; /* javascript on */ extern bool htmlGenerated; extern bool helpMessagesOn; /* no need to type h */ extern bool pluginsOn; /* plugins are active */ extern bool showHiddenFiles; /* during directory scan */ extern int context; /* which session (buffer) are we in? */ extern pst linePending; extern char *changeFileName; extern char *addressFile; /* your address book */ extern char *serverData; extern int serverDataLen; extern char *breakLineResult; extern char *currentReferrer; extern char *home; /* home directory */ extern char *recycleBin; /* holds deleted files */ extern char *configFile, *sigFile, *sigFileEnd; extern char *cookieFile; /* persistent cookies */ struct listHead { void *next; void *prev; }; /* Macros to loop through the items in a list. */ #define foreach(e,l) for((e)=(l).next; \ (e) != (void*)&(l); \ (e) = ((struct listHead *)e)->next) #define foreachback(e,l) for((e)=(l).prev; \ (e) != (void*)&(l); \ (e) = ((struct listHead *)e)->prev) /********************************************************************* a queue of input field values that have been changed by javascript. these are applied to the edbrowse buffer after js has run. Other changes can also accumulate in this queue, such as innerHTML. The major number indicates the change to be made. v = value in form, i is innerHTML or innerText (via minor), x is unspecified. *********************************************************************/ struct inputChange { struct inputChange *next, *prev; struct htmlTag *t; int tagno; char major, minor; char filler1, filler2; char value[4]; }; extern struct listHead inputChangesPending; /* A pointer to the text of a line, and other line attributes */ struct lineMap { pst text; char ds1, ds2; /* directory suffix */ bool gflag; /* for g// */ char filler; }; #define LMSIZE sizeof(struct lineMap) /* an edbrowse window */ struct ebWindow { /* windows stack up as you open new files or follow hyperlinks. * Use the back command to pop the stack. * The back command follows this link, which is 0 if you are at the top. */ struct ebWindow *prev; /* This is right out of the source for ed. Current and last line numbers. */ int dot, dol; /* remember dot and dol for the raw text, when in browse mode */ int r_dot, r_dol; char *fileName; /* name of file or url */ char *firstURL; /* before http redirection */ char *referrer; char *baseDirName; /* when scanning a directory */ char *ft, *fd, *fk; /* title, description, keywords */ char *hbase; /* base for href references */ char *mailInfo; char lhs[MAXRE], rhs[MAXRE]; /* remembered substitution strings */ struct lineMap *map, *r_map; /* The labels that you set with the k command, and access via 'x. * Basically, that's 26 line numbers. * Number 0 means the label is not set. */ int labels[26], r_labels[26]; /* Next is an array of html tags, generated by the browse command, * and used thereafter for hyperlinks, fill-out forms, etc. */ struct htmlTag **tags; int numTags, allocTags; const struct MIMETYPE *mt; bool lhs_yes:1; bool rhs_yes:1; bool binMode:1; /* binary file */ bool nlMode:1; /* newline at the end */ bool rnlMode:1; /* Two text modes:1; these are incompatible with binMode */ bool utf8Mode:1; bool iso8859Mode:1; bool browseMode:1; /* browsing html */ bool changeMode:1; /* something has changed in this file */ bool quitMode:1; /* you can quit this buffer any time */ bool dirMode:1; /* directory mode */ bool undoable:1; /* undo is possible */ bool sqlMode:1; /* accessing a table */ bool baseset:1; /* tag seen */ char *dw; /* document.write string */ int dw_l; /* length of the above */ /* The javascript context and window corresponding to this edbrowse buffer. * If this is null then javascript is not operational for this window. * We could still be browsing however, without javascript. * Refer to browseMode to test for that. */ jsobjtype jcx; jsobjtype winobj; jsobjtype docobj; /* window.document */ struct DBTABLE *table; /* if in sqlMode */ }; extern struct ebWindow *cw; /* current window */ extern struct ebWindow in_js_cw; /* window within edbrowse-js */ /* quickly grab a tag from the current window via its sequence number: * tagList[n] */ #define tagList (cw->tags) /* js is running in the current session */ #define isJSAlive (cw->jcx != NULL && allowJS) /********************************************************************* Temporary cap on the number of lines, so the integer index into cw->map doesn't overflow. This is basically signed int over LMSIZE. The former is 2^31 on most machines, the latter is at most 12 on a 64-bit machine. If ints are larger then I don't even use this constant. *********************************************************************/ #define MAXLINES 170000000 /* An edit session */ struct ebSession { struct ebWindow *fw, *lw; /* first window, last window */ }; extern struct ebSession sessionList[]; extern struct ebSession *cs; /* current session */ /* The information on an html tag */ #define MAXTAGNAME 12 struct tagInfo { const char name[MAXTAGNAME]; const char *desc; int action; uchar para; /* paragraph and line breaks */ ushort bits; /* a bunch of boolean attributes */ }; extern const struct tagInfo availableTags[]; /* Information on tagInfo->bits */ /* support innerHTML */ #define TAG_INNERHTML 1 /* You won't see the text between and */ #define TAG_INVISIBLE 2 /* sometimes means nothing. */ #define TAG_NOSLASH 4 /* The structure for an html tag. * These tags are at times linked with js objects, * or even created by js objects. */ struct htmlTag { /* maintain a tree structure */ struct htmlTag *parent, *firstchild, *sibling; /* connect and */ struct htmlTag *balance; jsobjtype jv; /* corresponding java variable */ int seqno; char *js_file; int js_ln; /* line number of javascript */ int lic; /* list item count, highly overloaded */ int slic; /* start list item count */ int action; const struct tagInfo *info; char *textval; /* for text tags only */ const char **attributes; const char **atvals; /* the form that owns this input tag */ struct htmlTag *controller; uchar step; /* prerender, decorate, runscript */ bool slash:1; /* as in */ bool textin:1; /* some text */ bool deleted:1; /* deleted from the current buffer */ bool multiple:1; bool rdonly:1; bool clickable:1; /* but not an input field */ bool secure:1; bool scriptgen:1; // script generated, not from source bool checked:1; bool rchecked:1; /* for reset */ bool post:1; /* post, rather than get */ bool javapost:1; /* post by calling javascript */ bool mime:1; /* encode as mime, rather than url encode */ bool bymail:1; /* send by mail, rather than http */ bool submitted:1; bool onclick:1; bool onchange:1; bool onsubmit:1; bool onreset:1; bool onload:1; bool onunload:1; bool doorway:1; /* doorway to javascript */ char subsup; /* span turned into sup or sub */ uchar itype; /* input type = */ int ninp; /* number of nonhidden inputs */ char *name, *id, *value, *href; const char *rvalue; /* for reset */ /* class=foo becomes className = "foo" when you carry from html to javascript, * don't ask me why. */ char *classname; char *innerHTML; /* the html string under this tag */ int inner; /* for inner html */ }; /* htmlTag.action */ enum { TAGACT_ZERO, TAGACT_A, TAGACT_INPUT, TAGACT_TITLE, TAGACT_TA, TAGACT_BUTTON, TAGACT_SELECT, TAGACT_OPTION, TAGACT_NOP, TAGACT_JS, TAGACT_H, TAGACT_SUB, TAGACT_SUP, TAGACT_OVB, TAGACT_OL, TAGACT_UL, TAGACT_DL, TAGACT_TEXT, TAGACT_BODY, TAGACT_HEAD, TAGACT_MUSIC, TAGACT_IMAGE, TAGACT_BR, TAGACT_IBR, TAGACT_P, TAGACT_BASE, TAGACT_META, TAGACT_PRE, TAGACT_TBODY, TAGACT_DT, TAGACT_DD, TAGACT_LI, TAGACT_TABLE, TAGACT_TR, TAGACT_TD, TAGACT_DIV, TAGACT_SPAN, TAGACT_HR, TAGACT_FORM, TAGACT_FRAME, TAGACT_MAP, TAGACT_AREA, TAGACT_SCRIPT, TAGACT_EMBED, TAGACT_OBJ, }; /* htmlTag.itype */ /* Warning - the order of these is important! */ /* Corresponds to inp_types in decorate.c */ enum { INP_RESET, INP_BUTTON, INP_IMAGE, INP_SUBMIT, INP_HIDDEN, INP_TEXT, INP_PW, INP_NUMBER, INP_FILE, INP_SELECT, INP_TA, INP_RADIO, INP_CHECKBOX, }; /* For traversing a tree of html nodes, this is the callback function */ typedef void (*nodeFunction) (struct htmlTag * node, bool opentag); extern nodeFunction traverse_callback; /* Return codes for base64Decode() */ #define GOOD_BASE64_DECODE 0 #define BAD_BASE64_DECODE 1 #define EXTRA_CHARS_BASE64_DECODE 2 #ifdef DOSLIKE /* windows mkdir takes only one argument */ #define mkdir(a,b) _mkdir(a) #endif /* function prototypes */ #include "ebprot.h" /* Symbolic constants for language independent messages */ #include "messages.h" #ifdef __cplusplus } #endif #endif edbrowse-3.6.0.1/src/PaxHeaders.22102/decorate.c0000644000000000000000000000006212637565441015754 xustar0020 atime=1451158305 30 ctime=1451409178.149337216 edbrowse-3.6.0.1/src/decorate.c0000644000000000000000000012052512637565441016232 0ustar00rootroot00000000000000/********************************************************************* decorate.c: sanitize a tree of nodes produced by html, and decorate the tree with the corresponding js objects. A

    tag has a corresponding Form object in the js world, etc. This is done for the html that is on the initial web page, and any html that is produced by javascript via foo.innerHTML = string or document.write(string). *********************************************************************/ #include "eb.h" /* The current (foreground) edbrowse window. * This is replaced with a stub when run within the javascript process. */ struct ebWindow *cw; /* traverse the tree of nodes with a callback function */ nodeFunction traverse_callback; /* possible callback functions in this file */ static void prerenderNode(struct htmlTag *node, bool opentag); static void jsNode(struct htmlTag *node, bool opentag); static void processStyles(jsobjtype so, const char *stylestring); static void traverseNode(struct htmlTag *node) { struct htmlTag *child; (*traverse_callback) (node, true); for (child = node->firstchild; child; child = child->sibling) traverseNode(child); (*traverse_callback) (node, false); } /* traverseNode */ void traverseAll(int start) { struct htmlTag *t; int i; for (i = start; i < cw->numTags; ++i) { t = tagList[i]; if (!t->parent && !t->slash && t->step < 100) traverseNode(t); } } /* traverseAll */ static int nopt; /* number of options */ /* None of these tags nest, so it is reasonable to talk about * the current open tag. */ static struct htmlTag *currentForm, *currentSel, *currentOpt; static struct htmlTag *currentTitle, *currentScript, *currentTA; static struct htmlTag *currentA; static char *radioCheck; static int radio_l; const char *attribVal(const struct htmlTag *t, const char *name) { const char *v; int j; if (!t->attributes) return 0; j = stringInListCI(t->attributes, name); if (j < 0) return 0; v = t->atvals[j]; if (!v || !*v) return 0; return v; } /* attribVal */ static bool attribPresent(const struct htmlTag *t, const char *name) { int j = stringInListCI(t->attributes, name); return (j >= 0); } /* attribPresent */ static void linkinTree(struct htmlTag *parent, struct htmlTag *child) { struct htmlTag *c, *d; child->parent = parent; if (!parent->firstchild) { parent->firstchild = child; return; } for (c = parent->firstchild; c; c = c->sibling) { d = c; } d->sibling = child; } /* linkinTree */ static void makeButton(void) { struct htmlTag *t = newTag("input"); t->controller = currentForm; t->itype = INP_SUBMIT; t->value = emptyString; linkinTree(currentForm, t); } /* makeButton */ struct htmlTag *findOpenTag(struct htmlTag *t, int action) { while (t = t->parent) if (t->action == action) return t; return 0; } /* findOpenTag */ struct htmlTag *findOpenList(struct htmlTag *t) { while (t = t->parent) if (t->action == TAGACT_OL || t->action == TAGACT_UL) return t; return 0; } /* findOpenList */ /********************************************************************* Consider html like this. Link1 Link2 Link3 Each anchor should close the one before, thus rendering as {Link1} {Link2} {Link3} But tidy does not do this; it allows anchors to nest, thus {Link1{Link2{Link3}}} Not a serious problem really, it just looks funny. And yes, html like this does appear in the wild. This routine restructures the tree to move the inner anchor back up to the same level as the outer anchor. *********************************************************************/ static void nestedAnchors(int start) { struct htmlTag *a1, *a2, *p, *c; int j; for (j = start; j < cw->numTags; ++j) { a2 = tagList[j]; if (a2->action != TAGACT_A) continue; a1 = findOpenTag(a2, TAGACT_A); if (!a1) continue; /* delete a2 from the tree */ p = a2->parent; a2->parent = 0; if (p->firstchild == a2) p->firstchild = a2->sibling; else { c = p->firstchild; while (c->sibling) { if (c->sibling == a2) { c->sibling = a2->sibling; break; } c = c->sibling; } } a2->sibling = 0; /* and link a2 up next to a1 */ a2->parent = a1->parent; a2->sibling = a1->sibling; a1->sibling = a2; } } /* nestedAnchors */ void formControl(struct htmlTag *t, bool namecheck) { int itype = t->itype; char *myname = (t->name ? t->name : t->id); struct htmlTag *cf = currentForm; if (!cf) { /* nodes could be created dynamically, not through html */ cf = findOpenTag(t, TAGACT_FORM); } if (cf) t->controller = cf; else if (itype != INP_BUTTON) debugPrint(3, "%s is not part of a fill-out form", t->info->desc); if (namecheck && !myname) debugPrint(3, "%s does not have a name", t->info->desc); } /* formControl */ static const char *const inp_types[] = { "reset", "button", "image", "submit", "hidden", "text", "password", "number", "file", "select", "textarea", "radio", "checkbox", 0 }; /* helper function for input tag */ void htmlInputHelper(struct htmlTag *t) { int n = INP_TEXT; int len; char *myname = (t->name ? t->name : t->id); const char *s = attribVal(t, "type"); if (s) { n = stringInListCI(inp_types, s); if (n < 0) { debugPrint(3, "unrecognized input type %s", s); n = INP_TEXT; } } else if (stringEqual(t->info->name, "BUTTON")) { n = INP_BUTTON; } t->itype = n; s = attribVal(t, "maxlength"); len = 0; if (s) len = stringIsNum(s); if (len > 0) t->lic = len; // No preset value on file, for security reasons. // then submit via onload(). if (n == INP_FILE) { nzFree(t->value); t->value = 0; cnzFree(t->rvalue); t->rvalue = 0; } /* In this case an empty value should be "", not null */ if (t->value == 0) t->value = emptyString; if (t->rvalue == 0) t->rvalue = cloneString(t->value); if (n == INP_RADIO && t->checked && radioCheck && myname) { char namebuf[200]; if (strlen(myname) < sizeof(namebuf) - 3) { if (!*radioCheck) stringAndChar(&radioCheck, &radio_l, '|'); sprintf(namebuf, "|%s|", t->name); if (strstr(radioCheck, namebuf)) { debugPrint(3, "multiple radio buttons have been selected"); return; } stringAndString(&radioCheck, &radio_l, namebuf + 1); } } /* Even the submit fields can have a name, but they don't have to */ formControl(t, (n > INP_SUBMIT)); } /* htmlInputHelper */ /* return an allocated string containing the text entries for the checked options */ char *displayOptions(const struct htmlTag *sel) { const struct htmlTag *t; char *opt; int opt_l; int i; opt = initString(&opt_l); for (i = 0; i < cw->numTags; ++i) { t = tagList[i]; if (t->controller != sel) continue; if (!t->checked) continue; if (*opt) stringAndChar(&opt, &opt_l, ','); stringAndString(&opt, &opt_l, t->textval); } return opt; } /* displayOptions */ static void prerenderNode(struct htmlTag *t, bool opentag) { int itype; /* input type */ int j; int action = t->action; const char *a; /* usually an attribute */ #if 0 printf("prend %c%s\n", (opentag ? ' ' : '/'), t->info->name); #endif if (t->step >= 1) return; if (!opentag) t->step = 1; switch (action) { case TAGACT_TEXT: if (!opentag || !t->textval) break; if (currentTitle) { if (!cw->ft) { cw->ft = cloneString(t->textval); spaceCrunch(cw->ft, true, false); } t->deleted = true; break; } if (currentOpt) { currentOpt->textval = cloneString(t->textval); spaceCrunch(currentOpt->textval, true, false); t->deleted = true; break; } if (currentScript) { currentScript->textval = cloneString(t->textval); t->deleted = true; break; } if (currentTA) { currentTA->value = cloneString(t->textval); /* Sometimes tidy lops off the last newline character; it depends on * the tag following. And even if it didn't end in nl in the original html, * , it probably should, * as it goes into a new buffer. */ j = strlen(currentTA->value); if (j && currentTA->value[j - 1] != '\n') { currentTA->value = reallocMem(currentTA->value, j + 2); currentTA->value[j] = '\n'; currentTA->value[j + 1] = 0; } currentTA->rvalue = cloneString(currentTA->value); t->deleted = true; break; } /* text is on the page */ if (currentA) { char *s; for (s = t->textval; *s; ++s) if (isalnumByte(*s)) { currentA->textin = true; break; } } break; case TAGACT_TITLE: currentTitle = (opentag ? t : 0); break; case TAGACT_SCRIPT: currentScript = (opentag ? t : 0); break; case TAGACT_A: currentA = (opentag ? t : 0); break; case TAGACT_FORM: if (opentag) { currentForm = t; a = attribVal(t, "method"); if (a) { if (stringEqualCI(a, "post")) t->post = true; else if (!stringEqualCI(a, "get")) debugPrint(3, "form method should be get or post"); } a = attribVal(t, "enctype"); if (a) { if (stringEqualCI(a, "multipart/form-data")) t->mime = true; else if (!stringEqualCI (a, "application/x-www-form-urlencoded")) debugPrint(3, "unrecognized enctype, plese use multipart/form-data or application/x-www-form-urlencoded"); } if (a = t->href) { const char *prot = getProtURL(a); if (prot) { if (stringEqualCI(prot, "mailto")) t->bymail = true; else if (stringEqualCI (prot, "javascript")) t->javapost = true; else if (stringEqualCI(prot, "https")) t->secure = true; else if (!stringEqualCI(prot, "http")) debugPrint(3, "form cannot submit using protocol %s", prot); } } nzFree(radioCheck); radioCheck = initString(&radio_l); } if (!opentag && currentForm) { if (t->href && !t->submitted) { makeButton(); t->submitted = true; } currentForm = 0; } break; case TAGACT_INPUT: if (!opentag) break; htmlInputHelper(t); itype = t->itype; if (itype == INP_HIDDEN) break; if (currentForm) { ++currentForm->ninp; if (itype == INP_SUBMIT || itype == INP_IMAGE) currentForm->submitted = true; if (itype == INP_BUTTON && t->onclick) currentForm->submitted = true; if (itype > INP_HIDDEN && itype <= INP_SELECT && t->onchange) currentForm->submitted = true; } break; case TAGACT_OPTION: if (!opentag) { currentOpt = 0; break; } if (!currentSel) { debugPrint(3, "option appears outside a select statement"); break; } currentOpt = t; t->controller = currentSel; t->lic = nopt++; if (attribPresent(t, "selected")) { if (currentSel->lic && !currentSel->multiple) debugPrint(3, "multiple options are selected"); else { t->checked = t->rchecked = true; ++currentSel->lic; } } if (!t->value) t->value = emptyString; t->textval = emptyString; break; case TAGACT_SELECT: if (opentag) { currentSel = t; nopt = 0; t->itype = INP_SELECT; formControl(t, true); } else { currentSel = 0; t->action = TAGACT_INPUT; t->value = displayOptions(t); } break; case TAGACT_TA: if (opentag) { currentTA = t; t->itype = INP_TA; formControl(t, true); } else { t->action = TAGACT_INPUT; if (!t->value) { /* This can only happen it no text inside, */ /* like the other value fields, it can't be null */ t->rvalue = t->value = emptyString; } j = sideBuffer(0, t->value, -1, 0); t->lic = j; currentTA = 0; } break; case TAGACT_META: if (opentag) { /* This function doesn't do anything inside the js process. * It only works when scanning the original web page. * Thus I assume meta tags that set cookies, or keywords, or description, * or a refresh directive, are there from the get-go. * If js was going to generate a cookie it would just set document.cookie, * it wouldn't build a meta tag to set the cookie and then * appendChild it to head, right? */ htmlMetaHelper(t); } break; case TAGACT_TR: if (opentag) t->controller = findOpenTag(t, TAGACT_TABLE); break; case TAGACT_TD: if (opentag) t->controller = findOpenTag(t, TAGACT_TR); break; case TAGACT_SPAN: if (!opentag) break; if (!(a = t->classname)) break; if (stringEqualCI(a, "sup")) action = TAGACT_SUP; if (stringEqualCI(a, "sub")) action = TAGACT_SUB; if (stringEqualCI(a, "ovb")) action = TAGACT_OVB; t->action = action; break; case TAGACT_OL: /* look for start parameter for numbered list */ if (opentag) { a = attribVal(t, "start"); if (a && (j = stringIsNum(a)) >= 0) t->slic = j - 1; } break; } /* switch */ } /* prerenderNode */ void prerender(int start) { nestedAnchors(start); currentForm = currentSel = currentOpt = NULL; currentTitle = currentScript = currentTA = NULL; nzFree(radioCheck); radioCheck = 0; traverse_callback = prerenderNode; traverseAll(start); currentForm = NULL; nzFree(radioCheck); radioCheck = 0; } /* prerender */ /* create a new url with constructor */ jsobjtype instantiate_url(jsobjtype parent, const char *name, const char *url) { jsobjtype uo; /* url object */ uo = instantiate(parent, name, "URL"); if (uo) set_property_string(uo, "href", url); return uo; } /* instantiate_url */ static void handlerSet(jsobjtype ev, const char *name, const char *code) { enum ej_proptype hasform = has_property(ev, "form"); char *newcode = allocMem(strlen(code) + 60); strcpy(newcode, "with(document) { "); if (hasform) strcat(newcode, "with(this.form) { "); strcat(newcode, code); if (hasform) strcat(newcode, " }"); strcat(newcode, " }"); set_property_function(ev, name, newcode); nzFree(newcode); } /* handlerSet */ static void set_onhandler(const struct htmlTag *t, const char *name) { const char *s; if (t->jv) { s = attribVal(t, name); if (s) handlerSet(t->jv, name, s); } } /* set_onhandler */ static void set_onhandlers(const struct htmlTag *t) { /* I don't do anything with onkeypress, onfocus, etc, * these are just the most common handlers */ if (t->onclick) set_onhandler(t, "onclick"); if (t->onchange) set_onhandler(t, "onchange"); if (t->onsubmit) set_onhandler(t, "onsubmit"); if (t->onreset) set_onhandler(t, "onreset"); if (t->onload) set_onhandler(t, "onload"); if (t->onunload) set_onhandler(t, "onunload"); } /* set_onhandlers */ static char fakePropLast[24]; static const char *fakePropName(void) { static int idx = 0; ++idx; sprintf(fakePropLast, "gc$$%d", idx); return fakePropLast; } /*fakePropName */ static jsobjtype establish_js_option(jsobjtype obj, int idx) { jsobjtype oa; /* option array */ jsobjtype oo; /* option object */ jsobjtype fo; /* form object */ if ((oa = get_property_object(obj, "options")) == NULL) return NULL; if ((oo = instantiate_array_element(oa, idx, "Option")) == NULL) return NULL; /* option.form = select.form */ fo = get_property_object(obj, "form"); if (fo) set_property_object(oo, "form", fo); return oo; } /* establish_js_option */ static void establish_inner(jsobjtype obj, const char *start, const char *end, bool isText) { const char *s = emptyString; const char *name = (isText ? "innerText" : "innerHTML"); if (start) { s = start; if (end) s = pullString(start, end - start); } set_property_string(obj, name, s); if (start && end) nzFree((char *)s); /* Anything with an innerHTML might also have a style. */ instantiate(obj, "style", 0); } /* establish_inner */ static void domLink(struct htmlTag *t, const char *classname, /* instantiate this class */ const char *href, const char *list, /* next member of this array */ jsobjtype owner, int radiosel) { jsobjtype master; jsobjtype alist = 0; jsobjtype io = 0; /* input object */ unsigned length; bool dupname = false; /* some strings from the html tag */ const char *symname = t->name; const char *idname = t->id; const char *membername = 0; /* usually symname */ const char *href_url = t->href; const char *htmlclass = t->classname; const char *stylestring = attribVal(t, "style"); jsobjtype so = 0; /* obj.style */ debugPrint(5, "domLink %s.%d name %s", classname, radiosel, (symname ? symname : emptyString)); if (symname && has_property(owner, symname)) { /********************************************************************* This could be a duplicate name. Yes, that really happens. Link to the first tag having this name, and link the second tag under a fake name, so gc won't throw it away. Or - it could be a duplicate name because multiple radio buttons all share the same name. The first time, we create the array, and thereafter we just link under that array. Or - and this really does happen - an input tag could have the name action, colliding with form.action. I have no idea what to do here. I will assume the tag displaces the action. That means javascript cannot change the action of the form, which it rarely does anyways. When it refers to form.action, that will be the input tag. I'll check for that one first. Yeah, it makes my head spin too. *********************************************************************/ if (stringEqual(symname, "action")) { jsobjtype ao; /* action object */ ao = get_property_object(owner, symname); if (ao == NULL) return; /* actioncrash tells me if we've already had this collision */ if (!has_property(ao, "actioncrash")) { delete_property(owner, symname); /* advance, as though this were not found */ goto afterfound; } } /* radiosel is 1 for radio buttons and 2 for select */ if (radiosel == 1) { /* name present and radio buttons, name should be the array of buttons */ io = get_property_object(owner, symname); if (io == NULL) return; } else { /* don't know why the duplicate name */ dupname = true; } } afterfound: /* The input object is nonzero if&only if the input is a radio button, * and not the first button in the set, thus it isce the array containing * these buttons. */ if (io == NULL) { /********************************************************************* Ok, the above condition does not hold. We'll be creating a new object under owner, but through what name? The name= tag, unless it's a duplicate, or id= if there is no name=, or a fake name just to protect it from gc. *********************************************************************/ if (!symname && idname) { /* id= must not displace submit, reset, or action. * Example www.startpage.com, where id=submit */ if (!stringEqual(idname, "submit") && !stringEqual(idname, "reset") && !stringEqual(idname, "action")) membername = idname; } else if (symname && !dupname) { membername = symname; } if (!membername) membername = fakePropName(); if (radiosel) { /* The first radio button, or input type=select */ /* Either way the form element is suppose to be an array. */ io = instantiate_array(owner, membername); if (io == NULL) return; if (radiosel == 1) { set_property_string(io, "type", "radio"); set_property_string(io, "nodeName", "radio"); } else { /* I've read some docs that say select is itself an array, * and then references itself as an array of options. * Self referencing? Really? Well it seems to work. */ set_property_object(io, "options", io); set_property_object(io, "childNodes", io); set_property_number(io, "selectedIndex", -1); } } else { /* A standard input element, just create it. */ io = instantiate(owner, membername, classname); if (io == NULL) return; /* not an array; needs the childNodes array beneath it for the children */ instantiate_array(io, "childNodes"); /* deal with the 'styles' here */ /* object will get 'style' regardless of whether there is anything to put under it, just like it gets childNodes whether or not there are any. After that, there is a conditional step. If this node contains style='' of one or more name-value pairs, call out to process those and add them to the object */ so = instantiate(io, "style", 0); /* now if there are any style pairs to unpack, processStyles can rely on obj.style existing */ if (stylestring) processStyles(so, stylestring); /* done with styles - on to forms */ /* in the special case of form, also need an array of elements */ if (stringEqual(classname, "Form")) instantiate_array(io, "elements"); } if (membername == symname) { /* link to document.all */ master = get_property_object(cw->docobj, "all"); if (master == NULL) return; set_property_object(master, symname, io); if (stringEqual(symname, "action")) set_property_bool(io, "actioncrash", true); } if (list) alist = get_property_object(owner, list); if (alist) { length = get_arraylength(alist); if (length < 0) return; set_array_element_object(alist, length, io); if (symname && !dupname) set_property_object(alist, symname, io); if (idname && membername != idname) set_property_object(alist, idname, io); } /* list indicated */ } if (radiosel == 1) { /* drop down to the element within the radio array, and return that element */ /* w becomes the object associated with this radio button */ /* io is, by assumption, an array */ jsobjtype w; length = get_arraylength(io); if (length < 0) return; w = instantiate_array_element(io, length, "Element"); if (w == NULL) return; io = w; } if (symname) set_property_string(io, "name", symname); if (idname) { /* io.id becomes idname, and idMaster.idname becomes io * In case of forms, v.id should remain undefined. So we can have * a form field named "id". */ if (!stringEqual(classname, "Form")) set_property_string(io, "id", idname); master = get_property_object(cw->docobj, "idMaster"); set_property_object(master, idname, io); } if (href && href_url) instantiate_url(io, href, href_url); if (stringEqual(classname, "Element")) { /* link back to the form that owns the element */ set_property_object(io, "form", owner); } if (htmlclass) { set_property_string(io, "className", htmlclass); } t->jv = io; set_property_string(io, "nodeName", t->info->name); /* documentElement is now set in the "Body" case because the "Html" does not appear ever to be encountered */ if (stringEqual(classname, "Body")) { /* here are a few attributes that come in with the body */ set_property_object(cw->docobj, "body", io); set_property_number(io, "clientHeight", 768); set_property_number(io, "clientWidth", 1024); set_property_number(io, "offsetHeight", 768); set_property_number(io, "offsetWidth", 1024); set_property_number(io, "scrollHeight", 768); set_property_number(io, "scrollWidth", 1024); set_property_number(io, "scrollTop", 0); set_property_number(io, "scrollLeft", 0); set_property_object(cw->docobj, "documentElement", io); } if (stringEqual(classname, "Head")) { set_property_object(cw->docobj, "head", io); } } /* domLink */ static const char defvl[] = "defaultValue"; static const char defck[] = "defaultChecked"; static const char defsel[] = "defaultSelected"; static void formControlJS(struct htmlTag *t) { const char *typedesc; int itype = t->itype; int isradio = itype == INP_RADIO; int isselect = (itype == INP_SELECT) * 2; char *myname = (t->name ? t->name : t->id); const struct htmlTag *form = t->controller; if (form && form->jv) domLink(t, "Element", 0, "elements", form->jv, isradio | isselect); else domLink(t, "Element", 0, 0, cw->docobj, isradio | isselect); if (!t->jv) return; set_onhandlers(t); if (itype <= INP_RADIO) { set_property_string(t->jv, "value", t->value); if (itype != INP_FILE) { /* No default value on file, for security reasons */ set_property_string(t->jv, defvl, t->value); } /* not file */ } if (isselect) typedesc = t->multiple ? "select-multiple" : "select-one"; else typedesc = inp_types[itype]; set_property_string(t->jv, "type", typedesc); if (itype >= INP_RADIO) { set_property_bool(t->jv, "checked", t->checked); set_property_bool(t->jv, defck, t->checked); } } /* formControlJS */ static void optionJS(struct htmlTag *t) { struct htmlTag *sel = t->controller; const char *tx = t->textval; if (!sel) return; if (!tx) { debugPrint(3, "empty option"); } else { if (!t->value) t->value = cloneString(tx); } /* no point if the controlling select doesn't have a js object */ if (!sel->jv) return; t->jv = establish_js_option(sel->jv, t->lic); set_property_string(t->jv, "text", t->textval); set_property_string(t->jv, "value", t->value); set_property_string(t->jv, "nodeName", "option"); set_property_bool(t->jv, "selected", t->checked); set_property_bool(t->jv, defsel, t->checked); if (t->checked && !sel->multiple) { set_property_number(sel->jv, "selectedIndex", t->lic); set_property_string(sel->jv, "value", t->value); } } /* optionJS */ static jsobjtype innerParent; static void jsNode(struct htmlTag *t, bool opentag) { int itype; /* input type */ const struct tagInfo *ti = t->info; int action = t->action; const struct htmlTag *above; const char *a; /* all the js variables are on the open tag */ if (!opentag) return; if (t->step >= 2) return; t->step = 2; #if 0 printf("decorate %s\n", t->info->name); #endif switch (action) { case TAGACT_TEXT: t->jv = instantiate(cw->docobj, fakePropName(), "TextNode"); if (t->jv) { const char *w = t->textval; if (!w) w = emptyString; set_property_string(t->jv, "data", w); set_property_string(t->jv, "nodeName", "text"); } break; case TAGACT_SCRIPT: domLink(t, "Script", "src", "scripts", cw->docobj, 0); a = attribVal(t, "type"); if (a) set_property_string(t->jv, "type", a); a = attribVal(t, "language"); if (a) set_property_string(t->jv, "language", a); break; case TAGACT_FORM: domLink(t, "Form", "action", "forms", cw->docobj, 0); set_onhandlers(t); break; case TAGACT_INPUT: formControlJS(t); if (t->itype == INP_TA) establish_inner(t->jv, t->value, 0, true); break; case TAGACT_OPTION: optionJS(t); // The parent child relationship has already been established, // don't break, just return; return; case TAGACT_A: domLink(t, "Anchor", "href", "anchors", cw->docobj, 0); set_onhandlers(t); break; case TAGACT_HEAD: domLink(t, "Head", 0, "heads", cw->docobj, 0); break; case TAGACT_BODY: domLink(t, "Body", 0, "bodies", cw->docobj, 0); set_onhandlers(t); break; case TAGACT_OL: case TAGACT_UL: case TAGACT_DL: domLink(t, "Lister", 0, 0, cw->docobj, 0); break; case TAGACT_LI: domLink(t, "Listitem", 0, 0, cw->docobj, 0); break; case TAGACT_TABLE: domLink(t, "Table", 0, "tables", cw->docobj, 0); /* create the array of rows under the table */ instantiate_array(t->jv, "rows"); break; case TAGACT_TR: if ((above = t->controller) && above->jv) { domLink(t, "Trow", 0, "rows", above->jv, 0); instantiate_array(t->jv, "cells"); } break; case TAGACT_TD: if ((above = t->controller) && above->jv) { domLink(t, "Cell", 0, "cells", above->jv, 0); establish_inner(t->jv, t->innerHTML, 0, false); } break; case TAGACT_DIV: domLink(t, "Div", 0, "divs", cw->docobj, 0); establish_inner(t->jv, t->innerHTML, 0, false); break; case TAGACT_SPAN: case TAGACT_SUB: case TAGACT_SUP: case TAGACT_OVB: domLink(t, "Span", 0, "spans", cw->docobj, 0); establish_inner(t->jv, t->innerHTML, 0, false); break; case TAGACT_AREA: domLink(t, "Area", "href", "areas", cw->docobj, 0); break; case TAGACT_FRAME: domLink(t, "Frame", "src", "frames", cw->winobj, 0); break; case TAGACT_IMAGE: domLink(t, "Image", "src", "images", cw->docobj, 0); break; case TAGACT_P: domLink(t, "P", 0, "paragraphs", cw->docobj, 0); establish_inner(t->jv, t->innerHTML, 0, false); break; case TAGACT_TITLE: if (cw->ft) set_property_string(cw->docobj, "title", cw->ft); break; } /* switch */ /* js tree mirrors the dom tree. */ if (t->jv && t->parent && t->parent->jv) run_function_onearg(t->parent->jv, "apch$", t->jv); if (t->jv && !t->parent) { if (innerParent) run_function_onearg(innerParent, "apch$", t->jv); /* head and body link to document */ else if (action == TAGACT_HEAD || action == TAGACT_BODY) run_function_onearg(cw->docobj, "apch$", t->jv); } /* TextNode linked to document/gc to protect if from garbage collection, * but now it is linked to its parent, and even if it isn't, * we don't need it hanging around anyways. */ if (action == TAGACT_TEXT && t->jv) delete_property(cw->docobj, fakePropLast); } /* jsNode */ /* decorate the tree of nodes with js objects */ void decorate(int start) { traverse_callback = jsNode; traverseAll(start); } /* decorate */ /* paranoia check on the number of tags */ static void tagCountCheck(void) { if (sizeof(int) == 4) { if (cw->numTags > MAXLINES) i_printfExit(MSG_LineLimit); } } /* tagCountCheck */ static void pushTag(struct htmlTag *t) { int a = cw->allocTags; if (cw->numTags == a) { /* make more room */ a = a / 2 * 3; cw->tags = (struct htmlTag **)reallocMem(cw->tags, a * sizeof(t)); cw->allocTags = a; } tagList[cw->numTags++] = t; tagCountCheck(); } /* pushTag */ const struct tagInfo availableTags[] = { {"html", "html", TAGACT_ZERO}, {"base", "base reference for relative URLs", TAGACT_BASE, 0, 4}, {"a", "an anchor", TAGACT_A, 0, 1}, {"input", "an input item", TAGACT_INPUT, 0, 4}, {"title", "the title", TAGACT_TITLE, 0, 0}, {"textarea", "an input text area", TAGACT_TA, 0, 0}, {"select", "an option list", TAGACT_SELECT, 0, 0}, {"option", "a select option", TAGACT_OPTION, 0, 0}, {"sub", "a subscript", TAGACT_SUB, 0, 0}, {"sup", "a superscript", TAGACT_SUP, 0, 0}, {"ovb", "an overbar", TAGACT_OVB, 0, 0}, {"font", "a font", TAGACT_NOP, 0, 0}, {"center", "centered text", TAGACT_P, 2, 5}, {"caption", "a caption", TAGACT_NOP, 5, 0}, {"head", "the html header information", TAGACT_HEAD, 0, 4}, {"body", "the html body", TAGACT_BODY, 0, 4}, {"text", "a text section", TAGACT_TEXT, 0, 4}, {"bgsound", "background music", TAGACT_MUSIC, 0, 4}, {"audio", "audio passage", TAGACT_MUSIC, 0, 4}, {"meta", "a meta tag", TAGACT_META, 0, 4}, {"img", "an image", TAGACT_IMAGE, 0, 4}, {"image", "an image", TAGACT_IMAGE, 0, 4}, {"br", "a line break", TAGACT_BR, 1, 4}, {"p", "a paragraph", TAGACT_P, 2, 5}, {"div", "a divided section", TAGACT_DIV, 5, 1}, {"map", "a map of images", TAGACT_NOP, 5, 0}, {"blockquote", "a quoted paragraph", TAGACT_NOP, 10, 1}, {"h1", "a level 1 header", TAGACT_NOP, 10, 1}, {"h2", "a level 2 header", TAGACT_NOP, 10, 1}, {"h3", "a level 3 header", TAGACT_NOP, 10, 1}, {"h4", "a level 4 header", TAGACT_NOP, 10, 1}, {"h5", "a level 5 header", TAGACT_NOP, 10, 1}, {"h6", "a level 6 header", TAGACT_NOP, 10, 1}, {"dt", "a term", TAGACT_DT, 2, 4}, {"dd", "a definition", TAGACT_DD, 1, 4}, {"li", "a list item", TAGACT_LI, 1, 5}, {"ul", "a bullet list", TAGACT_UL, 10, 1}, {"dir", "a directory list", TAGACT_NOP, 5, 0}, {"menu", "a menu", TAGACT_NOP, 5, 0}, {"ol", "a numbered list", TAGACT_OL, 10, 1}, {"dl", "a definition list", TAGACT_DL, 10, 1}, {"hr", "a horizontal line", TAGACT_HR, 5, 4}, {"form", "a form", TAGACT_FORM, 10, 1}, {"button", "a button", TAGACT_INPUT, 0, 4}, {"frame", "a frame", TAGACT_FRAME, 2, 4}, {"iframe", "a frame", TAGACT_FRAME, 2, 4}, {"map", "an image map", TAGACT_MAP, 2, 4}, {"area", "an image map area", TAGACT_AREA, 0, 4}, {"table", "a table", TAGACT_TABLE, 10, 1}, {"tbody", "a table body", TAGACT_TBODY, 0, 0}, {"tr", "a table row", TAGACT_TR, 5, 1}, {"td", "a table entry", TAGACT_TD, 0, 5}, {"th", "a table heading", TAGACT_TD, 0, 5}, {"pre", "a preformatted section", TAGACT_PRE, 10, 0}, {"listing", "a listing", TAGACT_PRE, 1, 0}, {"xmp", "an example", TAGACT_PRE, 1, 0}, {"fixed", "a fixed presentation", TAGACT_NOP, 1, 0}, {"code", "a block of code", TAGACT_NOP, 0, 0}, {"samp", "a block of sample text", TAGACT_NOP, 0, 0}, {"address", "an address block", TAGACT_NOP, 1, 0}, {"style", "a style block", TAGACT_NOP, 0, 2}, {"script", "a script", TAGACT_SCRIPT, 0, 0}, {"noscript", "no script section", TAGACT_NOP, 0, 2}, {"noframes", "no frames section", TAGACT_NOP, 0, 2}, {"embed", "embedded html", TAGACT_MUSIC, 0, 4}, {"noembed", "no embed section", TAGACT_NOP, 0, 2}, {"object", "an html object", TAGACT_OBJ, 0, 2}, {"em", "emphasized text", TAGACT_JS, 0, 0}, {"label", "a label", TAGACT_JS, 0, 0}, {"strike", "emphasized text", TAGACT_JS, 0, 0}, {"s", "emphasized text", TAGACT_JS, 0, 0}, {"strong", "emphasized text", TAGACT_JS, 0, 0}, {"b", "bold text", TAGACT_JS, 0, 0}, {"i", "italicized text", TAGACT_JS, 0, 0}, {"u", "underlined text", TAGACT_JS, 0, 0}, {"dfn", "definition text", TAGACT_JS, 0, 0}, {"q", "quoted text", TAGACT_JS, 0, 0}, {"abbr", "an abbreviation", TAGACT_JS, 0, 0}, {"span", "an html span", TAGACT_SPAN, 0, 1}, {"frameset", "a frame set", TAGACT_JS, 0, 0}, {"", NULL, 0} }; void freeTags(struct ebWindow *w) { int i, n; struct htmlTag *t; struct htmlTag **e; /* if not browsing ... */ if (!(e = w->tags)) return; /* drop empty textarea buffers created by this session */ for (i = 0; i < w->numTags; ++i, ++e) { t = *e; if (t->action != TAGACT_INPUT) continue; if (t->itype != INP_TA) continue; if (!(n = t->lic)) continue; freeEmptySideBuffer(n); } /* loop over tags */ e = w->tags; for (i = 0; i < w->numTags; ++i, ++e) { char **a; t = *e; nzFree(t->textval); nzFree(t->name); nzFree(t->id); nzFree(t->value); cnzFree(t->rvalue); nzFree(t->href); nzFree(t->classname); nzFree(t->js_file); nzFree(t->innerHTML); a = (char **)t->attributes; if (a) { while (*a) { nzFree(*a); ++a; } free(t->attributes); } a = (char **)t->atvals; if (a) { while (*a) { nzFree(*a); ++a; } free(t->atvals); } free(t); } free(w->tags); w->tags = 0; w->numTags = w->allocTags = 0; } /* freeTags */ struct htmlTag *newTag(const char *name) { struct htmlTag *t; const struct tagInfo *ti; int action; for (ti = availableTags; ti->name[0]; ++ti) if (stringEqualCI(ti->name, name)) break; if (!ti->name[0]) return 0; if ((action = ti->action) == TAGACT_ZERO) return 0; t = (struct htmlTag *)allocZeroMem(sizeof(struct htmlTag)); t->action = action; t->info = ti; t->seqno = cw->numTags; pushTag(t); return t; } /* newTag */ /* reserve space for 512 tags */ void initTagArray(void) { cw->numTags = 0; cw->allocTags = 512; cw->tags = (struct htmlTag **)allocMem(cw->allocTags * sizeof(struct htmlTag *)); } /* initTagArray */ bool htmlGenerated; static struct htmlTag *treeAttach; static int tree_pos; static bool treeDisable; static void intoTree(struct htmlTag *parent); void htmlNodesIntoTree(int start, struct htmlTag *attach) { treeAttach = attach; tree_pos = start; treeDisable = false; intoTree(0); } /* htmlNodesIntoTree */ /* Convert a list of html nodes, properly nested open close, into a tree. * Attach the tree to an existing tree here, for document.write etc, * or just build the tree if attach is null. */ static void intoTree(struct htmlTag *parent) { struct htmlTag *t, *prev = 0; int j; const char *v; int action; while (tree_pos < cw->numTags) { t = tagList[tree_pos++]; if (t->slash) { if (parent) parent->balance = t, t->balance = parent; return; } if (treeDisable) { debugPrint(5, "node skip %s", t->info->name); t->step = 100; intoTree(t); continue; } if (htmlGenerated) { /*Some things are different if the html is generated, not part of the original web page. * You can skip past altogether, including its * tidy generated descendants, and you want to pass through * to the children below. */ action = t->action; if (action == TAGACT_HEAD) { debugPrint(5, "node skip %s", t->info->name); t->step = 100; treeDisable = true; intoTree(t); treeDisable = false; continue; } if (action == TAGACT_BODY) { debugPrint(5, "node pass %s", t->info->name); t->step = 100; intoTree(t); continue; } /* this node is ok, but if parent is a pass through node... */ if (parent == 0 || /* this shouldn't happen */ parent->action == TAGACT_BODY) { /* link up to treeAttach */ const char *w = "root"; if (treeAttach) w = treeAttach->info->name; debugPrint(5, "node up %s to %s", t->info->name, w); t->parent = treeAttach; if (treeAttach) { struct htmlTag *c = treeAttach->firstchild; if (!c) treeAttach->firstchild = t; else { while (c->sibling) c = c->sibling; c->sibling = t; } } goto checkattributes; } } /* regular linking through the parent node */ t->parent = parent; if (prev) { prev->sibling = t; } else if (parent) { parent->firstchild = t; } prev = t; checkattributes: /* check for some common attributes here */ action = t->action; if (stringInListCI(t->attributes, "onclick") >= 0) t->onclick = t->doorway = true; if (stringInListCI(t->attributes, "onchange") >= 0) t->onchange = t->doorway = true; if (stringInListCI(t->attributes, "onsubmit") >= 0) t->onsubmit = t->doorway = true; if (stringInListCI(t->attributes, "onreset") >= 0) t->onreset = t->doorway = true; if (stringInListCI(t->attributes, "onload") >= 0) t->onload = t->doorway = true; if (stringInListCI(t->attributes, "onunload") >= 0) t->onunload = t->doorway = true; if (stringInListCI(t->attributes, "checked") >= 0) t->checked = t->rchecked = true; if (stringInListCI(t->attributes, "readonly") >= 0) t->rdonly = true; if (stringInListCI(t->attributes, "multiple") >= 0) t->multiple = true; if ((j = stringInListCI(t->attributes, "name")) >= 0) { /* temporarily, make another copy; some day we'll just point to the value */ v = t->atvals[j]; if (v && !*v) v = 0; t->name = cloneString(v); } if ((j = stringInListCI(t->attributes, "id")) >= 0) { v = t->atvals[j]; if (v && !*v) v = 0; t->id = cloneString(v); } if ((j = stringInListCI(t->attributes, "class")) >= 0) { v = t->atvals[j]; if (v && !*v) v = 0; t->classname = cloneString(v); } if ((j = stringInListCI(t->attributes, "value")) >= 0) { v = t->atvals[j]; if (v && !*v) v = 0; t->value = cloneString(v); t->rvalue = cloneString(v); } if ((j = stringInListCI(t->attributes, "href")) >= 0) { v = t->atvals[j]; if (v && !*v) v = 0; if (v) { v = resolveURL(cw->hbase, v); cnzFree(t->atvals[j]); t->atvals[j] = v; if (action == TAGACT_BASE && !cw->baseset) { nzFree(cw->hbase); cw->hbase = cloneString(v); cw->baseset = true; } t->href = cloneString(v); } } if ((j = stringInListCI(t->attributes, "src")) >= 0) { v = t->atvals[j]; if (v && !*v) v = 0; if (v) { v = resolveURL(cw->hbase, v); cnzFree(t->atvals[j]); t->atvals[j] = v; if (!t->href) t->href = cloneString(v); } } if ((j = stringInListCI(t->attributes, "action")) >= 0) { v = t->atvals[j]; if (v && !*v) v = 0; if (v) { v = resolveURL(cw->hbase, v); cnzFree(t->atvals[j]); t->atvals[j] = v; if (!t->href) t->href = cloneString(v); } } /* href=javascript:foo() is another doorway into js */ if (t->href && memEqualCI(t->href, "javascript:", 11)) t->doorway = true; /* And of course the primary doorway */ if (action == TAGACT_SCRIPT) { t->doorway = true; t->scriptgen = htmlGenerated; } intoTree(t); } } /* intoTree */ /* Here is an instance of the edbrowse window that exists for parsing * html from inside javascript. It belongs to edbrowse-js, * and isn't associated with a particular buffer on the edbrowse side. */ struct ebWindow in_js_cw; /* Parse some html, as generated by innerHTML or document.write. * This assumes in_js_cw has been set up properly, * with the correct winobj and docobj etc. */ void html_from_setter(jsobjtype inner, const char *h) { cw = &in_js_cw; initTagArray(); html2nodes(h, false); htmlGenerated = true; htmlNodesIntoTree(0, NULL); prerender(0); innerParent = inner; decorate(0); } /* html_from_setter */ static void processStyles(jsobjtype so, const char *stylestring) { char *workstring = cloneString(stylestring); char *s; // gets truncated to the style name char *sv; char *next; for (s = workstring; *s; s = next) { next = strchr(s, ';'); if (!next) { next = s + strlen(s); } else { *next++ = 0; skipWhite2(&next); } sv = strchr(s, ':'); // if there was something there, but it didn't // adhere to the expected syntax, skip this pair if (sv) { *sv++ = '\0'; skipWhite2(&sv); trimWhite(s); trimWhite(sv); set_property_string(so, s, sv); } } nzFree(workstring); } /* processStyles */ edbrowse-3.6.0.1/src/PaxHeaders.22102/dbstubs.c0000644000000000000000000000006212637565441015634 xustar0020 atime=1451158305 30 ctime=1451409178.149337216 edbrowse-3.6.0.1/src/dbstubs.c0000644000000000000000000000176512637565441016116 0ustar00rootroot00000000000000/* dbstubs.c * Stubs for the database functions. * After all, most people will compile this without database access. * This file is part of the edbrowse project, released under GPL. */ #include "eb.h" bool sqlPresent = false; bool sqlReadRows(const char *filename, char **bufptr) { setError(MSG_DBNotCompiled); *bufptr = emptyString; return false; } /* sqlReadRows */ void dbClose(void) { } /* dbClose */ void showColumns(void) { } /* showColumns */ void showForeign(void) { } /* showForeign */ bool showTables(void) { return false; } /* showTables */ bool sqlDelRows(int start, int end) { return false; } /* sqlDelRows */ bool sqlUpdateRow(pst source, int slen, pst dest, int dlen) { return false; } /* sqlUpdateRow */ bool sqlAddRows(int ln) { return false; } /* sqlAddRows */ bool ebConnect(void) { setError(MSG_DBNotCompiled); return false; } /* ebConnect */ int goSelect(int *startLine, char **rbuf) { *rbuf = emptyString; return -1; } /* goSelect */ edbrowse-3.6.0.1/src/PaxHeaders.22102/dbops.c0000644000000000000000000000006212637565441015275 xustar0020 atime=1451158305 30 ctime=1451409178.149337216 edbrowse-3.6.0.1/src/dbops.c0000644000000000000000000012214312637565441015551 0ustar00rootroot00000000000000/* dbops.c * Database operations. * This file is part of the edbrowse project, released under GPL. */ #include "eb.h" #include "dbapi.h" bool sqlPresent = true; const char *sql_debuglog = "/tmp/ebsql.log"; /* log of debug prints */ const char *sql_database; /* name of current database */ int rv_numRets; char rv_type[NUMRETS + 1]; bool rv_nullable[NUMRETS]; /* names of returned data, usually SQL column names */ char rv_name[NUMRETS + 1][COLNAMELEN]; LF rv_data[NUMRETS]; /* the returned values */ long rv_lastNrows, rv_lastSerial, rv_lastRowid; void *rv_blobLoc; /* location of blob in memory */ int rv_blobSize; const char *rv_blobFile; bool rv_blobAppend; /* text descriptions corresponding to our generic SQL error codes */ /* This has yet to be internationalized. */ const char *sqlErrorList[] = { 0, "miscelaneous SQL error", "syntax error in SQL statement", "filename cannot be used by SQL", "cannot convert/compare the columns/constants in the SQL statement", "bad string subscripting", "bad use of the rowid construct", "bad use of a blob column", "bad use of aggregate operators or columns", "bad use of a view", "bad use of a serial column", "bad use of a temp table", "operation cannot cross databases", "database is fucked up", "query interrupted by user", "could not connect to the database", "database has not yet been selected", "table not found", "duplicate table", "ambiguous table", "column not found", "duplicate column", "ambiguous column", "index not found", "duplicate index", "constraint not found", "duplicate constraint", "stored procedure not found", "duplicate stored procedure", "synonym not found", "duplicate synonym", "table has no primary or unique key", "duplicate primary or unique key", "cursor not specified, or cursor is not available", "duplicate cursor", "the database lacks the resources needed to complete this query", "check constrain violated", "referential integrity violated", "cannot manage or complete the transaction", "long transaction, too much log data generated", "this operation must be run inside a transaction", "cannot open, read, write, close, or otherwise manage a blob", "row, table, page, or database is already locked, or cannot be locked", "inserting null into a not null column", "no permission to modify the database in this way", "no current row established", "many rows were found where one was expected", "cannot union these select statements together", "cannot access or write the audit trail", "could not run SQL or gather data from a remote host", "where clause is semantically unmanageable", "deadlock detected", 0 }; char *lineFormat(const char *line, ...) { char *s; va_list p; va_start(p, line); s = lineFormatStack(line, 0, &p); va_end(p); return s; } /* lineFormat */ #define LFBUFSIZE 100000 static char lfbuf[LFBUFSIZE]; /* line formatting buffer */ static const char selfref[] = "@lineFormat attempts to expand within its own static buffer"; static const char lfoverflow[] = "@lineFormat(), line is too long, limit %d"; char *lineFormatStack(const char *line, /* the sprintf-like formatting string */ LF * argv, /* pointer to array of values */ va_list * parmv) { short i, len, maxlen, len_given, flags; long n; double dn; /* double number */ char *q, *r, pdir, inquote; const char *t, *perc; char fmt[12]; if (parmv && argv || !parmv && !argv) errorPrint ("@exactly one of the last two arguments to lineFormatStack should be null"); if (line == lfbuf) { if (strchr(line, '%')) errorPrint(selfref); return (char *)line; } lfbuf[0] = 0; q = lfbuf; t = line; while (*t) { /* more text to format */ /* copy up to the next % */ if (*t != '%' || t[1] == '%' && ++t) { if (q - lfbuf >= LFBUFSIZE - 1) errorPrint(lfoverflow, LFBUFSIZE); *q++ = *t++; continue; } /* % found */ perc = t++; inquote = 0; len = 0; len_given = 0; if (*t == '-') ++t; for (; isdigit(*t); ++t) { len_given = 1; len = 10 * len + *t - '0'; } while (*t == '.' || isdigit(*t)) ++t; pdir = *t++; if (isupper(pdir)) { pdir = tolower(pdir); inquote = '"'; } if (t - perc >= sizeof(fmt)) errorPrint("2percent directive in lineFormat too long"); strncpy(fmt, perc, t - perc); fmt[t - perc] = 0; maxlen = len; if (maxlen < 11) maxlen = 11; /* get the next vararg */ if (pdir == 'f') { if (parmv) dn = va_arg(*parmv, double); else dn = argv->f; } else { if (parmv) n = va_arg(*parmv, int); else n = argv->l; } if (argv) ++argv; if (pdir == 's' && n) { i = strlen((char *)n); if (i > maxlen) maxlen = i; if (inquote && strchr((char *)n, inquote)) { inquote = '\''; if (strchr((char *)n, inquote)) errorPrint ("2lineFormat() cannot put quotes around %s", n); } } if (inquote) maxlen += 2; if (q + maxlen >= lfbuf + LFBUFSIZE) errorPrint(lfoverflow, LFBUFSIZE); /* check for null parameter */ if (pdir == 'c' && !n || pdir == 's' && isnullstring((char *)n) || pdir == 'f' && dn == nullfloat || !strchr("scf", pdir) && isnull(n)) { if (!len_given) { char *q1; /* turn = %d to is null */ for (q1 = q - 1; q1 >= lfbuf && *q1 == ' '; --q1) ; if (q1 >= lfbuf && *q1 == '=') { if (q1 > lfbuf && q1[-1] == '!') { strcpy(q1 - 1, "IS NOT "); q = q1 + 6; } else { strcpy(q1, "IS "); q = q1 + 3; } } strcpy(q, "NULL"); q += 4; continue; } /* null with no length specified */ pdir = 's'; n = (int)""; } /* parameter is null */ if (inquote) *q++ = inquote; fmt[t - perc - 1] = pdir; switch (pdir) { case 'i': flags = DTDELIMIT; if (len) { if (len >= 11) flags |= DTAMPM; if (len < 8) flags = DTDELIMIT | DTCRUNCH; if (len < 5) flags = DTCRUNCH; } strcpy(q, timeString(n, flags)); break; case 'a': flags = DTDELIMIT; if (len) { if (len < 10) flags = DTCRUNCH | DTDELIMIT; if (len < 8) flags = DTCRUNCH; if (len == 5) flags = DTCRUNCH | DTDELIMIT; } strcpy(q, dateString(n, flags)); if (len == 4 || len == 5) q[len] = 0; break; case 'm': strcpy(q, moneyString(n)); break; case 'f': sprintf(q, fmt, dn); /* show float as an integer, if it is an integer, and it usually is */ r = strchr(q, '.'); if (r) { while (*++r == '0') ; if (!*r) { r = strchr(q, '.'); *r = 0; } } break; case 's': if (n == (int)lfbuf) errorPrint(selfref); /* extra code to prevent %09s from printing out all zeros when the argument is null (empty string) */ if (!*(char *)n && fmt[1] == '0') strmove(fmt + 1, fmt + 2); /* fall through */ default: sprintf(q, fmt, n); } /* switch */ q += strlen(q); if (inquote) *q++ = inquote; } /* loop printing pieces of the string */ *q = 0; /* null terminate */ /* we relie on the calling function to invoke va_end(), since the arg list is not always the ... varargs of a function, though it usually is. See lineFormat() above for a typical example. Note that the calling function may wish to process additional arguments before calling va_end. */ return lfbuf; } /* lineFormatStack */ /* given a datatype, return the character that, when appended to %, causes lineFormat() to print the data element properly. */ static char sprintfChar(char datatype) { char c; switch (datatype) { case 'S': c = 's'; break; case 'C': c = 'c'; break; case 'M': case 'N': c = 'd'; break; case 'D': c = 'a'; break; case 'I': c = 'i'; break; case 'F': c = 'f'; break; case 'B': case 'T': c = 'd'; break; default: c = 0; } /* switch */ return c; } /* sprintfChar */ /********************************************************************* Using the values just fetched or selected, build a line in unload format. All fields are expanded into ascii, with pipes between them. Conversely, given a line of pipe separated fields, put them back into binary, ready for retsCopy(). *********************************************************************/ char *sql_mkunld(char delim) { char fmt[NUMRETS * 4 + 1]; int i; char pftype; for (i = 0; i < rv_numRets; ++i) { pftype = sprintfChar(rv_type[i]); if (!pftype) errorPrint("2sql_mkunld cannot convert datatype %c", rv_type[i]); sprintf(fmt + 4 * i, "%%0%c%c", pftype, delim); } /* loop over returns */ return lineFormatStack(fmt, rv_data, 0); } /* sql_mkunld */ /* like the above, but we build a comma-separated list with quotes, ready for SQL insert or update. You might be tempted to call this routine first, obtaining a string, and then call lineFormat("insert into foo values(%s)", but don't do that! The returned string is built by lineFormat and is already in the buffer. You instead need to make a copy of the string and then call lineFormat. */ char *sql_mkinsupd() { char fmt[NUMRETS * 3 + 1]; int i; char pftype; for (i = 0; i < rv_numRets; ++i) { pftype = sprintfChar(rv_type[i]); if (!pftype) errorPrint("2sql_mkinsupd cannot convert datatype %c", rv_type[i]); if (pftype != 'd' && pftype != 'f') pftype = toupper(pftype); sprintf(fmt + 3 * i, "%%%c,", pftype); } /* loop over returns */ fmt[3 * i - 1] = 0; return lineFormatStack(fmt, rv_data, 0); } /* sql_mkinsupd */ /********************************************************************* Date time functions. *********************************************************************/ static char ndays[] = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; bool isLeapYear(int year) { if (year % 4) return false; if (year % 100) return true; if (year % 400) return false; return true; } /* isLeapYear */ /* convert year, month, and day into a date. */ /* return -1 = bad year, -2 = bad month, -3 = bad day */ date dateEncode(int year, int month, int day) { short i; long d; if ((year | month | day) == 0) return nullint; if (year < 1640 || year > 3000) return -1; if (month <= 0 || month > 12) return -2; if (day <= 0 || day > ndays[month]) return -3; if (day == 29 && month == 2 && !isLeapYear(year)) return -3; --year; d = year * 365L + year / 4 - year / 100 + year / 400; for (i = 1; i < month; ++i) d += ndays[i]; ++year; if (month > 2 && !isLeapYear(year)) --d; d += (day - 1); d -= 598632; return d; } /* dateEncode */ /* convert a date back into year, month, and day */ /* the inverse of the above */ void dateDecode(date d, int *yp, int *mp, int *dp) { int year, month, day; year = month = day = 0; if (d >= 0 && d <= 497094) { /* how many years have rolled by; at worst 366 days in each */ year = d / 366; year += 1640; while (dateEncode(++year, 1, 1) <= d) ; --year; d -= dateEncode(year, 1, 1); if (!isLeapYear(year)) ndays[2] = 28; for (month = 1; month <= 12; ++month) { if (d < ndays[month]) break; d -= ndays[month]; } day = d + 1; ndays[2] = 29; /* put it back */ } *yp = year; *mp = month; *dp = day; } /* dateDecode */ /* convert a string into a date */ /* return -4 for bad format */ date stringDate(const char *s, bool yearfirst) { short year, month, day, i, l; char delim; char buf[12]; char *t; if (!s) return nullint; l = strlen(s); while (l && s[l - 1] == ' ') --l; if (!l) return nullint; if (l != 8 && l != 10) return -4; strncpy(buf, s, l); buf[l] = 0; delim = yearfirst ? '-' : '/'; t = strchr(buf, delim); if (t) strmove(t, t + 1); t = strchr(buf, delim); if (t) strmove(t, t + 1); l = strlen(buf); if (l != 8) return -4; if (!strcmp(buf, " ")) return nullint; if (yearfirst) { char swap[4]; strncpy(swap, buf, 4); strncpy(buf, buf + 4, 4); strncpy(buf + 4, swap, 4); } for (i = 0; i < 8; ++i) if (!isdigit(buf[i])) return -4; month = 10 * (buf[0] - '0') + buf[1] - '0'; day = 10 * (buf[2] - '0') + buf[3] - '0'; year = atoi(buf + 4); return dateEncode(year, month, day); } /* stringDate */ /* convert a date into a string, held in a static buffer */ /* cram squashes out the century, delimit puts in slashes */ char *dateString(date d, int flags) { static char buf[12]; char swap[8]; int year, month, day; dateDecode(d, &year, &month, &day); if (!year) strcpy(buf, " / / "); else sprintf(buf, "%02d/%02d/%04d", month, day, year); if (flags & DTCRUNCH) strmove(buf + 6, buf + 8); if (flags & YEARFIRST) { strncpy(swap, buf, 6); swap[2] = swap[5] = 0; strmove(buf, buf + 6); if (flags & DTDELIMIT) strcat(buf, "-"); strcat(buf, swap); if (flags & DTDELIMIT) strcat(buf, "-"); strcat(buf, swap + 3); } else if (!(flags & DTDELIMIT)) { char *s; s = strchr(buf, '/'); strmove(s, s + 1); s = strchr(buf, '/'); strmove(s, s + 1); } return buf; } /* dateString */ char *timeString(interval seconds, int flags) { short h, m, s; char c = 'A'; static char buf[12]; if (seconds < 0 || seconds >= 86400) strcpy(buf, " : : AM"); else { h = seconds / 3600; seconds -= h * 3600L; m = seconds / 60; seconds -= m * 60; s = (short)seconds; if (flags & DTAMPM) { if (h == 0) h = 12; else if (h >= 12) { c = 'P'; if (h > 12) h -= 12; } } sprintf(buf, "%02d:%02d:%02d %cM", h, m, s, c); } if (!(flags & DTAMPM)) buf[8] = 0; if (flags & DTCRUNCH) strmove(buf + 5, buf + 8); if (!(flags & DTDELIMIT)) { strmove(buf + 2, buf + 3); if (buf[4] == ':') strmove(buf + 4, buf + 5); } return buf; } /* timeString */ /* convert string into time. * Like stringDate, we can return bad hour, bad minute, bad second, or bad format */ interval stringTime(const char *t) { short h, m, s; bool ampm = false; char c; char buf[12]; short i, l; if (!t) return nullint; l = strlen(t); while (l && t[l - 1] == ' ') --l; if (!l) return nullint; if (l < 4 || l > 11) return -4; strncpy(buf, t, l); buf[l] = 0; if (buf[l - 1] == 'M' && buf[l - 3] == ' ') { ampm = true; c = buf[l - 2]; if (c != 'A' && c != 'P') return -4; buf[l - 3] = 0; l -= 3; } if (l < 4 || l > 8) return -4; if (buf[2] == ':') strmove(buf + 2, buf + 3); if (buf[4] == ':') strmove(buf + 4, buf + 5); l = strlen(buf); if (l != 4 && l != 6) return -4; if (!strncmp(buf, " ", l)) return nullint; for (i = 0; i < l; ++i) if (!isdigit(buf[i])) return -4; h = 10 * (buf[0] - '0') + buf[1] - '0'; m = 10 * (buf[2] - '0') + buf[3] - '0'; s = 0; if (l == 6) s = 10 * (buf[4] - '0') + buf[5] - '0'; if (ampm) { if (h == 12) { if (c == 'A') h = 0; } else if (c == 'P') h += 12; } if (h < 0 || h >= 24) return -1; if (m < 0 || m >= 60) return -2; if (s < 0 || s >= 60) return -3; return h * 3600L + m * 60 + s; } /* stringTime */ char *moneyString(money m) { static char buf[20], *s = buf; if (m == nullint) return ""; if (m < 0) *s++ = '-', m = -m; sprintf(s, "$%ld.%02d", m / 100, (int)(m % 100)); return buf; } /* moneyString */ money stringMoney(const char *s) { short sign = 1; long m; double d; if (!s) return nullint; skipWhite(&s); if (*s == '-') sign = -sign, ++s; skipWhite(&s); if (*s == '$') ++s; skipWhite(&s); if (!*s) return nullint; if (!stringIsFloat(s, &d)) return -nullint; m = (long)(d * 100.0 + 0.5); return m * sign; } /* stringMoney */ /* Make sure edbrowse is connected to the database */ bool ebConnect(void) { if (sql_database) return true; if (!dbarea) { setError(MSG_DBUnspecified); return false; } sql_connect(dbarea, dblogin, dbpw); if (!sql_database) { setError(MSG_DBConnect, rv_vendorStatus); return false; } return true; } /* ebConnect */ void dbClose(void) { sql_disconnect(); } /* dbClose */ static char myTab[64]; static const char *myWhere; static char *scl; /* select clause */ static int scllen; static char *wcl; /* where clause */ static int wcllen; static char wherecol[COLNAMELEN + 2]; static struct DBTABLE *td; /* put quotes around a column value */ static void pushQuoted(char **s, int *slen, const char *value, int colno) { char quotemark = 0; char coltype = td->types[colno]; if (!value || !*value) { stringAndString(s, slen, "NULL"); return; } if (coltype != 'F' && coltype != 'N') { /* Microsoft insists on single quote. */ quotemark = '\''; if (strchr(value, quotemark)) quotemark = '"'; } if (quotemark) stringAndChar(s, slen, quotemark); stringAndString(s, slen, value); if (quotemark) stringAndChar(s, slen, quotemark); } /* pushQuoted */ static char *lineFields[MAXTCOLS]; static char *keysQuoted(void) { char *u; int ulen; int key1 = td->key1, key2 = td->key2; int key3 = td->key3; if (!key1) return 0; u = initString(&ulen); --key1; stringAndString(&u, &ulen, "where "); stringAndString(&u, &ulen, td->cols[key1]); stringAndString(&u, &ulen, " = "); pushQuoted(&u, &ulen, lineFields[key1], key1); if (!key2) return u; --key2; stringAndString(&u, &ulen, " and "); stringAndString(&u, &ulen, td->cols[key2]); stringAndString(&u, &ulen, " = "); pushQuoted(&u, &ulen, lineFields[key2], key2); if (!key3) return u; --key3; stringAndString(&u, &ulen, " and "); stringAndString(&u, &ulen, td->cols[key3]); stringAndString(&u, &ulen, " = "); pushQuoted(&u, &ulen, lineFields[key3], key3); return u; } /* keysQuoted */ static void buildSelectClause(void) { int i; scl = initString(&scllen); stringAndString(&scl, &scllen, "select "); for (i = 0; i < td->ncols; ++i) { if (i) stringAndChar(&scl, &scllen, ','); stringAndString(&scl, &scllen, td->cols[i]); } stringAndString(&scl, &scllen, " from "); stringAndString(&scl, &scllen, td->name); } /* buildSelectClause */ static char date2buf[24]; static bool dateBetween(const char *s) { char *e; if (strlen(s) >= 24) return false; strcpy(date2buf, s); e = strchr(date2buf, '-'); if (!e) return false; *e = 0; return stringIsDate(date2buf) | stringIsDate(e + 1); } /* dateBetween */ static bool buildWhereClause(void) { int i, l, n, colno; const char *w = myWhere; const char *e; wcl = initString(&wcllen); wherecol[0] = 0; if (stringEqual(w, "*")) return true; e = strchr(w, '='); if (!e) { if (!td->key1) { setError(MSG_DBNoKey); return false; } colno = td->key1; e = td->cols[colno - 1]; l = strlen(e); if (l > COLNAMELEN) { setError(MSG_DBColumnLong, e, COLNAMELEN); return false; } strcpy(wherecol, e); e = w - 1; } else if (isdigit(*w)) { colno = strtol(w, (char **)&w, 10); if (w != e) { setError(MSG_DBSyntax); return false; } if (colno == 0 || colno > td->ncols) { setError(MSG_DBColRange, colno); return false; } goto setcol_n; } else { colno = 0; if (e - w <= COLNAMELEN) { strncpy(wherecol, w, e - w); wherecol[e - w] = 0; for (i = 0; i < td->ncols; ++i) { if (!strstr(td->cols[i], wherecol)) continue; if (colno) { setError(MSG_DBManyColumns, wherecol); return false; } colno = i + 1; } } if (!colno) { setError(MSG_DBNoColumn, wherecol); return false; } setcol_n: w = td->cols[colno - 1]; l = strlen(w); if (l > COLNAMELEN) { setError(MSG_DBColumnLong, w, COLNAMELEN); return false; } strcpy(wherecol, w); } stringAndString(&wcl, &wcllen, "where "); stringAndString(&wcl, &wcllen, wherecol); ++e; w = e; if (!*e) { stringAndString(&wcl, &wcllen, " is null"); } else if ((i = strtol(e, (char **)&e, 10)) >= 0 && *e == '-' && (n = strtol(e + 1, (char **)&e, 10)) >= 0 && *e == 0) { stringAndString(&wcl, &wcllen, " between "); stringAndNum(&wcl, &wcllen, i); stringAndString(&wcl, &wcllen, " and "); stringAndNum(&wcl, &wcllen, n); } else if (dateBetween(w)) { stringAndString(&wcl, &wcllen, " between \""); stringAndString(&wcl, &wcllen, date2buf); stringAndString(&wcl, &wcllen, "\" and \""); stringAndString(&wcl, &wcllen, date2buf + strlen(date2buf) + 1); stringAndChar(&wcl, &wcllen, '"'); } else if (w[strlen(w) - 1] == '*') { stringAndString(&wcl, &wcllen, lineFormat(" matches %S", w)); } else { stringAndString(&wcl, &wcllen, " = "); pushQuoted(&wcl, &wcllen, w, colno - 1); } return true; } /* buildWhereClause */ static bool setTable(void) { static const short exclist[] = { EXCNOTABLE, EXCNOCOLUMN, 0 }; int cid, nc, i, part1, part2, part3, part4; const char *s = cw->fileName; const char *t = strchr(s, ']'); if (t - s >= sizeof(myTab)) errorPrint("2table name too long, limit %d characters", sizeof(myTab) - 4); strncpy(myTab, s, t - s); myTab[t - s] = 0; myWhere = t + 1; td = cw->table; if (td) return true; /* haven't glommed onto this table yet */ td = findTableDescriptor(myTab); if (td) { if (!td->types) { buildSelectClause(); sql_exclist(exclist); cid = sql_prepare(scl); nzFree(scl); if (rv_lastStatus) { if (rv_lastStatus == EXCNOTABLE) setError(MSG_DBNoTable, td->name); else if (rv_lastStatus == EXCNOCOLUMN) setError(MSG_DBBadColumn); return false; } td->types = cloneString(rv_type); nc = rv_numRets; td->nullable = allocMem(nc); memcpy(td->nullable, rv_nullable, nc); sql_free(cid); } } else { sql_exclist(exclist); cid = sql_prepare("select * from %s", myTab); if (rv_lastStatus) { if (rv_lastStatus == EXCNOTABLE) setError(MSG_DBNoTable, myTab); return false; } td = newTableDescriptor(myTab); if (!td) { sql_free(cid); return false; } nc = rv_numRets; if (nc > MAXTCOLS) { printf ("warning, only the first %d columns will be selected\n", MAXTCOLS); nc = MAXTCOLS; } td->types = cloneString(rv_type); td->types[nc] = 0; td->ncols = nc; td->nullable = allocMem(nc); memcpy(td->nullable, rv_nullable, nc); for (i = 0; i < nc; ++i) td->cols[i] = cloneString(rv_name[i]); sql_free(cid); getPrimaryKey(myTab, &part1, &part2, &part3, &part4); if (part1 > nc) part1 = 0; if (part2 > nc) part2 = 0; if (part3 > nc) part3 = 0; if (part4 > nc) part4 = 0; td->key1 = part1; td->key2 = part2; td->key3 = part3; td->key4 = part4; } cw->table = td; return true; } /* setTable */ void showColumns(void) { char c; const char *desc; int i; if (!setTable()) return; i_printf(MSG_Table); printf(" %s", td->name); if (!stringEqual(td->name, td->shortname)) printf(" [%s]", td->shortname); i = sql_selectOne("select count(*) from %s", td->name); printf(", %d ", i); i_printf(i == 1 ? MSG_Row : MSG_Rows); nl(); for (i = 0; i < td->ncols; ++i) { printf("%d ", i + 1); if (td->key1 == i + 1 || td->key2 == i + 1 || td->key3 == i + 1 || td->key4 == i + 1) printf("*"); if (td->nullable[i]) printf("+"); printf("%s ", td->cols[i]); c = td->types[i]; switch (c) { case 'N': desc = "int"; break; case 'D': desc = "date"; break; case 'I': desc = "time"; break; case 'M': desc = "money"; break; case 'F': desc = "float"; break; case 'S': desc = "string"; break; case 'C': desc = "char"; break; case 'B': desc = "blob"; break; case 'T': desc = "text"; break; default: desc = "?"; break; } /* switch */ printf("%s\n", desc); } } /* showColumns */ void showForeign(void) { if (!setTable()) return; i_printf(MSG_Fkeys, td->name); fetchForeign(td->name); } /* showForeign */ /* Select rows of data and put them into the text buffer */ static bool rowsIntoBuffer(int cid, const char *types, char **bufptr, int *lcnt) { char *rbuf, *unld, *u, *v, *s, *end; int rbuflen; bool rc = false; *bufptr = emptyString; *lcnt = 0; rbuf = initString(&rbuflen); while (sql_fetchNext(cid, 0)) { unld = sql_mkunld('\177'); if (strchr(unld, '|')) { setError(MSG_DBPipes); goto abort; } if (strchr(unld, '\n')) { setError(MSG_DBNewline); goto abort; } for (s = unld; *s; ++s) if (*s == '\177') *s = '|'; s[-1] = '\n'; /* overwrite the last pipe */ /* look for blob column */ if (rv_blobLoc && (s = strpbrk(types, "BT"))) { int bfi = s - types; /* blob field index */ int cx = 0; /* context, where to put the blob */ int j; u = unld; for (j = 0; j < bfi; ++j) u = strchr(u, '|') + 1; v = strpbrk(u, "|\n"); end = v + strlen(v); cx = sideBuffer(0, rv_blobLoc, rv_blobSize, 0); nzFree(rv_blobLoc); sprintf(myTab, "<%d>", cx); if (!cx) myTab[0] = 0; j = strlen(myTab); /* unld is pretty long; I'm just going to assume there is enough room for this */ memmove(u + j, v, end + 1 - v); memcpy(u, myTab, j); } stringAndString(&rbuf, &rbuflen, unld); ++*lcnt; } rc = true; abort: sql_closeFree(cid); *bufptr = rbuf; return rc; } /* rowsIntoBuffer */ bool sqlReadRows(const char *filename, char **bufptr) { int cid, lcnt; *bufptr = emptyString; if (!ebConnect()) return false; if (!setTable()) return false; myWhere = strchr(filename, ']') + 1; if (!*myWhere) return true; if (!buildWhereClause()) return false; buildSelectClause(); rv_blobFile = 0; cid = sql_prepOpen("%s %0s", scl, wcl); nzFree(scl); nzFree(wcl); if (cid < 0) return false; return rowsIntoBuffer(cid, td->types, bufptr, &lcnt); } /* sqlReadRows */ /* Split a line at pipe boundaries, and make sure the field count is correct */ static bool intoFields(char *line) { char *s = line; int j = 0; int c; while (1) { lineFields[j] = s; s = strpbrk(s, "|\n"); c = *s; *s++ = 0; ++j; if (c == '\n') break; if (j < td->ncols) continue; setError(MSG_DBAddField); return false; } if (j == td->ncols) return true; setError(MSG_DBLostField); return false; } /* intoFields */ static bool rowCountCheck(int action, int cnt1) { int cnt2 = rv_lastNrows; if (cnt1 == cnt2) return true; setError(MSG_DBDeleteCount + action, cnt1, cnt2); return false; } /* rowCountCheck */ static int keyCountCheck(void) { if (!td->key1) { setError(MSG_DBNoKeyCol); return false; } if (!td->key2) return 1; if (!td->key3) return 2; if (!td->key4) return 3; setError(MSG_DBManyKeyCol); return 0; } /* keyCountCheck */ /* Typical error conditions for insert update delete */ static const short insupdExceptions[] = { EXCVIEWUSE, EXCREFINT, EXCITEMLOCK, EXCPERMISSION, EXCDEADLOCK, EXCCHECK, EXCTIMEOUT, EXCNOTNULLCOLUMN, 0 }; static bool insupdError(int action, int rcnt) { int rc = rv_lastStatus; int msg; if (rc) { switch (rc) { case EXCVIEWUSE: msg = MSG_DBView; break; case EXCREFINT: msg = MSG_DBRefInt; break; case EXCITEMLOCK: msg = MSG_DBLocked; break; case EXCPERMISSION: msg = MSG_DBPerms; break; case EXCDEADLOCK: msg = MSG_DBDeadlock; break; case EXCNOTNULLCOLUMN: msg = MSG_DBNotNull; break; case EXCCHECK: msg = MSG_DBCheck; break; case EXCTIMEOUT: msg = MSG_DBTimeout; break; default: setError(MSG_DBMisc, rv_vendorStatus); return false; } setError(msg); return false; } return rowCountCheck(action, rcnt); } /* insupdError */ bool sqlDelRows(int start, int end) { int nkeys, ndel, ln; if (!setTable()) return false; nkeys = keyCountCheck(); if (!nkeys) return false; ndel = end - start + 1; ln = start; if (ndel > 100) { setError(MSG_DBMassDelete); return false; } /* We could delete all the rows with one statement, using an in(list), * but that won't work when the key is two columns. * I have to write the one-line-at-a-time code anyways, * I'll just use that for now. */ while (ndel--) { char *wherekeys; char *line = (char *)fetchLine(ln, 0); intoFields(line); wherekeys = keysQuoted(); sql_exclist(insupdExceptions); sql_exec("delete from %s %s", td->name, wherekeys); nzFree(wherekeys); nzFree(line); if (!insupdError(0, 1)) return false; delText(ln, ln); } return true; } /* sqlDelRows */ bool sqlUpdateRow(pst source, int slen, pst dest, int dlen) { char *d2; /* clone of dest */ char *wherekeys; char *s, *t; int j, l1, l2, nkeys, key1, key2; char *u1; /* column=value of the update statement */ int u1len; /* compare all the way out to newline, so we know both strings end at the same time */ if (slen == dlen && !memcmp(source, dest, slen + 1)) return true; if (!setTable()) return false; nkeys = keyCountCheck(); if (!nkeys) return false; key1 = td->key1 - 1; key2 = td->key2 - 1; d2 = (char *)clonePstring(dest); if (!intoFields(d2)) { nzFree(d2); return false; } j = 0; u1 = initString(&u1len); s = (char *)source; while (1) { t = strpbrk(s, "|\n"); l1 = t - s; l2 = strlen(lineFields[j]); if (l1 != l2 || memcmp(s, lineFields[j], l1)) { if (j == key1 || j == key2) { setError(MSG_DBChangeKey); goto abort; } if (td->types[j] == 'B') { setError(MSG_DBChangeBlob); goto abort; } if (td->types[j] == 'T') { setError(MSG_DBChangeText); goto abort; } if (*u1) stringAndString(&u1, &u1len, ", "); stringAndString(&u1, &u1len, td->cols[j]); stringAndString(&u1, &u1len, " = "); pushQuoted(&u1, &u1len, lineFields[j], j); } if (*t == '\n') break; s = t + 1; ++j; } wherekeys = keysQuoted(); sql_exclist(insupdExceptions); sql_exec("update %s set %s %s", td->name, u1, wherekeys); nzFree(wherekeys); if (!insupdError(2, 1)) goto abort; nzFree(d2); nzFree(u1); return true; abort: nzFree(d2); nzFree(u1); return false; } /* sqlUpdateRow */ bool sqlAddRows(int ln) { char *u1, *u2; /* pieces of the insert statement */ char *u3; /* line with pipes */ char *unld, *s; int u1len, u2len, u3len; int j, l, nkeys; double dv; char inp[256]; bool rc; if (!setTable()) return false; nkeys = keyCountCheck(); while (1) { u1 = initString(&u1len); u2 = initString(&u2len); u3 = initString(&u3len); for (j = 0; j < td->ncols; ++j) { reenter: if (strchr("BT", td->types[j])) continue; printf("%s: ", td->cols[j]); fflush(stdout); if (!fgets(inp, sizeof(inp), stdin)) { puts("EOF"); ebClose(1); } l = strlen(inp); if (l && inp[l - 1] == '\n') inp[--l] = 0; if (stringEqual(inp, ".")) { nzFree(u1); nzFree(u2); nzFree(u3); return true; } if (inp[0] == 0) { /* I thought it was a good idea to prevent nulls from going into not-null * columns, but then I remembered not null default value, * where the database converts null into something real. * I want to allow this. */ goto goodfield; } /* verify the integrity of the entered field */ if (strchr(inp, '|')) { puts("please, no pipes in the data"); goto reenter; } switch (td->types[j]) { case 'N': s = inp; if (*s == '-') ++s; if (stringIsNum(s) < 0) { puts("number expected"); goto reenter; } break; case 'F': if (!stringIsFloat(inp, &dv)) { puts("decimal number expected"); goto reenter; } break; case 'C': if (strlen(inp) > 1) { puts("one character expected"); goto reenter; } break; case 'D': if (stringDate(inp, false) < 0) { puts("date expected"); goto reenter; } break; case 'I': if (stringTime(inp) < 0) { puts("time expected"); goto reenter; } break; } goodfield: /* turn 0 into next serial number */ if (j == td->key1 - 1 && td->types[j] == 'N' && stringEqual(inp, "0")) { int nextkey = sql_selectOne("select max(%s) from %s", td->cols[j], td->name); if (isnull(nextkey)) { i_puts(MSG_DBNextSerial); goto reenter; } sprintf(inp, "%d", nextkey + 1); } if (*u1) stringAndChar(&u1, &u1len, ','); stringAndString(&u1, &u1len, td->cols[j]); if (*u2) stringAndChar(&u2, &u2len, ','); pushQuoted(&u2, &u2len, inp, j); stringAndString(&u3, &u3len, inp); stringAndChar(&u3, &u3len, '|'); } sql_exclist(insupdExceptions); sql_exec("insert into %s (%s) values (%s)", td->name, u1, u2); nzFree(u1); nzFree(u2); if (!insupdError(1, 1)) { nzFree(u3); printf("Error: "); showError(); continue; } #if 0 /* Fetch the row just entered. */ /* Don't know how to do this without rowid. */ rowid = rv_lastRowid; buildSelectClause(); sql_select("%s where rowid = %d", scl, rowid, 0); nzFree(scl); unld = sql_mkunld('|'); l = strlen(unld); unld[l - 1] = '\n'; /* overwrite the last pipe */ #else unld = u3; l = strlen(unld); unld[l - 1] = '\n'; /* overwrite the last pipe */ #endif rc = addTextToBuffer((pst) unld, l, ln, false); nzFree(u3); if (!rc) return false; ++ln; } /* This pointis not reached; make the compilerhappy */ return true; } /* sqlAddRows */ /********************************************************************* run the analog of /bin/comm on two open cursors, rather than two Unix files. This assumes a common unique key that we use to sync up the rows. The cursors should be sorted by this key. *********************************************************************/ static void cursor_comm(const char *stmt1, const char *stmt2, /* the two select statements */ const char *orderby, /* which fetched column is the unique key */ fnptr f, /* call this function for differences */ char delim) { /* sql_mkunld() delimiter, or call mkinsupd if delim = 0 */ short cid1, cid2; /* the cursor ID numbers */ char *line1, *line2, *s; /* the two fetched rows */ void *blob1, *blob2; /* one blob per table */ int blob1size, blob2size; bool eof1, eof2, get1, get2; int sortval1, sortval2; char sortstring1[80], sortstring2[80]; int sortcol; char sorttype; int passkey1, passkey2; static const char sortnull[] = "cursor_comm, sortval%d is null"; static const char sortlong[] = "cursor_comm cannot key on strings longer than %d"; static const char noblob[] = "sorry, cursor_comm cannot handle blobs yet"; cid1 = sql_prepOpen(stmt1); cid2 = sql_prepOpen(stmt2); sortcol = findColByName(orderby); sorttype = rv_type[sortcol]; if (charInList("NDIS", sorttype) < 0) errorPrint("2cursor_com(), column %s has bad type %c", orderby, sorttype); if (sorttype == 'S') passkey1 = (int)sortstring1, passkey2 = (int)sortstring2; eof1 = eof2 = false; get1 = get2 = true; rv_blobFile = 0; /* in case the cursor has a blob */ line1 = line2 = 0; blob1 = blob2 = 0; while (true) { if (get1) { /* fetch first row */ eof1 = !sql_fetchNext(cid1, 0); nzFree(line1); line1 = 0; nzFree(blob1); blob1 = 0; if (!eof1) { if (sorttype == 'S') { s = rv_data[sortcol].ptr; if (isnullstring(s)) errorPrint(sortnull, 1); if (strlen(s) >= sizeof(sortstring1)) errorPrint(sortlong, sizeof(sortstring1)); strcpy(sortstring1, s); } else { passkey1 = sortval1 = rv_data[sortcol].l; if (isnull(sortval1)) errorPrint(sortnull, 1); } line1 = cloneString(delim ? sql_mkunld(delim) : sql_mkinsupd()); if (rv_blobLoc) { blob1 = rv_blobLoc; blob1size = rv_blobSize; errorPrint(noblob); } } /* not eof */ } /* looking for first line */ if (get2) { /* fetch second row */ eof2 = !sql_fetchNext(cid2, 0); nzFree(line2); line2 = 0; nzFree(blob2); blob2 = 0; if (!eof2) { if (sorttype == 'S') { s = rv_data[sortcol].ptr; if (isnullstring(s)) errorPrint(sortnull, 2); if (strlen(s) >= sizeof(sortstring2)) errorPrint(sortlong, sizeof(sortstring2)); strcpy(sortstring2, rv_data[sortcol].ptr); } else { passkey2 = sortval2 = rv_data[sortcol].l; if (isnull(sortval2)) errorPrint(sortnull, 2); } line2 = cloneString(delim ? sql_mkunld(delim) : sql_mkinsupd()); if (rv_blobLoc) { blob2 = rv_blobLoc; blob2size = rv_blobSize; errorPrint(noblob); } } /* not eof */ } /* looking for second line */ if (eof1 & eof2) break; /* done */ get1 = get2 = false; /* in cid2, but not in cid1 */ if (eof1 || !eof2 && (sorttype == 'S' && strcmp(sortstring1, sortstring2) > 0 || sorttype != 'S' && sortval1 > sortval2)) { (*f) ('>', line1, line2, passkey2); get2 = true; continue; } /* in cid1, but not in cid2 */ if (eof2 || !eof1 && (sorttype == 'S' && strcmp(sortstring1, sortstring2) < 0 || sorttype != 'S' && sortval1 < sortval2)) { (*f) ('<', line1, line2, passkey1); get1 = true; continue; } /* insert case */ get1 = get2 = true; /* perhaps the lines are equal */ if (stringEqual(line1, line2)) continue; /* lines are different between the two cursors */ (*f) ('*', line1, line2, passkey2); } /* loop over parallel cursors */ nzFree(line1); nzFree(line2); nzFree(blob1); nzFree(blob2); sql_closeFree(cid1); sql_closeFree(cid2); } /* cursor_comm */ /********************************************************************* Sync up two tables, or corresponding sections of two tables. These are usually equischema tables in parallel databases or machines. This isn't used by edbrowse; it's just something I wrote, and I thought you might find it useful. It follows the C convention of copying the second argument to the first, like the string and memory functions, rather than the shell convention of copying (cp) the first argument to the second. Hey - why have one standard, when you can have two? *********************************************************************/ static const char *synctable; /* table being sync-ed */ static const char *synckeycol; /* key column */ static const char *sync_clause; /* additional clause, to sync only part of the table */ /* convert column name into column index */ int findColByName(const char *name) { int i; for (i = 0; rv_name[i][0]; ++i) if (stringEqual(name, rv_name[i])) break; if (!rv_name[i][0]) errorPrint ("2Column %s not found in the columns or aliases of your select statement", name); return i; } /* findColByName */ static int syncup_comm_fn(char action, char *line1, char *line2, int key) { switch (action) { case '<': /* delete */ sql_exec("delete from %s where %s = %d %0s", synctable, synckeycol, key, sync_clause); break; case '>': /* insert */ sql_exec("insert into %s values(%s)", synctable, line2); break; case '*': /* update */ sql_exec("update %s set * = (%s) where %s = %d %0s", synctable, line2, synckeycol, key, sync_clause); break; } /* switch */ return 0; } /* syncup_comm_fn */ /* make table1 look like table2 */ void syncup_table(const char *table1, const char *table2, /* the two tables */ const char *keycol, /* the key column */ const char *otherclause) { char stmt1[200], stmt2[200]; int len; synctable = table1; synckeycol = keycol; sync_clause = otherclause; len = strlen(table1); if ((int)strlen(table2) > len) len = strlen(table2); if (otherclause) len += strlen(otherclause); len += strlen(keycol); if (len + 30 > sizeof(stmt1)) errorPrint ("2constructed select statement in syncup_table() is too long"); if (otherclause) { skipWhite(&otherclause); if (strncmp(otherclause, "and ", 4) && strncmp(otherclause, "AND ", 4)) errorPrint ("2restricting clause in syncup_table() does not start with \"and\"."); sprintf(stmt1, "select * from %s where %s order by %s", table1, otherclause + 4, keycol); sprintf(stmt2, "select * from %s where %s order by %s", table2, otherclause + 4, keycol); } else { sprintf(stmt1, "select * from %s order by %s", table1, keycol); sprintf(stmt2, "select * from %s order by %s", table2, keycol); } cursor_comm(stmt1, stmt2, keycol, (fnptr) syncup_comm_fn, 0); } /* syncup_table */ int goSelect(int *startLine, char **rbuf) { int lineno = *startLine; pst line; char *cmd, *s; int cmdlen; int i, j, l, action, cid; bool rc; static const char *actionWords[] = { "select", "insert", "update", "delete", "execute", 0 }; static const int actionCodes[] = { MSG_Selected, MSG_Inserted, MSG_Updated, MSG_Deleted, MSG_ProcExec }; *rbuf = emptyString; /* Make sure first line begins with ] */ line = fetchLine(lineno, -1); if (!line || line[0] != ']') return -1; j = pstLength(line); cmd = initString(&cmdlen); stringAndBytes(&cmd, &cmdlen, line, j); cmd[0] = ' '; while (j == 1 || line[j - 2] != ';') { if (++lineno > cw->dol || !(line = fetchLine(lineno, -1))) { setError(MSG_UnterminatedSelect); nzFree(cmd); return 0; } if (line[0] == ']') { --lineno; break; } j = pstLength(line); stringAndBytes(&cmd, &cmdlen, line, j); } /* Try to infer action from the first word of the command. */ action = -1; s = cmd; skipWhite2(&s); for (i = 0; actionWords[i]; ++i) { l = strlen(actionWords[i]); if (memEqualCI(s, actionWords[i], l) && isspace(s[l])) { action = actionCodes[i]; break; } } if (!ebConnect()) { nzFree(cmd); return 0; } rv_blobFile = 0; if (action == MSG_Selected) { cid = sql_prepOpen(cmd); nzFree(cmd); if (cid < 0) return 0; grabrows: *startLine = lineno; rc = rowsIntoBuffer(cid, rv_type, rbuf, &j); printrows: printf("%d ", j); i_printf(j == 1 ? MSG_Row : MSG_Rows); printf(" "); i_printf(action); nl(); return rc; } if (action == MSG_ProcExec) { cid = sql_prepOpen(cmd); nzFree(cmd); if (cid < 0) return 0; if (rv_numRets) { action = MSG_Selected; goto grabrows; } sql_closeFree(cid); *startLine = lineno; i_puts(MSG_ProcExec); return 1; } /* Don't know what kind of sql command this is. */ /* Run it anyways. */ rc = sql_execNF(cmd); nzFree(cmd); j = rv_lastNrows; if (rc) *startLine = lineno; if (action >= MSG_Selected && action <= MSG_Deleted && (j || rc)) goto printrows; if (rc) i_puts(MSG_OK); return rc; } /* goSelect */ edbrowse-3.6.0.1/src/PaxHeaders.22102/dbodbc.c0000644000000000000000000000006212637565441015403 xustar0020 atime=1451158305 30 ctime=1451409178.149337216 edbrowse-3.6.0.1/src/dbodbc.c0000644000000000000000000013626512637565441015671 0ustar00rootroot00000000000000/********************************************************************* odbc.c: C-level interface to SQL. This is a layer above ODBC, since ODBC is often difficult to use, especially for new programmers. Most SQL queries are relatively simple, whence the ODBC API is overkill. Why mess with statement handles and parameter bindings when you can write: sql_select("select this, that from table1, table2 where keycolumn = %d", 27, &this, &that); Note that this API works within the context of our own C programming environment. Note that dbapi.h does NOT include the ODBC system header files. That would violate the spirit of this layer, which attempts to sheild the application from the details of ODBC. If the application needed to see anything in those header files, we would be doing something wrong. *********************************************************************/ #ifdef _WIN32 #include // also includes // needed for sql.h whihc uses say HWND, DWORD, etc... #endif #include /* ODBC header files */ #include #include "eb.h" #include "dbapi.h" enum { DRIVER_NONE, DRIVER_SQLITE, DRIVER_MYSQL, DRIVER_POSTGRESQL, DRIVER_INFORMIX, DRIVER_TDS, DRIVER_ORACLE, DRIVER_DB2, }; static int current_driver; /* Some drivers, Microsoft in particular, * provide no information until you actually run the query. * Prepare is not enough. * The openfirst variable tells us whether we are running in that mode. */ static bool openfirst = false; #define SQL_MONEY 100 /********************************************************************* The status variable rc holds the return code from an ODBC call. This is then used by the function errorTrap() below. If rc != 0, errorTrap() prints a message: the generic type of error from our translation, and the message it gets from odbc. It may skip this however, if you have trapped for this type of error. This lets the application print a message that is shorter, and easier to understand, and has the potential to be internationalized. In this case the calling routine should clean up as best it can and return. *********************************************************************/ /* characteristics of the current ODBC driver */ static short odbc_version; static long cursors_under_commit, cursors_under_rollback; static long getdata_opts; static long bookmarkBits; static SQLHENV henv = SQL_NULL_HENV; /* environment handle */ static SQLHDBC hdbc = SQL_NULL_HDBC; /* identify connect session */ static SQLHSTMT hstmt = SQL_NULL_HSTMT; /* current statement */ static const char *stmt_text = 0; /* text of the SQL statement */ static SQLRETURN rc; static const short *exclist; /* list of error codes trapped by the application */ static short translevel; static bool badtrans; /* Through globals, make error info available to the application. */ int rv_lastStatus, rv_stmtOffset; long rv_vendorStatus; char *rv_badToken; static void debugStatement(void) { if (!stmt_text) return; if (sql_debug) appendFileNF(sql_debuglog, stmt_text); if (sql_debug2) puts(stmt_text); } /* debugStatement */ /* Append the SQL statement to the debug log. This is not strictly necessary * if sql_debug is set, since the statement has already been appended. */ static void showStatement(void) { if (!stmt_text) return; if (!sql_debug) appendFileNF(sql_debuglog, stmt_text); } /* showStatement */ /* application sets the exception list */ void sql_exclist(const short *list) { exclist = list; } void sql_exception(int errnum) { static short list[2]; list[0] = errnum; exclist = list; } /* sql_exception */ /* map ODBC errors to our own exception codes, as defined in dbapi.h. */ static const struct ERRORMAP { const char odbc[6]; short excno; } errormap[] = { { "00000", 0}, { "S1001", EXCRESOURCE}, { "S1009", EXCARGUMENT}, { "S1012", EXCARGUMENT}, { "S1090", EXCARGUMENT}, { "01S02", EXCUNSUPPORTED}, { "S1096", EXCUNSUPPORTED}, { "28000", EXCARGUMENT}, { "S1010", EXCACTIVE}, { "08002", EXCACTIVE}, { "08001", EXCNOCONNECT}, { "08003", EXCNOCONNECT}, { "08007", EXCMANAGETRANS}, { "25000", EXCMANAGETRANS}, { "08004", EXCNOCONNECT}, { "IM003", EXCRESOURCE}, { "IM004", EXCRESOURCE}, { "IM005", EXCRESOURCE}, { "IM009", EXCRESOURCE}, { "IM006", EXCUNSUPPORTED}, { "S1092", EXCUNSUPPORTED}, { "S1C00", EXCUNSUPPORTED}, { "08S01", EXCREMOTE}, { "IM001", EXCUNSUPPORTED}, { "IM002", EXCNODB}, { "S1T00", EXCTIMEOUT}, { "24000", EXCNOCURSOR}, { "34000", EXCNOCURSOR}, { "S1011", EXCACTIVE}, { "IM013", EXCTRACE}, { "21S01", EXCAMBCOLUMN}, { "21S02", EXCAMBCOLUMN}, { "22003", EXCTRUNCATE}, { "22005", EXCCONVERT}, { "22008", EXCCONVERT}, { "22012", EXCCONVERT}, { "37000", EXCSYNTAX}, { "42000", EXCPERMISSION}, { "S0001", EXCDUPTABLE}, { "S0002", EXCNOTABLE}, { "S0011", EXCDUPINDEX}, { "S0012", EXCNOINDEX}, { "S0021", EXCDUPCOLUMN}, { "S0022", EXCNOCOLUMN}, { "S1008", EXCINTERRUPT}, { "01004", EXCTRUNCATE}, { "01006", EXCPERMISSION}, { "01S03", EXCNOROW}, { "01S04", EXCMANYROW}, { "07001", EXCARGUMENT}, { "07S01", EXCARGUMENT}, { "07006", EXCCONVERT}, { "22002", EXCARGUMENT}, { "S1002", EXCARGUMENT}, { "S1003", EXCARGUMENT}, { "23000", EXCCHECK}, { "40001", EXCDEADLOCK}, { "S1105", EXCARGUMENT}, { "S1106", EXCUNSUPPORTED}, { "S1109", EXCNOROW}, { "01S06", EXCNOROW}, { "", 0} }; /* ends of list */ static int errTranslate(const char *code) { const struct ERRORMAP *e; for (e = errormap; e->odbc[0]; ++e) { if (stringEqual(e->odbc, code)) return e->excno; } return EXCSQLMISC; } /* errTranslate */ static char errorText[200]; static bool errorTrap(const char *cxerr) { short i, waste; char errcodes[6]; bool firstError, errorFound; /* innocent until proven guilty */ rv_lastStatus = 0; rv_vendorStatus = 0; rv_stmtOffset = 0; rv_badToken = 0; if (!rc) return false; /* no problem */ /* log the SQL statement that elicitted the error */ showStatement(); if (rc == SQL_INVALID_HANDLE) errorPrint ("@ODBC fails to recognize one of the handles (env, connect, stmt)"); /* get error info from ODBC */ firstError = true; errorFound = false; while (true) { rc = SQLError(henv, hdbc, hstmt, errcodes, &rv_vendorStatus, errorText, sizeof(errorText), &waste); if (rc == SQL_NO_DATA) { if (firstError) { printf ("ODBC command failed, but SQLError() provided no additional information\n"); return true; } return errorFound; } /* Skip past the ERROR-IN-ROW errors. */ if (stringEqual(errcodes, "01S01")) continue; firstError = false; if (cxerr && stringEqual(cxerr, errcodes)) continue; if (errorFound) continue; errorFound = true; rv_lastStatus = errTranslate(errcodes); /* Don't know how to get statement ofset or invalid token from ODBC. /* I can get them from Informix; see dbinfx.ec */ /* if the application didn't trap for this exception, blow up! */ if (exclist) { for (i = 0; exclist[i]; ++i) if (exclist[i] == rv_lastStatus) break; if (exclist[i]) { exclist = 0; /* we've spent that exception */ continue; } } printf("ODBC error %s, %s, driver %s\n", errcodes, sqlErrorList[rv_lastStatus], errorText); setError(MSG_DBUnexpected, rv_vendorStatus); } } /* errorTrap */ static void newStatement(void) { rc = SQLAllocStmt(hdbc, &hstmt); if (rc) errorPrint("@could not alloc singleton ODBC statement handle"); } /* newStatement */ /********************************************************************* The OCURS structure given below maintains an open SQL cursor. A static array of these structures allows multiple cursors to be opened simultaneously. *********************************************************************/ static struct OCURS { SQLHSTMT hstmt; long rownum; char rv_type[NUMRETS]; short cid; /* cursor ID */ char flag; char numrets; } ocurs[NUMCURSORS]; /* values for struct OCURS.flag */ #define CURSOR_NONE 0 #define CURSOR_PREPARED 1 #define CURSOR_OPENED 2 /* find a free cursor structure */ static struct OCURS *findNewCursor(void) { struct OCURS *o; short i; for (o = ocurs, i = 0; i < NUMCURSORS; ++i, ++o) { if (o->flag != CURSOR_NONE) continue; o->cid = 6000 + i; rc = SQLAllocStmt(hdbc, &o->hstmt); if (rc) errorPrint ("@could not alloc ODBC statement handle for cursor %d", o->cid); return o; } errorPrint("2more than %d cursors opend concurrently", NUMCURSORS); return 0; /* make the compiler happy */ } /* findNewCursor */ /* dereference an existing cursor */ static struct OCURS *findCursor(int cid) { struct OCURS *o; if (cid < 6000 || cid >= 6000 + NUMCURSORS) errorPrint("2cursor number %d is out of range", cid); cid -= 6000; o = ocurs + cid; if (o->flag == CURSOR_NONE) errorPrint("2cursor %d is not currently active", cid + 6000); rv_numRets = o->numrets; memcpy(rv_type, o->rv_type, NUMRETS); return o; } /* findCursor */ /* This doesn't close/free anything; it simply puts variables in an initial state. */ /* part of the disconnect() procedure */ static void clearAllCursors(void) { short i; for (i = 0; i < NUMCURSORS; ++i) { ocurs[i].flag = CURSOR_NONE; ocurs[i].hstmt = SQL_NULL_HSTMT; } } /* clearAllCursors */ /********************************************************************* Connect and disconect to SQL databases. *********************************************************************/ /* disconnect from the database. Return true if * an error occurs that is trapped by the application. */ static bool disconnect(void) { stmt_text = 0; hstmt = SQL_NULL_HSTMT; if (!sql_database) return false; /* already disconnected */ stmt_text = "disconnect"; debugStatement(); rc = SQLDisconnect(hdbc); if (errorTrap(0)) return true; clearAllCursors(); /* those handles are freed as well */ translevel = 0; sql_database = 0; return false; } /* disconnect */ /* API level disconnect */ void sql_disconnect(void) { disconnect(); exclist = 0; } /* sql_disconnect */ void sql_connect(const char *db, const char *login, const char *pw) { short waste; char constring[200]; char outstring[200]; char drivername[40]; char *s; if (isnullstring(db)) errorPrint ("2sql_connect receives no data source, check your edbrowse config file"); if (debugLevel >= 1) i_printf(MSG_DBConnecting, db); /* first disconnect the old one */ if (disconnect()) return; /* initial call to sql_connect sets up ODBC */ if (henv == SQL_NULL_HENV) { char verstring[6]; /* Allocate environment and connection handles */ /* these two handles are never freed */ rc = SQLAllocEnv(&henv); if (rc) errorPrint("@could not alloc ODBC environment handle"); rc = SQLAllocConnect(henv, &hdbc); if (rc) errorPrint("@could not alloc ODBC connection handle"); /* Establish the ODBC major version number. * Course the call to make this determination doesn't exist * prior to version 2.0. */ odbc_version = 1; rc = SQLGetInfo(hdbc, SQL_DRIVER_ODBC_VER, verstring, 6, &waste); if (!rc) { verstring[2] = 0; odbc_version = atoi(verstring); } } /* connect to the database */ sprintf(constring, "DSN=%s", db); if (login) { s = constring + strlen(constring); sprintf(s, ";UID=%s", login); } if (pw) { s = constring + strlen(constring); sprintf(s, ";PWD=%s", pw); } stmt_text = constring; debugStatement(); rc = SQLDriverConnect(hdbc, NULL, constring, SQL_NTS, outstring, sizeof(outstring), &waste, SQL_DRIVER_NOPROMPT); if (errorTrap(0)) return; sql_database = db; exclist = 0; /* Set the persistent connect/statement options. * Note that some of these merely reassert the default, * but it's good documentation to spell it out here. */ stmt_text = "noscan on"; rc = SQLSetConnectOption(hdbc, SQL_NOSCAN, SQL_NOSCAN_ON); stmt_text = "repeatable read"; rc = SQLSetConnectOption(hdbc, SQL_TXN_ISOLATION, SQL_TXN_REPEATABLE_READ); /* fail */ stmt_text = "rowset size"; rc = SQLSetConnectOption(hdbc, SQL_ROWSET_SIZE, 1); stmt_text = "login timeout"; rc = SQLSetConnectOption(hdbc, SQL_LOGIN_TIMEOUT, 15); /* fail */ stmt_text = "query timeout"; rc = SQLSetConnectOption(hdbc, SQL_QUERY_TIMEOUT, 0); /* fail */ stmt_text = "async disable"; rc = SQLSetConnectOption(hdbc, SQL_ASYNC_ENABLE, SQL_ASYNC_ENABLE_OFF); /* fail */ stmt_text = "autocommit"; rc = SQLSetConnectOption(hdbc, SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_ON); stmt_text = "cursor forward"; rc = SQLSetConnectOption(hdbc, SQL_CURSOR_TYPE, SQL_CURSOR_FORWARD_ONLY); stmt_text = "concurrent reads"; rc = SQLSetConnectOption(hdbc, SQL_CONCURRENCY, SQL_CONCUR_READ_ONLY); stmt_text = "use driver"; rc = SQLSetConnectOption(hdbc, SQL_ODBC_CURSORS, SQL_CUR_USE_DRIVER); /* fail */ stmt_text = "no bookmarks"; rc = SQLSetConnectOption(hdbc, SQL_USE_BOOKMARKS, SQL_UB_OFF); /* fail */ /* this call is only necessary if SQL_NULL_HSTMT != 0 */ clearAllCursors(); /* set defaults, in case the GetInfo command fails */ cursors_under_commit = cursors_under_rollback = SQL_CB_DELETE; SQLGetInfo(hdbc, SQL_CURSOR_COMMIT_BEHAVIOR, &cursors_under_commit, 4, &waste); SQLGetInfo(hdbc, SQL_CURSOR_ROLLBACK_BEHAVIOR, &cursors_under_rollback, 4, &waste); getdata_opts = 0; SQLGetInfo(hdbc, SQL_GETDATA_EXTENSIONS, &getdata_opts, 4, &waste); bookmarkBits = false; SQLGetInfo(hdbc, SQL_BOOKMARK_PERSISTENCE, &bookmarkBits, 4, &waste); exclist = 0; /* Time to find out what the driver is, so we can have driver specific tweaks. */ SQLGetInfo(hdbc, SQL_DRIVER_NAME, drivername, sizeof(drivername), &waste); current_driver = DRIVER_NONE; if (stringEqual(drivername, "libsqliteodbc.so") || stringEqual(drivername, "sqlite3odbc.so")) current_driver = DRIVER_SQLITE; if (stringEqual(drivername, "libmyodbc.so")) current_driver = DRIVER_MYSQL; if (stringEqual(drivername, "libodbcpsql.so")) current_driver = DRIVER_POSTGRESQL; if (stringEqual(drivername, "iclis09b.so")) current_driver = DRIVER_INFORMIX; if (stringEqual(drivername, "libtdsodbc.so")) { current_driver = DRIVER_TDS; openfirst = true; } if (sql_debug) { if (current_driver) appendFile(sql_debuglog, "driver is %d", current_driver); else appendFile(sql_debuglog, "driver string is %s", drivername); } exclist = 0; } /* sql_connect */ /* make sure we're connected to a database */ static void checkConnect(void) { if (!sql_database) errorPrint("2SQL command issued, but no database selected"); } /* checkConnect */ /********************************************************************* Begin, commit, and abort transactions. Remember that a completed transaction might blow away all cursors. SQL does not permit nested transactions; this API does, to a limited degree. An inner transaction cannot fail while an outer one succeeds; that would require SQL support, which is not forthcoming. However, as long as all transactions succeed, or the outer most fails, everything works properly. The static variable transLevel holds the number of nested transactions. *********************************************************************/ /* begin a transaction */ void sql_begTrans(void) { checkConnect(); stmt_text = 0; hstmt = SQL_NULL_HSTMT; rv_lastStatus = 0; /* might never call errorTrap(0) */ /* count the nesting level of transactions. */ if (!translevel) { badtrans = false; stmt_text = "begin work"; debugStatement(); rc = SQLSetConnectOption(hdbc, SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF); if (errorTrap(0)) return; } ++translevel; exclist = 0; } /* sql_begTrans */ /* end a transaction */ static void endTrans(bool commit) { checkConnect(); stmt_text = 0; hstmt = SQL_NULL_HSTMT; rv_lastStatus = 0; /* might never call errorTrap(0) */ if (!translevel) errorPrint("2end transaction without a matching begTrans()"); --translevel; if (commit) { if (badtrans) errorPrint ("2Cannot commit a transaction around an aborted transaction"); if (!translevel) { stmt_text = "commit work"; debugStatement(); rc = SQLTransact(SQL_NULL_HENV, hdbc, SQL_COMMIT); if (rc) ++translevel; errorTrap(0); } } else { /* success or failure */ badtrans = true; if (!translevel) { /* bottom level */ stmt_text = "rollback work"; debugStatement(); rc = SQLTransact(SQL_NULL_HENV, hdbc, SQL_ROLLBACK); if (rc) ++translevel; errorTrap(0); badtrans = false; } } /* success or failure */ if (!translevel) { struct OCURS *o; short i, newstate; /* change the state of all cursors, if necessary */ newstate = CURSOR_OPENED; if (commit) { if (cursors_under_commit == SQL_CB_DELETE) newstate = CURSOR_NONE; if (cursors_under_commit == SQL_CB_CLOSE) newstate = CURSOR_PREPARED; } else { if (cursors_under_rollback == SQL_CB_DELETE) newstate = CURSOR_NONE; if (cursors_under_rollback == SQL_CB_CLOSE) newstate = CURSOR_PREPARED; } for (i = 0; i < NUMCURSORS; ++i) { o = ocurs + i; if (o->flag <= newstate) continue; o->flag = newstate; if (newstate > CURSOR_NONE) continue; if (o->hstmt == SQL_NULL_HSTMT) continue; SQLFreeHandle(SQL_HANDLE_STMT, o->hstmt); o->hstmt = SQL_NULL_HSTMT; } /* back to singleton transactions */ rc = SQLSetConnectOption(hdbc, SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_ON); errorTrap(0); } exclist = 0; } /* endTrans */ void sql_commitWork(void) { endTrans(true); } void sql_rollbackWork(void) { endTrans(false); } void sql_deferConstraints(void) { if (!translevel) errorPrint ("2Cannot defer constraints unless inside a transaction"); stmt_text = "defere constraints"; debugStatement(); /* is there a way to do this through ODBC? */ newStatement(); rc = SQLExecDirect(hstmt, (char *)stmt_text, SQL_NTS); errorTrap(0); SQLFreeHandle(SQL_HANDLE_STMT, hstmt); exclist = 0; } /* sql_deferConstraints */ /********************************************************************* Blob management routines, a somewhat awkward interface. Global variables tell SQL where to unload the next fetched blob: either a file (truncate or append) or an allocated chunk of memory. This assumes each fetch or select statement retrieves at most one blob. Since there is no %blob directive in lineFormat(), one cannot simply slip a blob in with the rest of the data as a row is updated or inserted. Instead the row must be created first, then the blob is entered separately, using blobInsert(). This means every blob column must permit nulls, at least within the schema. Also, what use to be an atomic insert might become a multi-statement transaction if data integrity is important. Future versions of our line formatting software may support a %blob directive, which makes sense only when the formatted string is destined for SQL. *********************************************************************/ static char blobbuf[100000]; void sql_blobInsert(const char *tabname, const char *colname, int rowid, const char *filename, void *offset, int length) { char blobcmd[100]; SQLINTEGER output_length; bool isfile; int fd; /* basic sanity checks */ checkConnect(); if (isnullstring(tabname)) errorPrint("2blobInsert, null table name"); if (isnullstring(colname)) errorPrint("2blobInsert, null column name"); if (rowid <= 0) errorPrint("2invalid rowid in blobInsert"); if (length < 0) errorPrint("2invalid length in blobInsert"); if (strlen(tabname) + strlen(colname) + 42 >= sizeof(blobcmd)) errorPrint("@internal blobInsert command too long"); isfile = true; if (isnullstring(filename)) { isfile = false; if (!offset) errorPrint ("2blobInsert is given null filename and null buffer"); } else { offset = blobbuf; fd = eopen(filename, O_RDONLY | O_BINARY, 0); length = fileSizeByHandle(fd); if (length == 0) { isfile = false; close(fd); } } /* set up the blob insert command, using one host variable */ sprintf(blobcmd, "update %s set %s = %s where rowid = %d", tabname, colname, (length ? "?" : "NULL"), rowid); stmt_text = blobcmd; debugStatement(); newStatement(); rv_lastNrows = 0; output_length = length; rc = SQL_SUCCESS; if (isfile) { output_length = SQL_LEN_DATA_AT_EXEC(length); rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARCHAR, length, 0, blobcmd, length, &output_length); if (rc) close(fd); } else if (length) { rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARCHAR, length, 0, offset, length, &output_length); } if (errorTrap(0)) { SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return; } rc = SQLExecDirect(hstmt, blobcmd, SQL_NTS); SQLRowCount(hstmt, &rv_lastNrows); if (isfile) { if (rc != SQL_NEED_DATA) { close(fd); if (rc == SQL_SUCCESS) errorPrint ("@blobInsert expected SQL_NEED_DATA"); errorTrap(0); SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return; } output_length = 0; rc = SQLParamData(hstmt, (void **)&output_length); if ((char *)output_length != blobcmd) { close(fd); errorPrint("2blobInsert got bad key from SQLParamData"); } lseek(fd, 0L, 0); while (length) { int n = length; if (n > sizeof(blobbuf)) n = sizeof(blobbuf); if (read(fd, blobbuf, n) != n) { close(fd); errorPrint("2cannot read file %s, errno %d", filename, errno); } length -= n; rc = SQLPutData(hstmt, blobbuf, n); if (rc) { close(fd); errorTrap(0); SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return; } } /* loop reading the file */ close(fd); /* since there are no more exec-time parameters, * this call completes the execution of the SQL statement. */ rc = SQLParamData(hstmt, (void **)&output_length); } if (errorTrap(0)) { SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return; } if (sql_debug) appendFile(sql_debuglog, "%d rows affected", rv_lastNrows); if (sql_debug2) printf("%ld rows affected\n", rv_lastNrows); SQLFreeHandle(SQL_HANDLE_STMT, hstmt); exclist = 0; } /* sql_blobInsert */ /********************************************************************* Pass back a variable number of returns from an SQL data gathering function such as select() or fetch(). This uses SQLGetData(), assuming a select or fetch has been performed. The prefix rv_ on the following global variables indicates returned values. *********************************************************************/ /* Where to stash the retrieved values */ static va_list sqlargs; /* Temp area to read the values as strings */ static char retstring[NUMRETS][STRINGLEN + 4]; static bool everything_null; static void retsFromOdbc(void) { void *q, *q1; int i, l; int fd, flags; bool yearfirst, indata = false; long dt; /* temporarily hold date or time */ char *s; short c_type; /* C data type */ long input_length, output_length; char tbuf[20]; /* temp buf, for dates and times */ double fmoney; /* float version of money */ int blobcount = 0; bool fbc = fetchBlobColumns; /* no blobs unless proven otherwise */ rv_blobLoc = 0; rv_blobSize = nullint; if (!rv_numRets) errorPrint("@calling retsFromOdbc() with no returns pending"); stmt_text = "retsFromOdbc"; debugStatement(); /* count the blobs */ if (fbc) for (i = 0; i < rv_numRets; ++i) if (rv_type[i] == 'B' || rv_type[i] == 'T') ++blobcount; if (blobcount > 1) { i_puts(MSG_DBManyBlobs); fbc = false; } for (i = 0; i < rv_numRets; ++i) { if (!indata) { q = va_arg(sqlargs, void *); if (!q) { if (i) break; indata = true; } } if (indata) { if (rv_type[i] == 'S') { q = retstring[i]; rv_data[i].ptr = q; } else q = rv_data + i; } if ((int)q < 1000 && (int)q > -1000) errorPrint("2retsFromOdbc, pointer too close to 0"); q1 = q; tbuf[0] = 0; output_length = 0; switch (rv_type[i]) { case 'S': c_type = SQL_C_CHAR; input_length = STRINGLEN + 1; *(char *)q = 0; /* null */ break; case 'C': c_type = SQL_C_CHAR; input_length = 2; *(char *)q = 0; /* null */ q1 = tbuf; break; case 'F': c_type = SQL_C_DOUBLE; input_length = 8; *(double *)q = nullfloat; /* null */ break; case 'N': c_type = SQL_C_SLONG; input_length = 4; *(long *)q = nullint; /* null */ break; case 'M': c_type = SQL_C_DOUBLE; input_length = 8; fmoney = nullfloat; q1 = &fmoney; break; case 'D': c_type = SQL_C_CHAR; input_length = 11; q1 = tbuf; break; case 'I': c_type = SQL_C_CHAR; input_length = 10; q1 = tbuf; break; case 'B': case 'T': c_type = SQL_C_BINARY; input_length = sizeof(blobbuf); q1 = blobbuf; *(long *)q = nullint; break; default: errorPrint("@retsFromOdbc, rv_type[%d] = %c", i, rv_type[i]); } /* switch */ if (everything_null || c_type == SQL_C_BINARY && !fbc) { rc = SQL_SUCCESS; output_length = SQL_NULL_DATA; } else { rc = SQLGetData(hstmt, (ushort) (i + 1), c_type, q1, input_length, &output_length); /* we'll deal with blob overflow later */ if (rc == SQL_SUCCESS_WITH_INFO && c_type == SQL_C_BINARY && output_length > sizeof(blobbuf)) rc = SQL_SUCCESS; if (errorTrap(0)) break; if (output_length == SQL_NO_TOTAL) errorPrint ("@retsFromOdbc cannot get size of data for column %d", i + 1); } /* Postprocess the return values. */ /* For instance, turn string dates into our own 4-byte format. */ s = tbuf; trimWhite(s); switch (rv_type[i]) { case 'C': *(char *)q = tbuf[0]; break; case 'S': trimWhite(q); break; case 'D': yearfirst = false; if (s[4] == '-') yearfirst = true; dt = stringDate(s, yearfirst); if (dt < 0) errorPrint("@database holds invalid date %s", s); *(long *)q = dt; break; case 'I': /* thanks to stringTime(), this works for either hh:mm or hh:mm:ss */ if (s[0] == 0) *(long *)q = nullint; else { /* Note that Informix introduces a leading space, how about ODBC? */ leftClipString(s); if (s[1] == ':') shiftRight(s, '0'); dt = stringTime(s); if (dt < 0) errorPrint ("@database holds invalid time %s", s); *(long *)q = dt; } break; case 'M': if (fmoney == nullfloat) dt = nullint; else dt = fmoney * 100.0 + 0.5; *(long *)q = dt; break; case 'B': case 'T': if (output_length == SQL_NULL_DATA) break; /* note, 0 length blob is treated as a null blob */ if (output_length == 0) break; /* the size of the blob is returned, in an int. */ *(long *)q = output_length; rv_blobSize = output_length; if (isnullstring(rv_blobFile)) { /* the blob is always allocated; you have to free it! */ /* SQL doesn't null terminate its text blobs, but we do. */ rv_blobLoc = allocMem(output_length + 1); l = output_length; if (l > sizeof(blobbuf)) l = sizeof(blobbuf); memcpy(rv_blobLoc, blobbuf, l); if (l < output_length) { /* more to do */ long waste; rc = SQLGetData(hstmt, (ushort) (i + 1), c_type, (char *)rv_blobLoc + l, output_length - l, &waste); if (rc) { nzFree(rv_blobLoc); rv_blobLoc = 0; *(long *)q = nullint; errorTrap(0); goto breakloop; } /* error getting rest of blob */ } /* blob is larger than the buffer */ if (rv_type[i] == 'T') /* null terminate */ ((char *)rv_blobLoc)[output_length] = 0; break; } /* blob in memory */ /* at this point the blob is being dumped into a file */ flags = O_WRONLY | O_BINARY | O_CREAT | O_TRUNC; if (rv_blobAppend) flags = O_WRONLY | O_BINARY | O_CREAT | O_APPEND; fd = eopen(rv_blobFile, flags, 0666); rc = SQL_SUCCESS; while (true) { int outbytes; l = output_length; if (l > sizeof(blobbuf)) l = sizeof(blobbuf); outbytes = write(fd, blobbuf, l); if (outbytes < l) { close(fd); errorPrint ("2cannot write to file %s, errno %d", rv_blobFile, errno); } if (l == output_length) break; /* get the next chunk from ODBC */ rc = SQLGetData(hstmt, (ushort) (i + 1), c_type, q1, input_length, &output_length); if (rc == SQL_SUCCESS_WITH_INFO && output_length > input_length) rc = SQL_SUCCESS; /* data truncation error */ } close(fd); errorTrap(0); break; } /* switch */ } /* loop over returned elements */ breakloop: va_end(sqlargs); exclist = 0; } /* retsFromOdbc */ /* make sure we got one return value, and it is integer compatible */ /* The dots make the compiler happy. */ /* You must call this with args of 0,0 */ static long oneRetValue(void *pre_x, ...) { char coltype = rv_type[0]; char c; long n; double f; void **x = (void **)((char *)&pre_x + 4); va_end(sqlargs); if (rv_numRets != 1) errorPrint ("2SQL statement has %d return values, 1 value expected", rv_numRets); if (!strchr("MNFDICS", coltype)) errorPrint ("2SQL statement returns a value whose type is not compatible with a 4-byte integer"); va_start(sqlargs, pre_x); /* I'm not sure float to int really works. */ if (coltype == 'F') { *x = &f; retsFromOdbc(); n = f; } else if (coltype == 'S') { *x = retstring[0]; retsFromOdbc(); if (!stringIsNum(retstring[0])) errorPrint ("2SQL statement returns a string %s that cannot be converted into a 4-byte integer", retstring[0]); n = atoi(retstring[0]); } else if (coltype == 'C') { *x = &c; retsFromOdbc(); n = c; } else { *x = &n; retsFromOdbc(); } SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return n; } /* oneRetValue */ /********************************************************************* Prepare a formatted SQL statement. Gather the types and names of the fetched columns and make this information available to the rest of the C routines in this file, and to the application. Returns false if the prepare failed. *********************************************************************/ static bool prepareInternal(const char *stmt) { short i, nc, coltype, colscale, nullable, namelen; unsigned long colprec; bool blobpresent = false; checkConnect(); everything_null = true; if (isnullstring(stmt)) errorPrint("2null SQL statement"); stmt_text = stmt; debugStatement(); /* look for delete with no where clause */ skipWhite(&stmt); if (!strncmp(stmt, "delete", 6) || !strncmp(stmt, "update", 6)) /* delete or update */ if (!strstr(stmt, "where") && !strstr(stmt, "WHERE")) { showStatement(); setError(MSG_DBNoWhere); return false; } rv_numRets = 0; memset(rv_type, 0, NUMRETS); memset(rv_nullable, 0, NUMRETS); rv_lastNrows = 0; if (openfirst) rc = SQLExecDirect(hstmt, (char *)stmt, SQL_NTS); else rc = SQLPrepare(hstmt, (char *)stmt, SQL_NTS); if (errorTrap(0)) return false; /* gather column headings and types */ rc = SQLNumResultCols(hstmt, &nc); if (errorTrap(0)) return false; if (nc > NUMRETS) { showStatement(); errorPrint("2cannot select more than %d values", NUMRETS); } for (i = 0; i < nc; ++i) { rc = SQLDescribeCol(hstmt, (USHORT) (i + 1), rv_name[i], COLNAMELEN, &namelen, &coltype, &colprec, &colscale, &nullable); if (errorTrap("01004")) return false; /********************************************************************* The following is an Informix kludge, because intervals are not mapped back into SQL_TIME, as they should be. Don't create any varchar(24,0) columns. Also, ODBC hasn't got any money type. Under informix, it comes back as decimal. We never use decimal columns, so we can turn decimal back into money. However, some aggregate expressions also come back as decimal. Count(*) becomes decimal(15,0). So be careful. *********************************************************************/ if (current_driver == DRIVER_INFORMIX) { if (coltype == SQL_VARCHAR && colprec == 24 && colscale == 0) coltype = SQL_TIME; #if 0 if (coltype == SQL_DECIMAL && (colprec != 15 || colscale != 0)) coltype = SQL_MONEY; #endif } if (current_driver == DRIVER_SQLITE) { /* Every column looks like a text blob, but it is really a string. */ coltype = SQL_CHAR; colprec = STRINGLEN; } rv_nullable[i] = (nullable != SQL_NO_NULLS); switch (coltype) { case SQL_BIT: case SQL_TINYINT: case SQL_SMALLINT: case SQL_INTEGER: case SQL_BIGINT: case SQL_NUMERIC: rv_type[i] = 'N'; break; case SQL_TIMESTAMP: /* I don't know what to do with these; just make them strings. */ rv_type[i] = 'S'; break; case SQL_DATE: rv_type[i] = 'D'; break; case SQL_DOUBLE: case SQL_FLOAT: case SQL_DECIMAL: case SQL_REAL: rv_type[i] = 'F'; break; case SQL_TIME: rv_type[i] = 'I'; break; case SQL_CHAR: case SQL_VARCHAR: rv_type[i] = 'S'; if (colprec == 1) rv_type[i] = 'C'; if (colprec > STRINGLEN && sql_debug) { appendFile(sql_debuglog, "column %s exceeds %d characters", rv_name[i], STRINGLEN); } break; case SQL_LONGVARCHAR: case SQL_BINARY: case SQL_VARBINARY: case SQL_LONGVARBINARY: if (blobpresent) { showStatement(); errorPrint ("2Statement selects more than one blob column"); } blobpresent = true; rv_type[i] = (coltype == SQL_LONGVARCHAR ? 'T' : 'B'); break; case SQL_MONEY: rv_type[i] = 'M'; break; default: errorPrint("@Unknown sql datatype %d", coltype); } /* switch on type */ } /* loop over returns */ rv_numRets = nc; return true; } /* prepareInternal */ /********************************************************************* Run an SQL statement internally, and gather any fetched values. This statement stands alone; it fetches at most one row. You might simply know this, perhaps because of a unique constraint, or you might be running a stored procedure. For efficiency we do not look for a second row, so this is really like the "select first" construct that some databases support. A mode variable says whether execution or selection or both are allowed. Return true if data was successfully fetched. *********************************************************************/ static bool execInternal(const char *stmt, int mode) { bool notfound = false; newStatement(); if (!prepareInternal(stmt)) return false; /* error */ if (!rv_numRets) { if (!(mode & 1)) { showStatement(); errorPrint("2SQL select statement returns no values"); } notfound = true; } else { /* end no return values */ if (!(mode & 2)) { showStatement(); errorPrint("2SQL statement returns %d values", rv_numRets); } } if (!openfirst) rc = SQLExecute(hstmt); if (!rc) { /* statement succeeded */ /* fetch the data, or get a count of the rows affected */ if (rv_numRets) { stmt_text = "fetch"; debugStatement(); rc = SQLFetchScroll(hstmt, (ushort) SQL_FD_FETCH_NEXT, 1); if (rc == SQL_NO_DATA) { rc = SQL_SUCCESS; notfound = true; } else everything_null = false; } else { rc = SQLRowCount(hstmt, &rv_lastNrows); if (sql_debug) appendFile(sql_debuglog, "%d rows affected", rv_lastNrows); if (sql_debug2) printf("%ld rows affected\n", rv_lastNrows); } } if (errorTrap(0)) return false; if (!rv_numRets) return true; return !notfound; } /* execInternal */ /********************************************************************* Run individual select or execute statements, using the above internal routine. *********************************************************************/ /* execute a stand-alone statement with no % formatting of the string */ bool sql_execNF(const char *stmt) { bool ok = execInternal(stmt, 1); SQLFreeHandle(SQL_HANDLE_STMT, hstmt); exclist = 0; return ok; } /* sql_execNF */ /* execute a stand-alone statement with % formatting */ bool sql_exec(const char *stmt, ...) { bool ok; va_start(sqlargs, stmt); stmt = lineFormatStack(stmt, 0, &sqlargs); ok = execInternal(stmt, 1); SQLFreeHandle(SQL_HANDLE_STMT, hstmt); exclist = 0; va_end(sqlargs); return ok; } /* sql_exec */ /* run a select statement with % formatting */ /* return true if the row was found */ bool sql_select(const char *stmt, ...) { bool rowfound; va_start(sqlargs, stmt); stmt = lineFormatStack(stmt, 0, &sqlargs); rowfound = execInternal(stmt, 2); retsFromOdbc(); SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return rowfound; } /* sql_select */ /* run a select statement with no % formatting of the string */ bool sql_selectNF(const char *stmt, ...) { bool rowfound; va_start(sqlargs, stmt); rowfound = execInternal(stmt, 2); retsFromOdbc(); SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return rowfound; } /* sql_selectNF */ /* run a select statement with one return value */ int sql_selectOne(const char *stmt, ...) { bool rowfound; va_start(sqlargs, stmt); stmt = lineFormatStack(stmt, 0, &sqlargs); rowfound = execInternal(stmt, 2); if (!rowfound) { SQLFreeHandle(SQL_HANDLE_STMT, hstmt); exclist = 0; va_end(sqlargs); return nullint; } return oneRetValue(0, 0); } /* sql_selectOne */ /* run a stored procedure with no % formatting */ static bool sql_procGo(const char *stmt) { bool rowfound; char *s = allocMem(20 + strlen(stmt)); strcpy(s, "execute procedure "); strcat(s, stmt); rowfound = execInternal(s, 3); /* if execInternal doesn't return, we have a memory leak */ nzFree(s); return rowfound; } /* sql_procGo */ /* run a stored procedure */ bool sql_proc(const char *stmt, ...) { bool rowfound; va_start(sqlargs, stmt); stmt = lineFormatStack(stmt, 0, &sqlargs); rowfound = sql_procGo(stmt); retsFromOdbc(); SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return rowfound; } /* sql_proc */ /* run a stored procedure with one return */ int sql_procOne(const char *stmt, ...) { bool rowfound; va_start(sqlargs, stmt); stmt = lineFormatStack(stmt, 0, &sqlargs); rowfound = sql_procGo(stmt); if (!rowfound) { SQLFreeHandle(SQL_HANDLE_STMT, hstmt); exclist = 0; va_end(sqlargs); return 0; } return oneRetValue(0, 0); } /* sql_procOne */ /********************************************************************* Prepare, open, close, and free SQL cursors. *********************************************************************/ /* prepare a cursor; return the ID number of that cursor */ static int prepareCursor(const char *stmt, bool scrollflag) { struct OCURS *o = findNewCursor(); stmt = lineFormatStack(stmt, 0, &sqlargs); va_end(sqlargs); hstmt = o->hstmt; if (sql_debug) appendFile(sql_debuglog, "new cursor %d", o->cid); if (sql_debug2) printf("new cursor %d\n", o->cid); rc = SQLSetStmtOption(hstmt, SQL_CURSOR_TYPE, scrollflag ? SQL_CURSOR_STATIC : SQL_CURSOR_FORWARD_ONLY); if (errorTrap(0)) return -1; if (!prepareInternal(stmt)) return -1; o->numrets = rv_numRets; memcpy(o->rv_type, rv_type, NUMRETS); o->flag = (openfirst ? CURSOR_OPENED : CURSOR_PREPARED); o->rownum = 0; return o->cid; } /* prepareCursor */ int sql_prepare(const char *stmt, ...) { int n; va_start(sqlargs, stmt); n = prepareCursor(stmt, false); exclist = 0; if (n < 0) SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return n; } /* sql_prepare */ int sql_prepareScrolling(const char *stmt, ...) { int n; va_start(sqlargs, stmt); n = prepareCursor(stmt, true); exclist = 0; if (n < 0) SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return n; } /* sql_prepareScrolling */ void sql_open(int cid) { short i; struct OCURS *o = findCursor(cid); if (o->flag == CURSOR_OPENED) return; /* already open */ if (!o->numrets) errorPrint("2cursor is being opened with no returns"); stmt_text = "open"; debugStatement(); hstmt = o->hstmt; rc = SQLExecute(hstmt); if (errorTrap(0)) return; o->flag = CURSOR_OPENED; o->rownum = 0; exclist = 0; } /* sql_open */ int sql_prepOpen(const char *stmt, ...) { int n; struct OCURS *o; va_start(sqlargs, stmt); n = prepareCursor(stmt, false); if (n < 0) { SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return n; } if (openfirst) goto done; o = findCursor(n); sql_open(n); if (rv_lastStatus) { o->flag = CURSOR_NONE; /* back to square 0 */ SQLFreeHandle(SQL_HANDLE_STMT, o->hstmt); o->hstmt = SQL_NULL_HSTMT; rv_numRets = 0; memset(rv_type, 0, sizeof(rv_type)); n = -1; } done: exclist = 0; return n; } /* sql_prepOpen */ void sql_close(int cid) { struct OCURS *o = findCursor(cid); if (o->flag < CURSOR_OPENED) errorPrint("2cannot close cursor %d, not yet opened", cid); stmt_text = "close"; debugStatement(); hstmt = o->hstmt; rc = SQLCloseCursor(hstmt); if (errorTrap(0)) return; o->flag = CURSOR_PREPARED; exclist = 0; } /* sql_close */ void sql_free(int cid) { struct OCURS *o = findCursor(cid); if (o->flag == CURSOR_OPENED) sql_close(cid); stmt_text = "free"; debugStatement(); hstmt = o->hstmt; rc = SQLFreeHandle(SQL_HANDLE_STMT, hstmt); o->flag = CURSOR_NONE; o->hstmt = SQL_NULL_HSTMT; rv_numRets = 0; memset(rv_type, 0, sizeof(rv_type)); /* free should never fail */ errorTrap(0); exclist = 0; } /* sql_free */ void sql_closeFree(int cid) { const short *exc = exclist; sql_close(cid); if (!rv_lastStatus) { exclist = exc; sql_free(cid); } } /* sql_closeFree */ /* fetch row n from the open cursor. * Flag can be used to fetch first, last, next, or previous. */ static bool fetchInternal(int cid, long n, int flag) { long nextrow, lastrow; struct OCURS *o = findCursor(cid); everything_null = true; /* don't do the fetch if we're looking for row 0 absolute, * that just nulls out the return values */ if (flag == SQL_FD_FETCH_ABSOLUTE && !n) { o->rownum = 0; fetchZero: return false; } lastrow = nextrow = o->rownum; if (flag == SQL_FD_FETCH_ABSOLUTE) nextrow = n; if (flag == SQL_FD_FETCH_FIRST) nextrow = 1; if (isnotnull(lastrow)) { /* we haven't lost track yet */ if (flag == SQL_FD_FETCH_NEXT) ++nextrow; if (flag == SQL_FD_FETCH_PREV && nextrow) --nextrow; } if (flag == SQL_FD_FETCH_LAST) { nextrow = nullint; /* we just lost track */ } if (!nextrow) goto fetchZero; if (o->flag != CURSOR_OPENED) errorPrint("2cannot fetch from cursor %d, not yet opened", cid); /* The next line of code is very subtle. I use to declare all cursors as scroll cursors. It's a little inefficient, but who cares. Then I discovered you can't fetch blobs from scroll cursors. You can however fetch them from regular cursors, even with an order by clause. So cursors became non-scrolling by default. If the programmer chooses to fetch by absolute number, but he is really going in sequence, I turn them into fetch-next statements, so that the cursor need not be a scroll cursor. */ if (flag == SQL_FD_FETCH_ABSOLUTE) { if (isnull(nextrow)) errorPrint ("2sql fetches absolute row using null index"); if (isnotnull(lastrow) && nextrow == lastrow + 1) flag = SQL_FD_FETCH_NEXT; } stmt_text = "fetch"; debugStatement(); hstmt = o->hstmt; rc = SQLFetchScroll(hstmt, (ushort) flag, nextrow); if (rc == SQL_NO_DATA) return false; if (errorTrap(0)) return false; o->rownum = nextrow; everything_null = false; return true; } /* fetchInternal */ bool sql_fetchFirst(int cid, ...) { bool rowfound; va_start(sqlargs, cid); rowfound = fetchInternal(cid, 0L, SQL_FD_FETCH_FIRST); retsFromOdbc(); return rowfound; } /* sql_fetchFirst */ bool sql_fetchLast(int cid, ...) { bool rowfound; va_start(sqlargs, cid); rowfound = fetchInternal(cid, 0L, SQL_FD_FETCH_LAST); retsFromOdbc(); return rowfound; } /* sql_fetchLast */ bool sql_fetchNext(int cid, ...) { bool rowfound; va_start(sqlargs, cid); rowfound = fetchInternal(cid, 0L, SQL_FD_FETCH_NEXT); retsFromOdbc(); return rowfound; } /* sql_fetchNext */ bool sql_fetchPrev(int cid, ...) { bool rowfound; va_start(sqlargs, cid); rowfound = fetchInternal(cid, 0L, SQL_FD_FETCH_PREV); retsFromOdbc(); return rowfound; } /* sql_fetchPrev */ bool sql_fetchAbs(int cid, long rownum, ...) { bool rowfound; va_start(sqlargs, rownum); rowfound = fetchInternal(cid, rownum, SQL_FD_FETCH_ABSOLUTE); retsFromOdbc(); return rowfound; } /* sql_fetchAbs */ void getPrimaryKey(char *tname, int *part1, int *part2, int *part3, int *part4) { char colname[COLNAMELEN]; SQLLEN pcbValue; char *dot; *part1 = *part2 = *part3 = *part4 = 0; newStatement(); stmt_text = "get primary key"; debugStatement(); dot = strchr(tname, '.'); if (dot) *dot++ = 0; rc = SQLPrimaryKeys(hstmt, NULL, SQL_NTS, (dot ? tname : NULL), SQL_NTS, (dot ? dot : tname), SQL_NTS); if (dot) dot[-1] = '.'; if (rc) goto abort; /* bind column 4, the name of the key column */ rc = SQLBindCol(hstmt, 4, SQL_CHAR, (SQLPOINTER) & colname, sizeof(colname), &pcbValue); if (rc) goto abort; /* I'm only grabbing the first 4 columns in a multi-column key */ rc = SQLFetch(hstmt); if (rc == SQL_NO_DATA) goto done; if (rc) goto abort; *part1 = findColByName(colname) + 1; rc = SQLFetch(hstmt); if (rc == SQL_NO_DATA) goto done; if (rc) goto abort; *part2 = findColByName(colname) + 1; rc = SQLFetch(hstmt); if (rc == SQL_NO_DATA) goto done; if (rc) goto abort; *part3 = findColByName(colname) + 1; rc = SQLFetch(hstmt); if (rc == SQL_NO_DATA) goto done; if (rc) goto abort; *part4 = findColByName(colname) + 1; goto done; abort: errorTrap(0); done: SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return; } /* getPrimaryKey */ bool showTables(void) { char tabname[40]; char tabtype[40]; char tabowner[40]; SQLLEN tabnameOut, tabtypeOut, tabownerOut; char *buf; int buflen, cx; int truevalue = SQL_TRUE; /* SQLSetConnectAttr(hdbc, SQL_ATTR_METADATA_ID, &truevalue, SQL_IS_INTEGER); */ newStatement(); stmt_text = "get tables"; debugStatement(); rc = SQLTables(hstmt, NULL, SQL_NTS, NULL, SQL_NTS, NULL, SQL_NTS, NULL, SQL_NTS); if (rc) goto abort; SQLBindCol(hstmt, 2, SQL_CHAR, (SQLPOINTER) tabowner, sizeof(tabowner), &tabownerOut); SQLBindCol(hstmt, 3, SQL_CHAR, (SQLPOINTER) tabname, sizeof(tabname), &tabnameOut); SQLBindCol(hstmt, 4, SQL_CHAR, (SQLPOINTER) tabtype, sizeof(tabtype), &tabtypeOut); buf = initString(&buflen); while (SQLFetch(hstmt) == SQL_SUCCESS) { char tabline[140]; sprintf(tabline, "%s.%s|%s\n", tabowner, tabname, tabtype); stringAndString(&buf, &buflen, tabline); } cx = sideBuffer(0, buf, buflen, 0); nzFree(buf); i_printf(MSG_ShowTables, cx); SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return true; abort: SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return false; } /* showTables */ /* display foreign keys, from this table to others */ bool fetchForeign(char *tname) { char farschema[40], fartab[40]; char farcol[40]; char nearcol[40]; SQLLEN nearcolOut, farschemaOut, fartabOut, farcolOut; char *dot; newStatement(); stmt_text = "foreign keys"; debugStatement(); dot = strchr(tname, '.'); if (dot) *dot++ = 0; rc = SQLForeignKeys(hstmt, NULL, SQL_NTS, NULL, SQL_NTS, NULL, SQL_NTS, NULL, SQL_NTS, (dot ? tname : NULL), SQL_NTS, (dot ? dot : tname), SQL_NTS); if (dot) dot[-1] = '.'; if (rc) goto abort; SQLBindCol(hstmt, 2, SQL_CHAR, (SQLPOINTER) farschema, sizeof(farschema), &farschemaOut); SQLBindCol(hstmt, 3, SQL_CHAR, (SQLPOINTER) fartab, sizeof(fartab), &fartabOut); SQLBindCol(hstmt, 4, SQL_CHAR, (SQLPOINTER) farcol, sizeof(farcol), &farcolOut); SQLBindCol(hstmt, 8, SQL_CHAR, (SQLPOINTER) nearcol, sizeof(nearcol), &nearcolOut); while (SQLFetch(hstmt) == SQL_SUCCESS) { printf("%s > ", nearcol); if (farschema[0]) printf("%s.", farschema); printf("%s.%s\n", fartab, farcol); } SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return true; abort: SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return false; } /* fetchForeign */ edbrowse-3.6.0.1/src/PaxHeaders.22102/dbinfx.ec0000644000000000000000000000006212637565441015605 xustar0020 atime=1451158305 30 ctime=1451409178.149337216 edbrowse-3.6.0.1/src/dbinfx.ec0000644000000000000000000011757612637565441016077 0ustar00rootroot00000000000000/********************************************************************* dbinfx.ec: C-level interface to SQL. This is a layer above esql/c, since embedded SQL is often difficult to use, especially for new programmers. Most SQL queries are relatively simple, whence the esql API is overkill. Why mess with cryptic $directives when you can write: sql_select("select this, that from table1, table2 where keycolumn = %d", 27, &this, &that); More important, this API automatically aborts (or longjumps) if an error occurs, unless that error has been specifically trapped by the program. This minimizes application-level error-leg programming, thereby reducing the code by as much as 1/3. To accomplish this, the errorPrint() function, supplied by the application, must never return. We assume it passes the error message to stderr and to a logfile, and then exits, or longjumps to a recovery point. Note that this API works within the context of our own C programming environment. Note that dbapi.h does NOT include the Informix header files. That would violate the spirit of this layer, which attempts to sheild the application from the details of the SQL API. If the application needed to see anything in the Informix header files, we would be doing something wrong. *********************************************************************/ /* bring in the necessary Informix headers */ $include sqlca; $include sqltypes; $include sqlda; $include locator; #include "eb.h" #include "dbapi.h" #define CACHELIMIT 10000 /* number of cached lines */ #define ENGINE_ERRCODE sqlca.sqlcode /********************************************************************* The status variable ENGINE_ERRCODE holds the return code from an Informix call. This is then used by the function errorTrap() below. If ENGINE_ERRCODE != 0, errorTrap() aborts the program, or performs a recovery longjmp, as directed by the generic error function errorPrint(). errorTrap() returns true if an SQL error occurred, but that error was trapped by the application. In this case the calling routine should clean up as best it can and return. *********************************************************************/ static const char *stmt_text = 0; /* text of the SQL statement */ static const short *exclist; /* list of error codes trapped by the application */ static short translevel; static eb_bool badtrans; /* Through globals, make error info available to the application. */ int rv_lastStatus, rv_stmtOffset; long rv_vendorStatus; char *rv_badToken; static void debugStatement(void) { if(sql_debug && stmt_text) appendFileNF(sql_debuglog, stmt_text); } /* debugStatement */ static void debugExtra(const char *s) { if(sql_debug) appendFileNF(sql_debuglog, s); } /* debugExtra */ /* Append the SQL statement to the debug log. This is not strictly necessary * if sql_debug is set, since the statement has already been appended. */ static void showStatement(void) { if(!sql_debug && stmt_text) appendFileNF(sql_debuglog, stmt_text); } /* showStatement */ /* application sets the exception list */ void sql_exclist(const short *list) { exclist = list; } void sql_exception(int errnum) { static short list[2]; list[0] = errnum; exclist = list; } /* sql_exception */ /* map Informix errors to our own exception codes, as defined in dbapi.h. */ static const struct ERRORMAP { short infcode; short excno; } errormap[] = { {200, EXCSYNTAX}, {201, EXCSYNTAX}, {202, EXCSYNTAX}, {203, EXCSYNTAX}, {204, EXCSYNTAX}, {205, EXCROWIDUSE}, {206, EXCNOTABLE}, /* 207 */ {208, EXCRESOURCE}, {209, EXCDBCORRUPT}, {210, EXCFILENAME}, {211, EXCDBCORRUPT}, {212, EXCRESOURCE}, {213, EXCINTERRUPT}, {214, EXCDBCORRUPT}, {215, EXCDBCORRUPT}, {216, EXCDBCORRUPT}, {217, EXCNOCOLUMN}, {218, EXCNOSYNONYM}, {219, EXCCONVERT}, {220, EXCSYNTAX}, {221, EXCRESOURCE}, {222, EXCRESOURCE}, {223, EXCAMBTABLE}, {224, EXCRESOURCE}, {225, EXCRESOURCE}, {226, EXCRESOURCE}, {227, EXCROWIDUSE}, {228, EXCROWIDUSE}, {229, EXCRESOURCE}, {230, EXCDBCORRUPT}, {231, EXCAGGREGATEUSE}, {232, EXCSERIAL}, {233, EXCITEMLOCK}, {234, EXCAMBCOLUMN}, {235, EXCCONVERT}, {236, EXCSYNTAX}, {237, EXCMANAGETRANS}, {238, EXCMANAGETRANS}, {239, EXCDUPKEY}, {240, EXCDBCORRUPT}, {241, EXCMANAGETRANS}, {249, EXCAMBCOLUMN}, {250, EXCDBCORRUPT}, {251, EXCSYNTAX}, {252, EXCITEMLOCK}, {253, EXCSYNTAX}, {255, EXCNOTINTRANS}, {256, EXCMANAGETRANS}, {257, EXCRESOURCE}, {258, EXCDBCORRUPT}, {259, EXCNOCURSOR}, {260, EXCNOCURSOR}, {261, EXCRESOURCE}, {262, EXCNOCURSOR}, {263, EXCRESOURCE}, {264, EXCRESOURCE}, {265, EXCNOTINTRANS}, {266, EXCNOCURSOR}, {267, EXCNOCURSOR}, {268, EXCDUPKEY}, {269, EXCNOTNULLCOLUMN}, {270, EXCDBCORRUPT}, {271, EXCDBCORRUPT}, {272, EXCPERMISSION}, {273, EXCPERMISSION}, {274, EXCPERMISSION}, {275, EXCPERMISSION}, {276, EXCNOCURSOR}, {277, EXCNOCURSOR}, {278, EXCRESOURCE}, {281, EXCTEMPTABLEUSE}, {282, EXCSYNTAX}, {283, EXCSYNTAX}, {284, EXCMANYROW}, {285, EXCNOCURSOR}, {286, EXCNOTNULLCOLUMN}, {287, EXCSERIAL}, {288, EXCITEMLOCK}, {289, EXCITEMLOCK}, {290, EXCNOCURSOR}, {292, EXCNOTNULLCOLUMN}, {293, EXCSYNTAX}, {294, EXCAGGREGATEUSE}, {295, EXCCROSSDB}, {296, EXCNOTABLE}, {297, EXCNOKEY}, {298, EXCPERMISSION}, {299, EXCPERMISSION}, {300, EXCRESOURCE}, {301, EXCRESOURCE}, {302, EXCPERMISSION}, { 303, EXCAGGREGATEUSE}, {304, EXCAGGREGATEUSE}, {305, EXCSUBSCRIPT}, {306, EXCSUBSCRIPT}, {307, EXCSUBSCRIPT}, {308, EXCCONVERT}, {309, EXCAMBCOLUMN}, {310, EXCDUPTABLE}, {311, EXCDBCORRUPT}, {312, EXCDBCORRUPT}, {313, EXCPERMISSION}, {314, EXCDUPTABLE}, {315, EXCPERMISSION}, {316, EXCDUPINDEX}, {317, EXCUNION}, {318, EXCFILENAME}, {319, EXCNOINDEX}, {320, EXCPERMISSION}, {321, EXCAGGREGATEUSE}, {323, EXCTEMPTABLEUSE}, {324, EXCAMBCOLUMN}, {325, EXCFILENAME}, {326, EXCRESOURCE}, {327, EXCITEMLOCK}, {328, EXCDUPCOLUMN}, {329, EXCNOCONNECT}, {330, EXCRESOURCE}, {331, EXCDBCORRUPT}, {332, EXCTRACE}, {333, EXCTRACE}, {334, EXCTRACE}, {335, EXCTRACE}, {336, EXCTEMPTABLEUSE}, {337, EXCTEMPTABLEUSE}, {338, EXCTRACE}, {339, EXCFILENAME}, {340, EXCTRACE}, {341, EXCTRACE}, {342, EXCREMOTE}, {343, EXCTRACE}, {344, EXCTRACE}, {345, EXCTRACE}, {346, EXCDBCORRUPT}, {347, EXCITEMLOCK}, {348, EXCDBCORRUPT}, {349, EXCNODB}, {350, EXCDUPINDEX}, {352, EXCNOCOLUMN}, {353, EXCNOTABLE}, {354, EXCSYNTAX}, {355, EXCDBCORRUPT}, {356, EXCCONVERT}, {361, EXCRESOURCE}, {362, EXCSERIAL}, {363, EXCNOCURSOR}, {365, EXCNOCURSOR}, {366, EXCCONVERT}, {367, EXCAGGREGATEUSE}, {368, EXCDBCORRUPT}, {369, EXCSERIAL}, {370, EXCAMBCOLUMN}, {371, EXCDUPKEY}, {372, EXCTRACE}, {373, EXCFILENAME}, {374, EXCSYNTAX}, {375, EXCMANAGETRANS}, {376, EXCMANAGETRANS}, {377, EXCMANAGETRANS}, {378, EXCITEMLOCK}, {382, EXCSYNTAX}, {383, EXCAGGREGATEUSE}, {384, EXCVIEWUSE}, {385, EXCCONVERT}, {386, EXCNOTNULLCOLUMN}, {387, EXCPERMISSION}, {388, EXCPERMISSION}, {389, EXCPERMISSION}, {390, EXCDUPSYNONYM}, {391, EXCNOTNULLCOLUMN}, {392, EXCDBCORRUPT}, {393, EXCWHERECLAUSE}, {394, EXCNOTABLE}, {395, EXCWHERECLAUSE}, {396, EXCWHERECLAUSE}, {397, EXCDBCORRUPT}, {398, EXCNOTINTRANS}, {399, EXCMANAGETRANS}, {400, EXCNOCURSOR}, {401, EXCNOCURSOR}, {404, EXCNOCURSOR}, {406, EXCRESOURCE}, {407, EXCDBCORRUPT}, {408, EXCDBCORRUPT}, {409, EXCNOCONNECT}, {410, EXCNOCURSOR}, {413, EXCNOCURSOR}, {414, EXCNOCURSOR}, {415, EXCCONVERT}, {417, EXCNOCURSOR}, {420, EXCREMOTE}, {421, EXCREMOTE}, {422, EXCNOCURSOR}, {423, EXCNOROW}, {424, EXCDUPCURSOR}, {425, EXCITEMLOCK}, {430, EXCCONVERT}, {431, EXCCONVERT}, {432, EXCCONVERT}, {433, EXCCONVERT}, {434, EXCCONVERT}, {439, EXCREMOTE}, {451, EXCRESOURCE}, {452, EXCRESOURCE}, {453, EXCDBCORRUPT}, {454, EXCDBCORRUPT}, {455, EXCRESOURCE}, {457, EXCREMOTE}, {458, EXCLONGTRANS}, {459, EXCREMOTE}, {460, EXCRESOURCE}, {465, EXCRESOURCE}, {468, EXCNOCONNECT}, {472, EXCCONVERT}, {473, EXCCONVERT}, {474, EXCCONVERT}, {482, EXCNOCURSOR}, {484, EXCFILENAME}, {500, EXCDUPINDEX}, {501, EXCDUPINDEX}, {502, EXCNOINDEX}, {503, EXCRESOURCE}, {504, EXCVIEWUSE}, {505, EXCSYNTAX}, {506, EXCPERMISSION}, {507, EXCNOCURSOR}, {508, EXCTEMPTABLEUSE}, {509, EXCTEMPTABLEUSE}, {510, EXCTEMPTABLEUSE}, {512, EXCPERMISSION}, {514, EXCPERMISSION}, {515, EXCNOCONSTRAINT}, {517, EXCRESOURCE}, {518, EXCNOCONSTRAINT}, {519, EXCCONVERT}, {521, EXCITEMLOCK}, {522, EXCNOTABLE}, {524, EXCNOTINTRANS}, {525, EXCREFINT}, {526, EXCNOCURSOR}, {528, EXCRESOURCE}, {529, EXCNOCONNECT}, {530, EXCCHECK}, {531, EXCDUPCOLUMN}, {532, EXCTEMPTABLEUSE}, {534, EXCITEMLOCK}, {535, EXCMANAGETRANS}, {536, EXCSYNTAX}, {537, EXCNOCONSTRAINT}, {538, EXCDUPCURSOR}, {539, EXCRESOURCE}, {540, EXCDBCORRUPT}, {541, EXCPERMISSION}, {543, EXCAMBCOLUMN}, {543, EXCSYNTAX}, {544, EXCAGGREGATEUSE}, {545, EXCPERMISSION}, {548, EXCTEMPTABLEUSE}, {549, EXCNOCOLUMN}, {550, EXCRESOURCE}, {551, EXCRESOURCE}, {554, EXCSYNTAX}, {559, EXCDUPSYNONYM}, {560, EXCDBCORRUPT}, {561, EXCAGGREGATEUSE}, {562, EXCCONVERT}, {536, EXCITEMLOCK}, {564, EXCRESOURCE}, {565, EXCRESOURCE}, {566, EXCRESOURCE}, {567, EXCRESOURCE}, {568, EXCCROSSDB}, {569, EXCCROSSDB}, {570, EXCCROSSDB}, {571, EXCCROSSDB}, {573, EXCMANAGETRANS}, {574, EXCAMBCOLUMN}, {576, EXCTEMPTABLEUSE}, {577, EXCDUPCONSTRAINT}, {578, EXCSYNTAX}, {579, EXCPERMISSION}, {580, EXCPERMISSION}, {582, EXCMANAGETRANS}, {583, EXCPERMISSION}, {586, EXCDUPCURSOR}, {589, EXCREMOTE}, {590, EXCDBCORRUPT}, {591, EXCCONVERT}, {592, EXCNOTNULLCOLUMN}, {593, EXCSERIAL}, {594, EXCBLOBUSE}, {595, EXCAGGREGATEUSE}, {597, EXCDBCORRUPT}, {598, EXCNOCURSOR}, {599, EXCSYNTAX}, {600, EXCMANAGEBLOB}, {601, EXCMANAGEBLOB}, {602, EXCMANAGEBLOB}, {603, EXCMANAGEBLOB}, {604, EXCMANAGEBLOB}, {605, EXCMANAGEBLOB}, {606, EXCMANAGEBLOB}, {607, EXCSUBSCRIPT}, {608, EXCCONVERT}, {610, EXCBLOBUSE}, {611, EXCBLOBUSE}, {612, EXCBLOBUSE}, {613, EXCBLOBUSE}, {614, EXCBLOBUSE}, {615, EXCBLOBUSE}, {616, EXCBLOBUSE}, {617, EXCBLOBUSE}, {618, EXCMANAGEBLOB}, {622, EXCNOINDEX}, {623, EXCNOCONSTRAINT}, {625, EXCDUPCONSTRAINT}, {628, EXCMANAGETRANS}, {629, EXCMANAGETRANS}, {630, EXCMANAGETRANS}, {631, EXCBLOBUSE}, {635, EXCPERMISSION}, {636, EXCRESOURCE}, {638, EXCBLOBUSE}, {639, EXCBLOBUSE}, {640, EXCDBCORRUPT}, {649, EXCFILENAME}, {650, EXCRESOURCE}, {651, EXCRESOURCE}, /* I'm not about to map all possible compile/runtime SPL errors. */ /* Here's a few. */ {655, EXCSYNTAX}, {667, EXCSYNTAX}, {673, EXCDUPSPROC}, {674, EXCNOSPROC}, {678, EXCSUBSCRIPT}, {681, EXCDUPCOLUMN}, {686, EXCMANYROW}, {690, EXCREFINT}, {691, EXCREFINT}, {692, EXCREFINT}, {702, EXCITEMLOCK}, {703, EXCNOTNULLCOLUMN}, {704, EXCDUPCONSTRAINT}, {706, EXCPERMISSION}, {707, EXCBLOBUSE}, {722, EXCRESOURCE}, {958, EXCDUPTABLE}, {1214, EXCCONVERT}, {1262, EXCCONVERT}, {1264, EXCCONVERT}, {25553, EXCNOCONNECT}, {25587, EXCNOCONNECT}, {25588, EXCNOCONNECT}, {25596, EXCNOCONNECT}, {0, 0} }; /* ends of list */ static int errTranslate(int code) { const struct ERRORMAP *e; for(e=errormap; e->infcode; ++e) { if(e->infcode == code) return e->excno; } return EXCSQLMISC; } /* errTranslate */ static eb_bool errorTrap(void) { short i; /* innocent until proven guilty */ rv_lastStatus = 0; rv_vendorStatus = 0; rv_stmtOffset = 0; rv_badToken = 0; if(ENGINE_ERRCODE >= 0) return eb_false; /* no problem */ /* log the SQL statement that elicitted the error */ showStatement(); rv_vendorStatus = -ENGINE_ERRCODE; rv_lastStatus = errTranslate(rv_vendorStatus); rv_stmtOffset = sqlca.sqlerrd[4]; rv_badToken = sqlca.sqlerrm; if(!rv_badToken[0]) rv_badToken = 0; /* if the application didn't trap for this exception, blow up! */ if(exclist) for(i=0; exclist[i]; ++i) if(exclist[i] == rv_lastStatus) { exclist = 0; /* we've spent that exception */ return eb_true; } /* Remember, errorPrint() should not return. */ errorPrint("2SQL error %d, %s", rv_vendorStatus, sqlErrorList[rv_lastStatus]); return eb_true; /* make the compiler happy */ } /* errorTrap */ /********************************************************************* The OCURS structure given below maintains an open SQL cursor. A static array of these structures allows multiple cursors to be opened simultaneously. *********************************************************************/ static struct OCURS { char sname[8]; /* statement name */ char cname[8]; /* cursor name */ struct sqlda *desc; char rv_type[NUMRETS]; long rownum; short cid; /* cursor ID */ char flag; char numRets; } ocurs[NUMCURSORS]; /* values for struct OCURS.flag */ #define CURSOR_NONE 0 #define CURSOR_PREPARED 1 #define CURSOR_OPENED 2 /* find a free cursor structure */ static struct OCURS *findNewCursor(void) { struct OCURS *o; short i; for(o=ocurs, i=0; iflag != CURSOR_NONE) continue; sprintf(o->cname, "c%u", i); sprintf(o->sname, "s%u", i); o->cid = 6000+i; return o; } errorPrint("2more than %d cursors opend concurrently", NUMCURSORS); return 0; /* make the compiler happy */ } /* findNewCursor */ /* dereference an existing cursor */ static struct OCURS *findCursor(int cid) { struct OCURS *o; if(cid < 6000 || cid >= 6000+NUMCURSORS) errorPrint("2cursor number %d is out of range", cid); cid -= 6000; o = ocurs+cid; if(o->flag == CURSOR_NONE) errorPrint("2cursor %d is not currently active", cid); rv_numRets = o->numRets; memcpy(rv_type, o->rv_type, NUMRETS); return o; } /* findCursor */ /* This doesn't close/free anything; it simply puts variables in an initial state. */ /* part of the disconnect() procedure */ static void clearAllCursors(void) { int i, j; struct OCURS *o; for(i=0, o=ocurs; iflag == CURSOR_NONE) continue; o->flag = CURSOR_NONE; o->rownum = 0; } /* loop over cursors */ translevel = 0; badtrans = eb_false; } /* clearAllCursors */ /********************************************************************* Connect and disconect to SQL databases. *********************************************************************/ void sql_connect(const char *db, const char *login, const char *pw) { $char *dblocal = (char*)db; login = pw = 0; /* not used here, so make the compiler happy */ if(isnullstring(dblocal)) { dblocal = getenv("DBNAME"); if(isnullstring(dblocal)) errorPrint("2sql_connect receives no database, check $DBNAME"); } if(sql_database) { stmt_text = "disconnect"; debugStatement(); $disconnect current; clearAllCursors(); sql_database = 0; } stmt_text = "connect"; debugStatement(); $connect to :dblocal; if(errorTrap()) return; sql_database = dblocal; /* set default lock mode and isolation level for transaction management */ stmt_text = "lock isolation"; debugStatement(); $ set lock mode to wait; if(errorTrap()) { abort: sql_disconnect(); return; } $ set isolation to committed read; if(errorTrap()) goto abort; exclist = 0; } /* sql_connect */ void sql_disconnect(void) { if(sql_database) { stmt_text = "disconnect"; debugStatement(); $disconnect current; clearAllCursors(); sql_database = 0; } exclist = 0; } /* sql_disconnect */ /* make sure we're connected to a database */ static void checkConnect(void) { if(!sql_database) errorPrint("2SQL command issued, but no database selected"); } /* checkConnect */ /********************************************************************* Begin, commit, and abort transactions. SQL does not permit nested transactions; this API does, to a limited degree. An inner transaction cannot fail while an outer one succeeds; that would require SQL support which is not forthcoming. However, as long as all transactions succeed, or the outer most fails, everything works properly. The static variable transLevel holds the number of nested transactions. *********************************************************************/ /* begin a transaction */ void sql_begTrans(void) { rv_lastStatus = 0; checkConnect(); stmt_text = 0; /* count the nesting level of transactions. */ if(!translevel) { badtrans = eb_false; stmt_text = "begin work"; debugStatement(); $begin work; if(errorTrap()) return; } ++translevel; exclist = 0; } /* sql_begTrans */ /* end a transaction */ static void endTrans(eb_bool commit) { rv_lastStatus = 0; checkConnect(); stmt_text = 0; if(translevel == 0) errorPrint("2end transaction without a matching begTrans()"); --translevel; if(commit) { stmt_text = "commit work"; debugStatement(); if(badtrans) errorPrint("2Cannot commit a transaction around an aborted transaction"); if(translevel == 0) { $commit work; if(ENGINE_ERRCODE) ++translevel; errorTrap(); } } else { /* success or failure */ stmt_text = "rollback work"; debugStatement(); badtrans = eb_true; if(!translevel) { /* bottom level */ $rollback work; if(ENGINE_ERRCODE) --translevel; errorTrap(); badtrans = eb_false; } } /* success or failure */ /* At this point I will make a bold assumption -- * that all cursors are declared with hold. * Hence they remain valid after the transaction is closed, * and we don't have to change any of the OCURS structures. */ exclist = 0; } /* endTrans */ void sql_commitWork(void) { endTrans(eb_true); } void sql_rollbackWork(void) { endTrans(eb_false); } void sql_deferConstraints(void) { if(!translevel) errorPrint("2Cannot defer constraints unless inside a transaction"); stmt_text = "defer constraints"; debugStatement(); $set constraints all deferred; errorTrap(); exclist = 0; } /* sql_deferConstraints */ /********************************************************************* Blob management routines, a somewhat awkward interface. Global variables tell SQL where to unload the next fetched blob: either a file (truncate or append) or an allocated chunk of memory. This assumes each fetch or select statement retrieves at most one blob. Since there is no %blob directive in lineFormat(), one cannot simply slip a blob in with the rest of the data as a row is updated or inserted. Instead the row must be created first, then the blob is entered separately, using blobInsert(). This means every blob column must permit nulls, at least within the schema. Also, what use to be an atomic insert might become a multi-statement transaction if data integrity is important. Future versions of our line formatting software may support a %blob directive, which makes sense only when the formatted string is destined for SQL. *********************************************************************/ static loc_t blobstruct; /* Informix structure to manage the blob */ /* insert a blob into the database */ void sql_blobInsert(const char *tabname, const char *colname, int rowid, const char *filename, void *offset, int length) { $char blobcmd[100]; $loc_t insblob; /* basic sanity checks */ checkConnect(); if(isnullstring(tabname)) errorPrint("2blobInsert, missing table name"); if(isnullstring(colname)) errorPrint("2blobInsert, missing column name"); if(rowid <= 0) errorPrint("2invalid rowid in blobInsert"); if(length < 0) errorPrint("2invalid length in blobInsert"); if(strlen(tabname) + strlen(colname) + 42 >= sizeof(blobcmd)) errorPrint("2internal blobInsert command too long"); /* set up the blob structure */ memset(&insblob, 0, sizeof(insblob)); if(!filename) { insblob.loc_loctype = LOCMEMORY; if(offset) { if(length == 0) offset = 0; } if(!offset) length = -1; insblob.loc_buffer = offset; insblob.loc_bufsize = length; insblob.loc_size = length; if(!offset) insblob.loc_indicator = -1; } else { insblob.loc_loctype = LOCFNAME; insblob.loc_fname = (char*)filename; insblob.loc_oflags = LOC_RONLY; insblob.loc_size = -1; } /* set up the blob insert command, using one host variable */ sprintf(blobcmd, "update %s set %s = ? where rowid = %d", tabname, colname, rowid); stmt_text = blobcmd; debugStatement(); $prepare blobinsert from :blobcmd; if(errorTrap()) return; $execute blobinsert using :insblob; errorTrap(); rv_lastNrows = sqlca.sqlerrd[2]; rv_lastRowid = sqlca.sqlerrd[5]; if(sql_debug) appendFile(sql_debuglog, "%d rows affected", rv_lastNrows); exclist = 0; } /* sql_blobInsert */ /********************************************************************* When an SQL statement is prepared, the engine tells us the types and lengths of the columns. Use this information to "normalize" the sqlda structure, so that columns are fetched using our preferred formats. For instance, smallints and ints both map into int variables, varchars become chars, dates map into strings (so that we can convert them into our own vendor-independent binary representations later), etc. We assume the number and types of returns have been established. Once retsSetup has "normalized" the sqlda structure, run the select or fetch, and then call retsCleanup to post-process the data. This will, for example, turn dates, fetched into strings, into our own 4-byte representations. The same for time intervals, money, etc. *********************************************************************/ /* Temp area to read the Informix values, as strings */ static char retstring[NUMRETS][STRINGLEN+4]; static va_list sqlargs; static void retsSetup(struct sqlda *desc) { short i; eb_bool blobpresent = eb_false; struct sqlvar_struct *v; for(i=0; (unsigned)i< NUMRETS; ++i) { rv_data[i].l = nullint; retstring[i][0] = 0; rv_name[i][0] = 0; } if(!desc) return; for(i=0,v=desc->sqlvar; isqlname, COLNAMELEN); switch(rv_type[i]) { case 'S': case 'C': case 'D': case 'I': v->sqltype = CCHARTYPE; v->sqllen = STRINGLEN+2; v->sqldata = retstring[i]; rv_data[i].ptr = retstring[i]; break; case 'N': v->sqltype = CINTTYPE; v->sqllen = 4; v->sqldata = (char *) &rv_data[i].l; break; case 'F': case 'M': v->sqltype = CDOUBLETYPE; v->sqllen = 8; v->sqldata = (char*) &rv_data[i].f; rv_data[i].f = nullfloat; break; case 'B': case 'T': if(blobpresent) errorPrint("2Cannot select more than one blob at a time"); blobpresent = eb_true; v->sqltype = CLOCATORTYPE; v->sqllen = sizeof(blobstruct); v->sqldata = (char*) &blobstruct; memset(&blobstruct, 0, sizeof(blobstruct)); if(!rv_blobFile) { blobstruct.loc_loctype = LOCMEMORY; blobstruct.loc_mflags = LOC_ALLOC; blobstruct.loc_bufsize = -1; } else { blobstruct.loc_loctype = LOCFNAME; blobstruct.loc_fname = (char*)rv_blobFile; blobstruct.lc_union.lc_file.lc_mode = 0600; blobstruct.loc_oflags = (rv_blobAppend ? LOC_WONLY|LOC_APPEND : LOC_WONLY); } break; default: errorPrint("@bad character %c in retsSetup", rv_type[i]); } /* switch */ } /* loop over fetched columns */ } /* retsSetup */ /* clean up fetched values, eg. convert date to our proprietary format. */ static void retsCleanup(void) { short i, l; eb_bool yearfirst; /* no blobs unless proven otherwise */ rv_blobLoc = 0; rv_blobSize = nullint; for(i=0; i STRINGLEN) errorPrint("2fetched string is too long, limit %d chars", STRINGLEN); break; case 'B': case 'T': if(blobstruct.loc_indicator >= 0) { /* not null blob */ rv_blobSize = blobstruct.loc_size; if(!rv_blobFile) rv_blobLoc = blobstruct.loc_buffer; if(rv_blobSize == 0) { /* turn empty blob into null blob */ nzFree(rv_blobLoc); rv_blobLoc = 0; rv_blobSize = nullint; } } rv_data[i].l = rv_blobSize; break; case 'N': /* Convert from Informix null to our nullint */ if(rv_data[i].l == 0x80000000) rv_data[i].l = nullint; break; default: errorPrint("@bad character %c in retsCleanup", rv_type[i]); } /* switch on datatype */ } /* loop over columsn fetched */ } /* retsCleanup */ void retsCopy(eb_bool allstrings, void *first, ...) { void *q; int i; if(!rv_numRets) errorPrint("@calling retsCopy() with no returns pending"); for(i=0; i -1000) errorPrint("2retsCopy, pointer too close to 0"); if(allstrings) *(char*)q = 0; if(rv_type[i] == 'S') { *(char*)q = 0; if(rv_data[i].ptr) strcpy(q, rv_data[i].ptr); } else if(rv_type[i] == 'C') { *(char *)q = rv_data[i].l; if(allstrings) ((char*)q)[1] = 0; } else if(rv_type[i] == 'F') { if(allstrings) { if(isnotnullfloat(rv_data[i].f)) sprintf(q, "%lf", rv_data[i].f); } else { *(double *)q = rv_data[i].f; } } else if(allstrings) { char type = rv_type[i]; long l = rv_data[i].l; if(isnotnull(l)) { if(type == 'D') { strcpy(q, dateString(l, DTDELIMIT)); } else if(type == 'I') { strcpy(q, timeString(l, DTDELIMIT)); } else if(type == 'M') { sprintf(q, "%ld.%02d", l/100, l%100); } else sprintf(q, "%ld", l); } } else { *(long *)q = rv_data[i].l; } } /* loop over result parameters */ if(!first) va_end(sqlargs); } /* retsCopy */ /* make sure we got one return value, and it is integer compatible */ static long oneRetValue(void) { char coltype = rv_type[0]; long n = rv_data[0].l; if(rv_numRets != 1) errorPrint("2SQL statement has %d return values, 1 value expected", rv_numRets); if(!strchr("MNFDIC", coltype)) errorPrint("2SQL statement returns a value whose type is not compatible with a 4-byte integer"); if(coltype == 'F') n = rv_data[0].f; return n; } /* oneRetValue */ /********************************************************************* Prepare a formatted SQL statement. Gather the types and names of the fetched columns and make this information available to the rest of the C routines in this file, and to the application. Returns the populated sqlda structure for the statement. Returns null if the prepare failed. *********************************************************************/ static struct sqlda *prepare(const char *stmt_parm, const char *sname_parm) { $char*stmt = (char*)stmt_parm; $char*sname = (char*)sname_parm; struct sqlda *desc; struct sqlvar_struct *v; short i, coltype; checkConnect(); if(isnullstring(stmt)) errorPrint("2null SQL statement"); stmt_text = stmt; debugStatement(); /* look for delete with no where clause */ while(*stmt == ' ') ++stmt; if(!strncmp(stmt, "delete", 6) || !strncmp(stmt, "update", 6)) /* delete or update */ if(!strstr(stmt, "where") && !strstr(stmt, "WHERE")) { showStatement(); errorPrint("2Old Mcdonald bug"); } /* set things up to nulls, in case the prepare fails */ retsSetup(0); rv_numRets = 0; memset(rv_type, 0, NUMRETS); rv_lastNrows = rv_lastRowid = rv_lastSerial = 0; $prepare :sname from :stmt; if(errorTrap()) return 0; /* gather types and column headings */ $describe: sname into desc; if(!desc) errorPrint("2$describe couldn't allocate descriptor"); rv_numRets = desc->sqld; if(rv_numRets > NUMRETS) { showStatement(); errorPrint("2cannot select more than %d values", NUMRETS); } for(i=0,v=desc->sqlvar; isqltype & SQLTYPE; /* kludge, count(*) should be int, not float, in my humble opinion */ if(stringEqual(v->sqlname, "(count(*))")) coltype = SQLINT; switch(coltype) { case SQLCHAR: case SQLVCHAR: rv_type[i] = 'S'; if(v->sqllen == 1) rv_type[i] = 'C'; break; case SQLDTIME: /* We only process datetime year to minute, for databases * other than Informix, which don't have a date type. */ if(v->sqllen != 5) errorPrint("2datetime field must be year to minute"); case SQLDATE: rv_type[i] = 'D'; break; case SQLINTERVAL: rv_type[i] = 'I'; break; case SQLSMINT: case SQLINT: case SQLSERIAL: case SQLNULL: rv_type[i] = 'N'; break; case SQLFLOAT: case SQLSMFLOAT: case SQLDECIMAL: rv_type[i] = 'F'; break; case SQLMONEY: rv_type[i] = 'M'; break; case SQLBYTES: rv_type[i] = 'B'; break; case SQLTEXT: rv_type[i] = 'T'; break; default: errorPrint ("@Unknown informix sql datatype %d", coltype); } /* switch on type */ } /* loop over returns */ retsSetup(desc); return desc; } /* prepare */ /********************************************************************* Run an SQL statement internally, and gather any fetched values. This statement stands alone; it fetches at most one row. You might simply know this, perhaps because of a unique key, or you might be running a stored procedure. For efficiency we do not look for a second row, so this is really like the "select first" construct that some databases support. A mode variable says whether execution or selection or both are allowed. Return true if data was successfully fetched. *********************************************************************/ static eb_bool execInternal(const char *stmt, int mode) { struct sqlda *desc; $static char singlestatement[] = "single_use_stmt"; $static char singlecursor[] = "single_use_cursor"; int i; eb_bool notfound = eb_false; short errorcode = 0; desc = prepare(stmt, singlestatement); if(!desc) return eb_false; /* error */ if(!rv_numRets) { if(!(mode&1)) { showStatement(); errorPrint("2SQL select statement returns no values"); } $execute :singlestatement; notfound = eb_true; } else { /* end no return values */ if(!(mode&2)) { showStatement(); errorPrint("2SQL statement returns %d values", rv_numRets); } $execute: singlestatement into descriptor desc; } if(errorTrap()) { errorcode = rv_vendorStatus; } else { /* select or execute ran properly */ /* error 100 means not found in Informix */ if(ENGINE_ERRCODE == 100) notfound = eb_true; /* set "last" parameters, in case the application is interested */ rv_lastNrows = sqlca.sqlerrd[2]; rv_lastRowid = sqlca.sqlerrd[5]; rv_lastSerial = sqlca.sqlerrd[1]; } /* successful run */ $free :singlestatement; errorTrap(); nzFree(desc); retsCleanup(); if(errorcode) { rv_vendorStatus = errorcode; rv_lastStatus = errTranslate(rv_vendorStatus); return eb_false; } exclist = 0; return !notfound; } /* execInternal */ /********************************************************************* Run individual select or execute statements, using the above internal routine. *********************************************************************/ /* pointer to vararg list; most of these are vararg functions */ /* execute a stand-alone statement with no % formatting of the string */ eb_bool sql_execNF(const char *stmt) { return execInternal(stmt, 1); } /* sql_execNF */ /* execute a stand-alone statement with % formatting */ eb_bool sql_exec(const char *stmt, ...) { eb_bool ok; va_start(sqlargs, stmt); stmt = lineFormatStack(stmt, 0, &sqlargs); ok = execInternal(stmt, 1); va_end(sqlargs); return ok; } /* sql_exec */ /* run a select statement with no % formatting of the string */ /* return true if the row was found */ eb_bool sql_selectNF(const char *stmt, ...) { eb_bool rc; va_start(sqlargs, stmt); rc = execInternal(stmt, 2); retsCopy(eb_false, 0); return rc; } /* sql_selectNF */ /* run a select statement with % formatting */ eb_bool sql_select(const char *stmt, ...) { eb_bool rc; va_start(sqlargs, stmt); stmt = lineFormatStack(stmt, 0, &sqlargs); rc = execInternal(stmt, 2); retsCopy(eb_false, 0); return rc; } /* sql_select */ /* run a select statement with one return value */ int sql_selectOne(const char *stmt, ...) { eb_bool rc; va_start(sqlargs, stmt); stmt = lineFormatStack(stmt, 0, &sqlargs); rc = execInternal(stmt, 2); if(!rc) { va_end(sqlargs); return nullint; } return oneRetValue(); } /* sql_selectOne */ /* run a stored procedure with no % formatting */ static eb_bool sql_procNF(const char *stmt) { eb_bool rc; char *s = allocMem(20+strlen(stmt)); strcpy(s, "execute procedure "); strcat(s, stmt); rc = execInternal(s, 3); /* if execInternal doesn't return, we have a memory leak */ nzFree(s); return rc; } /* sql_procNF */ /* run a stored procedure */ eb_bool sql_proc(const char *stmt, ...) { eb_bool rc; va_start(sqlargs, stmt); stmt = lineFormatStack(stmt, 0, &sqlargs); rc = sql_procNF(stmt); if(rv_numRets) retsCopy(eb_false, 0); return rc; } /* sql_proc */ /* run a stored procedure with one return */ int sql_procOne(const char *stmt, ...) { eb_bool rc; va_start(sqlargs, stmt); stmt = lineFormatStack(stmt, 0, &sqlargs); rc = sql_procNF(stmt); if(!rc) { va_end(sqlargs); return 0; } return oneRetValue(); } /* sql_procOne */ /********************************************************************* Prepare, open, close, and free SQL cursors. *********************************************************************/ /* prepare a cursor; return the ID number of that cursor */ static int prepareCursor(const char *stmt, eb_bool scrollflag) { $char *internal_sname, *internal_cname; struct OCURS *o = findNewCursor(); stmt = lineFormatStack(stmt, 0, &sqlargs); va_end(sqlargs); internal_sname = o->sname; internal_cname = o->cname; o->desc = prepare(stmt, internal_sname); if(!o->desc) return -1; if(o->desc->sqld == 0) { showStatement(); errorPrint("2statement passed to sql_prepare has no returns"); } /* declare with hold; * you might run transactions within this cursor. */ if(scrollflag) $declare :internal_cname scroll cursor with hold for :internal_sname; else $declare :internal_cname cursor with hold for :internal_sname; if(errorTrap()) { nzFree(o->desc); return -1; } o->numRets = rv_numRets; memcpy(o->rv_type, rv_type, NUMRETS); o->flag = CURSOR_PREPARED; return o->cid; } /* prepareCursor */ int sql_prepare(const char *stmt, ...) { int n; va_start(sqlargs, stmt); n = prepareCursor(stmt, eb_false); exclist = 0; return n; } /* sql_prepare */ int sql_prepareScrolling(const char *stmt, ...) { int n; va_start(sqlargs, stmt); n = prepareCursor(stmt, eb_true); exclist = 0; return n; } /* sql_prepareScrolling */ void sql_open(int cid) { short i; $char *internal_sname, *internal_cname; struct OCURS *o = findCursor(cid); if(o->flag == CURSOR_OPENED) errorPrint("2cannot open cursor %d, already opened", cid); internal_sname = o->sname; internal_cname = o->cname; debugExtra("open"); $open :internal_cname; if(!errorTrap()) o->flag = CURSOR_OPENED; o->rownum = 0; exclist = 0; } /* sql_open */ int sql_prepOpen(const char *stmt, ...) { int n; va_start(sqlargs, stmt); n = prepareCursor(stmt, eb_false); if(n < 0) return n; sql_open(n); if(rv_lastStatus) { short ev = rv_vendorStatus; short el = rv_lastStatus; sql_free(n); rv_vendorStatus = ev; rv_lastStatus = el; n = -1; } return n; } /* sql_prepOpen */ void sql_close(int cid) { $char *internal_sname, *internal_cname; struct OCURS *o = findCursor(cid); if(o->flag < CURSOR_OPENED) errorPrint("2cannot close cursor %d, not yet opened", cid); internal_cname = o->cname; debugExtra("close"); $close :internal_cname; if(errorTrap()) return; o->flag = CURSOR_PREPARED; exclist = 0; } /* sql_close */ void sql_free( int cid) { $char *internal_sname, *internal_cname; struct OCURS *o = findCursor(cid); if(o->flag == CURSOR_OPENED) errorPrint("2cannot free cursor %d, not yet closed", cid); internal_sname = o->sname; debugExtra("free"); $free :internal_sname; if(errorTrap()) return; o->flag = CURSOR_NONE; nzFree(o->desc); rv_numRets = 0; memset(rv_name, 0, sizeof(rv_name)); memset(rv_type, 0, sizeof(rv_type)); exclist = 0; } /* sql_free */ void sql_closeFree(int cid) { const short *exc = exclist; sql_close(cid); if(!rv_lastStatus) { exclist = exc; sql_free(cid); } } /* sql_closeFree */ /* fetch row n from the open cursor. * Flag can be used to fetch first, last, next, or previous. */ static eb_bool fetchInternal(int cid, long n, int flag, eb_bool remember) { $char *internal_sname, *internal_cname; $long nextrow, lastrow; struct sqlda *internal_desc; struct OCURS *o = findCursor(cid); internal_cname = o->cname; internal_desc = o->desc; retsSetup(internal_desc); /* don't do the fetch if we're looking for row 0 absolute, * that just nulls out the return values */ if(flag == 6 && !n) { o->rownum = 0; fetchZero: retsCleanup(); exclist = 0; return eb_false; } lastrow = nextrow = o->rownum; if(flag == 6) nextrow = n; if(flag == 3) nextrow = 1; if(isnotnull(lastrow)) { /* we haven't lost track yet */ if(flag == 1) ++nextrow; if(flag == 2 && nextrow) --nextrow; } if(flag == 4) { /* fetch the last row */ nextrow = nullint; /* we just lost track */ } if(!nextrow) goto fetchZero; if(o->flag != CURSOR_OPENED) errorPrint("2cannot fetch from cursor %d, not yet opened", cid); /* The next line of code is very subtle. I use to declare all cursors as scroll cursors. It's a little inefficient, but who cares. Then I discovered you can't fetch blobs from scroll cursors. You can however fetch them from regular cursors, even with an order by clause. So cursors became non-scrolling by default. If the programmer chooses to fetch by absolute number, but he is really going in sequence, I turn them into fetch-next statements, so that the cursor need not be a scroll cursor. */ if(flag == 6 && isnotnull(lastrow) && isnotnull(nextrow) && nextrow == lastrow+1) flag=1; debugExtra("fetch"); switch(flag) { case 1: $fetch :internal_cname using descriptor internal_desc; break; case 2: $fetch previous :internal_cname using descriptor internal_desc; break; case 3: $fetch first :internal_cname using descriptor internal_desc; break; case 4: $fetch last :internal_cname using descriptor internal_desc; break; case 6: if(isnull(nextrow)) errorPrint("2sql fetches absolute row using null index"); $fetch absolute :nextrow :internal_cname using descriptor internal_desc; break; default: errorPrint("@fetchInternal() receives bad flag %d", flag); } /* switch */ retsCleanup(); if(errorTrap()) return eb_false; exclist = 0; if(ENGINE_ERRCODE == 100) return eb_false; /* not found */ o->rownum = nextrow; return eb_true; } /* fetchInternal */ eb_bool sql_fetchFirst(int cid, ...) { eb_bool rc; va_start(sqlargs, cid); rc = fetchInternal(cid, 0L, 3, eb_false); retsCopy(eb_false, 0); return rc; } /* sql_fetchFirst */ eb_bool sql_fetchLast(int cid, ...) { eb_bool rc; va_start(sqlargs, cid); rc = fetchInternal(cid, 0L, 4, eb_false); retsCopy(eb_false, 0); return rc; } /* sql_fetchLast */ eb_bool sql_fetchNext(int cid, ...) { eb_bool rc; va_start(sqlargs, cid); rc = fetchInternal(cid, 0L, 1, eb_false); retsCopy(eb_false, 0); return rc; } /* sql_fetchNext */ eb_bool sql_fetchPrev(int cid, ...) { eb_bool rc; va_start(sqlargs, cid); rc = fetchInternal(cid, 0L, 2, eb_false); retsCopy(eb_false, 0); return rc; } /* sql_fetchPrev */ eb_bool sql_fetchAbs(int cid, long rownum, ...) { eb_bool rc; va_start(sqlargs, rownum); rc = fetchInternal(cid, rownum, 6, eb_false); retsCopy(eb_false, 0); return rc; } /* sql_fetchAbs */ /********************************************************************* Get the primary key for a table. In informix, you can use system tables to get this information. I haven't yet expanded this to a 3 part key. *********************************************************************/ /* The prototype looks right; but this function only returns the first 2 key columns */ void getPrimaryKey(char *tname, int *part1, int *part2, int *part3, int *part4) { int p1, p2, rc; char *s = strchr(tname, ':'); *part1 = *part2 = *part3 = *part4 = 0; if(!s) { rc = sql_select("select part1, part2 \ from sysconstraints c, systables t, sysindexes i \ where tabname = %S and t.tabid = c.tabid \ and constrtype = 'P' and c.idxname = i.idxname", tname, &p1, &p2); } else { *s = 0; rc = sql_select("select part1, part2 \ from %s:sysconstraints c, %s:systables t, %s:sysindexes i \ where tabname = %S and t.tabid = c.tabid \ and constrtype = 'P' and c.idxname = i.idxname", tname, tname, tname, s + 1, &p1, &p2); *s = ':'; } if(rc) *part1 = p1, *part2 = p2; } /* getPrimaryKey */ eb_bool showTables(void) { puts("Not implemented in Informix, but certainly doable through systables"); } /* showTables */ /* This can also be done by catalogs; it's on my list of things to do. */ eb_bool fetchForeign(char *tname) { i_puts(MSG_NYI); } /* fetchForeign */ edbrowse-3.6.0.1/src/PaxHeaders.22102/dbapi.h0000644000000000000000000000006212637565441015252 xustar0020 atime=1451158305 30 ctime=1451409178.145337243 edbrowse-3.6.0.1/src/dbapi.h0000644000000000000000000002145512637565441015532 0ustar00rootroot00000000000000/* dbapi.h: header file for the generic edbrowse sql database API */ /* Number of returns may as well be the max number of columns we can manage in edbrowse */ #define NUMRETS MAXTCOLS #define COLNAMELEN 40 /* length of column or table or database name */ #define NUMCURSORS 8 /* number of open cursors */ #define STRINGLEN 1000 /* longest string in the database */ /* constants to help format dates and times */ #define YEARFIRST 1 /* when printing dates */ #define DTDELIMIT 2 /* delimit datetime fields */ #define DTCRUNCH 4 /* leave off century or seconds */ #define DTAMPM 8 /* print with AM PM notation */ /* null values and isnull macros */ #define nullint 0x7fff0000 #define nulldate nullint #define nulltime nullint #define nullmoney nullint #define nullfloat (0x7fff*65536.0) #define nullstring ((void*)0) #define nullchar '\0' /* null test, for ints and dates and times, not floats or strings or chars */ #define isnull(x) ((x) == nullint) #define isnotnull(x) ((x) != nullint) #define isnullstring(s) (!(s) || !*(s)) #define isnotnullstring(s) ((s) && *(s)) #define isnullfloat(x) ((x) == nullfloat) #define isnotnullfloat(x) ((x) != nullfloat) /* useful C data types */ /* long and float together */ typedef union { long l; double f; void *ptr; } LF; typedef void (*fnptr) (); /* function pointer */ typedef long date; typedef long interval; typedef long money; #define sql_debug (debugLevel >= 3) #define sql_debug2 (debugLevel >= 4) extern const char *sql_debuglog; /* log of debug prints */ extern const char *sql_database; /* name of current database */ /* Arrays that hold the return values from a select statement, fetch, or any other function that has multiple returns. */ extern int rv_numRets; /* number of returned values */ extern char rv_type[NUMRETS + 1]; /* datatypes of returned values */ extern bool rv_nullable[NUMRETS]; /* can the columns take nulls */ extern char rv_name[NUMRETS + 1][COLNAMELEN]; /* column names */ extern LF rv_data[NUMRETS]; /* the returned values */ /* status variables, set by an SQL statement */ extern int rv_lastStatus; /* status of last sql_exec command */ extern long rv_vendorStatus; /* vendor-specific error code */ extern int rv_stmtOffset; /* offset of syntax error within sql statement */ extern long rv_lastNrows; /* number of rows affected */ extern long rv_lastSerial; /* serial number generated by last insert */ extern long rv_lastRowid; /* rowid generated by last insert */ extern void *rv_blobLoc; /* point to blob in memory */ extern int rv_blobSize; /* size of blob fetched */ extern const char *rv_blobFile; extern bool rv_blobAppend; /********************************************************************* The ODBC error codes are strings (somewhat inconvenient), subject to change, and supernumerous. In other words, the application usually doesn't require this level of granularity, except in the error messages, which are all handled by odbc.c. More often, the application wants to trap an integrity error, such as a check constraint violation, and abort on anything else. To this end, we have created our own SQL exception codes, listed below. By designating a couple of these exception codes, the application can direct error recovery at a high level. *********************************************************************/ #define EXCSQLMISC 1 /* miscelaneous SQL errors not covered below */ #define EXCSYNTAX 2 /* syntax error in SQL statement */ #define EXCFILENAME 3 /* this filename cannot be used by SQL */ #define EXCCONVERT 4 /* cannot convert/compare the columns/constants in the SQL statement */ #define EXCSUBSCRIPT 5 /* bad string subscripting */ #define EXCROWIDUSE 6 /* bad use of the rowid pseudo-column */ #define EXCBLOBUSE 7 /* bad use of a blob column */ #define EXCAGGREGATEUSE 8 /* bad use of aggregate operators or columns */ #define EXCVIEWUSE 9 /* bad use of a view */ #define EXCTEMPTABLEUSE 10 /* bad use of a temp table */ #define EXCTRUNCATE 11 /* data truncated, into the database or into local buffers */ #define EXCCROSSDB 12 /* operation cannot cross databases */ #define EXCDBCORRUPT 13 /* database is corrupted, time to panic. */ #define EXCINTERRUPT 14 /* user interrupted the query */ #define EXCNOCONNECT 15 /* error in the connection to the database */ #define EXCNODB 16 /* no database selected */ #define EXCNOTABLE 17 /* no such table */ #define EXCDUPTABLE 18 /* duplicate table */ #define EXCAMBTABLE 19 /* ambiguous table */ #define EXCNOCOLUMN 20 /* no such column */ #define EXCDUPCOLUMN 21 /* duplicate column */ #define EXCAMBCOLUMN 22 /* ambiguous column */ #define EXCNOINDEX 23 /* no index */ #define EXCDUPINDEX 24 /* duplicate index */ #define EXCNOCONSTRAINT 25 /* no constraint */ #define EXCDUPCONSTRAINT 26 /* duplicate constraint */ #define EXCNOSPROC 27 /* no stored procedure */ #define EXCDUPSPROC 28 /* duplicate stored procedure */ #define EXCNOSYNONYM 29 /* no such synonym */ #define EXCDUPSYNONYM 30 /* duplicate synonym */ #define EXCNOKEY 31 /* table has no primary or unique key */ #define EXCDUPKEY 32 /* duplicated primary or unique key */ #define EXCNOCURSOR 33 /* cursor not specified, or not available */ #define EXCDUPCURSOR 34 /* duplicating a cursor */ #define EXCRESOURCE 35 /* engine lacks the resources needed to complete this query */ #define EXCCHECK 36 /* check constrain violated */ #define EXCREFINT 37 /* referential integrity violated */ #define EXCMANAGETRANS 38 /* cannot manage or complete the transaction */ #define EXCLONGTRANS 39 /* long transaction, too much log data generated */ #define EXCNOTINTRANS 40 /* this operation requires a transaction */ #define EXCMANAGEBLOB 41 /* cannot open read write close or otherwise manage a blob */ #define EXCITEMLOCK 42 /* row, table, page, or database is locked, or cannot be locked */ #define EXCNOTNULLCOLUMN 43 /* inserting null into a not null column */ #define EXCPERMISSION 44 /* no permission to modify the database in this way */ #define EXCNOROW 45 /* no current row */ #define EXCMANYROW 46 /* many rows were found where one was expected */ #define EXCUNION 47 /* cannot union select statements together */ #define EXCACTIVE 48 /* an open connection or executing SQL statement prevents this function from running */ #define EXCREMOTE 49 /* could not run SQL or gather data from the remote host */ #define EXCWHERECLAUSE 50 /* where clause is semantically unmanageable */ #define EXCDEADLOCK 51 /* deadlock detected */ #define EXCARGUMENT 52 /* bad argument to ODBC, such as null pointer */ #define EXCUNSUPPORTED 53 /* function or capability or option not supported */ #define EXCTIMEOUT 54 /* timeout before SQL completed */ #define EXCTRACE 55 /* error tracing SQL statements, or leaving audit trails */ #define EXCSERIAL 56 /* bad use of serial column */ /* text descriptions corresponding to our generic SQL error codes */ extern const char *sqlErrorList[]; /********************************************************************* Function prototypes. *********************************************************************/ void sql_exclist(const short *list); void sql_exception(int errnum); void sql_connect(const char *db, const char *login, const char *pw); void sql_disconnect(void); void sql_begTrans(void); void sql_commitWork(void); void sql_rollbackWork(void); void sql_deferConstraints(void); bool sql_execNF(const char *stmt); bool sql_exec(const char *stmt, ...); void retsCopy(bool allstrings, void *first, ...); bool sql_select(const char *stmt, ...); bool sql_selectNF(const char *stmt, ...); int sql_selectOne(const char *stmt, ...); bool sql_proc(const char *stmt, ...); int sql_procOne(const char *stmt, ...); int sql_prepare(const char *stmt, ...); int sql_prepareScrolling(const char *stmt, ...); void sql_open(int cid); int sql_prepOpen(const char *stmt, ...); void sql_close(int cid); void sql_free(int cid); void sql_closeFree(int cid); bool sql_fetchFirst(int cid, ...); bool sql_fetchLast(int cid, ...); bool sql_fetchNext(int cid, ...); bool sql_fetchPrev(int cid, ...); bool sql_fetchAbs(int cid, long rownum, ...); void sql_blobInsert(const char *tabname, const char *colname, int rowid, const char *filename, void *offset, int length); void getPrimaryKey(char *tname, int *part1, int *part2, int *part3, int *part4); /* sourcefile=dbops.c */ char *lineFormat(const char *line, ...); char *lineFormatStack(const char *line, LF * argv, va_list * parmv_p); char *sql_mkunld(char delim); char *sql_mkinsupd(void); bool isLeapYear(int year); date dateEncode(int year, int month, int day); void dateDecode(date d, int *yp, int *mp, int *dp); date stringDate(const char *s, bool yearfirst); char *dateString(date d, int flags); char *timeString(interval seconds, int flags); interval stringTime(const char *t); char *moneyString(money m); money stringMoney(const char *s); void syncup_table(const char *table1, const char *table2, const char *keycol, const char *otherclause); edbrowse-3.6.0.1/src/PaxHeaders.22102/cookies.c0000644000000000000000000000006212637565441015622 xustar0020 atime=1451158305 30 ctime=1451409178.145337243 edbrowse-3.6.0.1/src/cookies.c0000644000000000000000000002747012637565441016105 0ustar00rootroot00000000000000/* cookies.c * Cookies * (c) 2002 Mikulas Patocka * This file is part of the Links project, released under GPL * * Modified by Karl Dahlke for integration with edbrowse. * Modified by Chris Brannon to allow cooperation with libcurl. */ #include "eb.h" struct cookie { struct cookie *next; struct cookie *prev; /* These are allocated */ char *name, *value; char *server, *path, *domain; bool tail; /* tail is needed for libcurl, to tell it to tail-match. */ /* Why doesn't it just look for the damned dot at the front of the domain? */ time_t expires; /* zero means undefined */ bool secure; }; static const char *httponly_prefix = "#HttpOnly_"; static const size_t httponly_prefix_len = 10; static bool isComment(const char *line) { return (line[0] == '#' && strncmp(line, httponly_prefix, httponly_prefix_len) != 0); } /* isComment */ static int count_tabs(const char *the_string) { int str_index = 0, num_tabs = 0; for (str_index = 0; the_string[str_index] != '\0'; str_index++) if (the_string[str_index] == '\t') num_tabs++; return num_tabs; } /* count_tabs */ #define FIELDS_PER_COOKIE_LINE 7 static struct cookie *cookie_from_netscape_line(char *cookie_line) { struct cookie *new_cookie = NULL; if (cookie_line && cookie_line[0]) { /* Only parse the line if it is not a comment and it has the requisite number * of tabs. Comment lines begin with a leading # symbol. * Syntax checking is rudimentary, because these lines are * machine-generated. */ if (!isComment(cookie_line) && count_tabs(cookie_line) == FIELDS_PER_COOKIE_LINE - 1) { char *start, *end; new_cookie = allocZeroMem(sizeof(struct cookie)); start = cookie_line; end = strchr(cookie_line, '\t'); new_cookie->domain = pullString1(start, end); start = end + 1; end = strchr(start, '\t'); if ((*start == 't') || (*start == 'T')) new_cookie->tail = true; else new_cookie->tail = false; start = end + 1; end = strchr(start, '\t'); new_cookie->path = pullString1(start, end); start = end + 1; if (*start == 'T' || *start == 't') new_cookie->secure = true; else new_cookie->secure = false; start = strchr(start, '\t') + 1; new_cookie->expires = strtol(start, &end, 10); /* Now end points to the tab following the expiration time. */ start = end + 1; end = strchr(start, '\t'); new_cookie->name = pullString1(start, end); start = end + 1; /* strcspn gives count of non-newline characters in string, which is the * length of the final field. Either CR or LF is considered a newline. */ new_cookie->value = pullString(start, strcspn(start, "\r\n")); /* Whenever new_cookie->tail is true, there's going to be a dot at the front of the * domain name. Libcurl even puts one there when it parses set-cookie * headers. But let's be sure. */ if (new_cookie->tail && (new_cookie->domain[0] != '.') && strncmp(new_cookie->domain, httponly_prefix, httponly_prefix_len)) new_cookie->domain = prependString(new_cookie->domain, "."); } } return new_cookie; } /* cookie_from_netscape_line */ static void freeCookie(struct cookie *c) { nzFree(c->name); nzFree(c->value); nzFree(c->server); nzFree(c->path); nzFree(c->domain); } /* freeCookie */ static struct listHead cookies = { &cookies, &cookies }; static bool displacedCookie; static void acceptCookie(struct cookie *c) { struct cookie *d; displacedCookie = false; foreach(d, cookies) { if (stringEqualCI(d->name, c->name) && stringEqualCI(d->domain, c->domain)) { displacedCookie = true; delFromList(d); freeCookie(d); nzFree(d); break; } } addToListBack(&cookies, c); } /* acceptCookie */ /* * Construct a cookie line of the form used by Netscape's file format, * from a cookie c. Returns dynamically-allocated memory, which the * caller must free. */ static char *netscapeCookieLine(const struct cookie *c) { /* Netscape format */ /* * The second field of a cookie file entry is not documented anywhere. * It's been a great mystery since 2001. But if you look at the libcurl * source, you'll see that they use it. A value of TRUE indicates that * the domain field should be tail-matched, and a value of FALSE indicates * that it should not. */ const char *tailstr; char *cookLine = allocMem(strlen(c->path) + strlen(c->domain) + strlen(c->name) + strlen(c->value) + 128); if (c->tail) tailstr = "TRUE"; else tailstr = "FALSE"; sprintf(cookLine, "%s\t%s\t%s\t%s\t%u\t%s\t%s\n", c->domain, tailstr, c->path, c->secure ? "TRUE" : "FALSE", (unsigned)c->expires, c->name, c->value); return cookLine; } /* Tell libcurl about a new cookie. Called when setting cookies from * JavaScript. * The function is pretty simple. Construct a line of the form used by * the Netscape cookie file format, and pass that to libcurl. */ static CURLcode cookieForLibcurl(const struct cookie *c) { CURLcode ret; char *cookLine = netscapeCookieLine(c); debugPrint(3, "cookie for libcurl"); ret = curl_easy_setopt(http_curl_handle, CURLOPT_COOKIELIST, cookLine); nzFree(cookLine); return ret; } /* cookieForLibcurl */ /* Should this server really specify this domain in a cookie? */ /* Domain must be the trailing substring of server. */ bool domainSecurityCheck(const char *server, const char *domain) { int i, dl, nd; dl = strlen(domain); /* x.com or x.y.z */ if (dl < 5) return false; if (dl > strlen(server)) return false; i = strlen(server) - dl; if (!stringEqualCI(server + i, domain)) return false; if (i && server[i - 1] != '.') return false; nd = 2; /* number of dots */ if (dl > 4 && domain[dl - 4] == '.') { static const char *const tld[] = { "com", "edu", "net", "org", "gov", "mil", "int", "biz", NULL }; if (stringInListCI(tld, domain + dl - 3) >= 0) nd = 1; } for (i = 0; domain[i]; i++) if (domain[i] == '.') if (!--nd) return true; return false; } /* domainSecurityCheck */ /* Let's jump right into it - parse a cookie, as received from a website. */ bool receiveCookie(const char *url, const char *str) { struct cookie *c; const char *p, *q, *server; char *date, *s; debugPrint(3, "cookie %s", str); server = getHostURL(url); if (server == 0 || !*server) return false; /* Cookie starts with name=value. If we can't get that, go home. */ for (p = str; *p != ';' && *p; p++) ; for (q = str; *q != '='; q++) if (!*q || q >= p) return false; if (str == q) return false; c = allocZeroMem(sizeof(struct cookie)); c->tail = false; c->name = pullString1(str, q); ++q; if (p - q > 0) c->value = pullString1(q, p); else c->value = emptyString; c->server = cloneString(server); if (date = extractHeaderParam(str, "expires")) { c->expires = parseHeaderDate(date); nzFree(date); } else if (date = extractHeaderParam(str, "max-age")) { int n = stringIsNum(date); if (n >= 0) { time_t now = time(0); c->expires = now + n; } nzFree(date); } c->path = extractHeaderParam(str, "path"); if (!c->path) { /* The url indicates the path for this cookie, if a path is not explicitly given */ const char *dir, *dirend; getDirURL(url, &dir, &dirend); c->path = pullString1(dir, dirend); } else { if (!c->path[0] || c->path[strlen(c->path) - 1] != '/') c->path = appendString(c->path, "/"); if (c->path[0] != '/') c->path = prependString(c->path, "/"); } if (!(c->domain = extractHeaderParam(str, "domain"))) { c->domain = cloneString(server); } else { /* Is this safe for tail-matching? */ const char *domtemp = c->domain; if (domtemp[0] == '.') domtemp++; if (!domainSecurityCheck(server, domtemp)) { nzFree(c->domain); c->domain = cloneString(server); } else { /* It's safe to do tail-matching with this domain. */ c->tail = true; /* Guarantee that it does in fact start with dot, prepending if necessary.. */ if (c->domain[0] != '.') c->domain = prependString(c->domain, "."); } } if (s = extractHeaderParam(str, "secure")) { c->secure = true; nzFree(s); } cookieForLibcurl(c); freeCookie(c); nzFree(c); return true; } /* receiveCookie */ /* I'm assuming I can read the cookie file, process it, * and if necessary, write it out again, with the expired cookies deleted, * all before another edbrowse process interferes. * I've given it some thought, and I think I can ignore the race conditions. */ void cookiesFromJar(void) { char *cbuf, *s, *t; FILE *f; int n, cnt, expired, displaced; char *cbuf_end; time_t now; struct cookie *c; if (!cookieFile) return; if (!fileIntoMemory(cookieFile, &cbuf, &n)) showErrorAbort(); cbuf[n] = 0; cbuf_end = cbuf + n; time(&now); cnt = expired = displaced = 0; s = cbuf; while (s < cbuf_end) { t = s + strcspn(s, "\r\n"); /* t points to the first newline past s. If there is no newline in s, * then it points to the NUL byte at end of s. */ *t = '\0'; c = cookie_from_netscape_line(s); if (c) { /* Got a valid cookie line. */ cnt++; if (c->expires < now) { freeCookie(c); nzFree(c); ++expired; } else { acceptCookie(c); displaced += displacedCookie; } } s = t + 1; /* Get ready to read more lines. */ /* Skip over blank lines, if necessary. */ while (s < cbuf_end && (*s == '\r' || *s == '\n')) s++; } debugPrint(3, "%d persistent cookies, %d expired, %d displaced", cnt, expired, displaced); nzFree(cbuf); if (!(expired + displaced)) return; /* Pour the cookies back into the jar */ f = fopen(cookieFile, "w"); if (!f) i_printfExit(MSG_NoRebCookie, cookieFile); foreach(c, cookies) { char *cookLine = netscapeCookieLine(c); fputs(cookLine, f); nzFree(cookLine); } fclose(f); } /* cookiesFromJar */ static bool isInDomain(const char *d, const char *s) { int dl = strlen(d); int sl = strlen(s); int j = sl - dl; if (j < 0) return false; if (!memEqualCI(d, s + j, dl)) return false; if (j && s[j - 1] != '.') return false; return true; } /* isInDomain */ static bool isPathPrefix(const char *d, const char *s) { int dl = strlen(d); int sl = strlen(s); if (dl > sl) return false; return !memcmp(d, s, dl); } /* isPathPrefix */ void sendCookies(char **s, int *l, const char *url, bool issecure) { const char *server = getHostURL(url); const char *data = getDataURL(url); int nc = 0; /* new cookie */ struct cookie *c = NULL; time_t now; struct curl_slist *known_cookies = NULL; struct curl_slist *cursor = NULL; curl_easy_getinfo(http_curl_handle, CURLINFO_COOKIELIST, &known_cookies); if (!url || !server || !data) return; if (data > url && data[-1] == '/') data--; if (!*data) data = "/"; time(&now); /* Can't use foreach here, since known_cookies is just a pointer. */ cursor = known_cookies; /* The code at the top of the loop guards against a memory leak. * Otherwise, structs could become inaccessible after continue statements. */ while (cursor != NULL) { if (c != NULL) { /* discard un-freed cookie structs */ freeCookie(c); nzFree(c); } c = cookie_from_netscape_line(cursor->data); cursor = cursor->next; if (c == NULL) /* didn't read a cookie line. */ continue; /* This next test is technically redundant, but let's be clear that * HttpOnly cookies *never ever ever* get passed to JavaScript... */ if (!strncmp(c->domain, httponly_prefix, httponly_prefix_len)) continue; if (!isInDomain(c->domain, server)) continue; if (!isPathPrefix(c->path, data)) continue; if (c->expires && c->expires < now) continue; if (c->secure && !issecure) continue; /* We're good to go. */ if (!nc) stringAndString(s, l, "Cookie: "), nc = 1; else stringAndString(s, l, "; "); stringAndString(s, l, c->name); stringAndChar(s, l, '='); stringAndString(s, l, c->value); debugPrint(3, "send cookie %s=%s", c->name, c->value); } if (c != NULL) { freeCookie(c); nzFree(c); } if (known_cookies != NULL) curl_slist_free_all(known_cookies); if (nc) stringAndString(s, l, eol); } /* sendCookies */ edbrowse-3.6.0.1/src/PaxHeaders.22102/buffers.c0000644000000000000000000000006212637565441015622 xustar0020 atime=1451158305 30 ctime=1451409178.145337243 edbrowse-3.6.0.1/src/buffers.c0000644000000000000000000035614412637565441016110 0ustar00rootroot00000000000000/* buffers.c * Text buffer support routines, manage text and edit sessions. * This file is part of the edbrowse project, released under GPL. */ #include "eb.h" #ifndef DOSLIKE // no #include #include #endif // !DOSLIKE /* If this include file is missing, you need the pcre package, * and the pcre-devel package. */ #include static bool pcre_utf8_error_stop = false; #include #include // rename() in linux is all inclusive, moving either files or directories. // The comparable function in windows is MoveFile. #ifdef DOSLIKE #define rename(a, b) MoveFile(a, b) #endif // Truncate the display of a really long line. int displayLength = 500; /* Static variables for this file. */ static uchar dirWrite; /* directories read write */ static char lsformat[12]; /* size date etc on a directory listing */ static uchar endMarks; /* ^ $ on printed lines */ static bool jexmode; /* The valid edbrowse commands. */ static const char valid_cmd[] = "aAbBcdDefghHijJklmMnpqrstuvwXz=^<"; /* Commands that can be done in browse mode. */ static const char browse_cmd[] = "AbBdDefghHiklMnpqsvwXz=^<"; /* Commands for sql mode. */ static const char sql_cmd[] = "AadDefghHiklmnpqrsvwXz=^<"; /* Commands for directory mode. */ static const char dir_cmd[] = "AdDefghHklmnpqsvwXz=^<"; /* Commands that work at line number 0, in an empty file. */ static const char zero_cmd[] = "aAbefhHMqruwz=^<"; /* Commands that expect a space afterward. */ static const char spaceplus_cmd[] = "befrw"; /* Commands that should have no text after them. */ static const char nofollow_cmd[] = "aAcdDhHjlmnptuX="; /* Commands that can be done after a g// global directive. */ static const char global_cmd[] = "dDijJlmnpstX"; static int startRange, endRange; /* as in 57,89p */ static int destLine; /* as in 57,89m226 */ static int last_z = 1; static char cmd, icmd, scmd; static uchar subPrint; /* print lines after substitutions */ static bool noStack; /* don't stack up edit sessions */ static bool globSub; /* in the midst of a g// command */ static bool inscript; /* run from inside an edbrowse function */ static int lastq, lastqq; static char icmd; /* input command, usually the same as cmd */ /********************************************************************* * If a rendered line contains a hyperlink, the link is indicated * by a code that is stored inline. * If the hyperlink is number 17 on the list of hyperlinks for this window, * it is indicated by InternalCodeChar 17 { text }. * The "text" is what you see on the page, what you click on. * {Click here for more information}. * And the braces tell you it's a hyperlink. * That's just my convention. * The prior chars are for internal use only. * I'm assuming these chars don't/won't appear on the rendered page. * Yeah, sometimes nonascii chars appear, especially if the page is in * a European language, but I just assume a rendered page will not contain * the sequence: InternalCodeChar number { * In fact I assume the rendered text won't contain InternalCodeChar at all. * So I use this char to demark encoded constructs within the lines. * And why do I encode things right in the text? * Well it took me a few versions to reach this point. * But it makes so much sense! * If I move a line, the referenced hyperlink moves with it. * I don't have to update some other structure that says, * "At line 73, characters 29 through 47, that's hyperlink 17. * I use to do it that way, and wow, what a lot of overhead * when you move lines about, or delete them, or make substitutions. * Yes, you really need to change rendered html text, * because that's how you fill out forms. * Add just one letter to the first name in your fill out form, * and the hyperlink that comes later on in the line shifts down. * I use to go back and update the pointers, * so that the hyperlink started at offset 30, rather than 29. * That was a lot of work, and very error prone. * Finally I got smart, and coded the html tags inline. * They can't get lost as text is modified. They move with the text. * * So now, certain sequences in the text are for internal use only. * This routine strips out these sequences, for display. * After all, you don't want to see those code characters. * You just want to see {Click here for more information}. */ static void removeHiddenNumbers(pst p, uchar terminate) { pst s, t, u; uchar c, d; s = t = p; while ((c = *s) != terminate) { if (c != InternalCodeChar) { addchar: *t++ = c; ++s; continue; } u = s + 1; d = *u; if (!isdigitByte(d)) goto addchar; do { d = *++u; } while (isdigitByte(d)); if (d == '*') { s = u + 1; continue; } if (strchr("<>{}", d)) { s = u; continue; } /* This is not a code sequence I recognize. */ /* This should never happen; just move along. */ goto addchar; } /* loop over p */ *t = c; /* terminating character */ } /* removeHiddenNumbers */ /* Fetch line n from the current buffer, or perhaps another buffer. * This returns an allocated copy of the string, * and you need to free it when you're done. * Good grief, how inefficient! * I know, but perl use to do it, and I never noticed the slowdown. * This is not the Linux kernel, we don't need to be ultra-efficient. * We just need to be consistent in our programming efforts. * Sometimes the returned line is changed, * and if it happens sometimes, we may as well make the new copy * all the time, even if the line doesn't change, to be consistent. * You can supress the copy feature with -1. */ static pst fetchLineContext(int n, int show, int cx) { struct ebWindow *lw = sessionList[cx].lw; struct lineMap *map, *t; int dol, idx; unsigned len; pst p; /* the resulting copy of the string */ if (!lw) i_printfExit(MSG_InvalidSession, cx); map = lw->map; dol = lw->dol; if (n <= 0 || n > dol) i_printfExit(MSG_InvalidLineNb, n); t = map + n; if (show < 0) return t->text; p = clonePstring(t->text); if (show && lw->browseMode) removeHiddenNumbers(p, '\n'); return p; } /* fetchLineContext */ pst fetchLine(int n, int show) { return fetchLineContext(n, show, context); } /* fetchLine */ static int apparentSizeW(const struct ebWindow *w, bool browsing) { int ln, size = 0; pst p; for (ln = 1; ln <= w->dol; ++ln) { p = w->map[ln].text; while (*p != '\n') { if (*p == InternalCodeChar && browsing && w->browseMode) { ++p; while (isdigitByte(*p)) ++p; if (strchr("<>{}", *p)) ++size; ++p; continue; } ++p, ++size; } ++size; } /* loop over lines */ if (w->nlMode) --size; return size; } /* apparentSizeW */ static int apparentSize(int cx, bool browsing) { const struct ebWindow *w; if (cx <= 0 || cx >= MAXSESSION || (w = sessionList[cx].lw) == 0) { setError(MSG_SessionInactive, cx); return -1; } return apparentSizeW(w, browsing); } /* apparentSize */ /* get the directory suffix for a file. * This only makes sense in directory mode. */ static char *dirSuffixContext(int n, int cx) { static char suffix[4]; struct ebWindow *lw = sessionList[cx].lw; suffix[0] = 0; if (lw->dirMode) { struct lineMap *s = lw->map + n; suffix[0] = s->ds1; suffix[1] = s->ds2; suffix[2] = 0; } return suffix; } /* dirSuffixContext */ static char *dirSuffix(int n) { return dirSuffixContext(n, context); } /* dirSuffix */ /* Display a line to the screen, with a limit on output length. */ void displayLine(int n) { pst line = fetchLine(n, 1); pst s = line; int cnt = 0; uchar c; int output_l = 0; char *output = initString(&output_l); char buf[10]; if (cmd == 'n') { stringAndNum(&output, &output_l, n); stringAndChar(&output, &output_l, ' '); } if (endMarks == 2 || endMarks && cmd == 'l') stringAndChar(&output, &output_l, '^'); while ((c = *s++) != '\n') { bool expand = false; if (c == 0 || c == '\r' || c == '\x1b') expand = true; if (cmd == 'l') { /* show tabs and backspaces, ed style */ if (c == '\b') c = '<'; if (c == '\t') c = '>'; if (c < ' ' || c == 0x7f || c >= 0x80 && listNA) expand = true; } /* list */ if (expand) { sprintf(buf, "~%02X", c), cnt += 3; stringAndString(&output, &output_l, buf); } else stringAndChar(&output, &output_l, c), ++cnt; if (cnt >= displayLength) break; } /* loop over line */ if (cnt >= displayLength) stringAndString(&output, &output_l, "..."); if (cw->dirMode) { stringAndString(&output, &output_l, dirSuffix(n)); if (cw->r_map) { s = cw->r_map[n].text; if (*s) { stringAndChar(&output, &output_l, ' '); stringAndString(&output, &output_l, s); } } } if (endMarks == 2 || endMarks && cmd == 'l') stringAndChar(&output, &output_l, '$'); eb_puts(output); free(line); nzFree(output); } /* displayLine */ static void printDot(void) { if (cw->dot) displayLine(cw->dot); else i_puts(MSG_Empty); } /* printDot */ /* By default, readline's filename completion appends a single space * character to a filename when there are no alternative completions. * Since the r, w, and e commands treat spaces literally, this default * causes tab completion to behave unintuitively in edbrowse. * Solution: write our own completion function, * which wraps readline's filename completer. */ static char *edbrowse_completion(const char *text, int state) { rl_completion_append_character = '\0'; return rl_filename_completion_function(text, state); } /* edbrowse_completion */ void initializeReadline(void) { rl_completion_entry_function = edbrowse_completion; } /* initializeReadline */ #ifdef DOSLIKE /* unix can use the select function on a file descriptor, like stdin this function provides a work around for windows */ int select_stdin(struct timeval *ptv) { int ms_delay = 55; int delay_secs = ptv->tv_sec; int delay_ms = ptv->tv_usec / 1000; int res = _kbhit(); while (!res && (delay_secs || delay_ms)) { if (!delay_secs && (delay_ms < ms_delay)) ms_delay = delay_ms; // reduce this last sleep Sleep(ms_delay); if (delay_ms >= ms_delay) delay_ms -= ms_delay; else { if (delay_secs) { delay_ms += 1000; delay_secs--; } if (delay_ms >= ms_delay) delay_ms -= ms_delay; else delay_ms = 0; } res = _kbhit(); } return res; } #endif // DOSLIKE /* Get a line from standard in. Need not be a terminal. * Each input line is limited to 255 chars. * On Unix cooked mode, that's as long as a line can be anyways. * This routine returns the line in a static string. * If you want to keep it, better make a copy. * ~xx is converted from hex, a way to enter nonascii chars. * This is the opposite of displayLine() above. * But you can't enter a newline this way; I won't permit it. * The only newline is the one corresponding to the end of your text, * when you hit enter. This terminates the line. * As we described earlier, this is a perl string. * It may contain nulls, and is terminated by newline. * The program exits on EOF. * If you hit interrupt at this point, I print a message * and ask for your line again. */ pst inputLine(void) { static uchar line[MAXTTYLINE]; int i, j, len; uchar c, d, e; static char *last_rl; uchar *s; int delay_sec, delay_ms; top: intFlag = false; inInput = true; nzFree(last_rl); last_rl = 0; s = 0; if (timerWait(&delay_sec, &delay_ms)) { /* timers are pending, use select to wait on input or run the first timer. */ fd_set channels; int rc; struct timeval tv; if (!(delay_sec | delay_ms)) goto dotimers; tv.tv_sec = delay_sec; tv.tv_usec = delay_ms * 1000; #ifdef DOSLIKE rc = select_stdin(&tv); #else // !DOSLIKE memset(&channels, 0, sizeof(channels)); FD_SET(0, &channels); rc = select(1, &channels, 0, 0, &tv); #endif // DOSLIKE y/n if (rc < 0) goto interrupt; if (rc == 0) { /* timeout */ dotimers: runTimers(); goto top; } } if (inputReadLine && isInteractive) { last_rl = readline(""); if (*last_rl) add_history(last_rl); s = (uchar *) last_rl; } else { s = (uchar *) fgets((char *)line, sizeof(line), stdin); } if (!s) { interrupt: if (intFlag) goto top; i_puts(MSG_EndFile); ebClose(1); } inInput = false; intFlag = false; i = j = 0; if (last_rl) { len = strlen(s); } else { len = sizeof(line) - 1; } while (i < len && (c = s[i]) != '\n') { /* A bug in my keyboard causes nulls to be entered from time to time. */ if (c == 0) c = ' '; if (c != '~') { addchar: s[j++] = c; ++i; continue; } d = s[i + 1]; if (d == '~') { ++i; goto addchar; } if (!isxdigit(d)) goto addchar; e = s[i + 2]; if (!isxdigit(e)) goto addchar; c = fromHex(d, e); if (c == '\n') c = 0; i += 2; goto addchar; } /* loop over input chars */ s[j] = 0; if (jexmode) { if (stringEqual(s, ".")) { jexmode = false; puts("bye"); jSideEffects(); } else { char *result = jsRunScriptResult(cw->winobj, s, "jdb", 1); if (result) puts(result); nzFree(result); } goto top; } /* rest of edbrowse expects this line to be nl terminated */ s[j] = '\n'; return s; } /* inputLine */ static struct { char lhs[MAXRE], rhs[MAXRE]; bool lhs_yes, rhs_yes; } globalSubs; static void saveSubstitutionStrings(void) { if (!searchStringsAll) return; if (!cw) return; globalSubs.lhs_yes = cw->lhs_yes; strcpy(globalSubs.lhs, cw->lhs); globalSubs.rhs_yes = cw->rhs_yes; strcpy(globalSubs.rhs, cw->rhs); } /* saveSubstitutionStrings */ static void restoreSubstitutionStrings(struct ebWindow *nw) { if (!searchStringsAll) return; if (!nw) return; nw->lhs_yes = globalSubs.lhs_yes; strcpy(nw->lhs, globalSubs.lhs); nw->rhs_yes = globalSubs.rhs_yes; strcpy(nw->rhs, globalSubs.rhs); } /* restoreSubstitutionStrings */ /* Create a new window, with default variables. */ static struct ebWindow *createWindow(void) { struct ebWindow *nw; /* the new window */ nw = allocZeroMem(sizeof(struct ebWindow)); saveSubstitutionStrings(); restoreSubstitutionStrings(nw); return nw; } /* createWindow */ /* for debugging */ static void print_pst(pst p) { do printf("%c", *p); while (*p++ != '\n'); } /* print_pst */ static void freeLine(struct lineMap *t) { if (debugLevel >= 8) { printf("free "); print_pst(t->text); } nzFree(t->text); } /* freeLine */ static void freeWindowLines(struct lineMap *map) { struct lineMap *t; int cnt = 0; if (t = map) { for (++t; t->text; ++t) { freeLine(t); ++cnt; } free(map); } debugPrint(6, "freeWindowLines = %d", cnt); } /* freeWindowLines */ /********************************************************************* Garbage collection for text lines. There is an undo window that holds a snapshot of the buffer as it was before. not a copy of all the text, but a copy of map, and dot and dollar and some other things. This starts out with map = 0. The u command swaps undoWindow and current window (cw). Thus another u undoes your undo. You can step back through all your changes, popping a stack like a modern editor. Sorry. Just don't screw up more than once, and don't save your file til you're sure it's ok. No autosave feature here, I never liked that anyways. So at the start of every command not under g//, set madeChanges = false. If we're about to change something in the buffer, set madeChanges = true. But if madeChanges was false, i.e. this is the first change coming, call undoPush(). This pushes the undo window off the cliff, and that means we have to free the text first. Call undoCompare(). This finds any lines in the undo window that aren't in cw and frees them. That is a quicksort on both maps and then a comm -23. Then it frees undoWindow.map just to make sure we don't free things twice. Then undoPush copies cw onto undoWindow, ready for the u command. Return, and the calling function makes its change. But we call undoCompare at other times, like switching buffers, pop the window stack, browse, or quit. These make undo impossible, so free the lines in the undo window. *********************************************************************/ static bool madeChanges; static struct ebWindow undoWindow; /* quick sort compare */ static int qscmp(const void *s, const void *t) { return memcmp(s, t, sizeof(char *)); } /* qscmp */ /* Free undo lines not used by the current session. */ static void undoCompare(void) { const struct lineMap *cmap = cw->map; struct lineMap *map = undoWindow.map; struct lineMap *cmap2; struct lineMap *s, *t; int diff, ln, cnt = 0; if (!cmap) { debugPrint(6, "undoCompare no current map"); freeWindowLines(undoWindow.map); undoWindow.map = 0; return; } if (!map) { debugPrint(6, "undoCompare no undo map"); return; } /* sort both arrays, run comm, and find out which lines are not needed any more, then free them. * Use quick sort; some files are a million lines long. * I have to copy the second array, so I can sort it. * The first I can sort in place, cause it's going to get thrown away. */ cmap2 = allocMem((cw->dol + 2) * LMSIZE); memcpy(cmap2, cmap, (cw->dol + 2) * LMSIZE); debugPrint(8, "qsort %d %d", undoWindow.dol, cw->dol); qsort(map + 1, undoWindow.dol, LMSIZE, qscmp); qsort(cmap2 + 1, cw->dol, LMSIZE, qscmp); s = map + 1; t = cmap2 + 1; while (s->text && t->text) { diff = memcmp(s, t, sizeof(char *)); if (!diff) { ++s, ++t; continue; } if (diff > 0) { ++t; continue; } freeLine(s); ++s; ++cnt; } while (s->text) { freeLine(s); ++s; ++cnt; } free(cmap2); free(map); undoWindow.map = 0; debugPrint(6, "undoCompare strip %d", cnt); } /* undoCompare */ static void undoPush(void) { struct ebWindow *uw; /* if in browse mode, we really shouldn't be here at all! * But we could if substituting on an input field, since substitute is also * a regular ed command. */ if (cw->browseMode) return; if (madeChanges) return; madeChanges = true; cw->undoable = true; if (!cw->quitMode) cw->changeMode = true; undoCompare(); uw = &undoWindow; uw->dot = cw->dot; uw->dol = cw->dol; memcpy(uw->labels, cw->labels, 26 * sizeof(int)); uw->binMode = cw->binMode; uw->nlMode = cw->nlMode; uw->dirMode = cw->dirMode; if (cw->map) { uw->map = allocMem((cw->dol + 2) * LMSIZE); memcpy(uw->map, cw->map, (cw->dol + 2) * LMSIZE); } } /* undoPush */ static void freeWindow(struct ebWindow *w) { freeTags(w); delTimers(w); freeJavaContext(w); freeWindowLines(w->map); freeWindowLines(w->r_map); nzFree(w->dw); nzFree(w->ft); nzFree(w->hbase); nzFree(w->fd); nzFree(w->fk); nzFree(w->mailInfo); nzFree(w->fileName); nzFree(w->firstURL); nzFree(w->referrer); nzFree(w->baseDirName); free(w); } /* freeWindow */ /********************************************************************* Here are a few routines to switch contexts from one buffer to another. This is how the user edits multiple sessions, or browses multiple web pages, simultaneously. *********************************************************************/ bool cxCompare(int cx) { if (cx == 0) { setError(MSG_Session0); return false; } if (cx >= MAXSESSION) { setError(MSG_SessionHigh, cx, MAXSESSION - 1); return false; } if (cx != context) return true; /* ok */ setError(MSG_SessionCurrent, cx); return false; } /*cxCompare */ /* is a context active? */ bool cxActive(int cx) { if (cx <= 0 || cx >= MAXSESSION) i_printfExit(MSG_SessionOutRange, cx); if (sessionList[cx].lw) return true; setError(MSG_SessionInactive, cx); return false; } /* cxActive */ static void cxInit(int cx) { struct ebWindow *lw = createWindow(); if (sessionList[cx].lw) i_printfExit(MSG_DoubleInit, cx); sessionList[cx].fw = sessionList[cx].lw = lw; } /* cxInit */ bool cxQuit(int cx, int action) { struct ebWindow *w = sessionList[cx].lw; if (!w) i_printfExit(MSG_QuitNoActive, cx); /* action = 3 means we can trash data */ if (action == 3) { w->changeMode = false; action = 2; } /* We might be trashing data, make sure that's ok. */ if (w->changeMode && /* something in the buffer has changed */ lastq != cx && /* last command was not q */ !ismc && /* not in fetchmail mode, which uses the same buffer over and over again */ !(w->dirMode | w->sqlMode) && /* not directory or sql mode */ (!w->fileName || !isURL(w->fileName))) { /* not changing a url */ lastqq = cx; setError(MSG_ExpectW); if (cx != context) setError(MSG_ExpectWX, cx); return false; } if (!action) return true; /* just a test */ if (cx == context) { /* Don't need to retain the undo lines. */ undoCompare(); } if (action == 2) { while (w) { struct ebWindow *p = w->prev; freeWindow(w); w = p; } sessionList[cx].fw = sessionList[cx].lw = 0; } else freeWindow(w); if (cx == context) cw = 0; return true; } /* cxQuit */ /* Switch to another edit session. * This assumes cxCompare has succeeded - we're moving to a different context. * Pass the context number and an interactive flag. */ void cxSwitch(int cx, bool interactive) { bool created = false; struct ebWindow *nw = sessionList[cx].lw; /* the new window */ if (!nw) { cxInit(cx); nw = sessionList[cx].lw; created = true; } else { saveSubstitutionStrings(); restoreSubstitutionStrings(nw); } if (cw) { undoCompare(); cw->undoable = false; } cw = nw; cs = sessionList + cx; context = cx; if (interactive && debugLevel) { if (created) i_puts(MSG_SessionNew); else if (cw->fileName) puts(cw->fileName); else i_puts(MSG_NoFile); } /* The next line is required when this function is called from main(), * when the first arg is a url and there is a second arg. */ startRange = endRange = cw->dot; } /* cxSwitch */ /* This function is called for web redirection, by the refresh command, * or by window.location = new_url. */ char *newlocation; int newloc_d; /* possible delay */ bool newloc_r; /* replace the buffer */ bool js_redirects; void gotoLocation(char *url, int delay, bool rf) { if (newlocation && delay >= newloc_d) { nzFree(url); return; } nzFree(newlocation); newlocation = url; newloc_d = delay; newloc_r = rf; if (!delay) js_redirects = true; } /* gotoLocation */ static struct lineMap *newpiece; /* for debugging */ static void show_newpiece(int cnt) { const struct lineMap *t = newpiece; const char *s; printf("newpiece %d\n", cnt); while (cnt) { for (s = t->text; *s != '\n'; ++s) printf("%c", *s); printf("\n"); ++t, --cnt; } } /* show_newpiece */ /* Adjust the map of line numbers -- we have inserted text. * Also shift the downstream labels. * Pass the string containing the new line numbers, and the dest line number. */ static void addToMap(int nlines, int destl) { struct lineMap *newmap; int i, ln; int svdol = cw->dol; if (nlines == 0) i_printfExit(MSG_EmptyPiece); if (sizeof(int) == 4) { if (nlines > MAXLINES - cw->dol) i_printfExit(MSG_LineLimit); } /* browse has no undo command */ if (!cw->browseMode) undoPush(); /* adjust labels */ for (i = 0; i < 26; ++i) { ln = cw->labels[i]; if (ln <= destl) continue; cw->labels[i] += nlines; } cw->dot = destl + nlines; cw->dol += nlines; newmap = allocMem((cw->dol + 2) * LMSIZE); if (destl) memcpy(newmap, cw->map, (destl + 1) * LMSIZE); else memset(newmap, 0, LMSIZE); /* insert new piece here */ memcpy(newmap + destl + 1, newpiece, nlines * LMSIZE); /* put on the last piece */ if (destl < svdol) memcpy(newmap + destl + nlines + 1, cw->map + destl + 1, (svdol - destl + 1) * LMSIZE); else memset(newmap + destl + nlines + 1, 0, LMSIZE); nzFree(cw->map); cw->map = newmap; free(newpiece); newpiece = 0; } /* addToMap */ /* Add a block of text into the buffer; uses addToMap(). */ bool addTextToBuffer(const pst inbuf, int length, int destl, bool onside) { int i, j, linecount = 0; struct lineMap *t; for (i = 0; i < length; ++i) if (inbuf[i] == '\n') { ++linecount; if (sizeof(int) == 4) { if (linecount + cw->dol > MAXLINES) i_printfExit(MSG_LineLimit); } } if (destl == cw->dol) cw->nlMode = false; if (inbuf[length - 1] != '\n') { /* doesn't end in newline */ ++linecount; /* last line wasn't counted */ if (destl == cw->dol) { cw->nlMode = true; if (cmd != 'b' && !cw->binMode && !ismc && !onside) i_puts(MSG_NoTrailing); } } /* missing newline */ newpiece = t = allocZeroMem(linecount * LMSIZE); i = 0; while (i < length) { /* another line */ j = i; while (i < length) if (inbuf[i++] == '\n') break; if (inbuf[i - 1] == '\n') { /* normal line */ t->text = allocMem(i - j); } else { /* last line with no nl */ t->text = allocMem(i - j + 1); t->text[i - j] = '\n'; } memcpy(t->text, inbuf + j, i - j); ++t; } /* loop breaking inbuf into lines */ addToMap(linecount, destl); return true; } /* addTextToBuffer */ /* Pass input lines straight into the buffer, until the user enters . */ static bool inputLinesIntoBuffer(void) { pst line; int linecount = 0, cap; struct lineMap *t; /* I would use the static variable newpiece to build the new map of lines, * as other routines do, but this one is multiline input, and a javascript * timer can sneak in and add text, thus clobbering newpiece, * so I need a local variable. */ struct lineMap *np; cap = 128; np = t = allocZeroMem(cap * LMSIZE); if (linePending) line = linePending; else line = inputLine(); while (line[0] != '.' || line[1] != '\n') { if (linecount == cap) { cap *= 2; np = reallocMem(np, cap * LMSIZE); t = np + linecount; } t->text = clonePstring(line); t->ds1 = t->ds2 = 0; ++t, ++linecount; line = inputLine(); } nzFree(linePending); linePending = 0; if (!linecount) { /* no lines entered */ free(np); cw->dot = endRange; if (!cw->dot && cw->dol) cw->dot = 1; return true; } if (endRange == cw->dol) cw->nlMode = false; newpiece = np; addToMap(linecount, endRange); return true; } /* inputLinesIntoBuffer */ /* create the full pathname for a file that you are viewing in directory mode. */ /* This is static, with a limit on path length. */ static char *makeAbsPath(const char *f) { static char path[ABSPATH + 200]; if (strlen(cw->baseDirName) + strlen(f) > ABSPATH - 2) { setError(MSG_PathNameLong, ABSPATH); return 0; } sprintf(path, "%s/%s", cw->baseDirName, f); return path; } /* makeAbsPath */ /* Delete a block of text. */ void delText(int start, int end) { int i, j, ln; /* browse has no undo command */ if (cw->browseMode) { for (ln = start; ln <= end; ++ln) nzFree(cw->map[ln].text); } else { undoPush(); } if (end == cw->dol) cw->nlMode = false; j = end - start + 1; memmove(cw->map + start, cw->map + end + 1, (cw->dol - end + 1) * LMSIZE); /* move the labels */ for (i = 0; i < 26; ++i) { ln = cw->labels[i]; if (ln < start) continue; if (ln <= end) { cw->labels[i] = 0; continue; } cw->labels[i] -= j; } cw->dol -= j; cw->dot = start; if (cw->dot > cw->dol) cw->dot = cw->dol; /* by convention an empty buffer has no map */ if (!cw->dol) { free(cw->map); cw->map = 0; } } /* delText */ /* Delete files from a directory as you delete lines. * Set dw to move them to your recycle bin. * Set dx to delete them outright. */ static bool delFiles(void) { int ln, cnt; if (!dirWrite) { setError(MSG_DirNoWrite); return false; } if (dirWrite == 1 && !recycleBin) { setError(MSG_NoRecycle); return false; } ln = startRange; cnt = endRange - startRange + 1; while (cnt--) { char *file, *t, *path, *ftype; file = (char *)fetchLine(ln, 0); t = strchr(file, '\n'); if (!t) i_printfExit(MSG_NoNlOnDir, file); *t = 0; path = makeAbsPath(file); if (!path) { free(file); return false; } ftype = dirSuffix(ln); if (dirWrite == 2 && *ftype == '/') { setError(MSG_NoDirDelete); free(file); return false; } if (dirWrite == 2 || *ftype && strchr("@<*^|", *ftype)) { unlink: if (unlink(path)) { setError(MSG_NoRemove, file); free(file); return false; } } else { char bin[ABSPATH]; sprintf(bin, "%s/%s", recycleBin, file); if (rename(path, bin)) { if (errno == EXDEV) { char *rmbuf; int rmlen; if (*ftype == '/') { setError(MSG_CopyMoveDir); free(file); return false; } if (!fileIntoMemory (path, &rmbuf, &rmlen)) { free(file); return false; } if (!memoryOutToFile(bin, rmbuf, rmlen, MSG_TempNoCreate2, MSG_NoWrite2)) { free(file); nzFree(rmbuf); return false; } nzFree(rmbuf); goto unlink; } setError(MSG_NoMoveToTrash, file); free(file); return false; } } free(file); delText(ln, ln); } return true; } /* delFiles */ /* Move or copy a block of text. */ /* Uses range variables, hence no parameters. */ static bool moveCopy(void) { int sr = startRange; int er = endRange + 1; int dl = destLine + 1; int i_sr = sr * LMSIZE; /* indexes into map */ int i_er = er * LMSIZE; int i_dl = dl * LMSIZE; int n_lines = er - sr; struct lineMap *map = cw->map; struct lineMap *newmap, *t; int lowcut, highcut, diff, i, ln; if (dl > sr && dl < er) { setError(MSG_DestInBlock); return false; } if (cmd == 'm' && (dl == er || dl == sr)) { if (globSub) setError(MSG_NoChange); return false; } undoPush(); if (cmd == 't') { newpiece = t = allocZeroMem(n_lines * LMSIZE); for (i = sr; i < er; ++i, ++t) t->text = fetchLine(i, 0); addToMap(n_lines, destLine); return true; } if (destLine == cw->dol || endRange == cw->dol) cw->nlMode = false; /* All we really need do is rearrange the map. */ newmap = allocMem((cw->dol + 2) * LMSIZE); memcpy(newmap, map, (cw->dol + 2) * LMSIZE); if (dl < sr) { memcpy(newmap + dl, map + sr, i_er - i_sr); memcpy(newmap + dl + er - sr, map + dl, i_sr - i_dl); } else { memcpy(newmap + sr, map + er, i_dl - i_er); memcpy(newmap + sr + dl - er, map + sr, i_er - i_sr); } free(cw->map); cw->map = newmap; /* now for the labels */ if (dl < sr) { lowcut = dl; highcut = er; diff = sr - dl; } else { lowcut = sr; highcut = dl; diff = dl - er; } for (i = 0; i < 26; ++i) { ln = cw->labels[i]; if (ln < lowcut) continue; if (ln >= highcut) continue; if (ln >= startRange && ln <= endRange) { ln += (dl < sr ? -diff : diff); } else { ln += (dl < sr ? n_lines : -n_lines); } cw->labels[i] = ln; } /* loop over labels */ cw->dot = endRange; cw->dot += (dl < sr ? -diff : diff); return true; } /* moveCopy */ /* Join lines from startRange to endRange. */ static bool joinText(void) { int j, size; pst newline, t; if (startRange == endRange) { setError(MSG_Join1); return false; } size = 0; for (j = startRange; j <= endRange; ++j) size += pstLength(fetchLine(j, -1)); t = newline = allocMem(size); for (j = startRange; j <= endRange; ++j) { pst p = fetchLine(j, -1); size = pstLength(p); memcpy(t, p, size); t += size; if (j < endRange) { t[-1] = ' '; if (cmd == 'j') --t; } } delText(startRange, endRange); newpiece = allocZeroMem(LMSIZE); newpiece->text = newline; addToMap(1, startRange - 1); cw->dot = startRange; return true; } /* joinText */ /* Read the contents of a directory into the current buffer */ static bool readDirectory(const char *filename) { int len, j, linecount; char *v; struct lineMap *mptr; struct lineMap *backpiece = 0; cw->baseDirName = cloneString(filename); /* get rid of trailing slash */ len = strlen(cw->baseDirName); if (len && cw->baseDirName[len - 1] == '/') cw->baseDirName[len - 1] = 0; /* Understand that the empty string now means / */ /* get the files, or fail if there is a problem */ if (!sortedDirList(filename, &newpiece, &linecount)) return false; if (!cw->dol) { cw->dirMode = true; i_puts(MSG_DirMode); if (lsformat[0]) backpiece = allocZeroMem(LMSIZE * (linecount + 2)); } if (!linecount) { /* empty directory */ cw->dot = endRange; fileSize = 0; free(newpiece); newpiece = 0; nzFree(backpiece); goto success; } /* change 0 to nl and count bytes */ fileSize = 0; mptr = newpiece; for (j = 0; j < linecount; ++j, ++mptr) { char c, ftype; pst t = mptr->text; char *abspath = makeAbsPath((char *)t); // make sure this gets done. if (backpiece) backpiece[j + 1].text = emptyString; while (*t) { if (*t == '\n') *t = '\t'; ++t; } *t = '\n'; len = t - mptr->text; fileSize += len + 1; if (!abspath) continue; /* should never happen */ ftype = fileTypeByName(abspath, true); if (!ftype) continue; if (isupperByte(ftype)) { /* symbolic link */ if (!cw->dirMode) *t = '@', *++t = '\n'; else mptr->ds1 = '@'; ++fileSize; } ftype = tolower(ftype); c = 0; if (ftype == 'd') c = '/'; if (ftype == 's') c = '^'; if (ftype == 'c') c = '<'; if (ftype == 'b') c = '*'; if (ftype == 'p') c = '|'; if (c) { if (!cw->dirMode) *t = c, *++t = '\n'; else if (mptr->ds1) mptr->ds2 = c; else mptr->ds1 = c; ++fileSize; } /* extra stat entries on the line */ if (!lsformat[0]) continue; v = lsattr(abspath, lsformat); if (!v || !*v) continue; len = strlen(v); fileSize += len + 1; if (cw->dirMode) { backpiece[j + 1].text = cloneString(v); } else { /* have to realloc at this point */ int l1 = t - mptr->text; mptr->text = reallocMem(mptr->text, l1 + len + 3); t = mptr->text + l1; *t++ = ' '; strcpy(t, v); t += len; *t++ = '\n'; *t = 0; } } /* loop fixing files in the directory scan */ addToMap(linecount, endRange); cw->r_map = backpiece; success: if (cmd == 'r') debugPrint(1, "%d", fileSize); return true; } /* readDirectory */ /* Read a file, or url, into the current buffer. * Post/get data is passed, via the second parameter, if it's a URL. */ static bool readFile(const char *filename, const char *post) { char *rbuf; /* read buffer */ int readSize; /* should agree with fileSize */ int fh; /* file handle */ bool rc; /* return code */ bool is8859, isutf8; char *nopound; char filetype; serverData = 0; serverDataLen = 0; if (memEqualCI(filename, "file://", 7)) { filename += 7; if (!*filename) { setError(MSG_MissingFileName); return false; } goto fromdisk; } if (isURL(filename)) { const char *domain = getHostURL(filename); if (!domain) return false; /* some kind of error */ if (!*domain) { setError(MSG_DomainEmpty); return false; } rc = httpConnect(filename, (cmd != 'r'), true); if (!rc) { /* The error could have occured after redirection */ nzFree(changeFileName); changeFileName = 0; return false; } /* We got some data. Any warnings along the way have been printed, * like 404 file not found, but it's still worth continuing. */ rbuf = serverData; fileSize = readSize = serverDataLen; if (fileSize == 0) { /* empty file */ nzFree(rbuf); cw->dot = endRange; return true; } goto gotdata; } if (isSQL(filename)) { const char *t1, *t2; if (!cw->sqlMode) { setError(MSG_DBOtherFile); return false; } t1 = strchr(cw->fileName, ']'); t2 = strchr(filename, ']'); if (t1 - cw->fileName != t2 - filename || memcmp(cw->fileName, filename, t2 - filename)) { setError(MSG_DBOtherTable); return false; } rc = sqlReadRows(filename, &rbuf); if (!rc) { nzFree(rbuf); if (!cw->dol && cmd != 'r') { cw->sqlMode = false; nzFree(cw->fileName); cw->fileName = 0; } return false; } serverData = rbuf; fileSize = strlen(rbuf); if (rbuf == emptyString) return true; goto intext; } fromdisk: /* reading a file from disk */ filetype = fileTypeByName(filename, false); fileSize = 0; if (filetype == 'd') return readDirectory(filename); if ((cmd == 'e' || cmd == 'b') && !cw->mt) cw->mt = findMimeByFile(filename); #if 0 /* special optimization code for going to a renderable file, * via a plugin, from directory mode. */ /* Not sure if this is a good idea. */ if (cmd == 'b' && icmd == 'g' && pluginsOn && cw->mt && cw->mt->outtype) { fileSize = 0; return true; } #endif nopound = cloneString(filename); rbuf = findHash(nopound); if (rbuf && !filetype) *rbuf = 0; rc = fileIntoMemory(nopound, &rbuf, &fileSize); nzFree(nopound); if (!rc) return false; serverData = rbuf; if (fileSize == 0) { /* empty file */ free(rbuf); cw->dot = endRange; return true; } gotdata: if (!looksBinary(rbuf, fileSize)) { char *tbuf; /* looks like text. In DOS, we should have compressed crlf. * Let's do that now. */ #ifdef DOSLIKE int i, j; for (i = j = 0; i < fileSize; ++i) { char c = rbuf[i]; if (c == '\r' && rbuf[i + 1] == '\n') continue; rbuf[j++] = c; } rbuf[j] = 0; fileSize = j; #endif if (iuConvert) { /* Classify this incoming text as ascii or 8859 or utf8 */ looks_8859_utf8(rbuf, fileSize, &is8859, &isutf8); debugPrint(3, "text type is %s", (isutf8 ? "utf8" : (is8859 ? "8859" : "ascii"))); if (cons_utf8 && is8859) { if (debugLevel >= 2 || debugLevel == 1 && !isURL(filename)) i_puts(MSG_ConvUtf8); iso2utf(rbuf, fileSize, &tbuf, &fileSize); nzFree(rbuf); rbuf = tbuf; } if (!cons_utf8 && isutf8) { if (debugLevel >= 2 || debugLevel == 1 && !isURL(filename)) i_puts(MSG_Conv8859); utf2iso(rbuf, fileSize, &tbuf, &fileSize); nzFree(rbuf); rbuf = tbuf; } if (!cw->dol) { if (isutf8) { cw->utf8Mode = true; debugPrint(3, "setting utf8 mode"); } if (is8859) { cw->iso8859Mode = true; debugPrint(3, "setting 8859 mode"); } } } } else if (binaryDetect & !cw->binMode) { i_puts(MSG_BinaryData); cw->binMode = true; } intext: rc = addTextToBuffer((const pst)rbuf, fileSize, endRange, false); free(rbuf); return rc; } /* readFile */ /* from the command line */ bool readFileArgv(const char *filename) { cmd = 'e'; return readFile(filename, emptyString); } /* readFileArgv */ /* Write a range to a file. */ static bool writeFile(const char *name, int mode) { int i; FILE *fh; char *modeString; int modeString_l; if (memEqualCI(name, "file://", 7)) name += 7; if (!*name) { setError(MSG_MissingFileName); return false; } if (isURL(name)) { setError(MSG_NoWriteURL); return false; } if (isSQL(name)) { setError(MSG_WriteDB); return false; } if (!cw->dol) { setError(MSG_WriteEmpty); return false; } /* mode should be TRUNC or APPEND */ modeString = initString(&modeString_l); if (mode & O_APPEND) stringAndChar(&modeString, &modeString_l, 'a'); else stringAndChar(&modeString, &modeString_l, 'w'); if (cw->binMode) stringAndChar(&modeString, &modeString_l, 'b'); fh = fopen(name, modeString); nzFree(modeString); if (fh == NULL) { setError(MSG_NoCreate2, name); return false; } if (name == cw->fileName && iuConvert) { /* should we locale convert back? */ if (cw->iso8859Mode && cons_utf8) if (debugLevel >= 1) i_puts(MSG_Conv8859); if (cw->utf8Mode && !cons_utf8) if (debugLevel >= 1) i_puts(MSG_ConvUtf8); } fileSize = 0; for (i = startRange; i <= endRange; ++i) { pst p = fetchLine(i, (cw->browseMode ? 1 : -1)); int len = pstLength(p); char *suf = dirSuffix(i); char *tp; int tlen; bool alloc_p = cw->browseMode; bool rc = true; if (!cw->dirMode) { if (i == cw->dol && cw->nlMode) --len; if (name == cw->fileName && iuConvert) { if (cw->iso8859Mode && cons_utf8) { utf2iso((char *)p, len, &tp, &tlen); if (alloc_p) free(p); alloc_p = true; p = (pst) tp; if (fwrite(p, tlen, 1, fh) <= 0) rc = false; len = tlen; goto endline; } if (cw->utf8Mode && !cons_utf8) { iso2utf((char *)p, len, &tp, &tlen); if (alloc_p) free(p); alloc_p = true; p = (pst) tp; if (fwrite(p, tlen, 1, fh) <= 0) rc = false; len = tlen; goto endline; } } if (fwrite(p, len, 1, fh) <= 0) rc = false; goto endline; } /* Write this line with directory suffix, and possibly attributes */ --len; if (fwrite(p, len, 1, fh) <= 0) { badwrite: rc = false; goto endline; } fileSize += len; if (cw->r_map) { int l; /* extra ls stats to write */ char *extra; len = strlen(suf); if (len && fwrite(suf, len, 1, fh) <= 0) goto badwrite; ++len; /* for nl */ extra = cw->r_map[i].text; l = strlen(extra); if (l) { if (fwrite(" ", 1, 1, fh) <= 0) goto badwrite; ++len; if (fwrite(extra, l, 1, fh) <= 0) goto badwrite; len += l; } if (fwrite("\n", 1, 1, fh) <= 0) goto badwrite; goto endline; } strcat(suf, "\n"); len = strlen(suf); if (fwrite(suf, len, 1, fh) <= 0) goto badwrite; endline: if (alloc_p) free(p); if (!rc) { setError(MSG_NoWrite2, name); fclose(fh); return false; } fileSize += len; } /* loop over lines */ fclose(fh); /* This is not an undoable operation, nor does it change data. * In fact the data is "no longer modified" if we have written all of it. */ if (startRange == 1 && endRange == cw->dol) cw->changeMode = false; return true; } /* writeFile */ static bool readContext(int cx) { struct ebWindow *lw; int i, fardol; struct lineMap *t; if (!cxCompare(cx)) return false; if (!cxActive(cx)) return false; fileSize = 0; lw = sessionList[cx].lw; fardol = lw->dol; if (!fardol) return true; if (cw->dol == endRange) cw->nlMode = false; newpiece = t = allocZeroMem(fardol * LMSIZE); for (i = 1; i <= fardol; ++i, ++t) { pst p = fetchLineContext(i, (lw->dirMode ? -1 : 1), cx); int len = pstLength(p); pst q; if (lw->dirMode) { char *suf = dirSuffixContext(i, cx); char *q; if (lw->r_map) { char *extra = lw->r_map[i].text; int elen = strlen(extra); q = allocMem(len + 4 + elen); memcpy(q, p, len); --len; strcpy(q + len, suf); if (elen) { strcat(q, " "); strcat(q, extra); } strcat(q, "\n"); } else { q = allocMem(len + 3); memcpy(q, p, len); --len; strcat(suf, "\n"); strcpy(q + len, suf); } len = strlen(q); p = (pst) q; } t->text = p; fileSize += len; } /* loop over lines in the "other" context */ addToMap(fardol, endRange); if (lw->nlMode) { --fileSize; if (cw->dol == endRange) cw->nlMode = true; } if (binaryDetect & !cw->binMode && lw->binMode) { cw->binMode = true; i_puts(MSG_BinaryData); } return true; } /* readContext */ static bool writeContext(int cx) { struct ebWindow *lw; int i, len; struct lineMap *newmap, *t; pst p; int fardol = endRange - startRange + 1; if (!startRange) fardol = 0; if (!cxCompare(cx)) return false; if (cxActive(cx) && !cxQuit(cx, 2)) return false; cxInit(cx); lw = sessionList[cx].lw; fileSize = 0; if (startRange) { newmap = t = allocZeroMem((fardol + 2) * LMSIZE); for (i = startRange, ++t; i <= endRange; ++i, ++t) { p = fetchLine(i, (cw->dirMode ? -1 : 1)); len = pstLength(p); if (cw->dirMode) { pst q; char *suf = dirSuffix(i); if (cw->r_map) { char *extra = cw->r_map[i].text; int elen = strlen(extra); q = allocMem(len + 4 + elen); memcpy(q, p, len); --len; strcpy((char *)q + len, suf); if (elen) { strcat(q, " "); strcat(q, extra); } strcat(q, "\n"); } else { q = allocMem(len + 3); memcpy(q, p, len); --len; strcat(suf, "\n"); strcpy((char *)q + len, suf); } len = strlen((char *)q); p = q; } t->text = p; fileSize += len; } lw->map = newmap; lw->binMode = cw->binMode; if (cw->nlMode && endRange == cw->dol) { lw->nlMode = true; --fileSize; } } lw->dot = lw->dol = fardol; return true; } /* writeContext */ static void debrowseSuffix(char *s) { if (!s) return; while (*s) { if (*s == '.' && stringEqual(s, ".browse")) { *s = 0; return; } ++s; } } /* debrowseSuffix */ static char *get_interactive_shell(const char *sh) { char *ishell = NULL; #ifdef DOSLIKE return cloneString(sh); #else if (asprintf(&ishell, "exec %s -i", sh) == -1) i_printfExit(MSG_NoMem); return ishell; #endif } /* get_interactive_shell */ static bool shellEscape(const char *line) { char *newline, *s; const char *t; pst p; char key; int linesize, pass, n; char *sh; #ifdef DOSLIKE /* cmd.exe is the windows shell */ sh = "cmd"; #else /* preferred shell */ sh = getenv("SHELL"); if (!sh || !*sh) sh = "/bin/sh"; #endif linesize = strlen(line); if (!linesize) { /* interactive shell */ if (!isInteractive) { setError(MSG_SessionBackground); return false; } char *interactive_shell_cmd = get_interactive_shell(sh); eb_system(interactive_shell_cmd, true); nzFree(interactive_shell_cmd); return true; } /* Make substitutions within the command line. */ for (pass = 1; pass <= 2; ++pass) { for (t = line; *t; ++t) { if (*t != '\'') goto addchar; if (t > line && isalnumByte(t[-1])) goto addchar; key = t[1]; if (key && isalnumByte(t[2])) goto addchar; if (key == '_') { ++t; if (!cw->fileName) continue; if (pass == 1) { linesize += strlen(cw->fileName); } else { strcpy(s, cw->fileName); s += strlen(s); } continue; } /* '_ filename */ if (key == '.' || key == '-' || key == '+') { n = cw->dot; if (key == '-') --n; if (key == '+') ++n; if (n > cw->dol || n == 0) { setError(MSG_OutOfRange, key); return false; } frombuf: ++t; if (pass == 1) { p = fetchLine(n, -1); linesize += pstLength(p) - 1; } else { p = fetchLine(n, 1); if (perl2c((char *)p)) { free(p); setError(MSG_ShellNull); return false; } strcpy(s, (char *)p); s += strlen(s); free(p); } continue; } /* '. current line */ if (islowerByte(key)) { n = cw->labels[key - 'a']; if (!n) { setError(MSG_NoLabel, key); return false; } goto frombuf; } /* 'x the line labeled x */ addchar: if (pass == 1) ++linesize; else *s++ = *t; } /* loop over chars */ if (pass == 1) s = newline = allocMem(linesize + 1); else *s = 0; } /* two passes */ /* Run the command. Note that this routine returns success * even if the shell command failed. * Edbrowse succeeds if it is *able* to run the system command. */ eb_system(newline, true); free(newline); return true; } /* shellEscape */ /* Valid delimiters for search/substitute. * note that \ is conspicuously absent, not a valid delimiter. * I alsso avoid nestable delimiters such as parentheses. * And no alphanumerics please -- too confusing. * ed allows it, but I don't. */ static const char valid_delim[] = "_=!;:`\"',/?@-"; /* And a valid char for starting a line address */ static const char valid_laddr[] = "0123456789-'.$+/?"; /* Check the syntax of a regular expression, before we pass it to pcre. * The first char is the delimiter -- we stop at the next delimiter. * A pointer to the second delimiter is returned, along with the * (possibly reformatted) regular expression. */ static bool regexpCheck(const char *line, bool isleft, bool ebmuck, char **rexp, const char **split) { /* result parameters */ static char re[MAXRE + 20]; const char *start; char *e = re; char c, d; /* Remember whether a char is "on deck", ready to be modified by * etc. */ bool ondeck = false; bool was_ques = true; /* previous modifier was ? */ bool cc = false; /* are we in a [...] character class */ int mod; /* length of modifier */ int paren = 0; /* nesting level of parentheses */ /* We wouldn't be here if the line was empty. */ char delim = *line++; *rexp = re; if (!strchr(valid_delim, delim)) { setError(MSG_BadDelimit); return false; } start = line; c = *line; if (ebmuck) { if (isleft) { if (c == delim || c == 0) { if (!cw->lhs_yes) { setError(MSG_NoSearchString); return false; } strcpy(re, cw->lhs); *split = line; return true; } /* Interpret lead * or lone [ as literal */ if (strchr("*?+", c) || c == '[' && !line[1]) { *e++ = '\\'; *e++ = c; ++line; ondeck = true; } } else if (c == '%' && (line[1] == delim || line[1] == 0)) { if (!cw->rhs_yes) { setError(MSG_NoReplaceString); return false; } strcpy(re, cw->rhs); *split = line + 1; return true; } } /* ebmuck tricks */ while (c = *line) { if (e >= re + MAXRE - 3) { setError(MSG_RexpLong); return false; } d = line[1]; if (c == '\\') { line += 2; if (d == 0) { setError(MSG_LineBackslash); return false; } ondeck = true; was_ques = false; /* I can't think of any reason to remove the escaping \ from any character, * except ()|, where we reverse the sense of escape. */ if (ebmuck && isleft && !cc && (d == '(' || d == ')' || d == '|')) { if (d == '|') ondeck = false, was_ques = true; if (d == '(') ++paren, ondeck = false, was_ques = false; if (d == ')') --paren; if (paren < 0) { setError(MSG_UnexpectedRight); return false; } *e++ = d; continue; } if (d == delim || ebmuck && !isleft && d == '&') { *e++ = d; continue; } /* Nothing special; we retain the escape character. */ *e++ = c; if (isleft && d >= '0' && d <= '7' && (*line < '0' || *line > '7')) *e++ = '0'; *e++ = d; continue; } /* escaping backslash */ /* Break out if we hit the delimiter. */ if (c == delim) break; /* Remember, I reverse the sense of ()| */ if (isleft) { if (ebmuck && (c == '(' || c == ')' || c == '|') || c == '^' && line != start && !cc) /* don't know why we have to do this */ *e++ = '\\'; if (c == '$' && d && d != delim) *e++ = '\\'; } if (c == '$' && !isleft && isdigitByte(d)) { if (d == '0' || isdigitByte(line[2])) { setError(MSG_RexpDollar); return false; } } /* dollar digit on the right */ if (!isleft && c == '&' && ebmuck) { *e++ = '$'; *e++ = '0'; ++line; continue; } /* push the character */ *e++ = c; ++line; /* No more checks for the rhs */ if (!isleft) continue; if (cc) { /* character class */ if (c == ']') cc = false; continue; } if (c == '[') cc = true; /* Skip all these checks for javascript, * it probably has the expression right anyways. */ if (!ebmuck) continue; /* Modifiers must have a preceding character. * Except ? which can reduce the greediness of the others. */ if (c == '?' && !was_ques) { ondeck = false; was_ques = true; continue; } mod = 0; if (c == '?' || c == '*' || c == '+') mod = 1; if (c == '{' && isdigitByte(d)) { const char *t = line + 1; while (isdigitByte(*t)) ++t; if (*t == ',') ++t; while (isdigitByte(*t)) ++t; if (*t == '}') mod = t + 2 - line; } if (mod) { --mod; if (mod) { strncpy(e, line, mod); e += mod; line += mod; } if (!ondeck) { *e = 0; setError(MSG_RexpModifier, e - mod - 1); return false; } ondeck = false; continue; } /* modifier */ ondeck = true; was_ques = false; } /* loop over chars in the pattern */ *e = 0; *split = line; if (ebmuck) { if (cc) { setError(MSG_NoBracket); return false; } if (paren) { setError(MSG_NoParen); return false; } if (isleft) { cw->lhs_yes = true; strcpy(cw->lhs, re); } else { cw->rhs_yes = true; strcpy(cw->rhs, re); } } debugPrint(7, "%s regexp %s", (isleft ? "search" : "replace"), re); return true; } /* regexpCheck */ /* regexp variables */ static int re_count; static int re_vector[11 * 3]; static pcre *re_cc; /* compiled */ static bool re_utf8 = true; static void regexpCompile(const char *re, bool ci) { static signed char try8 = 0; /* 1 is utf8 on, -1 is utf8 off */ const char *re_error; int re_offset; int re_opt; top: /* Do we need PCRE_NO_AUTO_CAPTURE? */ re_opt = 0; if (ci) re_opt |= PCRE_CASELESS; if (re_utf8) { if (cons_utf8 && !cw->binMode && try8 >= 0) { if (try8 == 0) { const char *s = getenv("PCREUTF8"); if (s && stringEqual(s, "off")) { try8 = -1; goto top; } } try8 = 1; re_opt |= PCRE_UTF8; } } re_cc = pcre_compile(re, re_opt, &re_error, &re_offset, 0); if (!re_cc && try8 > 0 && strstr(re_error, "PCRE_UTF8 support")) { i_puts(MSG_PcreUtf8); try8 = -1; goto top; } if (!re_cc && try8 > 0 && strstr(re_error, "invalid UTF-8 string")) { i_puts(MSG_BadUtf8String); } if (!re_cc) setError(MSG_RexpError, re_error); } /* regexpCompile */ /* Get the start or end of a range. * Pass the line containing the address. */ static bool getRangePart(const char *line, int *lineno, const char **split) { /* result parameters */ int ln = cw->dot; /* this is where we start */ char first = *line; if (isdigitByte(first)) { ln = strtol(line, (char **)&line, 10); } else if (first == '.') { ++line; /* ln is already set */ } else if (first == '$') { ++line; ln = cw->dol; } else if (first == '\'' && islowerByte(line[1])) { ln = cw->labels[line[1] - 'a']; if (!ln) { setError(MSG_NoLabel, line[1]); return false; } line += 2; } else if (first == '/' || first == '?') { char *re; /* regular expression */ bool ci = caseInsensitive; signed char incr; /* forward or back */ /* Don't look through an empty buffer. */ if (cw->dol == 0) { setError(MSG_EmptyBuffer); return false; } if (!regexpCheck(line, true, true, &re, &line)) return false; if (*line == first) { ++line; if (*line == 'i') ci = true, ++line; } /* second delimiter */ regexpCompile(re, ci); if (!re_cc) return false; /* We should probably study the pattern, if the file is large. * But then again, it's probably not worth it, * since the expressions are simple, and the lines are short. */ incr = (first == '/' ? 1 : -1); while (true) { char *subject; ln += incr; if (ln > cw->dol) ln = 1; if (ln == 0) ln = cw->dol; subject = (char *)fetchLine(ln, 1); re_count = pcre_exec(re_cc, 0, subject, pstLength((pst) subject) - 1, 0, 0, re_vector, 33); free(subject); // An error in evaluation is treated like text not found. // This usually happens because this particular line has bad binary, not utf8. if (re_count < -1 && pcre_utf8_error_stop) { pcre_free(re_cc); setError(MSG_RexpError2, ln); return (globSub = false); } if (re_count >= 0) break; if (ln == cw->dot) { pcre_free(re_cc); setError(MSG_NotFound); return false; } } /* loop over lines */ pcre_free(re_cc); /* and ln is the line that matches */ } /* search pattern */ /* Now add or subtract from this number */ while ((first = *line) == '+' || first == '-') { int add = 1; ++line; if (isdigitByte(*line)) add = strtol(line, (char **)&line, 10); ln += (first == '+' ? add : -add); } if (ln > cw->dol) { setError(MSG_LineHigh); return false; } if (ln < 0) { setError(MSG_LineLow); return false; } *lineno = ln; *split = line; return true; } /* getRangePart */ /* Apply a regular expression to each line, and then execute * a command for each matching, or nonmatching, line. * This is the global feature, g/re/p, which gives us the word grep. */ static bool doGlobal(const char *line) { int gcnt = 0; /* global count */ bool ci = caseInsensitive; bool change; char delim = *line; struct lineMap *t; char *re; /* regular expression */ int i, origdot, yesdot, nodot; if (!delim) { setError(MSG_RexpMissing, icmd); return false; } if (!regexpCheck(line, true, true, &re, &line)) return false; if (*line != delim) { setError(MSG_NoDelimit); return false; } ++line; if (*line == 'i') ++line, ci = true; skipWhite(&line); /* clean up any previous global flags. * Also get ready for javascript, as in g/<->/ i=+ * which I use in web based gmail to clear out spam etc. */ for (t = cw->map + 1; t->text; ++t) t->gflag = false; /* Find the lines that match the pattern. */ regexpCompile(re, ci); if (!re_cc) return false; for (i = startRange; i <= endRange; ++i) { char *subject = (char *)fetchLine(i, 1); re_count = pcre_exec(re_cc, 0, subject, pstLength((pst) subject) - 1, 0, 0, re_vector, 33); free(subject); if (re_count < -1 && pcre_utf8_error_stop) { pcre_free(re_cc); setError(MSG_RexpError2, i); return false; } if (re_count < 0 && cmd == 'v' || re_count >= 0 && cmd == 'g') { ++gcnt; cw->map[i].gflag = true; } } /* loop over line */ pcre_free(re_cc); if (!gcnt) { setError((cmd == 'v') + MSG_NoMatchG); return false; } /* apply the subcommand to every line with a star */ globSub = true; setError(-1); if (!*line) line = "p"; origdot = cw->dot; yesdot = nodot = 0; change = true; while (gcnt && change) { change = false; /* kinda like bubble sort */ for (i = 1; i <= cw->dol; ++i) { t = cw->map + i; if (!t->gflag) continue; if (intFlag) goto done; change = true, --gcnt; t->gflag = false; cw->dot = i; /* so we can run the command at this line */ if (runCommand(line)) { yesdot = cw->dot; /* try this line again, in case we deleted or moved it somewhere else */ --i; } else { /* error in subcommand might turn global flag off */ if (!globSub) { nodot = i, yesdot = 0; goto done; } /* serious error */ } /* subcommand succeeds or fails */ } /* loop over lines */ } /* loop making changes */ done: globSub = false; /* yesdot could be 0, even on success, if all lines are deleted via g/re/d */ if (yesdot || !cw->dol) { cw->dot = yesdot; if ((cmd == 's' || cmd == 'i') && subPrint == 1) printDot(); } else if (nodot) { cw->dot = nodot; } else { cw->dot = origdot; if (!errorMsg[0]) setError(MSG_NotModifiedG); } if (!errorMsg[0] && intFlag) setError(MSG_Interrupted); return (errorMsg[0] == 0); } /* doGlobal */ static void fieldNumProblem(int desc, char c, int n, int nt, int nrt) { if (!nrt) { setError(MSG_NoInputFields + desc); return; } if (!n) { setError(MSG_ManyInputFields + desc, c, c, nt); return; } if (nt > 1) setError(MSG_InputRange, n, c, c, nt); else setError(MSG_InputRange2, n, c, c); } /* fieldNumProblem */ /* Perform a substitution on a given line. * The lhs has been compiled, and the rhs is passed in for replacement. * Refer to the static variable re_cc for the compiled lhs. * Return true for a replacement, false for no replace, and -1 for a problem. */ static char *replaceString; static int replaceStringLength; static char *replaceStringEnd; static int replaceText(const char *line, int len, const char *rhs, bool ebmuck, int nth, bool global, int ln) { int offset = 0, lastoffset, instance = 0; int span; char *r; int rlen; const char *s = line, *s_end, *t; char c, d; r = initString(&rlen); while (true) { /* find the next match */ re_count = pcre_exec(re_cc, 0, line, len, offset, 0, re_vector, 33); if (re_count < -1 && (pcre_utf8_error_stop || startRange == endRange)) { setError(MSG_RexpError2, ln); nzFree(r); return -1; } if (re_count < 0) break; ++instance; /* found another match */ lastoffset = offset; offset = re_vector[1]; /* ready for next iteration */ if (offset == lastoffset && (nth > 1 || global)) { setError(MSG_ManyEmptyStrings); nzFree(r); return -1; } if (!global &&instance != nth) continue; /* copy up to the match point */ s_end = line + re_vector[0]; span = s_end - s; stringAndBytes(&r, &rlen, s, span); s = line + offset; /* Now copy over the rhs */ /* Special case lc mc uc */ if (ebmuck && (rhs[0] == 'l' || rhs[0] == 'm' || rhs[0] == 'u') && rhs[1] == 'c' && rhs[2] == 0) { int savelen = rlen; span = re_vector[1] - re_vector[0]; stringAndBytes(&r, &rlen, line + re_vector[0], span); caseShift(r + savelen, rhs[0]); if (!global) break; continue; } /* copy rhs, watching for $n */ t = rhs; while (c = *t) { d = t[1]; if (c == '\\') { t += 2; if (d == '$') { stringAndChar(&r, &rlen, d); continue; } if (d == 'n') { stringAndChar(&r, &rlen, '\n'); continue; } if (d == 't') { stringAndChar(&r, &rlen, '\t'); continue; } if (d == 'b') { stringAndChar(&r, &rlen, '\b'); continue; } if (d == 'r') { stringAndChar(&r, &rlen, '\r'); continue; } if (d == 'f') { stringAndChar(&r, &rlen, '\f'); continue; } if (d == 'a') { stringAndChar(&r, &rlen, '\a'); continue; } if (d >= '0' && d <= '7') { int octal = d - '0'; d = *t; if (d >= '0' && d <= '7') { ++t; octal = 8 * octal + d - '0'; d = *t; if (d >= '0' && d <= '7') { ++t; octal = 8 * octal + d - '0'; } } stringAndChar(&r, &rlen, octal); continue; } /* octal */ if (!ebmuck) stringAndChar(&r, &rlen, '\\'); stringAndChar(&r, &rlen, d); continue; } /* backslash */ if (c == '$' && isdigitByte(d)) { int y, z; t += 2; d -= '0'; if (d > re_count) continue; y = re_vector[2 * d]; z = re_vector[2 * d + 1]; if (y < 0) continue; span = z - y; stringAndBytes(&r, &rlen, line + y, span); continue; } stringAndChar(&r, &rlen, c); ++t; } if (!global) break; } /* loop matching the regular expression */ if (!instance) { nzFree(r); return false; } if (!global &&instance < nth) { nzFree(r); return false; } /* We got a match, copy the last span. */ s_end = line + len; span = s_end - s; stringAndBytes(&r, &rlen, s, span); replaceString = r; replaceStringLength = rlen; return true; } /* replaceText */ static void findField(const char *line, int ftype, int n, int *total, int *realtotal, int *tagno, char **href, const struct htmlTag **tagp) { const struct htmlTag *t; int nt = 0; /* number of fields total */ int nrt = 0; /* the real total, for input fields */ int nm = 0; /* number match */ int j; const char *s, *ss; char *h, *nmh; char c; static const char urlok[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890,./?@#%&-_+=:~"; if (href) *href = 0; if (tagp) *tagp = 0; if (cw->browseMode) { s = line; while ((c = *s) != '\n') { ++s; if (c != InternalCodeChar) continue; j = strtol(s, (char **)&s, 10); if (!ftype) { if (*s != '{') continue; ++nt, ++nrt; if (n == nt || n < 0) nm = j; if (!n) { if (!nm) nm = j; else nm = -1; } } else { if (*s != '<') continue; if (n > 0) { ++nt, ++nrt; if (n == nt) nm = j; continue; } if (n < 0) { nm = j; ++nt, ++nrt; continue; } ++nrt; t = tagList[j]; if (ftype == 1 && t->itype <= INP_SUBMIT) continue; if (ftype == 2 && t->itype > INP_SUBMIT) continue; ++nt; if (!nm) nm = j; else nm = -1; } } /* loop over line */ } if (nm < 0) nm = 0; if (total) *total = nrt; if (realtotal) *realtotal = nt; if (tagno) *tagno = nm; if (!ftype && nm) { t = tagList[nm]; if (href) *href = cloneString(t->href); if (tagp) *tagp = t; if (href && isJSAlive && t->jv) { /* defer to the java variable for the reference */ char *jh = get_property_url(t->jv, false); if (jh) { if (!*href || !stringEqual(*href, jh)) { nzFree(*href); *href = jh; } } } } if (nt || ftype) return; /* Second time through, maybe the url is in plain text. */ nmh = 0; s = line; while (true) { /* skip past weird characters */ while ((c = *s) != '\n') { if (strchr(urlok, c)) break; ++s; } if (c == '\n') break; ss = s; while (strchr(urlok, *s)) ++s; h = pullString1(ss, s); unpercentURL(h); if (!isURL(h)) { free(h); continue; } ++nt; if (n == nt || n < 0) { nm = nt; nzFree(nmh); nmh = h; continue; } if (n) { free(h); continue; } if (!nm) { nm = nt; nmh = h; continue; } free(h); nm = -1; free(nmh); nmh = 0; } /* loop over line */ if (nm < 0) nm = 0; if (total) *total = nt; if (realtotal) *realtotal = nt; if (href) *href = nmh; else nzFree(nmh); } /* findField */ static void findInputField(const char *line, int ftype, int n, int *total, int *realtotal, int *tagno) { findField(line, ftype, n, total, realtotal, tagno, 0, 0); } /* findInputField */ /* Substitute text on the lines in startRange through endRange. * We could be changing the text in an input field. * If so, we'll call infReplace(). * Also, we might be indirectory mode, whence we must rename the file. * This is a complicated function! * The return can be true or false, with the usual meaning, * but also a return of -1, which is failure, * and an indication that we need to abort any g// in progress. * It's a serious problem. */ static int substituteText(const char *line) { int whichField = 0; bool bl_mode = false; /* running the bl command */ bool g_mode = false; /* s/x/y/g */ bool ci = caseInsensitive; bool save_nlMode; char c, *s, *t; int nth = 0; /* s/x/y/7 */ int lastSubst = 0; /* last successful substitution */ char *re; /* the parsed regular expression */ int ln; /* line number */ int j, linecount, slashcount, nullcount, tagno, total, realtotal; char lhs[MAXRE], rhs[MAXRE]; struct lineMap *mptr; replaceString = 0; subPrint = 1; /* default is to print the last line substituted */ re_cc = 0; if (stringEqual(line, "`bl")) bl_mode = true, breakLineSetup(); if (!bl_mode) { /* watch for s2/x/y/ for the second input field */ if (isdigitByte(*line)) whichField = strtol(line, (char **)&line, 10); else if (*line == '$') whichField = -1, ++line; if (!*line) { setError(MSG_RexpMissing2, icmd); return -1; } if (cw->dirMode && !dirWrite) { setError(MSG_DirNoWrite); return -1; } if (!regexpCheck(line, true, true, &re, &line)) return -1; strcpy(lhs, re); if (!*line) { setError(MSG_NoDelimit); return -1; } if (!regexpCheck(line, false, true, &re, &line)) return -1; strcpy(rhs, re); if (*line) { /* third delimiter */ ++line; subPrint = 0; while (c = *line) { if (c == 'g') { g_mode = true; ++line; continue; } if (c == 'i') { ci = true; ++line; continue; } if (c == 'p') { subPrint = 2; ++line; continue; } if (isdigitByte(c)) { if (nth) { setError(MSG_SubNumbersMany); return -1; } nth = strtol(line, (char **)&line, 10); continue; } /* number */ setError(MSG_SubSuffixBad); return -1; } /* loop gathering suffix flags */ if (g_mode && nth) { setError(MSG_SubNumberG); return -1; } } /* closing delimiter */ if (nth == 0 && !g_mode) nth = 1; regexpCompile(lhs, ci); if (!re_cc) return -1; } else { subPrint = 0; } /* bl_mode or not */ if (!globSub) setError(-1); for (ln = startRange; ln <= endRange && !intFlag; ++ln) { char *p; int len; replaceString = 0; p = (char *)fetchLine(ln, -1); len = pstLength((pst) p); if (bl_mode) { int newlen; if (!breakLine(p, len, &newlen)) { /* you just should never be here */ setError(MSG_BreakLong, 0); nzFree(breakLineResult); breakLineResult = 0; return -1; } /* empty line is not allowed */ if (!newlen) breakLineResult[newlen++] = '\n'; /* perhaps no changes were made */ if (newlen == len && !memcmp(p, breakLineResult, len)) { nzFree(breakLineResult); breakLineResult = 0; continue; } replaceString = breakLineResult; /* But the regular substitute doesn't have the \n on the end. * We need to make this one conform. */ replaceStringLength = newlen - 1; } else { if (cw->browseMode) { char search[20]; char searchend[4]; undoPush(); findInputField(p, 1, whichField, &total, &realtotal, &tagno); if (!tagno) { fieldNumProblem(0, 'i', whichField, total, realtotal); continue; } sprintf(search, "%c%d<", InternalCodeChar, tagno); sprintf(searchend, "%c0>", InternalCodeChar); /* Ok, if the line contains a null, this ain't gonna work. */ s = strstr(p, search); if (!s) continue; s = strchr(s, '<') + 1; t = strstr(s, searchend); if (!t) continue; j = replaceText(s, t - s, rhs, true, nth, g_mode, ln); } else { j = replaceText(p, len - 1, rhs, true, nth, g_mode, ln); } if (j < 0) goto abort; if (!j) continue; } /* Did we split this line into many lines? */ replaceStringEnd = replaceString + replaceStringLength; linecount = slashcount = nullcount = 0; for (t = replaceString; t < replaceStringEnd; ++t) { c = *t; if (c == '\n') ++linecount; if (c == 0) ++nullcount; if (c == '/') ++slashcount; } if (cw->sqlMode) { if (linecount) { setError(MSG_ReplaceNewline); goto abort; } if (nullcount) { setError(MSG_ReplaceNull); goto abort; } *replaceStringEnd = '\n'; if (!sqlUpdateRow((pst) p, len - 1, (pst) replaceString, replaceStringLength)) goto abort; } if (cw->dirMode) { /* move the file, then update the text */ char src[ABSPATH], *dest; if (slashcount + nullcount + linecount) { setError(MSG_DirNameBad); goto abort; } p[len - 1] = 0; /* temporary */ t = makeAbsPath(p); p[len - 1] = '\n'; if (!t) goto abort; strcpy(src, t); *replaceStringEnd = 0; dest = makeAbsPath(replaceString); if (!dest) goto abort; if (!stringEqual(src, dest)) { if (fileTypeByName(dest, true)) { setError(MSG_DestFileExists); goto abort; } if (rename(src, dest)) { setError(MSG_NoRename, dest); goto abort; } } /* source and dest are different */ } if (cw->browseMode) { if (nullcount) { setError(MSG_InputNull2); goto abort; } if (linecount) { setError(MSG_InputNewline2); goto abort; } *replaceStringEnd = 0; /* We're managing our own printing, so leave notify = 0 */ if (!infReplace(tagno, replaceString, false)) goto abort; undoCompare(); cw->undoable = false; } else { *replaceStringEnd = '\n'; if (!linecount) { /* normal substitute */ undoPush(); mptr = cw->map + ln; mptr->text = allocMem(replaceStringLength + 1); memcpy(mptr->text, replaceString, replaceStringLength + 1); if (cw->dirMode || cw->sqlMode) { undoCompare(); cw->undoable = false; } } else { /* Becomes many lines, this is the tricky case. */ save_nlMode = cw->nlMode; delText(ln, ln); addTextToBuffer((pst) replaceString, replaceStringLength + 1, ln - 1, false); cw->nlMode = save_nlMode; endRange += linecount; ln += linecount; /* There's a quirk when adding newline to the end of a buffer * that had no newline at the end before. */ if (cw->nlMode && ln == cw->dol && replaceStringEnd[-1] == '\n') { delText(ln, ln); --ln, --endRange; } } } /* browse or not */ if (subPrint == 2) displayLine(ln); lastSubst = ln; nzFree(replaceString); /* we may have just freed the result of a breakline command */ breakLineResult = 0; } /* loop over lines in the range */ if (re_cc) pcre_free(re_cc); if (intFlag) { setError(MSG_Interrupted); return -1; } if (!lastSubst) { if (!globSub) { if (!errorMsg[0]) setError(bl_mode ? MSG_NoChange : MSG_NoMatch); } return false; } cw->dot = lastSubst; if (subPrint == 1 && !globSub) printDot(); return true; abort: if (re_cc) pcre_free(re_cc); nzFree(replaceString); /* we may have just freed the result of a breakline command */ breakLineResult = 0; return -1; } /* substituteText */ /********************************************************************* Implement various two letter commands. Most of these set and clear modes. Return 1 or 0 for success or failure as usual. But return 2 if there is a new command to run. The second parameter is a result parameter, the new command to run. In rare cases we might allocate a new (longer) command line to run, like rf (refresh), which could be a long url. *********************************************************************/ static char *allocatedLine = 0; static int twoLetter(const char *line, const char **runThis) { static char shortline[60]; char c; bool rc, ub; int i, n; *runThis = shortline; if (stringEqual(line, "qt")) ebClose(0); if (line[0] == 'd' && line[1] == 'b' && isdigitByte(line[2]) && !line[3]) { debugLevel = line[2] - '0'; if (debugLevel >= 4) curl_easy_setopt(http_curl_handle, CURLOPT_VERBOSE, 1); else curl_easy_setopt(http_curl_handle, CURLOPT_VERBOSE, 0); return true; } if (stringEqual(line, "bw")) { cw->changeMode = false; cw->quitMode = true; return true; } if (stringEqual(line, "rr")) { cmd = 'e'; if (!cw->browseMode) { setError(MSG_NoBrowse); return false; } if (!isJSAlive) { setError(MSG_JavaOff); return false; } rerender(true); return true; } if (line[0] == 'u' && line[1] == 'a' && isdigitByte(line[2]) && !line[3]) { char *t = userAgents[line[2] - '0']; cmd = 'e'; if (!t) { setError(MSG_NoAgent, line[2]); return false; } currentAgent = t; curl_easy_setopt(http_curl_handle, CURLOPT_USERAGENT, currentAgent); if (helpMessagesOn || debugLevel >= 1) puts(currentAgent); return true; } if (stringEqual(line, "re") || stringEqual(line, "rea")) { undoCompare(); cw->undoable = false; cmd = 'e'; /* so error messages are printed */ rc = setupReply(line[2] == 'a'); if (rc && cw->browseMode) { ub = false; cw->browseMode = false; goto et_go; } return rc; } /* ^^^^ is the same as ^4 */ if (line[0] == '^' && line[1] == '^') { const char *t = line + 2; while (*t == '^') ++t; if (!*t) { sprintf(shortline, "^%ld", t - line); return 2; } } if (line[0] == 'l' && line[1] == 's') { char lsmode[12]; bool setmode = false; char *file, *path, *t; const char *s = line + 2; cmd = 'e'; /* so error messages are printed */ skipWhite(&s); if (*s == '=') { setmode = true; ++s; skipWhite(&s); } else { if (!cw->dirMode) { setError(MSG_NoDir); return false; } if (cw->dot == 0) { setError(MSG_AtLine0); return false; } } if (!lsattrChars(s, lsmode)) { setError(MSG_LSBadChar); return false; } if (setmode) { strcpy(lsformat, lsmode); return true; } /* default ls mode is size time */ if (!lsmode[0]) strcpy(lsmode, "st"); file = (char *)fetchLine(cw->dot, -1); t = strchr(file, '\n'); if (!t) i_printfExit(MSG_NoNlOnDir, file); *t = 0; path = makeAbsPath(file); *t = '\n'; if (!path) t = emptyString; else t = lsattr(path, lsmode); if (*t) puts(t); else i_puts(MSG_Inaccess); return true; } if (line[0] == 'c' && line[1] == 'd') { c = line[2]; if (!c || isspaceByte(c)) { const char *t = line + 2; skipWhite(&t); c = *t; cmd = 'e'; /* so error messages are printed */ if (!c) { char cwdbuf[ABSPATH]; pwd: if (!getcwd(cwdbuf, sizeof(cwdbuf))) { setError(c ? MSG_CDGetError : MSG_CDSetError); return false; } puts(cwdbuf); return true; } if (!envFile(t, &t)) return false; if (!chdir(t)) goto pwd; setError(MSG_CDInvalid); return false; } } if (line[0] == 'p' && line[1] == 'b') { rc = playBuffer(line, NULL); if (rc == 2) goto no_action; return rc; } if (stringEqual(line, "rf")) { cmd = 'e'; if (!cw->fileName) { setError(MSG_NoRefresh); return false; } if (cw->browseMode) cmd = 'b'; noStack = true; allocatedLine = allocMem(strlen(cw->fileName) + 3); sprintf(allocatedLine, "%c %s", cmd, cw->fileName); debrowseSuffix(allocatedLine); *runThis = allocatedLine; return 2; } if (stringEqual(line, "shc")) { if (!cw->sqlMode) { setError(MSG_NoDB); return false; } showColumns(); return true; } if (stringEqual(line, "shf")) { if (!cw->sqlMode) { setError(MSG_NoDB); return false; } showForeign(); return true; } if (stringEqual(line, "sht")) { if (!ebConnect()) return false; return showTables(); } if (stringEqual(line, "jdb")) { char *cxbuf; int cxbuflen; cmd = 'e'; if (!cw->browseMode) { setError(MSG_NoBrowse); return false; } if (!isJSAlive) { setError(MSG_JavaOff); return false; } jexmode = true; jSyncup(); return true; } if (stringEqual(line, "ub") || stringEqual(line, "et")) { ub = (line[0] == 'u'); rc = true; cmd = 'e'; if (!cw->browseMode) { setError(MSG_NoBrowse); return false; } undoCompare(); cw->undoable = false; cw->browseMode = false; if (ub) { debrowseSuffix(cw->fileName); cw->nlMode = cw->rnlMode; cw->dot = cw->r_dot, cw->dol = cw->r_dol; memcpy(cw->labels, cw->r_labels, sizeof(cw->labels)); freeWindowLines(cw->map); cw->map = cw->r_map; cw->r_map = 0; } else { et_go: for (i = 1; i <= cw->dol; ++i) removeHiddenNumbers(cw->map[i].text, '\n'); freeWindowLines(cw->r_map); cw->r_map = 0; } freeTags(cw); delTimers(cw); freeJavaContext(cw); cw->jcx = NULL; nzFree(cw->dw); cw->dw = 0; nzFree(cw->ft); cw->ft = 0; nzFree(cw->hbase); cw->hbase = 0; nzFree(cw->fd); cw->fd = 0; nzFree(cw->fk); cw->fk = 0; nzFree(cw->mailInfo); cw->mailInfo = 0; if (ub) fileSize = apparentSize(context, false); return rc; } if (stringEqual(line, "f/") || stringEqual(line, "w/")) { char *t; cmd = line[0]; if (!cw->fileName) { setError(MSG_NoRefresh); return false; } t = strrchr(cw->fileName, '/'); if (!t) { setError(MSG_NoSlash); return false; } ++t; if (!*t) { setError(MSG_YesSlash); return false; } t = getFileURL(cw->fileName, false); allocatedLine = allocMem(strlen(t) + 8); /* ` prevents wildcard expansion, which normally happens on an f command */ sprintf(allocatedLine, "%c `%s", cmd, t); *runThis = allocatedLine; return 2; } if (line[0] == 'f' && line[2] == 0 && (line[1] == 'd' || line[1] == 'k' || line[1] == 't')) { const char *s; int t; cmd = 'e'; if (!cw->browseMode) { setError(MSG_NoBrowse); return false; } if (line[1] == 't') s = cw->ft, t = MSG_NoTitle; if (line[1] == 'd') s = cw->fd, t = MSG_NoDesc; if (line[1] == 'k') s = cw->fk, t = MSG_NoKeywords; if (s) puts(s); else i_puts(t); return true; } if (line[0] == 's' && line[1] == 'm') { const char *t = line + 2; bool dosig = true; int account = 0; cmd = 'e'; if (*t == '-') { dosig = false; ++t; } if (isdigitByte(*t)) account = strtol(t, (char **)&t, 10); if (!*t) { /* send mail */ return sendMailCurrent(account, dosig); } else { setError(MSG_SMBadChar); return false; } } if (stringEqual(line, "sg")) { searchStringsAll = true; if (helpMessagesOn) i_puts(MSG_SubGlobal); return true; } if (stringEqual(line, "sl")) { searchStringsAll = false; if (helpMessagesOn) i_puts(MSG_SubLocal); return true; } if (stringEqual(line, "ci")) { caseInsensitive = true; if (helpMessagesOn) i_puts(MSG_CaseIns); return true; } if (stringEqual(line, "cs")) { caseInsensitive = false; if (helpMessagesOn) i_puts(MSG_CaseSen); return true; } if (stringEqual(line, "dr")) { dirWrite = 0; if (helpMessagesOn) i_puts(MSG_DirReadonly); return true; } if (stringEqual(line, "dw")) { dirWrite = 1; if (helpMessagesOn) i_puts(MSG_DirWritable); return true; } if (stringEqual(line, "dx")) { dirWrite = 2; if (helpMessagesOn) i_puts(MSG_DirX); return true; } if (stringEqual(line, "hr")) { allowRedirection ^= 1; /* We're doing this manually for now. curl_easy_setopt(http_curl_handle, CURLOPT_FOLLOWLOCATION, allowRedirection); */ if (helpMessagesOn || debugLevel >= 1) i_puts(allowRedirection + MSG_RedirectionOff); return true; } if (stringEqual(line, "pg")) { pluginsOn ^= 1; if (helpMessagesOn || debugLevel >= 1) i_puts(pluginsOn + MSG_PluginsOff); return true; } if (stringEqual(line, "bg")) { #ifdef DOSLIKE puts("download in background not available on Windows at this time."); #else down_bg ^= 1; if (helpMessagesOn || debugLevel >= 1) i_puts(down_bg + MSG_DownForeground); #endif return true; } if (stringEqual(line, "bgl")) { bg_jobs(false); return true; } if (stringEqual(line, "iu")) { iuConvert ^= 1; if (helpMessagesOn || debugLevel >= 1) i_puts(iuConvert + MSG_IUConvertOff); return true; } if (stringEqual(line, "sr")) { sendReferrer ^= 1; /* In case of redirect, let libcurl send URL of redirecting page. */ curl_easy_setopt(http_curl_handle, CURLOPT_AUTOREFERER, sendReferrer); if (helpMessagesOn || debugLevel >= 1) i_puts(sendReferrer + MSG_RefererOff); return true; } if (stringEqual(line, "js")) { allowJS ^= 1; if (helpMessagesOn || debugLevel >= 1) i_puts(allowJS + MSG_JavaOff); return true; } if (stringEqual(line, "bd")) { binaryDetect ^= 1; if (helpMessagesOn || debugLevel >= 1) i_puts(binaryDetect + MSG_BinaryIgnore); return true; } if (stringEqual(line, "rl")) { inputReadLine ^= 1; if (helpMessagesOn || debugLevel >= 1) i_puts(inputReadLine + MSG_InputTTY); return true; } if (stringEqual(line, "lna")) { listNA ^= 1; if (helpMessagesOn || debugLevel >= 1) i_puts(listNA + MSG_ListControl); return true; } if (line[0] == 'f' && line[1] == 'm' && line[2] && strchr("pa", line[2]) && !line[3]) { bool doHelp = helpMessagesOn || debugLevel >= 1; /* Can't support passive/active mode with libcurl, or at least not easily. */ if (line[2] == 'p') { curl_easy_setopt(http_curl_handle, CURLOPT_FTPPORT, NULL); if (doHelp) i_puts(MSG_PassiveMode); } else { curl_easy_setopt(http_curl_handle, CURLOPT_FTPPORT, "-"); if (doHelp) i_puts(MSG_ActiveMode); } /* See "man curl_easy_setopt.3" for info on CURLOPT_FTPPORT. Supplying * "-" makes libcurl select the best IP address for active ftp. */ return true; } if (stringEqual(line, "vs")) { verifyCertificates ^= 1; if (helpMessagesOn || debugLevel >= 1) i_puts(verifyCertificates + MSG_CertifyOff); return true; } if (stringEqual(line, "hf")) { showHiddenFiles ^= 1; if (helpMessagesOn || debugLevel >= 1) i_puts(showHiddenFiles + MSG_HiddenOff); return true; } if (stringEqual(line, "su8")) { re_utf8 ^= 1; if (helpMessagesOn || debugLevel >= 1) i_puts(re_utf8 + MSG_ReAscii); return true; } if (!strncmp(line, "ds=", 3)) { if (!line[3]) { if (!dbarea || !*dbarea) { i_puts(MSG_DBNoSource); } else { printf("%s", dbarea); if (dblogin) printf(",%s", dblogin); if (dbpw) printf(",%s", dbpw); nl(); } return true; } dbClose(); setDataSource(cloneString(line + 3)); return true; } if (stringEqual(line, "fbc")) { fetchBlobColumns ^= 1; if (helpMessagesOn || debugLevel >= 1) i_puts(MSG_FetchBlobOff + fetchBlobColumns); return true; } if (stringEqual(line, "eo")) { endMarks = 0; if (helpMessagesOn) i_puts(MSG_MarkOff); return true; } if (stringEqual(line, "el")) { endMarks = 1; if (helpMessagesOn) i_puts(MSG_MarkList); return true; } if (stringEqual(line, "ep")) { endMarks = 2; if (helpMessagesOn) i_puts(MSG_MarkOn); return true; } no_action: *runThis = line; return 2; /* no change */ } /* twoLetter */ /* Return the number of unbalanced punctuation marks. * This is used by the next routine. */ static void unbalanced(char c, char d, int ln, int *back_p, int *for_p) { /* result parameters */ char *t, *open; char *p = (char *)fetchLine(ln, 1); bool change; int backward, forward; change = true; while (change) { change = false; open = 0; for (t = p; *t != '\n'; ++t) { if (*t == c) open = t; if (*t == d && open) { *open = 0; *t = 0; change = true; open = 0; } } } backward = forward = 0; for (t = p; *t != '\n'; ++t) { if (*t == c) ++forward; if (*t == d) ++backward; } free(p); *back_p = backward; *for_p = forward; } /* unbalanced */ /* Find the line that balances the unbalanced punctuation. */ static bool balanceLine(const char *line) { char c, d; /* open and close */ char selected; static char openlist[] = "{([<`"; static char closelist[] = "})]>'"; static const char alllist[] = "{}()[]<>`'"; char *t; int level = 0; int i, direction, forward, backward; if (c = *line) { if (!strchr(alllist, c) || line[1]) { setError(MSG_BalanceChar, alllist); return false; } if (t = strchr(openlist, c)) { d = closelist[t - openlist]; direction = 1; } else { d = c; t = strchr(closelist, d); c = openlist[t - closelist]; direction = -1; } unbalanced(c, d, endRange, &backward, &forward); if (direction > 0) { if ((level = forward) == 0) { setError(MSG_BalanceNoOpen, c); return false; } } else { if ((level = backward) == 0) { setError(MSG_BalanceNoOpen, d); return false; } } } else { /* Look for anything unbalanced, probably a brace. */ for (i = 0; i <= 2; ++i) { c = openlist[i]; d = closelist[i]; unbalanced(c, d, endRange, &backward, &forward); if (backward && forward) { setError(MSG_BalanceAmbig, c, d, c, d); return false; } level = backward + forward; if (!level) continue; direction = 1; if (backward) direction = -1; break; } if (!level) { setError(MSG_BalanceNothing); return false; } } /* explicit character passed in, or look for one */ selected = (direction > 0 ? c : d); /* search for the balancing line */ i = endRange; while ((i += direction) > 0 && i <= cw->dol) { unbalanced(c, d, i, &backward, &forward); if (direction > 0 && backward >= level || direction < 0 && forward >= level) { cw->dot = i; printDot(); return true; } level += (forward - backward) * direction; } /* loop over lines */ setError(MSG_Unbalanced, selected); return false; } /* balanceLine */ /* Unfold the buffer into one long, allocated string. */ bool unfoldBufferW(const struct ebWindow * w, bool cr, char **data, int *len) { char *buf; int l, ln; int size = apparentSizeW(w, false); if (size < 0) return false; if (w->dirMode) { setError(MSG_SessionDir, context); return false; } if (cr) size += w->dol; /* a few bytes more, just for safety */ buf = allocMem(size + 4); *data = buf; for (ln = 1; ln <= w->dol; ++ln) { pst line = w->map[ln].text; l = pstLength(line) - 1; if (l) { memcpy(buf, line, l); buf += l; } if (cr) { *buf++ = '\r'; if (l && buf[-2] == '\r') --buf, --size; } *buf++ = '\n'; } /* loop over lines */ if (w->dol && w->nlMode) { if (cr) --size; } *len = size; (*data)[size] = 0; return true; } /* unfoldBufferW */ bool unfoldBuffer(int cx, bool cr, char **data, int *len) { const struct ebWindow *w = sessionList[cx].lw; return unfoldBufferW(w, cr, data, len); } /* unfoldBuffer */ static char *showLinks(void) { int a_l; char *a = initString(&a_l); bool click, dclick; char c, *p, *s, *t, *q, *line, *h, *h2; int j, k = 0, tagno; const struct htmlTag *tag; if (cw->browseMode && endRange) { line = (char *)fetchLine(endRange, -1); for (p = line; (c = *p) != '\n'; ++p) { if (c != InternalCodeChar) continue; if (!isdigitByte(p[1])) continue; j = strtol(p + 1, &s, 10); if (*s != '{') continue; p = s; ++k; findField(line, 0, k, 0, 0, &tagno, &h, &tag); if (tagno != j) continue; /* should never happen */ click = tagHandler(tagno, "onclick"); dclick = tagHandler(tagno, "ondblclick"); /* find the closing brace */ /* It might not be there, could be on the next line. */ for (s = p + 1; (c = *s) != '\n'; ++s) if (c == InternalCodeChar && s[1] == '0' && s[2] == '}') break; /* Ok, everything between p and s exclusive is the description */ if (!h) h = emptyString; if (stringEqual(h, "#")) { nzFree(h); h = emptyString; } if (memEqualCI(h, "mailto:", 7)) { stringAndBytes(&a, &a_l, p + 1, s - p - 1); stringAndChar(&a, &a_l, ':'); s = h + 7; t = s + strcspn(s, "?"); stringAndBytes(&a, &a_l, s, t - s); stringAndChar(&a, &a_l, '\n'); nzFree(h); continue; } /* mail link */ stringAndString(&a, &a_l, "
    \n"); } else if (!*h && (click | dclick)) { char buf[20]; sprintf(buf, "%s>\n", click ? "onclick" : "ondblclick"); stringAndString(&a, &a_l, buf); } else { if (*h) { h2 = htmlEscape(h); stringAndString(&a, &a_l, h2); nzFree(h2); } stringAndString(&a, &a_l, ">\n"); } nzFree(h); /* next line is the description of the bookmark */ h = pullString(p + 1, s - p - 1); h2 = htmlEscape(h); stringAndString(&a, &a_l, h2); stringAndString(&a, &a_l, "\n\n"); nzFree(h); nzFree(h2); } /* loop looking for hyperlinks */ } if (!a_l) { /* nothing found yet */ if (!cw->fileName) { setError(MSG_NoFileName); return 0; } h = htmlEscape(cw->fileName); debrowseSuffix(h); stringAndString(&a, &a_l, "
    \n"); /* get text from the html title if you can */ s = cw->ft; if (s && *s) { h2 = htmlEscape(s); stringAndString(&a, &a_l, h2); nzFree(h2); } else { /* no title - getting the text from the url, very kludgy */ s = (char *)getDataURL(h); if (!s || !*s) s = h; t = findHash(s); if (t) *t = 0; t = s + strcspn(s, "\1?"); if (t > s && t[-1] == '/') --t; *t = 0; q = strrchr(s, '/'); if (q && q < t) s = q + 1; stringAndBytes(&a, &a_l, s, t - s); } stringAndString(&a, &a_l, "\n\n"); nzFree(h); } removeHiddenNumbers(a, 0); return a; } /* showLinks */ static bool lineHasTag(const char *p, const char *s) { const struct htmlTag *t; char c; int j; while ((c = *p++) != '\n') { if (c != InternalCodeChar) continue; j = strtol(p, (char **)&p, 10); t = tagList[j]; if (t->id && stringEqual(t->id, s)) return true; if (t->action == TAGACT_A && t->name && stringEqual(t->name, s)) return true; } return false; } /* lineHasTag */ /********************************************************************* Run the entered edbrowse command. This is indirectly recursive, as in g/x/d Pass in the ed command, and return success or failure. We assume it has been turned into a C string. This means no embeded nulls. If you want to use null in a search or substitute, use \0. *********************************************************************/ bool runCommand(const char *line) { int i, j, n; int writeMode = O_TRUNC; struct ebWindow *w = NULL; const struct htmlTag *tag = NULL; /* event variables */ bool nogo = true, rc = true; bool postSpace = false, didRange = false; char first; int cx = 0; /* numeric suffix as in s/x/y/3 or w2 */ int tagno; const char *s = NULL; static char newline[MAXTTYLINE]; if (allocatedLine) { nzFree(allocatedLine); allocatedLine = 0; } nzFree(currentReferrer); currentReferrer = cloneString(cw->fileName); js_redirects = false; cmd = icmd = 'p'; skipWhite(&line); first = *line; if (!globSub) { madeChanges = false; /* Allow things like comment, or shell escape, but not if we're * in the midst of a global substitute, as in g/x/ !echo hello world */ if (first == '#') return true; if (first == '!') return shellEscape(line + 1); /* Watch for successive q commands. */ lastq = lastqq, lastqq = 0; noStack = false; /* special 2 letter commands - most of these change operational modes */ j = twoLetter(line, &line); if (j != 2) return j; } startRange = endRange = cw->dot; /* default range */ /* Just hit return to read the next line. */ first = *line; if (first == 0) { didRange = true; ++startRange, ++endRange; if (endRange > cw->dol) { setError(MSG_EndBuffer); return false; } } if (first == ',') { didRange = true; ++line; startRange = 1; if (cw->dol == 0) startRange = 0; endRange = cw->dol; } if (first == ';') { didRange = true; ++line; startRange = cw->dot; endRange = cw->dol; } if (first == 'j' || first == 'J') { didRange = true; endRange = startRange + 1; if (endRange > cw->dol) { setError(MSG_EndJoin); return false; } } if (first == '=') { didRange = true; startRange = endRange = cw->dol; } if (first == 'w' || first == 'v' || first == 'g' && line[1] && strchr(valid_delim, line[1])) { didRange = true; startRange = 1; if (cw->dol == 0) startRange = 0; endRange = cw->dol; } if (!didRange) { if (!getRangePart(line, &startRange, &line)) return (globSub = false); endRange = startRange; if (line[0] == ',') { ++line; endRange = cw->dol; /* new default */ first = *line; if (first && strchr(valid_laddr, first)) { if (!getRangePart(line, &endRange, &line)) return (globSub = false); } } } if (endRange < startRange) { setError(MSG_BadRange); return false; } /* change uc into a substitute command, converting the whole line */ skipWhite(&line); first = *line; if ((first == 'u' || first == 'l' || first == 'm') && line[1] == 'c' && line[2] == 0) { sprintf(newline, "s/.*/%cc/", first); line = newline; } /* Breakline is actually a substitution of lines. */ if (stringEqual(line, "bl")) { if (cw->dirMode) { setError(MSG_BreakDir); return false; } if (cw->sqlMode) { setError(MSG_BreakDB); return false; } if (cw->browseMode) { setError(MSG_BreakBrowse); return false; } line = "s`bl"; } /* get the command */ cmd = *line; if (cmd) ++line; else cmd = 'p'; icmd = cmd; if (!strchr(valid_cmd, cmd)) { setError(MSG_UnknownCommand, cmd); return (globSub = false); } first = *line; if (cmd == 'w' && first == '+') writeMode = O_APPEND, first = *++line; if (cw->dirMode && !strchr(dir_cmd, cmd)) { setError(MSG_DirCommand, icmd); return (globSub = false); } if (cw->sqlMode && !strchr(sql_cmd, cmd)) { setError(MSG_DBCommand, icmd); return (globSub = false); } if (cw->browseMode && !strchr(browse_cmd, cmd)) { setError(MSG_BrowseCommand, icmd); return (globSub = false); } if (startRange == 0 && !strchr(zero_cmd, cmd)) { setError(MSG_AtLine0); return (globSub = false); } while (isspaceByte(first)) postSpace = true, first = *++line; if (strchr(spaceplus_cmd, cmd) && !postSpace && first) { s = line; while (isdigitByte(*s)) ++s; if (*s) { setError(MSG_NoSpaceAfter); return (globSub = false); } } if (globSub && !strchr(global_cmd, cmd)) { setError(MSG_GlobalCommand, icmd); return (globSub = false); } /* move/copy destination, the third address */ if (cmd == 't' || cmd == 'm') { if (!first) { destLine = cw->dot; } else { if (!strchr(valid_laddr, first)) { setError(MSG_BadDest); return (globSub = false); } if (!getRangePart(line, &destLine, &line)) return (globSub = false); first = *line; } /* was there something after m or t */ } /* env variable and wild card expansion */ if (strchr("brewf", cmd) && first && !isURL(line) && !isSQL(line)) { if (cmd != 'r' || !cw->sqlMode) { if (!envFile(line, &line)) return false; first = *line; } } if (cmd == 'z') { if (isdigitByte(first)) { last_z = strtol(line, (char **)&line, 10); if (!last_z) last_z = 1; first = *line; } startRange = endRange + 1; endRange = startRange; if (startRange > cw->dol) { startRange = endRange = 0; setError(MSG_LineHigh); return false; } cmd = 'p'; endRange += last_z - 1; if (endRange > cw->dol) endRange = cw->dol; } /* the a+ feature, when you thought you were in append mode */ if (cmd == 'a') { if (stringEqual(line, "+")) ++line, first = 0; else { nzFree(linePending); linePending = 0; } } else { nzFree(linePending); linePending = 0; } if (first && strchr(nofollow_cmd, cmd)) { setError(MSG_TextAfter, icmd); return (globSub = false); } if (cmd == 'h') { showError(); return true; } if (cmd == 'H') { if (helpMessagesOn ^= 1) if (debugLevel >= 1) i_puts(MSG_HelpOn); return true; } if (cmd == 'X') { cw->dot = endRange; return true; } if (strchr("Llpn", cmd)) { for (i = startRange; i <= endRange; ++i) { displayLine(i); cw->dot = i; if (intFlag) break; } return true; } if (cmd == '=') { printf("%d\n", endRange); return true; } if (cmd == 'B') { return balanceLine(line); } if (cmd == 'u') { struct ebWindow *uw = &undoWindow; struct lineMap *swapmap; if (!cw->undoable) { setError(MSG_NoUndo); return false; } /* swap, so we can undo our undo, if need be */ i = uw->dot, uw->dot = cw->dot, cw->dot = i; i = uw->dol, uw->dol = cw->dol, cw->dol = i; for (j = 0; j < 26; ++j) { i = uw->labels[j], uw->labels[j] = cw->labels[j], cw->labels[j] = i; } swapmap = uw->map, uw->map = cw->map, cw->map = swapmap; return true; } if (cmd == 'k') { if (!islowerByte(first) || line[1]) { setError(MSG_EnterKAZ); return false; } if (startRange < endRange) { setError(MSG_RangeLabel); return false; } cw->labels[first - 'a'] = endRange; return true; } /* Find suffix, as in 27,59w2 */ if (!postSpace) { cx = stringIsNum(line); if (!cx) { setError((cmd == '^') ? MSG_Backup0 : MSG_Session0); return false; } if (cx < 0) cx = 0; } if (cmd == 'q') { if (cx) { if (!cxCompare(cx)) return false; if (!cxActive(cx)) return false; } else { cx = context; if (first) { setError(MSG_QAfter); return false; } } saveSubstitutionStrings(); if (!cxQuit(cx, 2)) return false; if (cx != context) return true; /* look around for another active session */ while (true) { if (++cx == MAXSESSION) cx = 1; if (cx == context) ebClose(0); if (!sessionList[cx].lw) continue; cxSwitch(cx, true); return true; } /* loop over sessions */ } if (cmd == 'f') { if (cx) { if (!cxCompare(cx)) return false; if (!cxActive(cx)) return false; s = sessionList[cx].lw->fileName; if (s) printf("%s", s); else i_printf(MSG_NoFile); if (sessionList[cx].lw->binMode) i_printf(MSG_BinaryBrackets); nl(); return true; } /* another session */ if (first) { if (cw->dirMode) { setError(MSG_DirRename); return false; } if (cw->sqlMode) { setError(MSG_TableRename); return false; } nzFree(cw->fileName); cw->fileName = cloneString(line); } s = cw->fileName; if (s) printf("%s", s); else i_printf(MSG_NoFile); if (cw->binMode) i_printf(MSG_BinaryBrackets); nl(); return true; } if (cmd == 'w') { if (cx) { /* write to another buffer */ if (writeMode == O_APPEND) { setError(MSG_BufferAppend); return false; } return writeContext(cx); } if (!first) line = cw->fileName; if (!line) { setError(MSG_NoFileSpecified); return false; } if (cw->dirMode && stringEqual(line, cw->fileName)) { setError(MSG_NoDirWrite); return false; } if (cw->sqlMode && stringEqual(line, cw->fileName)) { setError(MSG_NoDBWrite); return false; } return writeFile(line, writeMode); } if (cmd == '^') { /* back key, pop the stack */ if (first && !cx) { setError(MSG_ArrowAfter); return false; } if (!cx) cx = 1; while (cx) { struct ebWindow *prev = cw->prev; if (!prev) { setError(MSG_NoPrevious); return false; } saveSubstitutionStrings(); if (!cxQuit(context, 1)) return false; sessionList[context].lw = cw = prev; restoreSubstitutionStrings(cw); --cx; } printDot(); return true; } if (cmd == 'M') { /* move this to another session */ if (first && !cx) { setError(MSG_MAfter); return false; } if (!first) { setError(MSG_NoDestSession); return false; } if (!cw->prev) { setError(MSG_NoBackup); return false; } if (!cxCompare(cx)) return false; if (cxActive(cx) && !cxQuit(cx, 2)) return false; /* If changes were made to this buffer, they are undoable after the move */ undoCompare(); cw->undoable = false; /* Magic with pointers, hang on to your hat. */ sessionList[cx].fw = sessionList[cx].lw = cw; cs->lw = cw->prev; cw->prev = 0; cw = cs->lw; printDot(); return true; } if (cmd == 'A') { char *a; if (!cxQuit(context, 0)) return false; if (!(a = showLinks())) return false; undoCompare(); cw->undoable = cw->changeMode = false; w = createWindow(); w->prev = cw; cw = w; cs->lw = w; rc = addTextToBuffer((pst) a, strlen(a), 0, false); nzFree(a); cw->changeMode = false; fileSize = apparentSize(context, false); return rc; } if (cmd == '<') { /* run a function */ return runEbFunction(line); } /* go to a file in a directory listing */ if (cmd == 'g' && cw->dirMode && !first) { char *p, *dirline, *endline; const struct MIMETYPE *gmt; /* the go mime type */ if (endRange > startRange) { setError(MSG_RangeG); return false; } p = (char *)fetchLine(endRange, -1); j = pstLength((pst) p); --j; p[j] = 0; /* temporary */ dirline = makeAbsPath(p); p[j] = '\n'; cmd = 'e'; if (!dirline) return false; gmt = findMimeByFile(dirline); if (pluginsOn && gmt) { if (gmt->outtype) cmd = 'b'; else if (!gmt->stream) return playBuffer("pb", dirline); } /* I don't think we need to make a copy here. */ line = dirline; first = *line; } if (cmd == 'e') { if (cx) { if (!cxCompare(cx)) return false; cxSwitch(cx, true); return true; } if (!first) { i_printf(MSG_SessionX, context); return true; } /* more e to come */ } /* see if it's a go command */ if (cmd == 'g' && !(cw->sqlMode | cw->binMode)) { char *p, *h; int tagno; bool click, dclick, over; bool jsh, jsgo, jsdead; /* Check to see if g means run an sql command. */ if (!first) { char *rbuf; j = goSelect(&startRange, &rbuf); if (j >= 0) { cmd = 'e'; /* for autoprint of errors */ cw->dot = startRange; if (*rbuf) { int savedol = cw->dol; addTextToBuffer((pst) rbuf, strlen(rbuf), cw->dot, false); nzFree(rbuf); if (cw->dol > savedol) { cw->labels[0] = startRange + 1; cw->labels[1] = startRange + cw->dol - savedol; } cw->dot = startRange; } return j ? true : false; } } /* Now try to go to a hyperlink */ s = line; j = 0; if (first) { if (isdigitByte(first)) j = strtol(s, (char **)&s, 10); else if (first == '$') j = -1, ++s; } if (!*s) { if (cw->sqlMode) { setError(MSG_DBG); return false; } jsh = jsgo = nogo = false; jsdead = !isJSAlive; click = dclick = over = false; cmd = 'b'; if (endRange > startRange) { setError(MSG_RangeG); return false; } p = (char *)fetchLine(endRange, -1); findField(p, 0, j, &n, 0, &tagno, &h, &tag); debugPrint(5, "findField returns %d, %s", tagno, h); if (!h) { fieldNumProblem(1, 'g', j, n, n); return false; } jsh = memEqualCI(h, "javascript:", 11); if (tagno) { over = tagHandler(tagno, "onmouseover"); click = tagHandler(tagno, "onclick"); dclick = tagHandler(tagno, "ondblclick"); } if (click) jsgo = true; jsgo |= jsh; nogo = stringEqual(h, "#"); nogo |= jsh; debugPrint(5, "go %d nogo %d jsh %d dead %d", jsgo, nogo, jsh, jsdead); debugPrint(5, "click %d dclick %d over %d", click, dclick, over); if (jsgo & jsdead) { if (nogo) i_puts(MSG_NJNoAction); else i_puts(MSG_NJGoing); jsgo = jsh = false; } line = allocatedLine = h; first = *line; setError(-1); rc = false; if (jsgo) { /* javascript might update fields */ /* The program might depend on the mouseover code running first */ if (over) { jSyncup(); rc = handlerGoBrowse(tag, "onmouseover"); jSideEffects(); if (newlocation) goto redirect; } } /* This is the only handler where false tells the browser to do something else. */ if (!rc && !jsdead) set_property_string(cw->winobj, "status", h); if (jsgo && click) { jSyncup(); rc = handlerGoBrowse(tag, "onclick"); jSideEffects(); if (newlocation) goto redirect; if (!rc) return true; } if (jsh) { jSyncup(); jsRunScript(cw->winobj, h, 0, 0); jSideEffects(); if (newlocation) goto redirect; return true; } if (nogo) return true; } } if (cmd == 's') { /* Some shorthand, like s,2 to split the line at the second comma */ if (!first) { strcpy(newline, "//%"); line = newline; } else if (strchr(",.;:!?)-\"", first) && (!line[1] || isdigitByte(line[1]) && !line[2])) { char esc[2]; esc[0] = esc[1] = 0; if (first == '.' || first == '?') esc[0] = '\\'; sprintf(newline, "/%s%c +/%c\\n%s%s", esc, first, first, (line[1] ? "/" : ""), line + 1); debugPrint(7, "shorthand regexp %s", newline); line = newline; } first = *line; } scmd = ' '; if ((cmd == 'i' || cmd == 's') && first) { char c; s = line; if (isdigitByte(*s)) cx = strtol(s, (char **)&s, 10); else if (*s == '$') cx = -1, ++s; c = *s; if (c && (strchr(valid_delim, c) || cmd == 'i' && strchr("*browseMode && (cmd == 'i' || cx)) { setError(MSG_NoBrowse); return false; } if (endRange > startRange && cmd == 'i') { setError(MSG_RangeI, c); return false; } if (cmd == 'i' && strchr("?=<*", c)) { char *p; int realtotal; scmd = c; line = s + 1; first = *line; debugPrint(5, "scmd = %c", scmd); cw->dot = endRange; p = (char *)fetchLine(cw->dot, -1); j = 1; if (scmd == '*') j = 2; if (scmd == '?') j = 3; findInputField(p, j, cx, &n, &realtotal, &tagno); debugPrint(5, "findField returns %d.%d", n, tagno); if (!tagno) { fieldNumProblem((c == '*' ? 2 : 0), 'i', cx, n, realtotal); return false; } if (scmd == '?') { infShow(tagno, line); return true; } cw->undoable = false; if (c == '<') { bool fromfile = false; if (globSub) { setError(MSG_IG); return (globSub = false); } skipWhite(&line); if (!*line) { setError(MSG_NoFileSpecified); return false; } n = stringIsNum(line); if (n >= 0) { char *p; int plen, dol; if (!cxCompare(n) || !cxActive(n)) return false; dol = sessionList[n].lw->dol; if (!dol) { setError (MSG_BufferXEmpty, n); return false; } if (dol > 1) { setError (MSG_BufferXLines, n); return false; } p = (char *)fetchLineContext(1, 1, n); plen = pstLength((pst) p); if (plen > sizeof(newline)) plen = sizeof(newline); memcpy(newline, p, plen); n = plen; nzFree(p); } else { int fd; fromfile = true; if (!envFile(line, &line)) return false; fd = open(line, O_RDONLY | O_TEXT); if (fd < 0) { setError(MSG_NoOpen, line); return false; } n = read(fd, newline, sizeof(newline)); close(fd); if (n < 0) { setError(MSG_NoRead, line); return false; } } for (j = 0; j < n; ++j) { if (newline[j] == 0) { setError(MSG_InputNull, line); return false; } if (newline[j] == '\r' && !fromfile && j < n - 1 && newline[j + 1] != '\n') { setError(MSG_InputCR); return false; } if (newline[j] == '\r' || newline[j] == '\n') break; } if (j == sizeof(newline)) { setError(MSG_FirstLineLong, line); return false; } newline[j] = 0; prepareForField(newline); line = newline; scmd = '='; } if (scmd == '=') { rc = infReplace(tagno, line, true); if (newlocation) goto redirect; return rc; } if (c == '*') { jSyncup(); if (!infPush(tagno, &allocatedLine)) return false; if (newlocation) goto redirect; /* No url means it was a reset button */ if (!allocatedLine) return true; line = allocatedLine; first = *line; cmd = 'b'; } } else cmd = 's'; } else { setError(MSG_TextAfter, icmd); return false; } } rebrowse: if (cmd == 'e' || cmd == 'b' && first && first != '#') { if (cw->fileName && !noStack && sameURL(line, cw->fileName)) { if (stringEqual(line, cw->fileName)) { setError(MSG_AlreadyInBuffer); return false; } /* Same url, but a different #section */ s = findHash(line); if (!s) { /* no section specified */ cw->dot = 1; if (!cw->dol) cw->dot = 0; printDot(); return true; } line = s; first = '#'; goto browse; } /* Different URL, go get it. */ /* did you make changes that you didn't write? */ if (!cxQuit(context, 0)) return false; undoCompare(); cw->undoable = cw->changeMode = false; startRange = endRange = 0; changeFileName = 0; /* should already be zero */ w = createWindow(); cw = w; /* we might wind up putting this back */ /* Check for sendmail link */ if (cmd == 'b' && memEqualCI(line, "mailto:", 7)) { char *addr, *subj, *body; char *q; int ql; decodeMailURL(line, &addr, &subj, &body); ql = strlen(addr); ql += 4; /* to:\n */ ql += subj ? strlen(subj) : 5; ql += 9; /* subject:\n */ if (body) ql += strlen(body); q = allocMem(ql + 1); sprintf(q, "to:%s\nSubject:%s\n%s", addr, subj ? subj : "Hello", body ? body : ""); j = addTextToBuffer((pst) q, ql, 0, false); nzFree(q); nzFree(addr); nzFree(subj); nzFree(body); if (j) i_puts(MSG_MailHowto); } else { cw->fileName = cloneString(line); cw->firstURL = cloneString(line); if (isSQL(line)) cw->sqlMode = true; if (icmd == 'g' && !nogo && isURL(line)) debugPrint(2, "*%s", line); j = readFile(line, emptyString); } w->undoable = w->changeMode = false; cw = cs->lw; /* Don't push a new session if we were trying to read a url, * and didn't get anything. */ if (!serverData && (isURL(line) || isSQL(line))) { fileSize = -1; freeWindow(w); if (noStack && cw->prev) { w = cw; cw = w->prev; cs->lw = cw; freeWindow(w); } return j; } if (noStack) { w->prev = cw->prev; nzFree(w->firstURL); w->firstURL = cw->firstURL; cw->firstURL = 0; cxQuit(context, 1); } else { w->prev = cw; } cs->lw = cw = w; if (!w->prev) cs->fw = w; if (!j) return false; if (changeFileName) { nzFree(w->fileName); w->fileName = changeFileName; changeFileName = 0; } /* Some files we just can't browse */ if (!cw->dol || cw->dirMode) cmd = 'e'; if (cw->binMode && (!cw->mt || !cw->mt->outtype)) cmd = 'e'; if (cmd == 'e') return true; } browse: if (cmd == 'b') { if (!cw->browseMode) { if (cw->binMode && (!cw->mt || !cw->mt->outtype)) { setError(MSG_BrowseBinary); return false; } if (!cw->dol) { setError(MSG_BrowseEmpty); return false; } if (fileSize >= 0) { debugPrint(1, "%d", fileSize); fileSize = -1; } if (!browseCurrentBuffer()) { if (icmd == 'b') return false; return true; } } else if (!first) { setError(MSG_BrowseAlready); return false; } if (newlocation) { if (!refreshDelay(newloc_d, newlocation)) { nzFree(newlocation); newlocation = 0; } else { redirect: noStack = newloc_r; nzFree(allocatedLine); line = allocatedLine = newlocation; debugPrint(2, "redirect %s", line); newlocation = 0; icmd = cmd = 'b'; first = *line; if (intFlag) { i_puts(MSG_RedirectionInterrupted); return true; } goto rebrowse; } } /* Jump to the #section, if specified in the url */ s = findHash(line); if (!s) return true; ++s; /* Sometimes there's a # in the midst of a long url, * probably with post data. It really screws things up. * Here is a kludge to avoid this problem. * Some day I need to figure this out. */ if (strpbrk(line, "?\1")) return true; /* Print the file size before we print the line. */ if (fileSize >= 0) { debugPrint(1, "%d", fileSize); fileSize = -1; } for (i = 1; i <= cw->dol; ++i) { char *p = (char *)fetchLine(i, -1); if (lineHasTag(p, s)) { cw->dot = i; printDot(); return true; } } setError(MSG_NoLable2, s); return false; } if (cmd == 'g' || cmd == 'v') { return doGlobal(line); } if (cmd == 'm' || cmd == 't') { return moveCopy(); } if (cmd == 'i') { if (cw->browseMode) { setError(MSG_BrowseI); return false; } cmd = 'a'; --startRange, --endRange; } if (cmd == 'c') { delText(startRange, endRange); endRange = --startRange; cmd = 'a'; } if (cmd == 'a') { if (inscript) { setError(MSG_InsertFunction); return false; } if (cw->sqlMode) { j = cw->dol; rc = sqlAddRows(endRange); /* adjust dot */ j = cw->dol - j; if (j) cw->dot = endRange + j; else if (!endRange && cw->dol) cw->dot = 1; else cw->dot = endRange; return rc; } return inputLinesIntoBuffer(); } if (cmd == 'd' || cmd == 'D') { if (cw->dirMode) { j = delFiles(); undoCompare(); cw->undoable = false; goto afterdelete; } if (cw->sqlMode) { j = sqlDelRows(startRange, endRange); undoCompare(); cw->undoable = false; goto afterdelete; } if (cw->browseMode) delTags(startRange, endRange); delText(startRange, endRange); j = 1; afterdelete: if (!j) globSub = false; else if (cmd == 'D') printDot(); return j; } if (cmd == 'j' || cmd == 'J') { return joinText(); } if (cmd == 'r') { if (cx) return readContext(cx); if (first) { if (cw->sqlMode && !isSQL(line)) { strcpy(newline, cw->fileName); strmove(strchr(newline, ']') + 1, line); line = newline; } j = readFile(line, emptyString); if (!serverData) fileSize = -1; return j; } setError(MSG_NoFileSpecified); return false; } if (cmd == 's') { j = substituteText(line); if (j < 0) { globSub = false; j = false; } if (newlocation) goto redirect; return j; } setError(MSG_CNYI, icmd); return (globSub = false); } /* runCommand */ bool edbrowseCommand(const char *line, bool script) { bool rc; globSub = intFlag = false; inscript = script; fileSize = -1; skipWhite(&line); rc = runCommand(line); if (fileSize >= 0) debugPrint(1, "%d", fileSize); fileSize = -1; if (!rc) { if (!script) showErrorConditional(cmd); eeCheck(); } return rc; } /* edbrowseCommand */ /* Take some text, usually empty, and put it in a side buffer. */ int sideBuffer(int cx, const char *text, int textlen, const char *bufname) { int svcx = context; bool rc; if (cx) { cxQuit(cx, 3); } else { for (cx = 1; cx < MAXSESSION; ++cx) if (!sessionList[cx].lw) break; if (cx == MAXSESSION) { i_puts(MSG_NoBufferExtraWindow); return 0; } } cxSwitch(cx, false); if (bufname) { cw->fileName = cloneString(bufname); debrowseSuffix(cw->fileName); } if (textlen < 0) { textlen = strlen(text); } else { cw->binMode = looksBinary(text, textlen); } if (textlen) { rc = addTextToBuffer((pst) text, textlen, 0, true); cw->changeMode = false; if (!rc) i_printf(MSG_BufferPreload, cx); } /* back to original context */ cxSwitch(svcx, false); return cx; } /* sideBuffer */ void freeEmptySideBuffer(int n) { struct ebWindow *side; if (!(side = sessionList[n].lw)) return; if (side->fileName) return; if (side->dol) return; if (side != sessionList[n].fw) return; /* We could have added a line, then deleted it */ cxQuit(n, 3); } /* freeEmptySideBuffer */ bool browseCurrentBuffer(void) { char *rawbuf, *newbuf, *tbuf; int rawsize, tlen, j; bool rc, remote = false; bool save_ch = cw->changeMode; uchar bmode = 0; if (cw->fileName) remote = isURL(cw->fileName); if (cw->mt && cw->mt->outtype) bmode = 3; else /* A mail message often contains lots of html tags, * so we need to check for email headers first. */ if (!remote && emailTest()) bmode = 1; else if (htmlTest()) bmode = 2; else { setError(MSG_Unbrowsable); return false; } rawbuf = NULL; rawsize = 0; if (bmode != 3 || remote || access(cw->fileName, 4)) { if (!unfoldBuffer(context, false, &rawbuf, &rawsize)) return false; /* should never happen */ } if (bmode == 3) { /* convert raw text via a plugin */ char *outfile = runPluginConverter(rawbuf, rawsize); if (!outfile) { nzFree(rawbuf); return false; } nzFree(rawbuf); if (stringEqual(outfile, "|")) { rawbuf = serverData; rawsize = serverDataLen; serverData = NULL; } else { rc = fileIntoMemory(outfile, &rawbuf, &rawsize); unlink(outfile); if (!rc) return false; } iuReformat(rawbuf, rawsize, &tbuf, &tlen); if (tbuf) { nzFree(rawbuf); rawbuf = tbuf; rawsize = tlen; } /* make it look like remote html, so we don't get a lot of errors printed */ remote = true; bmode = (cw->mt->outtype == 'h' ? 2 : 0); if (!allowRedirection) bmode = 0; } /* this shouldn't do any harm if the output is text */ prepareForBrowse(rawbuf, rawsize); /* No harm in running this code in mail client, but no help either, * and it begs for bugs, so leave it out. */ if (!ismc) { undoCompare(); cw->undoable = false; } if (bmode == 1) { newbuf = emailParse(rawbuf); j = strlen(newbuf); /* mail could need utf8 conversion, after qp decode */ iuReformat(newbuf, j, &tbuf, &tlen); if (tbuf) { nzFree(newbuf); newbuf = tbuf; j = tlen; } if (memEqualCI(newbuf, "\n", 7) && allowRedirection) { /* double browse, mail then html */ bmode = 2; remote = true; rawbuf = newbuf; rawsize = j; prepareForBrowse(rawbuf, rawsize); } } if (bmode == 2) { if (javaOK(cw->fileName)) createJavaContext(); nzFree(newlocation); /* should already be 0 */ newlocation = 0; newbuf = htmlParse(rawbuf, remote); } if (bmode == 0) newbuf = rawbuf; cw->rnlMode = cw->nlMode; cw->nlMode = false; /* I'm gonna assume it ain't binary no more */ cw->binMode = false; cw->r_dot = cw->dot, cw->r_dol = cw->dol; cw->dot = cw->dol = 0; cw->r_map = cw->map; cw->map = 0; memcpy(cw->r_labels, cw->labels, sizeof(cw->labels)); memset(cw->labels, 0, sizeof(cw->labels)); j = strlen(newbuf); rc = addTextToBuffer((pst) newbuf, j, 0, false); free(newbuf); cw->undoable = false; cw->changeMode = save_ch; if (cw->fileName) { j = strlen(cw->fileName); cw->fileName = reallocMem(cw->fileName, j + 8); strcat(cw->fileName, ".browse"); } if (!rc) { /* should never happen */ fileSize = -1; cw->browseMode = true; return false; } if (bmode == 2) cw->dot = cw->dol; cw->browseMode = true; fileSize = apparentSize(context, true); return true; } /* browseCurrentBuffer */ bool locateTagInBuffer(int tagno, int *ln_p, char **p_p, char **s_p, char **t_p) { int ln, n; char *p, *s, *t, c; char search[20]; char searchend[4]; sprintf(search, "%c%d<", InternalCodeChar, tagno); sprintf(searchend, "%c0>", InternalCodeChar); n = strlen(search); for (ln = 1; ln <= cw->dol; ++ln) { p = (char *)fetchLine(ln, -1); for (s = p; (c = *s) != '\n'; ++s) { if (c != InternalCodeChar) continue; if (!memcmp(s, search, n)) break; } if (c == '\n') continue; /* not here, try next line */ s = strchr(s, '<') + 1; t = strstr(s, searchend); if (!t) i_printfExit(MSG_NoClosingLine, ln); *ln_p = ln; *p_p = p; *s_p = s; *t_p = t; return true; } return false; } /* locateTagInBuffer */ char *getFieldFromBuffer(int tagno) { int ln; char *p, *s, *t; if (locateTagInBuffer(tagno, &ln, &p, &s, &t)) return pullString1(s, t); /* line has been deleted, revert to the reset value */ return 0; } /* getFieldFromBuffer */ int fieldIsChecked(int tagno) { int ln; char *p, *s, *t, *new; if (locateTagInBuffer(tagno, &ln, &p, &s, &t)) return (*s == '+'); return -1; } /* fieldIsChecked */ edbrowse-3.6.0.1/src/PaxHeaders.22102/auth.c0000644000000000000000000000006212637565441015127 xustar0020 atime=1451158305 30 ctime=1451409178.145337243 edbrowse-3.6.0.1/src/auth.c0000644000000000000000000000474512637565441015412 0ustar00rootroot00000000000000/* auth.c * user password authorization for web access * (c) 2002 Mikulas Patocka * This file is part of the Links project, released under GPL. * * Modified by Karl Dahlke for integration with edbrowse. */ #include "eb.h" struct httpAuth { struct httpAuth *next; struct httpAuth *prev; /* These strings are allocated. */ char *host; char *directory; char *user_password; int port; bool proxy; }; static struct listHead authlist = { &authlist, &authlist }; bool getUserPass(const char *url, char *creds, bool find_proxy) { const char *host = getHostURL(url); int port = getPortURL(url); const char *dir, *dirend; struct httpAuth *a; struct httpAuth *found = NULL; int l, d1len, d2len; getDirURL(url, &dir, &dirend); d2len = dirend - dir; foreach(a, authlist) { if (found == NULL && a->proxy == find_proxy && stringEqualCI(a->host, host) && a->port == port) { if (!a->proxy) { /* Directory match not done for proxy records. */ d1len = strlen(a->directory); if (d1len > d2len) continue; if (memcmp(a->directory, dir, d1len)) continue; found = a; } else /* not proxy */ found = a; } } if (found) strcpy(creds, found->user_password); return (found != NULL); } /* getUserPass */ bool addWebAuthorization(const char *url, const char *credentials, bool proxy) { struct httpAuth *a; const char *host; const char *dir = 0, *dirend; int port, dl; bool urlProx = isProxyURL(url); bool updated = true; char *p; if (proxy) { if (!urlProx) { setError(MSG_ProxyAuth); return false; } } else if (urlProx) url = getDataURL(url); host = getHostURL(url); port = getPortURL(url); if (!proxy) { getDirURL(url, &dir, &dirend); dl = dirend - dir; } /* See if we've done this one before. */ foreach(a, authlist) { if (a->proxy == proxy && a->port == port && stringEqualCI(a->host, host) && (proxy || dl == strlen(a->directory) && !memcmp(a->directory, dir, dl))) { nzFree(a->user_password); break; } } if (a == (struct httpAuth *)&authlist) { updated = false; a = allocZeroMem(sizeof(struct httpAuth)); addToListFront(&authlist, a); } a->proxy = proxy; a->port = port; if (!a->host) a->host = cloneString(host); if (dir && !a->directory) a->directory = pullString1(dir, dirend); a->user_password = cloneString(credentials); debugPrint(3, "%s authorization for %s%s", updated ? "updated" : "new", a->host, a->directory); return true; } /* addWebAuthorization */ edbrowse-3.6.0.1/src/PaxHeaders.22102/.gitignore0000644000000000000000000000006212637565441016012 xustar0020 atime=1451158305 30 ctime=1451409178.145337243 edbrowse-3.6.0.1/src/.gitignore0000644000000000000000000000011512637565441016260 0ustar00rootroot00000000000000*.[oa] edbrowse edbrowse-js edbrowse-odbc startwindow.c ebrc.c msg-strings.c edbrowse-3.6.0.1/PaxHeaders.22102/perl0000644000000000000000000000006212637565441014120 xustar0020 atime=1451158305 30 ctime=1451409178.157337163 edbrowse-3.6.0.1/perl/0000755000000000000000000000000012637565441014446 5ustar00rootroot00000000000000edbrowse-3.6.0.1/perl/PaxHeaders.22102/sample.ebrc0000644000000000000000000000006212637565441016313 xustar0020 atime=1451158305 30 ctime=1451409178.145337243 edbrowse-3.6.0.1/perl/sample.ebrc0000644000000000000000000001452212637565441016570 0ustar00rootroot00000000000000# .ebrc file, configure edbrowse. # This file contains passwords - make sure it is not readable by others. # The pop3 account format is, # pop3server:login:password:returnmail:smtp # My primary mail account, through my isp. mail.comcast.net:cdahlke179212:x:cdahlke179212@comcast.net:smtp.comcast.net # My personal email account. *mail.comcast.net:eklhad:x:eklhad@comcast.net:smtp.comcast.net # My wife's account. # Sometimes she doesn't log on for a week, so I check it once in a while. mail.comcast.net:kdwife:x:kdwife@comcast.net:smtp.comcast.net # Mail that looks like it came from my other web sites. mail.comcast.net:eklhad:x:webmaster@mathreference.com:smtp.comcast.net mail.comcast.net:eklhad:x:webmaster@scrapsayings.com:smtp.comcast.net # You should probably use one server, your ISP's local mail server, # to transmit all your outgoing mail. # Your ISP trusts you, and nobody else does, # and there's no password in the SMTP protocol, # so you may find that your far mailbox, in another time zone, # will let you get mail (with your password), but not send mail. # So send everything through your local mail server. # The entry with the leading * is assumed to be the local mail account. # If you tell this program to send mail via another account, # The mail will "look" like it came from that other account, # and replies will go back to that other account, # but the local server is still used to send the mail. # The sm feature in edbrowse uses your local mail server. # Your full name is sent with every outgoing email, # so the recipient knows who you are. fullname = Karl Dahlke # Now for some internal parameters. # Give the full pathname for your address book. # This file contains lines of the form alias:email, # which makes it easier to specify recipients when sending mail. addressbook = /home/eklhad/progs/ebsys/adbook # A file keeps track of junk subjects, # mail that you don't want to see any more. junkfile = /home/eklhad/progs/ebsys/junk # A file specifies annoying advertisements (usually), # that you don't want to read again and again. annoyfile = /home/eklhad/progs/ebsys/annoy # Move to your standard email directory when this program # is run as a mail client. # This and all subsequent parameters are optional. cd = /home/eklhad/mbox # Here's the first filter rule. # Each morning I receive the "word of the day", with definition and etimology, # from Merriam Webster. # It's really quite interesting, but I often want to read it at my leisure, # not while I'm plowing through my mail messages. # So I dump it into a file "wod". # I use to do this manually, almost a reflex action, # but now the filtering rule does it for me. # Keep in mind, if I haven't read my mail for three days, # there will be three of these. # That's ok - they all get appended onto the wod file. # I'll read them all, then delete the file. from = word@m-w.com > wod # Regular updates from nasa science news and space.com. # These are definitely to be read at leisure. from = spaceupdate@space.com > scom from = snglist@snglist.msfc.nasa.gov > nasa # Now for the lies. # Some websites won't even let you in unless you use Explorer or Netscape. # So lie about who you are. # Tell them you're Explorer, and they'll let you in the door. # At that point the site may or may not work with edbrowse. # Might not, if it uses javascript all over the place. # But it's worth a try. At least you're in. # Here are the lies. # Activate the first lie with ua1, the second lie with ua2, etc. # Type ua0 to go back to User-Agent edbrowse agent = Lynx/2.8.4rel.1 libwww-FM/2.14 agent = Mozilla/4.0 (compatible; MSIE 5.5; Windows 98; Win 9x 4.90) # Predefined command sets. # The set called init is run at startup. # This is used to set global flags, as you prefer. #cmdlist = init #< ci # Add a link to your web book cmdlist = wb < w+ /home/eklhad/progs/ebsys/webbook # Undos and unword a file. # The leading + means the command set will stop if it encounters an error. cmdlist = +ud < ,s/\r$// cmdlist = uw < ,s/’/'/g # Strip out nonascii stuff. # Sorry to be so English-centric. # This will mess you up if you're reading a Spanish word document, # and you lose your nonascii letters. < ,s/[\0-\11\13\14\16-\37\200-\377]//g # After the above, there'll be lots of blank lines. < g/^ *$/ d # Break lines at return boundaries. < ,bl # Strip off trailing junk. < /^ !"#\$%&'()\*/,$d # Web express shortcuts. # Type @ for the list of shortcuts. # They will be printed in the order they appear in this file. # Search engines first. # Type @gg elephant to look up elephant on google. # In the case of google, the post processing commands will be given first, # since they are applied to google in various countries. cmdlist = gg_post < g/^Your browser may not have a PDF reader available.$/d < /^{search Next}$/+,$d < 1,/Search took [\d.]+ seconds/d < /^Did you mean:$/,/^Did you mean:$/+2m$ < 1,/^Categor[iesy]+:|/d < 1,/{See your message here...}/d <1,/^|Sponsored Link$/d < 1g/./d < 1g/./d < 1g/^$/d < /^Did you mean:$/,$m0 # Access google, for the world, and for a couple of countries. shortcut = gg desc = google search # in the following url, $1 is replaced by your keywords # We may support multiple parameters, eventually, but not right now. url = www.google.com/search?q=$1&hl=n&btnG=Google+Search&meta= # And the post processing commands. < gg_post shortcut = ggca desc = google search in Canada url = www.google.ca/search?q=$1&hl=n&btnG=Google+Search&meta=cr%3dcountryCA < gg_post shortcut = mw desc = merriam webster dictionary lookup # The * means post method, whereas ? indicates the get method. url = www.m-w.com/cgi-bin/dictionary*book=Dictionary&va=$1 < /^{Get the Word of the Day/,$d < 1,/^Click on the Collegiate Thesaurus tab to look up the current word/+2d < 1,/^<>|$/-d < 1,/^One entry found for/+d < 1,/^Suggestions for/-d shortcut = dict desc = dictionary.com lookup url = www.dictionary.com/search?q=$1&db=%2a # No post-processing yet. shortcut = yf desc = stock quotes from yahoo finance url = finance.yahoo.com/q?s=$1&Get+Quotes&d=v1 < 1,/^Symbol|Last Trade|Change/d < /^Get a snapshot of today's market action/,$d < ,S/}|\(\d\)/}\n$1/ # First field is date, drop it < ,S/.*?|// # third and following fields aren't important < ,S/|/#/2 < ,S/#.*// < ,S/|/, /g < ,S/\+/up / < ,S/-/down / < ,S/ 0\.00$/ no change/ < g/^\d/ -,.J < g/{[\w ,.-]+},*$/ d edbrowse-3.6.0.1/perl/PaxHeaders.22102/edbrowse.pl0000644000000000000000000000006212637565441016344 xustar0020 atime=1451158305 30 ctime=1451409178.145337243 edbrowse-3.6.0.1/perl/edbrowse.pl0000755000000000000000000067666612637565441016653 0ustar00rootroot00000000000000#!/usr/bin/perl # edbrowse: line editor/browser use IO::Handle; use IO::Socket; use Time::Local; =head1 Author Karl Dahlke eklhad@gmail.com =HEAD1 Current Maintainer Chris Brannon maintainers@edbrowse.org http://edbrowse.org =head1 Copyright Notice This program is copyright (C) (C) Karl Dahlke, 2000-2003. It is made available, by the author, under the terms of the General Public License (GPL), as articulated by the Free Software Foundation. It may be used for any purpose, and redistributed, provided this copyright notice is included. =head1 Redirection This program, and its associated documentation, are becoming quite large. Therefore the documentation has been moved to a separate html file. Please visit: http://edbrowse.org/usersguide.html Realize that this documentation covers the C version of edbrowse. Development of the Perl version stopped years ago, and there have been significant changes. If you have lynx on hand, you can run: lynx -dump http://edbrowse.org/usersguide.html > usersguide.txt If you are using lynx to download the actual program, do this: lynx -source 'http://edbrowse.org/edbrowse.pl' > edbrowse.pl =cut $version = "1.5.17"; @agents = ("edbrowse/$version"); $agent = $agents[0]; # It's tempting to let perl establish the global variables as you go. # Let's try not to do this. # That's where all the side effects are - that's where the bugs come in. # Below are the global variables, with some explanations. $debug = 0; # general debugging $errorExit = 0; $ismc = 0; # is mail client $zapmail = 0; # just get rid of the mail $maxfile = 40000000; # Max size of an editable file. $eol = "\r\n"; # end-of-line, as far as http is concerned $doslike = 0; # Is it a Dos-like OS? $doslike = 1 if $^O =~ /^(dos|win|mswin)/i; $errorMsg = ""; # Set this if the last operation produced an error. $inglob = 0; # Are we in global mode, under a g// operation? $onloadSubmit = 0; $inscript = 0; # plowing through javascript $filesize = 0; # size of file just read or written $global_lhs_rhs = 0; # remember lhs and rhs across sessions $caseInsensitive = 0; # Do we send crnl or nl after the lines in a text buffer? # What is the standard - I think it's DOS newlines. $textAreaCR = 1; $pdf_convert = 1; # convert pdf to html $fetchFrames = 1; # fetch the frames into a web page $allsub = 0; # enclose all superscripts and subscripts $allowCookies = 1; # allow all cookies. %cookies = (); # the in-memory cookie jar %authHist = (); # authorization strings by domain $authAttempt = 0; # count authorization attempts for this page $ssl_verify = 1; # By default we verify all certs. $ssl = undef; # ssl connection $ctx = undef; # ssl certificate $allowReferer = 1; # Allow referer header by default. $referer = ""; # refering web page $reroute = 1; # follow http redirections to find the actual web page $rerouteCount = 0; # but prevent infinite loops %didFrame = (); # which frames have we fetched already $passive = 1; # ftp passive mode on by default. $nostack = 0; # suppress stacking of edit sessions $last_z = 1; # line count for the z command $endmarks = 0; # do we print ^ $ at the start and end of lines? $subprint = 0; # print lines after substitutions? $delprint = 0; # print line after delete $dw = 0; # directory write enabled $altattach = 0; # attachments are really alternative presentations of the same email $do_input = 0; # waiting for the next input from the tty $intFlag = 0; # control c was hit $intMsg = "operation interrupted"; # Interrupt handler, for control C. # Close file handle if we were reading from disk or socket. sub intHandler() { $intFlag = 1; if($do_input) { print "\ninterrupt, type qt to quit completely\n"; return; } # Reading from an http server. close FH if defined FH; # Kill ftp data connection if open. close FDFH if defined FDFH; # and mail connection or ftp control connection close SERVER_FH if defined SERVER_FH; # And listening ftp socket. close FLFH if defined FLFH; exit 1 if $ismc; } # intHandler $SIG{INT} = \&intHandler; # A quieter form of die, without the edbrowse line number, which just confuses people. sub dieq($) { my $msg = shift; print "fatal: $msg\n"; exit 1; } # dieq @weekDaysShort = ("Sun","Mon","Tue","Wed","Thu","Fri","Sat"); @monthsShort = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"); sub mailTimeString() { my ($ss, $nn, $hh, $dd, $mm, $yy, $wd) = localtime time; my $wds = $weekDaysShort[$wd]; my $mths = $monthsShort[$mm]; return sprintf "%s, %02d %s %d %02d:%02d:%02d", $wds, $dd, $mths, $yy+1900, $hh, $nn, $ss; } # mailTimeString # ubackup is set when the command has changed something. # The previous text, stored in the save_* variables, # is copied into the last* variables. If you type u, # the last* variables and current variables are swapped. $ubackup = 0; # Did we successfully read the edbrowse config file? # If so, set some variables. $myname = $annoyFile = $junkFile = $addressFile = ""; %adbook = (); $adbooktime = 0; @inmailserver = (); # list of pop3 servers $mailDir = ""; $localMail = -1; $whichMail = 0; # which account to use $smMail = ""; $naccounts = 0; # number of pop accounts $outmailserver = ""; # smtp $smtplogin = ""; # smtp login my $mailToSend = ""; @pop3login = (); @pop3password = (); @replyAddress = (); @fromSource = (); @fromDest = (); $serverLine = ""; # line received from mail or ftp server # web express configuration variables and arrays. %shortcut = (); %commandList = (); %commandCheck = (); $currentShortcut = ""; $currentCommandList = ""; # Specify the start and end of a range for an operation. # 1,3m5 will set these variables to 1, 3, and 5. $startRange = $endRange = $dest = 0; # The input command, but only the one-letter commands. $icmd = ""; # Now the command that is actually executed is in $cmd. # This is usually the same as $icmd, but not always. # 8i becomes 7a, for instance. $cmd = ""; # The valid edbrowse commands. $valid_cmd = "aAbBcdefghHiIjJklmnpqrsStuvwz=^@<"; # Commands that can be done in browse mode. $browse_cmd = "AbBdefghHIjJklmnpqsuvwz=^@<"; # Commands for directory mode. $dir_cmd = "AbdefghHklnpqsvwz=^@<"; # Commands that work at line number 0, in an empty file. $zero_cmd = "aAbefhHqruwz=^@<"; # Commands that expect a space afterward. $spaceplus_cmd = "befrw"; # Commands that should have no text after them. $nofollow_cmd = "aAcdhHijlmnptu="; # Commands that can be done after a g// global directive. $global_cmd = "dIjJlmnpst"; # Show the error message, not just the question mark, after these commands. $showerror_cmd = "Abefqrw^@"; $helpall = 0; # show the error message all the time # Remember that two successive q's will quit the session without changes. # here we must track which session, by number, you were trying to quit. $lastq = $lastqq = -1; # For any variable x, there are usually multiple copies of x, one per session. # These are housed in an array @x. # In contrast, the variable $x holds $x[$context], # according to the current context. # I hope this isn't too confusing. $context = 0; # dot and dol, current and last line numbers. @dot = (0); $dot = $dot[0]; @dol = (0); $dol = $dol[0]; @factive = (1); # which sessions are active # Retain file names, and whether the text has been modified. @fname = (""); $fname = $fname[0]; $baseref = ""; # usually the same as $fname @fmode = (0); # file modes $fmode = $fmode[0]; $binmode = 1; # binary file $nlmode = 2; # newline apended $browsemode = 4; # browsing html text $changemode = 8; # something has changed in this file $dirmode = 16; # directory mode $firstopmode = 32; # first operation issued - undo is possible $nobrowse = "not in browse mode"; # common error message $nixbrowse = "command not available in browse mode"; $nixdir = "command not available in directory mode"; sub dirBrowseCheck($) { my $cmd = shift; $fmode&$browsemode and $errorMsg = "$cmd $nixbrowse", $inglob = 0, return 0; $fmode&$dirmode and $errorMsg = "$cmd $nixdir", $inglob = 0, return 0; return 1; } # dirBrowseCheck # retain base directory name when scanning a directory @dirname = (""); $dirname = $dirname[0]; # Remember substitution strings. @savelhs = (); # save left hand side $savelhs = $savelhs[0]; @saverhs = (); # save right hand side $saverhs = $saverhs[0]; # month hash, to encode dates. %monhash = (jan => 1, feb => 2, mar => 3, apr => 4, may => 5, jun => 6, jul => 7, aug => 8, sep => 9, oct => 10, nov => 11, dec => 12); $home = $ENV{HOME}; defined $home and length $home or dieq 'home directory not defined by $HOME.'; -d $home or dieq "$home is not a directory."; # Establish the trash bin, for deleted files. $rbin = "$home/.trash"; if(! -d $rbin) { $rbin = "" unless mkdir $rbin, 0700; } # Config file for this browser. # Sample file is available at http://edbrowse.org/sample.perl.ebrc $rcFile = "$home/.ebprc"; # Last http header, normally deleted before you read the web page. $ebhttp = "$rbin/eb.http"; truncate $ebhttp, 0; # When we need a temp file. $ebtmp = "$rbin/eb.tmp"; # A file containing SSL certificates in PEM format, concatinated together. # This will be used for certificate verification. $ebcerts = "$home/.ssl-certs"; # file for persistant cookies. $ebcooks = "$home/.cookies"; sub fillJar() ; fillJar(); # fill up that cooky jar # Let's see if we can read the config file? if(open FH, $rcFile) { my $sort = 0; while() { s/^\s+//; s/^#.*$//; next if /^$/; s/\s+$//; my ($server, $login, $passwd, $retpath, $key, $value); if(/^([^:\s]+)\s*:\s*([^:\s]+)\s*:\s*([^:\s]+)\s*:\s*([^:\s]+)\s*:\s*([^:\s]*)/) { ($server, $login, $passwd, $retpath) = ($1, $2, $3, $4); my $smtpbox = $5; if($server =~ s/^\*\s*//) { dieq "multiple accounts are marked as local, with a star." if $localMail >= 0; $localMail = $naccounts; $smtpbox = $server unless length $smtpbox; $outmailserver = $smtpbox; $smtplogin = $login; } $inmailserver[$naccounts] = $server; $pop3login[$naccounts] = $login; $pop3password[$naccounts] = $passwd; $replyAddress[$naccounts] = $retpath; ++$naccounts; next; } # describing a mail server # Now look form keyword = string. # Initial < is shorthand for cmd = s/^\]+)\s*>\s*(.+)$/) { push @fromSource, lc $1; push @fromDest, $2; next; } dieq "from filter \"$value\" does not look like \"emailAddress > file\"."; } # from if($key eq "agent") { push @agents, $value; next; } # agent # web express keywords if($key eq "shortcut") { if(length $currentShortcut and ! defined $shortcut{$currentShortcut}{url}) { dieq "shortcut $currentShortcut has not been assigned a url"; } $value =~ /^[\w-]+$/ or dieq "the name of a shortcut must consist of letters digits or dashes, $value is invalid"; $currentShortcut = $value; # Start out with no post processing commands. $shortcut{$value}{after} = []; $shortcut{$value}{sort} = sprintf "%04d", $sort; ++$sort; $currentCommandList = ""; next; } # shortcut if($key eq "cmdlist") { if(length $currentShortcut and ! defined $shortcut{$currentShortcut}{url}) { dieq "shortcut $currentShortcut has not been assigned a url"; } $currentShortcut = ""; my $check = 0; $check = 1 if $value =~ s/^\+//; $value =~ /^[\w-]+$/ or dieq "the name of a command list must consist of letters digits or dashes, $value is invalid."; $currentCommandList = $value; $commandList{$value} = []; $commandCheck{$value} = $check; next; } # cmdlist if($key eq "cmd") { length $currentShortcut or length $currentCommandList or dieq "postprocessing command is not part of a command list or shortcut"; my $cref; # command reference $cref = $shortcut{$currentShortcut}{after} if length $currentShortcut; $cref = $commandList{$currentCommandList} if length $currentCommandList; # is this a command list? if($value =~ /^[a-zA-Z_-]+$/ and defined $commandList{$value}) { my $cpush = $commandList{$value}; push @$cref, @$cpush; } else { push @$cref, $value; } next; } # cmd if($key eq "url") { length $currentShortcut or dieq "$key command without a current shortcut"; $shortcut{$currentShortcut}{url} = $value; next; } # url if($key eq "desc") { length $currentShortcut or dieq "$key command without a current shortcut"; $shortcut{$currentShortcut}{desc} = $value; next; } # desc dieq "Unrecognized keyword <$key> in config file."; } dieq "garbled line <$_> in config file."; } # loop over lines in config file close FH; if(length $currentShortcut and ! defined $shortcut{$currentShortcut}{url}) { dieq "shortcut $currentShortcut has not been assigned a url"; } if($naccounts) { $localMail = 0 if $naccounts == 1; dieq "None of the pop3 accounts is marked as local." if $localMail < 0; dieq "fullname not specified in the config file." if ! length $myname; } # mail accounts } # open succeeded # One array holds all the lines of text (without the newlines) # for all the files in all the sessions. # Within a given session, the actual file is represented by a list of numbers, # indexes into this large array. # Note that when text is copied, we actually copy the strings in the array. # I could just have different lines use the same index, thus pointing to the # same string, and there would be no need to copy that string, # but then I'd have to maintain reference counts on all these strings, # and that would make the program very messy! @text = (); # If a file has 30 lines, it is represented by 30 numbers, # indexes into @text above. # Should we use an array of numbers, or a string of numbers # represented by decimal digits? # Both are painful, in different ways. # Consider inserting a block of text, a very common operation. # In a list, we would have to slide all the following numbers down. # Granted, that's better than copying all those lines of text down, # but it's still a pain to program, and somewhat inefficient. # If we use strings, we take the original string of numbers, # break it at the insert point, and make a new string # by concatenating these two pieces with the new block. # The same issues arise when deleting text near the top of a file. # This and other considerations push me towards strings. # I currently use 6 characters for a line number, and a seventh for the g// flag. $lnwidth = 7; # width of a line number field in $map $lnwidth1 = $lnwidth - 1; $lnformat = "%6d "; $lnspace = ' ' x $lnwidth; $lnmax = 999999; # Note that line 0 never maps to anything in @text. @map = ($lnspace); $map = $map[0]; # The 26 labels, corresponding to the lower case letters. # These are stored in a packed string, like $map above. # labels also holds the filetype suffixes when in directory mode. @labels = ($lnspace x 26); $labels = $labels[0]; # offset into $labels, where directory suffixes begin. $dirSufStart = 26 * $lnwidth; # The anchor/form/input tags, for browsing. # The browse tags are in an array of hashes. # Each hash has tag=tagname, # and attrib=value for each attrib=value in the tag. # Be advised that certain special tags, such as those defining # title and description and keywords, are placed in btag[0]. @btags = (); $btags = $btags[0]; # When we focus on an input field, for edit or manipulation, # we need its type, size, and list of options. $inf = ""; # current text displayed by this input field. $itype = ""; # Type of the input field. $isize = 0; # size of the input field. $iopt = {}; # hash of input options in a discrete list. $irows = $icols = 0; # for a text area window. $iwrap = ""; # Can we scroll beyond this window? $itag = undef; # the input tag from which the previous variables were derived. $iline = 0; # line where this input field was found. $ifield = 0; # field number, within the line, the nth input field on the line. $itagnum = 0; # tag number for this input field. $inorange = "this input directive cannot be applied to a range of lines"; $inoglobal = "this input directive cannot be applied globally"; # last* and save* variables mirror the variables that define your session. # This supports the undo command. $lastdot = $savedot = $lastdol = $savedol = 0; $lastmap = $savemap = $lastlabels = $savelabels = ""; # Variables to format text, i.e. break lines at sentence/phrase boundaries. $refbuf = ""; # The new, reformatted buffer. $lineno = $colno = 0; # line/column number $optimalLine = 80; # optimal line length $cutLineAfter = 36; # cut sentence or phrase after this column $paraLine = 120; # longer lines are assumed to be self-contained paragraphs $longcut = 0; # last cut of a long line $lspace = 3; # last space value, 3 = paragraph $lperiod = $lcomma = $lright = $lany = 0; # columns for various punctuations $idxperiod = $idxcomma = $idxright = $idxany = 0; # Push the entire edit session onto a stack, for the back key. # A hash will hold all the variables that make a session, # such as $map, $fname, $btags, etc. @backup = (); $backup = $backup[0]; $hexChars = "0123456789abcdefABCDEF"; # Valid delimiters for search/substitute. # note that \ is conspicuously absent, not a valid delimiter. # I alsso avoid nestable delimiters such as parentheses. # And no alphanumerics please -- too confusing. $valid_delim = "-_=!|#*;:`\"',./?+@"; # $linePending holds a line of text that you accidentally typed in # while edbrowse was in command mode. # When you see the question mark, immediately type a+ to recover the line. $linePending = undef; # That's it for the globals, here comes the code. # First a few support routines. # Strip white space from either side. sub stripWhite($) { my $line = shift; $$line =~ s/^\s+//; $$line =~ s/\s+$//; } # stripWhite # Is a filename a URL? # If it is, return the transport protocol, e.g. http. sub is_url($) { my $line = shift; return 'http' if $line =~ m,^http://[^\s],i; return 'https' if $line =~ m,^https://[^\s],i; return 'gopher' if $line =~ m,^gopher://[^\s],i; return 'telnet' if $line =~ m,^telnet://[^\s],i; return 'ftp' if $line =~ m,^ftp://[^\s],i; # I assume that the following will be regular http. # Strip off the ?this=that stuff $line =~ s:\?.*::; # Strip off the file name and .browse suffix. $line =~ s:/.*::; $line =~ s/\.browse$//; $line =~ s/:\d+$//; return 0 if $line !~ /\w\.\w.*\w\.\w/; # we need at least two internal dots # Look for an ip address, four numbers and three dots. return 'http' if $line =~ /^\d+\.\d+\.\d+\.\d+$/; $line =~ s/.*\.//; return 'http' if index(".com.biz.info.net.org.gov.edu.us.uk.au.ca.de.jp.be.nz.sg.", ".$line.") >= 0; } # is_url # Apply a (possibly) relative path to a preexisting url. # The new url is returned. # resolveUrl("http://www.eklhad.net/linux/index.html", "app") returns # "http://www.eklhad.net/linux/app" sub resolveUrl($$) { my ($line, $href) = @_; my $scheme; $line = "" unless defined $line; $line =~ s/\.browse$//; # debug print - this is a very subtle routine. print "resolve($line, $href)\n" if $debug >= 2; # Some people, or generators, actually write http://../whatever.html $href =~ s/^http:(\.+)/$1/i; $href =~ s,^http://(\.*/),$1,i; return $href unless length $href and length $line and ! is_url($href); if(substr($href, 0, 1) ne '/') { $line =~ s/\?.*//; # hope this is right if(substr($href, 0, 1) ne '?') { if($line =~ s,^/[^/]*$,, or $line =~ s,([^/])/[^/]*$,$1,) { # We stripped off the last directory $line .= '/'; } else { if($scheme = is_url $line) { $line .= '/'; } else { $line = ""; } } # stripping off last directory } # doesn't start with ? } elsif($scheme = is_url $line) { # Keep the scheme and server, lose the filename $line =~ s/\?.*//; # hope this is right $line =~ s,^($scheme://[^/]*)/.*,$1,i; } else { $line = ""; } return $line.$href; } # resolveUrl # Prepare a string for http transmition. # No, I really don't know which characters to encode. # I'm probably encoding more than I need to -- hope that's ok. sub urlEncode($) { $_ = shift; s/([^-\w .@])/sprintf('%%%02X',ord($1))/ge; y/ /+/; return $_; } # urlEncode sub urlDecode($) { $_ = shift; y/+/ /; s/%([0-9a-fA-F]{2})/chr hex "$1"/ge; return $_; } # urlDecode # The javascript unescape function, sort of sub unescape($) { $_ = shift; s/(%|\\u00)([0-9a-fA-F]{2})/chr hex "$2"/ge; s/&#(\d+);/chr "$1"/ge; return $_; } # unescape # Pull the subject out of a sendmail url. sub urlSubject($) { my $href = shift; if($$href =~ s/\?(.*)$//) { my @pieces = split '&', $1; foreach my $j (@pieces) { next unless $j =~ s/^subject=//i; my $subj = urlDecode $j; stripWhite \$subj; return $subj; } # loop } # attributes after the email return ""; } # urlSubject # Get raw text ready for html display. sub textUnmeta($) { my $tbuf = shift; return unless length $$tbuf; $$tbuf =~ s/&/&/g; $$tbuf =~ s//>/g; $$tbuf =~ s/^/

    /;
    $$tbuf =~ s/$/<\/PRE>

    \n/; } # textUnmeta # Derive the alt description for an image or hyperlink. sub deriveAlt($$) { my $h = shift; my $href = shift; my $alt = $$h{alt}; $alt = "" unless defined $alt; stripWhite \$alt; # Some alt descriptions are flat-out useless. $alt =~ s/^[^\w]+$//; return $alt if length $alt; if(!length $href) { $href = $$h{href}; $href = "" unless defined $href; } $alt = $href; $alt =~ s/^javascript.*$//i; $alt =~ s/^\?//; $alt =~ s:\?.*::s; $alt =~ s:.*/::; $alt =~ s/\.[^.]*$//; $alt =~ s:/$::; return $alt; } # deriveAlt # Pull the reference out of a javascript openWindow() call. $foundFunc = ""; sub javaWindow($) { my $jc = shift; # java call my $page = ""; $foundFunc = ""; $page = $1 if $jc =~ /(?:open|location|window)[\w.]* *[(=] *["']([\w._\/:,=@&?+-]+)["']/i; return $page if length $page; return "submit" if $jc =~ /\bsubmit *\(/i; while($jc =~ /(\w+) *\(/g) { my $f = $1; my $href = $$btags[0]{fw}{$f}; if($href) { $href =~ s/^\*//; $foundFunc = $f; $page = $href; } } return $page; } # javaWindow # Try to find the Java functions sub javaFunctions($) { my $tbuf = shift; my $flc = 0; # function line count my $f; # java function while($$tbuf =~ /(.+)/g) { my $line = $1; if($line =~ /function *(\w+)\(/) { $f = $1; print "java function $f\n" if $debug >= 6; $flc = 1; } my $win = javaWindow $line; if(length $win) { if($flc) { if(not defined $$btags[0]{fw}{$f}) { $$btags[0]{fw}{$f} = "*$win"; print "$f: $win\n" if $debug >= 3; } } elsif($win ne "submit") { my $h = {}; push @$btags, $h; $attrhidden = hideNumber($#$btags); $$h{ofs1} = length $refbuf; my $alt = deriveAlt($h, $win); $alt = "relocate" unless length $alt; createHyperLink($h, $win, $alt); } } next unless $flc; ++$flc; $flc = 0 if $flc == 12; } # loop over lines } # javaFunctions # Mixed case. sub mixCase($) { my $w = lc shift; $w =~ s/\b([a-z])/uc $1/ge; # special McDonald code $w =~ s/Mc([a-z])/"Mc".uc $1/ge; return $w; } # mixCase # Create a hyperlink where there was none before. sub createHyperLink($$$) { my ($h, $href, $desc) = @_; $$h{tag} = "a"; $$h{bref} = $baseref; $$h{href} = $href; $refbuf .= "\x80$attrhidden" . "{$desc}"; $colno += 2 + length $desc; $$h{ofs2} = length $refbuf; $lspace = 0; } # createHyperLink # meta html characters. # There's lots more -- this is just a starter. %charmap = ( # Normal ascii symbols gt => '>', lt => '<', quot => '"', plus => '+', minus => '-', colon => ':', apos => '`', star => '*', comma => ',', period => '.', dot => ".", dollar => '$', percnt => '%', amp => '&', # International letters ntilde => "\xf1", Ntilde => "\xd1", agrave => "\xe0", Agrave => "\xc0", egrave => "\xe8", Egrave => "\xc8", igrave => "\xec", Igrave => "\xcc", ograve => "\xf2", Ograve => "\xd2", ugrave => "\xf9", Ugrave => "\xd9", auml => "\xe4", Auml => "\xc4", euml => "\xeb", Euml => "\xcb", iuml => "\xef", Iuml => "\xcf", ouml => "\xf6", Ouml => "\xd6", uuml => "\xfc", Uuml => "\xdc", yuml => "\xff", Yuml => 'Y', aacute => "\xe1", Aacute => "\xc1", eacute => "\xe9", Eacute => "\xc9", iacute => "\xed", Iacute => "\xcd", oacute => "\xf3", Oacute => "\xd3", uacute => "\xfa", Uacute => "\xda", yacute => "\xfd", Yacute => "\xdd", atilde => "\xe3", Atilde => "\xc3", itilde => 'i', Itilde => 'I', otilde => "\xf5", Otilde => "\xd5", utilde => 'u', Utilde => 'U', acirc => "\xe2", Acirc => "\xc2", ecirc => "\xea", Ecirc => "\xca", icirc => "\xee", Icirc => "\xce", ocirc => "\xf4", Ocirc => "\xd4", ucirc => "\xfb", Ucirc => "\xdb", # Other 8-bit symbols. # I turn these into their 8 bit equivalents, # then a follow-on routine turns them into words for easy reading. # Some speech adapters do this as well, saying "cents" for the cents sign, # but yours may not, so I do some of these translations for you. # But not here, because some people put the 8-bit cents sign in directly, # rather then ¢, so I've got to do that translation later. pound => "\xa3", cent => "\xa2", sdot => "\xb7", middot => "\xb7", edot => 'e', nbsp => ' ', times => "\xd7", divide => "\xf7", deg => "\xb0", frac14 => "\xbc", half => "\xbd", frac34 => "\xbe", frac13 => "1/3", frac23 => "2/3", copy => "\xa9", reg => "\xae", trade => "(TM)", ); %symbolmap = ( a => "945", b => "946", g => "947", d => "948", e => "949", z => "950", h => "951", q => "952", i => "953", k => "954", l => "955", m => "956", n => "957", x => "958", o => "959", p => "960", r => "961", s => "963", t => "964", u => "965", f => "966", c => "967", y => "968", w => "969", 177 => "8177", # kludge!! I made up 8177 198 => "8709", 219 => "8660", 209 => "8711", 229 => "8721", 206 => "8712", 207 => "8713", 242 => "8747", 192 => "8501", 172 => "8592", 174 => "8594", 165 => "8734", 199 => "8745", 200 => "8746", 64 => "8773", 182 => "8706", 185 => "8800", 162 => "8242", 163 => "8804", 179 => "8805", 204 => "8834", 205 => "8838", 201 => "8835", 203 => "8836", 202 => "8839", 208 => "8736", ); # map certain font=symbol characters to words %symbolWord = ( 176 => "degrees", 188 => "1fourth", 189 => "1half", 190 => "3fourths", 215 => "times", 247 => "divided by", 913 => "Alpha", 914 => "Beta", 915 => "Gamma", 916 => "Delta", 917 => "Epsilon", 918 => "Zeta", 919 => "Eta", 920 => "Theta", 921 => "Iota", 922 => "Kappa", 923 => "Lambda", 924 => "Mu", 925 => "Nu", 926 => "Xi", 927 => "Omicron", 928 => "Pi", 929 => "Rho", 931 => "Sigma", 932 => "Tau", 933 => "Upsilon", 934 => "Phi", 935 => "Chi", 936 => "Psi", 937 => "Omega", 945 => "alpha", 946 => "beta", 947 => "gamma", 948 => "delta", 949 => "epsilon", 950 => "zeta", 951 => "eta", 952 => "theta", 953 => "iota", 954 => "kappa", 955 => "lambda", 956 => "mu", 957 => "nu", 958 => "xi", 959 => "omicron", 960 => "pi", 961 => "rho", 962 => "sigmaf", 963 => "sigma", 964 => "tau", 965 => "upsilon", 966 => "phi", 967 => "chi", 968 => "psi", 969 => "omega", 8177 => "+-", # kludge!! I made up 8177 8242 => "prime", 8501 => "aleph", 8592 => "left arrow", 8594 => "arrow", 8660 => "double arrow", 8706 => "d", 8709 => "empty set", 8711 => "del", 8712 => "member of", 8713 => "not a member of", 8721 => "sum", 8734 => "infinity", 8736 => "angle", 8745 => "intersect", 8746 => "union", 8747 => "integral", 8773 => "congruent to", 8800 => "not equal", 8804 => "less equal", 8805 => "greater equal", 8834 => "proper subset of", 8835 => "proper superset of", 8836 => "not a subset of", 8838 => "subset of", 8839 => "superset of", ); # Map an html meta character using the above hashes. # Usually run from within a global substitute. sub metaChar($) { my $meta = shift; if($meta =~ /^#(\d+)$/) { return chr $1 if $1 <= 255; return "'" if $1 == 8217; return "\x82$1#" if $symbolWord{$1}; return "?"; } my $real = $charmap{$meta}; defined $real or $real = "?"; return $real; } # metaChar # Translate number. # This is highly specific to my web pages - doesn't work in general! sub metaSymbol($) { my $meta = shift; $meta =~ s/^&#//; $meta =~ s/;$//; my $real = $symbolmap{$meta}; return "?" unless $real; return "&#$real;"; } # metaSymbol # replace VAR with $VAR, as defined by the environment. sub envVar($) { my $var = shift; my $newvar = $ENV{$var}; if(defined $newvar) { # There shouldn't be any whitespace at the front or back. stripWhite \$newvar; return $newvar if length $newvar; } length $errorMsg or $errorMsg = "environment variable $var not set"; return ""; } # envVar # Replace the variables in a line, using the above. sub envLine($) { my $line = shift; $errorMsg = ""; # $errorMsg will be set if something goes wrong. $line =~ s,^~/,\$HOME/,; $line =~ s/\$([a-zA-Z]\w*)/envVar($1)/ge; return $line; } # envLine # The filename can be specified using environment variables, # and shell meta characters such as *. # But not if it's a url. sub envFile($) { my $filename = shift; $errorMsg = ""; if(! is_url($filename)) { $filename = envLine($filename); return if length $errorMsg; my @filelist; # This is real kludgy - I just don't understand how glob works. if($filename =~ / / and $filename !~ /"/) { @filelist = glob '"'.$filename.'"'; } else { @filelist = glob $filename; } $filelist[0] = $filename if $#filelist < 0; $errorMsg = "wild card expansion produces multiple files" if $#filelist; $filename = $filelist[0]; } return $filename; } # envFile # Drop any active edit sessions that have no text, and no associated file. # This housecleaning routine is run on every quit or backup command. sub dropEmptyBuffers() { foreach my $cx (0..$#factive) { next if $cx == $context; next unless $factive[$cx]; next if length $fname[$cx]; next if $dol[$cx]; $factive[$cx] = undef; } } # dropEmptyBuffers # Several small functions to switch between contexts, i.e. editing sessions. # In all these functions, we have to map between our context numbers, # that start with 0, and the user's session numbers, that start with 1. # C and fortran programmers will be use to this problem. # Is a context different from the currently running context? sub cxCompare($) { my $cx = shift; $errorMsg = "session 0 is invalid", return 0 if $cx < 0; return 1 if $cx != $context; # ok ++$cx; $errorMsg = "you are already in session $cx"; return 0; } # cxCompare # Is a context active? sub cxActive($) { my $cx = shift; return 1 if $factive[$cx]; ++$cx; $errorMsg = "session $cx is not active"; return 0; } # cxActive # Switch to another editing session. # This assumes cxCompare has succeeded - we're moving to a different context. # Pass the context number and an interactive flag. sub cxSwitch($$) { my ($cx, $ia) = @_; # Put the variables in a known start state if this is a virgin session. cxReset($cx, 0) if ! defined $factive[$cx]; $dot[$context] = $dot, $dot = $dot[$cx]; $dol[$context] = $dol, $dol = $dol[$cx]; $fname[$context] = $fname, $fname = $fname[$cx]; $dirname[$context] = $dirname, $dirname = $dirname[$cx]; $map[$context] = $map, $map = $map[$cx]; $labels[$context] = $labels, $labels = $labels[$cx]; $btags = $btags[$cx]; $backup[$context] = $backup, $backup = $backup[$cx]; if(!$global_lhs_rhs) { $savelhs[$context] = $savelhs, $savelhs = $savelhs[$cx]; $saverhs[$context] = $saverhs, $saverhs = $saverhs[$cx]; } $fmode[$context] = $fmode, $fmode = $fmode[$cx]; # But we don't replicate the last* variables per context, # so your ability to undo is destroyed if you switch contexts. $fmode &= ~$firstopmode; if($ia) { if(defined $factive[$cx]) { print ((length($fname[$cx]) ? $fname[$cx] : "no file")."\n"); } else { print "new session\n"; } } $factive[$cx] = 1; $context = $cx; return 1; } # cxSwitch # Can we trash the data in a context? # If so, trash it, and reset all the variables. # The second parameter is a close directive. # If nonzero, we clear out empty buffers associated with # text areas in the fill-out forms (browse mode). # A value of 1, as opposed to 2, means close down the entire session. sub cxReset($$) { my ($cx, $close) = @_; if(defined $factive[$cx]) { # We might be trashing data, make sure that's ok. $fname[$cx] = $fname, $fmode[$cx] = $fmode if $cx == $context; if($fmode[$cx]&$changemode and !( $fmode[$cx]&$dirmode) and $lastq != $cx and length $fname[$cx] and ! is_url($fname[$cx])) { $errorMsg = "expecting `w'"; $lastqq = $cx; if($cx != $context) { ++$cx; $errorMsg .= " on session $cx"; } return 0; } # warning message if($close) { dropEmptyBuffers(); if($close&1) { # And we're closing this session. $factive[$cx] = undef; $backup[$cx] = undef; } } } # session was active # reset the variables $dot[$cx] = $dol[$cx] = 0; $map[$cx] = $lnspace; $fname[$cx] = ""; $dirname[$cx] = ""; $labels[$cx] = $lnspace x 26; $btags[$cx] = []; $savelhs[$cx] = $saverhs[$cx] = undef; $fmode[$cx] = 0; if($cx == $context) { $dot = $dol = 0; $map = $map[$cx]; $fname = ""; $labels = $labels[$cx]; $btags = $btags[$cx]; $global_lhs_rhs or $savelhs = $saverhs = undef; $fmode = 0; } # current context return 1; } # cxReset # Pack all the information about the current context into a hash. # This will be pushed onto a virtual stack. # When you enter the back key, it all gets unpacked again, # to restore your session. sub cxPack() { my $h = { dot =>$dot, dol => $dol, map => $map, labels => $labels, lastdot =>$lastdot, lastdol => $lastdol, lastmap => $lastmap, lastlabels => $lastlabels, fname => $fname, dirname => $dirname, fmode => $fmode&~$changemode, savelhs => $savelhs, saverhs => $saverhs, btags => $btags, }; return $h; } # cxPack sub cxUnpack($) { my $h = shift; return if ! defined $h; $dot = $$h{dot}; $lastdot = $$h{lastdot}; $dol = $$h{dol}; $lastdol = $$h{lastdol}; $map = $$h{map}; $lastmap = $$h{lastmap}; $labels = $$h{labels}; $lastlabels = $$h{lastlabels}; $fmode = $$h{fmode}; $fname = $$h{fname}; $dirname = $$h{dirname}; if(!$global_lhs_rhs) { $savelhs = $$h{savelhs}; $saverhs = $$h{saverhs}; } $btags[$context] = $btags = $$h{btags}; } # cxUnpack # find an available session and load it with some initial data. # Returns the context number. sub cxCreate($$) { my ($text_ptr, $filename) = @_; # Look for an unused buffer my ($cx, $j); for($cx=0; $cx<=$#factive; ++$cx) { last unless defined $factive[$cx]; } cxReset($cx, 0); $factive[$cx] = 1; $fname[$cx] = $filename; my $bincount = $$text_ptr =~ y/\0\x80-\xff/\0\x80-\xff/; if($bincount*4 - 10 < length $$text_ptr) { # A text file - remove crlf in the dos world. $$text_ptr =~ s/\r\n/\n/g if $doslike; } else { $fmode[$cx] |= $binmode; } $fmode[$cx] |= $nlmode unless $$text_ptr =~ s/\n$//; $j = $#text; if(length $$text_ptr) { push @text, split "\n", $$text_ptr, -1; } if(!lineLimit(0)) { my $newpiece = $lnspace; ++$dol[$cx], $newpiece .= sprintf($lnformat, $j) while ++$j <= $#text; $map[$cx] = $newpiece; $dot[$cx] = $dol[$cx]; } else { warn $errorMsg; } return $cx; } # cxCreate # See if @text is too big. # Pass the number of lines we will be adding. sub lineLimit($) { my $more = shift; return 0 if $#text + $more <= $lnmax; $errorMsg = "Your limit of 1 million lines has been reached.\nSave your files, then exit and restart this program."; return 1; } # lineLimit # Hide and reveal numbers that are internal to the line. # These numbers indicate links and input fields, and are not displayed by the next routine. sub hideNumber($) { my $n = shift; $n =~ y/0-9/\x85-\x8e/; return $n; } # hideNumber sub revealNumber($) { my $n = shift; $n =~ y/\x85-\x8f/0-9/; return $n; } # revealNumber sub removeHiddenNumbers($) { my $t = shift; $$t =~ s/\x80[\x85-\x8f]+([<>{])/$1/g; $$t =~ s/\x80[\x85-\x8f]+\*//g; } # removeHiddenNumbers # Small helper function to retrieve the text for line number n. # If the second parameter is set, hidden numbers are left in place; # otherwise they are stripped out via removeHiddenNumbers(). sub fetchLine($$) { my $n = shift; my $show = shift; return "" unless $n; # should never happen my $t = $text[substr($map, $n*$lnwidth, $lnwidth1)]; removeHiddenNumbers(\$t) if $show and $fmode&$browsemode; return $t; } # fetchLine # Here's the same function, but for another context. sub fetchLineContext($$$) { my $n = shift; my $show = shift; my $cx = shift; $t = $text[substr($map[$cx], $n*$lnwidth, $lnwidth1)]; removeHiddenNumbers(\$t) if $show and $fmode[$cx]&$browsemode; return $t; } # fetchLineContext # Print size of the text in buffer. sub apparentSize() { my $j = 0; $j += length(fetchLine($_, 1)) + 1 foreach (1..$dol); --$j if $fmode&$nlmode; print "$j\n"; } # apparentSize # Read a line from stdin. # Could be a command, could be text going into the buffer. sub readLine() { my ($i, $j, $c, $d, $line); getline: { $intFlag = 0; $do_input = 1; $line = ; $do_input = 0; redo getline if $intFlag and ! defined $line; # interrupt $intFlag = 0; } exit 0 unless defined $line; # EOF $line =~ s/\n$//; # A bug in my keyboard causes nulls to be entered from time to time. $line =~ s/\0/ /g; return $line if $line !~ /~/; # shortcut # We have to process it, character by character. my $line2 = ""; for($i=0; $i= 16; my $val = $j*16; $d = substr $line, $i+2, 1; $j = index $hexChars, $d; next if $j < 0; $j -= 6 if $j >= 16; $val += $j; # We don't use this mechanism to enter normal ascii characters. next if $val >= 32 and $val < 127; # And don't stick a newline in the middle of an entered line. next if $val == 10; $c = chr $val; $i += 2; } # loop over input chars return $line2; } # readLine # Read a block of lines into the buffer. sub readLines() { my $tbuf = ""; # Put the pending line in first, if it's there. my $line = $linePending; $line = readLine() unless defined $line; while($line ne ".") { $tbuf .= "$line\n"; $line = readLine(); } # loop gathering input lines return addTextToSession(\$tbuf) if length $tbuf; $dot = $endRange; $dot = 1 if $dot == 0 and $dol; return 1; } # readLines # Display a line. Show line number if $cmd is n. # Expand binary characters if $cmd is l. # Pass the line number. sub dispLine($) { my $ln = shift; print "$ln " if $cmd eq 'n'; my $line = fetchLine($ln, 1); # Truncate, if the line is pathologically long. $line = substr($line, 0, 500) . "..." if length($line) > 500; print '^' if $endmarks and ($endmarks == 2 or $cmd eq 'l'); if($cmd eq 'l') { $line =~ y/\10\11/<>/; $line =~ s/([\0-\x1f\x80-\xff])/sprintf("~%02x",ord($1))/ge; } else { # But we always remap return, null, and escape $line =~ s/(\00|\r|\x1b)/sprintf("~%02x",ord($1))/ge; } print $line; print dirSuffix($ln); print '$' if $endmarks and ($endmarks == 2 or $cmd eq 'l'); print "\n"; } # dispLine # If we've printed a line in directory mode, and the entry isn't # a regular file, we've got to find and print the special character at the end. # / means directory, for example. # This is used by the previous routine, among others. sub dirSuffix($) { my $ln = shift; my $suf = ""; if($fmode&$dirmode) { $suf = substr($labels, $dirSufStart + 2*$ln, 2); $suf =~ s/ +$//; } return $suf; } # dirSuffix # Routines to help format a string, i.e. cut at sentence boundaries. # This isn't real smart; it will happily split Mr. Flintstone. sub appendWhiteSpace($$) { my($chunk, $breakable) = @_; my $nlc = $chunk =~ y/\n//d; # newline count if($breakable) { # Don't interrogate the last few characters of a huge string -- that's inefficient. my $short = substr $refbuf, -2; my $l = length $refbuf; $lperiod = $colno, $idxperiod = $l if $short =~ /[.!?:][)"|}]?$/; $lcomma = $colno, $idxcomma = $l if $short =~ /[-,;][)"|]?$/; $lright = $colno, $idxright = $l if $short =~ /[)"|]$/; $lany = $colno, $idxany = $l; # Tack short fragment onto previous long line. if($longcut and ($nlc or $lperiod == $colno) and $colno <= 14) { substr($refbuf, $longcut, 1) = " "; $chunk = "", $nlc = 1 unless $nlc; } # pasting small fragment onto previous line } # allowing line breaks $nlc = 0 if $lspace == 3; if($nlc) { $nlc = 1 if $lspace == 2; $refbuf .= "\n"; $refbuf .= "\n" if $nlc > 1; $colno = 1; $longcut = $lperiod = $lcomma = $lright = $lany = 0; $lspace = 3 if $lspace >= 2 or $nlc > 1; $lspace = 2 if $lspace < 2; } $refbuf .= $chunk; $lspace = 1 if length $chunk; $colno += $chunk =~ y/ / /; $colno += 4 * ($chunk =~ y/\t/\t/); } # appendWhiteSpace sub appendPrintable($) { my $chunk = shift; $refbuf .= $chunk; $colno += length $chunk; $lspace = 0; return if $colno <= $optimalLine; # Oops, line is getting long. Let's see where we can cut it. my ($i, $j) = (0, 0); if($lperiod > $cutLineAfter) { $i = $lperiod, $j = $idxperiod; } elsif($lcomma > $cutLineAfter) { $i = $lcomma, $j = $idxcomma; } elsif($lright > $cutLineAfter) { $i = $lright, $j = $idxright; } elsif($lany > $cutLineAfter) { $i = $lany, $j = $idxany; } return unless $j; # nothing we can do about it $longcut = 0; $longcut = $j if $i != $lperiod; substr($refbuf, $j, 1) = "\n"; $colno -= $i; $lperiod -= $i; $lcomma -= $i; $lright -= $i; $lany -= $i; } # appendPrintable # Break up a line using the above routines. sub breakLine($) { my $t = shift; my $ud = $$t =~ s/\r$//; if($lspace eq "2l") { $$t =~ s/^/\r/ if length $$t; $lspace = 2; } $$t =~ s/^/\r/ if length $$t > $paraLine; my $rc = $$t =~ y/\r/\n/; $ud |= $$t =~ s/[ \t]+$//gm; $ud |= $$t =~ s/([^ \t\n])[ \t]{2,}/$1 /g; $ud |= $$t =~ s/([^ \t\n])\t/$1 /g; $ud |= $$t =~ s/ +\t/\t/g; $lspace = 2 if $lspace < 2; # should never happen $lspace = 3 unless length $$t; return $ud if ! $rc and length $$t < $optimalLine; $rc |= $ud; # The following 120 comes from $paraLine. $$t =~ s/(\n.{120})/\n$1/g; $$t =~ s/(.{120,}\n)/$1\n/g; $refbuf = ""; $colno = 1; $longcut = $lperiod = $lcomma = $lright = $lany = 0; while($$t =~ /(\s+|[^\s]+)/g) { my $chunk = $1; if($chunk =~ /\s/) { appendWhiteSpace($chunk, 1); } else { appendPrintable($chunk); } } if($lspace < 2) { # line didn't have a \r at the end # We might want to paste the last word back on. appendWhiteSpace("\n", 1); chop $refbuf; } $rc = 1 if $refbuf =~ /\n/; return 0 unless $rc; $$t = $refbuf; $lspace = "2l" if length $refbuf > $paraLine; return 1; } # breakLine # Check the syntax of a regular expression, before we pass it to perl. # If perl doesn't like it, it dies, and you've lost your edits. # The first char is the delimiter -- we stop at the next delimiter. # The regexp, up to the second delimiter, is returned, # along with the remainder of the string in the second return variable. # return (regexp, remainder), or return () if there is a problem. # As usual, $errorMsg will be set. # Pass the line containing the regexp, and a flag indicating # left or right side of a substitute. sub regexpCheck($$) { my ($line, $isleft) = @_; my ($c, $d); # We wouldn't be here if the line was empty. my $delim = substr $line, 0, 1; index($valid_delim, $delim) >= 0 or $errorMsg = "invalid delimiter $delim", return (); $line = substr $line, 1; # remove lead delimiter # Remember whether a character is "on deck", ready to be modified by * etc. my $ondeck = 0; my $offdeck = ' '; my $exp = ""; my $cc = 0; # in character class my $paren = 0; # nested parentheses while(length $line) { $c = substr $line, 0, 1; if($c eq '\\') { $errorMsg = "line ends in backslash", return () if length($line) == 1; $d = substr $line, 1, 1; $ondeck = 1; $offdeck = ' '; # I can't think of any reason to remove the escape \ from any character, # except ()|, where we reverse the sense of escape, # and \& on the right, which becomes &. if(index("()|", $d) >= 0 and ! $cc and $isleft) { $ondeck = 0, ++$paren if $c eq '('; --$paren if $c eq ')'; $errorMsg = "Unexpected closing )", return () if $paren < 0; $c = ''; } $c = '' if $d eq '&' and ! $isleft; $exp .= "$c$d"; $line = substr $line, 2; next; } # escape character # Break out if you've hit the delimiter $paren or $c ne $delim or last; # Not the delimiter, I'll assume I can copy it over to $exp. # But I have to watch out for slash, which is *my* delimiter. $exp .= '\\' if $c eq '/'; # Then there's ()|, which I am reversing the sense of escape. $exp .= '\\' if index("()|", $c) >= 0 and $isleft; # Sometimes $ is interpolated when I don't want it to be. # Even if there is no alphanumeric following, a bare $ seems to cause trouble. # Escape it, unless followed by delimiter, or digit (rhs). if($c eq '$') { $exp .= '\\' if $isleft and length($line) > 1 and substr($line, 1, 1) ne $delim; $exp .= '\\' if ! $isleft and $line !~ /^\$\d/; } if($c eq '^') { $exp .= '\\' if $isleft and $cc != length $exp; } # And we have to escape every @, to avoid interpolation. # Good thing we don't have to escape %, # or it might mess up our % remembered rhs logic. $exp .= '\\' if $c eq '@'; # Turn & into $& $exp .= '$' if $c eq '&' and ! $isleft; # Finally push the character. $exp .= $c; $line = substr $line, 1; # Are there any syntax checks I need to make on the rhs? # I don't think so. next if ! $isleft; if($cc) { # character class # All that matters here is the ] $cc = 0 if $c eq ']'; next; } # Modifiers must have a preceding character. # Except ? which can reduce the greediness of the others. if($c eq '?' and $offdeck ne '?') { $ondeck = 0; $offdeck = '?'; next; } if(index("?+*", $c) >= 0 or $c eq '{' and $line =~ s/^(\d+,?\d*})//) { my $mod = ( $c eq '{' ? "{$1" : $c); $errorMsg = "$mod modifier has no preceding character", return () if ! $ondeck; $ondeck = 0; $offdeck = $c; $exp .= "$1" if $c eq '{'; next; } # modifier $ondeck = 1; $offdeck = ' '; $cc = length $exp if $c eq '['; } # loop over chars in the pattern $cc == 0 or $errorMsg = "no closing ]", return (); $paren == 0 or $errorMsg = "no closing )", return (); if(! length $exp and $isleft) { $exp = $savelhs; $errorMsg = "no remembered search string", return () if ! defined $exp; } $savelhs = $exp if $isleft; if(! $isleft) { if($exp eq '%') { $exp = $saverhs; $errorMsg = "no remembered replacement string", return () if ! defined $exp; } elsif($exp eq '\\%') { $exp = '%'; } $saverhs = $exp; } # rhs return ($exp, $line); } # regexpCheck # Get the start or end of a range. # Pass the line containing the address. sub getRangePart($) { my $line = shift; my $ln = $dot; if($line =~ s/^(\d+)//) { $ln = $1; } elsif($line =~ s/^\.//) { # $ln is already set to dot } elsif($line =~ s/^\$//) { $ln = $dol; } elsif($line =~ s/^'([a-z])//) { $ln = substr $labels, (ord($1) - ord('a'))*$lnwidth, $lnwidth; $errorMsg = "label $1 not set", return () if $ln eq $lnspace; } elsif($line =~ m:^([/?]):) { $errorMsg = "search string not found", return () if $dot == 0; my $delim = $1; my @pieces = regexpCheck($line, 1); return () if $#pieces < 0; my $exp = $pieces[0]; $line = $pieces[1]; my $icase = ""; # case independent $icase = "i" if $caseInsensitive; if($delim eq substr $line, 0, 1) { $line = substr $line, 1; if('i' eq substr $line, 0, 1) { $line = substr $line, 1; $icase = 'i'; } } my $incr = ($delim eq '/' ? 1 : -1); # Recompile the regexp after each command, but don't compile it on every line. # Is there a better way to do this, besides using eval? my $notfound = 0; eval ' while(1) { $ln += $incr; $ln = 1 if $ln > $dol; $ln = $dol if $ln == 0; last if fetchLine($ln, 1) =~ ' . "/$exp/o$icase; " . '$notfound = 1, last if $ln == $dot; } # looking for match '; # end evaluated string $errorMsg = "search string not found", return () if $notfound; } # search pattern # Now add or subtract from this base line number while($line =~ s/^([+-])(\d*)//) { my $add = ($2 eq "" ? 1 : $2); $ln += ($1 eq '+' ? $add : -$add); } $errorMsg = "line number too large", return () if $ln > $dol; $errorMsg = "negative line number", return () if $ln < 0; return ($ln, $line); } # getRangePart # Read the data as a string from a url. # Data is retrieved using http, https, or ftp. # Parameters: url, post data, result buffer. # You can return 0 (failure) and leave text and the buffer, # and I'll report the error, and still assimilate the buffer. sub readUrl($$$) { my ($filename, $post, $tbuf) = @_; my $rc = 1; # return code, success $lfsz = 0; # local file size my $rsize = 0; # size read my $weburl; my $scheme; my $encoding = ""; my $pagetype = ""; my %url_desc = (); # Description of the current URL # I don't know if we need a full url encode or what?? # This is a major kludge! I just don't understand this. $filename =~ s/ /%20/g; $filename =~ s/[\t\r\n]//g; # I don't know what http://foo@this.that.com/file.htm means, # but I see it all the time. $filename =~ s,^http://[^/]*@,http://,i; $$tbuf = ""; # start with a clear buffer $errorMsg = "too many nested frames", return 0 unless $rerouteCount; --$rerouteCount; # split into machine, file, and post parameters separate: { my $oldname = $filename; # remember where we started my $authinfo = ""; # login password for web sites that return error 401 $scheme = is_url $filename; # scheme could have changed $weburl = 0; $weburl = 1 if $scheme =~ /^https?$/; if(!length $post and $filename =~ s/^(.*?)(\?.*)$/$1/ ) { $post = $2; } # $post should be url encoded, but sometimes it's not, and I don't know why. $post =~ y/ /+/; my $postfilename = ""; # We assume $post starts with ? or *, if it is present at all. my $meth = "GET"; my $postapplic = ""; if(substr($post, 0, 1) eq '*') { $meth = "POST"; } else { $postfilename = $post; } print "$meth: $post\n" if $debug >= 2; $filename =~ s,^$scheme://,,i; my $serverPort = 80; $serverPort = 443 if $scheme eq 'https'; $serverPort = 21 if $scheme eq 'ftp'; $serverPort = 23 if $scheme eq 'telnet'; my $serverPortString = ""; my $server = $filename; $server =~ s,/.*,,; # Sometimes we need to do this -- got me hanging! $server =~ s/%([0-9a-fA-F]{2})/chr hex "$1"/ge; if($server =~ s/:(\d+)$//) { $serverPort = $1; } # If a server is on port 443, assume it speaks SSL. # This is a real bastardization of the html standard, # but it's the explorer standard. Need I say more? $scheme = 'https' if$serverPort == 443; $serverPortString = ":$serverPort" if $serverPort != 80; $filename =~ s,^[^/]*,,; # Lots of http servers can't handle /./ or /../ or // $filename =~ s:/{2,}:/:g; # Oops, put internal http:// back the way it was. # The bug is caused by a line like this. # # Because it's post, the get parameters after the ? are still here. # And I just turned http:// into http:/ # This is very rare, but it happened to me, so I'm trying to fix it. $filename =~ s,http:/,http://,gi; $filename =~ s,ftp:/,ftp://,gi; $filename =~ s:^/(\.{1,2}/)+:/:; $filename =~ s:/(\./)+:/:g; 1 while $filename =~ s:/[^/]+/\.\./:/:; $filename =~ s:^/(\.\./)+:/:; # Ok, create some more variables so we either fetch this file # or convert it if it's pdf. # Too bad I did all this work, and the pdf converter doesn't work for crap. # Probably because pdf is irreparably inaccessible. # Thanks a lot adobe! my $go_server = $server; my $go_port = $serverPort; my $go_portString = $serverPortString; my $go_file = $filename; my $go_post = $post; my $go_postfilename = $postfilename; my $go_meth = $meth; if($filename =~ /\.pdf$/ and $pdf_convert) { ($meth eq "GET" and $scheme eq "http") or $errorMsg = "online conversion from pdf to html only works when the pdf file is accessed via the http get method\ntype pr to download pdf in raw mode", return 0; $go_server="access.adobe.com"; $go_port = 80; $go_portString = ""; $go_file = "/perl/convertPDF.pl"; # It would be simpler if this bloody form wer get, but it's post. $go_meth = "POST"; $go_post = "http://$server$serverPortString$filename$postfilename"; $go_post = "*submit=submit&url=" . urlEncode($go_post); $go_postfilename = ""; } # redirecting to adobe to convert pdf if($go_meth eq "POST") { $postapplic = "Pragma: no-cache$eol" . "Cache-Control: no-cache$eol" . "Content-Type: application/x-www-form-urlencoded$eol" . "Content-Length: " . (length($go_post)-1) . $eol; } my $newname = ""; $authAttempt = 0; makeconnect: { my $chunk; $lfsz = 0; $$tbuf = ""; $go_file = "/" if ! length $go_file; %url_desc = (SCHEME => $scheme, SERVER => $go_server, PORT => $go_port, PATH => $go_file, method => $go_meth); $url_desc{content} = substr($go_post, 1) if length $go_post; # Kinda silly. # If you're using digest authentication with the POST method, # the content needs to be digestified. # This is for message integrity checking, when that option is used. # Consider completely replacing $go_x variables with elements of the %url_desc # hash? There is massive redundancy here. my $domainCookies = ""; $domainCookies = fetchCookies(\%url_desc) if $allowCookies; # Grab the cookies. my $send_server = # Send this to the http server - maybe via SSL "$go_meth $go_file$go_postfilename HTTP/1.0$eol" . # Do we need $go_portString here??? # If I put it in, paypal doesn't work. "Host: $go_server$eol" . (length $referer ? "Referer: $referer$eol" : "") . $domainCookies . $authinfo . "Accept: text/*, audio/*, image/*, application/*, message/*$eol" . "Accept: audio-file, postscript-file, mail-file, default, */*;q=0.01$eol" . "Accept-Encoding: gzip, compress$eol" . "Accept-Language: en$eol" . "User-Agent: $agent$eol" . $postapplic . $eol; # blank line at the end # send data after if post method $send_server .= substr($go_post, 1) if $go_meth eq "POST"; if($debug >= 4) { my $temp_server = $send_server; $temp_server =~ y/\r//d; print $temp_server; } if($scheme eq 'http') { # Connect to the http server. my $iaddr = inet_aton($go_server) or $errorMsg = "cannot identify $go_server on the network", return 0; my $paddr = sockaddr_in($go_port, $iaddr); my $proto = getprotobyname('tcp'); socket(FH, PF_INET, SOCK_STREAM, $proto) or $errorMsg = "cannot allocate a socket", return 0; connect(FH, $paddr) or $errorMsg = "cannot connect to $go_server", return 0; FH->autoflush(1); print FH $send_server; # Send the HTTP request message # Now retrieve the page and update the user after every 100K of data. my $last_fk = 0; STDOUT->autoflush(1) if ! $doslike; while(defined($rsize = sysread FH, $chunk, 100000)) { print "sockread $rsize\n" if $debug >= 5; $$tbuf .= $chunk; $lfsz += $rsize; last if $rsize == 0; my $fk = int($lfsz/100000); if($fk > $last_fk) { print "."; $last_fk = $fk; } last if $lfsz >= $maxfile; } close FH; print "\n" if $last_fk; STDOUT->autoflush(0) if ! $doslike; $lfsz <= $maxfile or $errorMsg = "file is too large, limit 40MB", return 0; defined $rsize or $$tbuf = "", $errorMsg = "error reading data from the socket", return 0; } elsif ($scheme eq 'https') { $lfsz = do_ssl($go_server, $go_port, $send_server, $tbuf); Net::SSLeay::free($ssl) if defined $ssl; Net::SSLeay::CTX_free($ctx) if defined $ctx; return 0 unless $lfsz; } elsif ($scheme eq 'ftp') { $lfsz = ftp_connect($go_server, $go_port, $go_file, $tbuf); return 0 unless $lfsz; } elsif ($scheme eq "telnet") { if($go_server =~ s/^([^:@]*):([^:@]*)@//) { print "This URL gives a suggested username of $1 and password of $2\n" . "to be used with the telnet connection you are about to establish.\n"; # See RFC 1738, section 3.8. The username and password in a telnet URL # are advisory. There is no standard method of logging into telnet services. # I guess this is especially useful for public services, which offer guest accounts and such. } print "Starting telnet.\n"; system("telnet $go_server $go_port"); return 1; } else { $errorMsg = "this browser cannot access $scheme URLs.", return 0; } # We got the web page. # But it might be a redirection to another url. if($weburl and $$tbuf =~ /^http\/[\d.]+ 30[12]/i) { if($$tbuf =~ /\nlocation:[ \t]+(.*[^\s])[ \t\r]*\n/i) { $newname = $1; print "relocate $newname\n" if $debug >= 2; }} if($rc and ! length $newname and # Some web sites serve up pages with no headers at all! # aspace.whizy.com/forum/ultimate.cgi $$tbuf =~ /^http/i and $$tbuf =~ /^http\/[\d.]+ 404 /i) { $errorMsg = "file not found on the remote server"; $rc = 0; } # not found # there is yet another way to redirect to a url if($rc and $$tbuf =~ /]*(url=|\d+;)['"]?([^'">\s]+)/i) { $newname = $2; print "refresh $newname\n" if $debug >= 2; # This is almost always an absolute url, even without the http prefix, # but sometimes it's relative. Got me hanging! # Here's a looser criterion for web url. if($newname =~ /^[\w,-]+\.[\w,-]+\.[\w,-]/) { $newname = "http://$newname"; } } # Extract information from the http header - primarily cookies. $encoding = $pagetype = ""; if($$tbuf =~ s/^(http\/\d.*?\r?\n\r?\n)//si) { my $header = $1; my @lines = split "\n", $header; open BFH, ">>$ebhttp"; if(defined BFH) { print BFH $header; close BFH; } $authinfo = ""; while(my $hline = shift @lines) { $hline =~ s/\r$//; print "$hline\n" if $debug >= 4; setCookies($hline, \%url_desc) if $hline =~ /^Set-Cookie:/i and $allowCookies; $authinfo = parseWWWAuth($hline, \%url_desc) if $hline =~ /^WWW-Authenticate/i; return 0 if $authinfo eq "x"; # I shouldn't really discard things like charset=blablabla, # but I don't really know what to do with it anyways. $hline =~s/;.*//; $encoding = lc $1 if $hline =~ /^content-encoding:\s+['"]?(\w+)['"]?\s*$/i; $pagetype = lc $1 if $hline =~ /^content-type:\s+['"]?([^\s'"]+)['"]?\s*$/i; } # loop over lines ++$authAttempt, redo makeconnect if length $authinfo; } else { # http header extracted if($scheme =~ /^https?$/) { $errorMsg = "http response doesn't have a head-body structure"; $rc = 0; } else { # For now, this means ftp. # We could have retrieved an html page via ftp, but probably not. # Turn off browse command. $cmd = 'e' unless $$tbuf =~ /^<[hH!]/; } } } # makeconnect # cookies that are set via http-equiv # The content of the cookie must be quoted. while($$tbuf =~ /= 2; if($newname ne $oldname) { # It's not really diferent if one has :80 and the other doesn't. # I wouldn't code this up if it didn't really happen. See www.claritin.com $oldname =~ s,^HTTP://,http://,; $oldname =~ s,^(http://)?([^/]*):80/,$1$2/,; $oldname =~ s,^(http://)?([^/]*):80$,$1$2,; $newname =~ s,^HTTP://,http://,; $newname =~ s,^(http://)?([^/]*):80/,$1$2/,; $newname =~ s,^(http://)?([^/]*):80$,$1$2,; if($oldname ne $newname) { if(--$rerouteCount) { print "$newname\n" if $debug >= 1; # Post method becomes get after redirection, I think. # $post = "" if length $post and $newname =~ /\?[^\/]*$/; $post = ""; $filename = $newname; redo separate; } $errorMsg = "too many url redirections"; $rc = 0; }}} # automatic url redirection $changeFname = "$scheme://$server$serverPortString$filename$postfilename"; } # separate # Check for complressed data. if($rc and $lfsz and length $encoding and $pagetype =~ /^text/i) { print "$lfsz\ndecoding $encoding\n" if $debug >= 2; my $program = ""; my $csuf = ""; # compression suffix $program = "zcat", $csuf = "gz" if $encoding eq "gzip"; $program = "zcat", $csuf = "Z" if $encoding eq "compress"; length $program or $errorMsg = "unrecognized compression method", return 0; $cfn = "$ebtmp.$csuf"; # compressed file name open FH, ">$cfn" or $errorMsg = "cannot create temp file $cfn", return 0; binmode FH, ':raw' if $doslike; print FH $$tbuf or $errorMsg = "cannot write to temp file $cfn", return 0; close FH; unlink $ebtmp; if(! system "$program $ebtmp.$csuf >$ebtmp 2>/dev/null") { # There are web pages out there that are improperly compressed. # We'll call it good if we got any data at all. $errorMsg = "could not uncompress the data", return 0 unless (stat($ebtmp))[7]; } # Read in the uncompressed data. $$tbuf = ""; open FH, $ebtmp or $errorMsg = "cannot open the uncompressed file $ebtmp", return 0; $lfsz = (stat(FH))[7]; $lfsz <= $maxfile or $errorMsg = "uncompressed file is too large, limit 40MB", close FH, return 0; binmode FH, ':raw' if $doslike; $rsize = sysread FH, $$tbuf, $lfsz; close FH; $rsize and $rsize == $lfsz or $errorMsg = "cannot read the uncompressed data from $ebtmp", return 0; unlink $ebtmp; } # compressed data if($rc and $fetchFrames) { $errorMsg = ""; # This really isn't right - to do this here I mean. # If a line of javascript happens to contain a frame tag # I'm going to fetch that frame and put it in right here. # Hopefully that won't happen. # Note that the entire frame tag must be on one line. $$tbuf =~ s/(\0\x80-\xff]+>)/readFrame($1)/gei; $rc = 0 if length $errorMsg; } # looking for frames return $rc; } # readUrl # Read a frame. sub readFrame($) { my $tag = shift; my $saveFname = $changeFname; my($tc, $fbuf, $src, $name); $tag =~ s/\bsrc *= */src=/gi; $tag =~ s/\bname *= */name=/gi; $tc = $tag; if($tc =~ s/^.*\bsrc=//s) { $src = $tc; $src =~ s/ .*//s; $src =~ s/^['"]//; $src =~ s/['"]?>?$//; if(length $src) { print "fetch frame $src\n" if $debug >= 1; $src = resolveUrl($saveFname, $src); if($didFrame{$src}) { print "already fetched\n" if $debug >= 2; $changeFname = $saveFname; return ""; } $didFrame{$src} = 1; print "* $src\n" if $debug >= 1; $name = ""; $tc = $tag; if($tc =~ s/^.*\bname=//s) { $tc =~ s/ .*//s; $tc =~ s/^['"]//; $tc =~ s/['"]?>?$//; $name = urlDecode $tc if length $tc; } # name attribute if(readUrl($src, "", \$fbuf)) { # Replace the tag with the data, and some stuff prepended. $name = " $name" if length $name; $tag = "

    Frame$name:

    \n\n"; $changeFname = $saveFname; return $tag.$fbuf; } # frame read successfully }} # src attribute present $changeFname = $saveFname; return $tag; } # readFrame # Adjust the map of line numbers -- we have inserted text. # Also shift the downstream labels. # Pass the string containing the new line numbers, and the dest line number. sub addToMap($$) { my ($newpiece, $dln) = @_; my $offset = length($newpiece)/$lnwidth; $offset > 0 or die "negative offset in addToMap"; my ($i, $j); foreach $i (0..25) { my $ln = substr($labels, $i*$lnwidth, $lnwidth); # line number next if $ln eq $lnspace or $ln <= $dln; substr($labels, $i*$lnwidth, $lnwidth) = sprintf($lnformat, $ln + $offset); } # loop over 26 labels $j = ($dln+1) * $lnwidth; substr($map, $j, 0) = $newpiece; $dot = $dln + $offset; $dol += $offset; $fmode |= $changemode|$firstopmode; $ubackup = 1; } # addToMap # Fold in the text buffer (parameter) at $endRange (global variable). # Assumes the text has the last newline on it. sub addTextToSession($) { my $tbuf = shift; # text buffer return 1 unless length $$tbuf; $fmode &= ~$nlmode if $endRange == $dol; if(not $$tbuf =~ s/\n$// and $endRange == $dol) { $fmode |= $nlmode; print "no trailing newline\n" if ! ($fmode&$binmode) and $cmd ne 'b'; } # missing newline my $j = $#text; my $newpiece = ""; # At this point $tbuf could be empty, whence split doesn't work properly. # This only happens when reading a file containing one blank line. if(length $$tbuf) { push @text, split "\n", $$tbuf, -1; } else { push @text, ""; } $#text = $j, return 0 if lineLimit 0; $newpiece .= sprintf($lnformat, $j) while ++$j <= $#text; addToMap($newpiece, $endRange); return 1; } # addTextToSession # Read a file into memory. # As described earlier, the lines are appended to @text. # Then the indexes for those lines are pasted into $map, # using addToMap(). # Check to see if the data is binary, and set $fmode accordingly. # Parameters are the filename or URL, and the post data (for URLs). sub readFile($$) { my ($filename, $post) = @_; my $tbuf; # text buffer my $rc = 1; # return code, success $filesize = 0; my $rsize = 0; # size read my $j; if(is_url $filename) { $rerouteCount = 24; %didFrame = (); $rc = readUrl($filename, $post, \$tbuf); $filesize = length $tbuf; return 0 unless $rc + $filesize; } else { # url or file open FH, "<$filename" or $errorMsg = "cannot open $filename, $!", return 0; # Check for directory here if(-d FH) { close FH; $j = $filename; $j =~ s,/$,,; $j .= "/*"; my @dirlist; if($j =~ / /) { @dirlist = glob '"'.$j.'"'; } else { @dirlist = glob $j; } if($#dirlist < 0) { $dot = $endRange; $filesize = 0; return $rc; } # empty directory $dirname = $j; $dirname =~ s/..$//; # get rid of /* return 0 if lineLimit($#dirlist + 1); $filesize = 0; $tbuf = ""; $j = $dirSufStart; substr($labels, $j, 2) = " "; foreach (@dirlist) { my $entry = $_; $entry =~ s,.*/,,; # leave only the file $entry =~ s/\n/\t/g; my $suf = ""; $suf .= '@' if -l; if(! -f) { $suf .= '/' if -d; $suf .= '|' if -p; $suf .= '*' if -b; $suf .= '<' if -c; $suf .= '^' if -S; } # not a regular file $filesize += length($entry) + length($suf) + 1; if($dol) { $entry .= $suf; } else { $suf .= " "; $j += 2; substr($labels, $j, 2) = substr($suf, 0, 2); } $tbuf .= "$entry\n"; } $dol or $fmode = $dirmode, print "directory mode\n"; return addTextToSession(\$tbuf); } # directory -f FH or $errorMsg = "$filename is not a regular file", close FH, return 0; $filesize = (stat(FH))[7]; if(! $filesize) { close FH; $dot = $endRange; $filesize = 0; return $rc; } # empty file $filesize <= $maxfile or $errorMsg = "file is too large, limit 40MB", close FH, return 0; binmode FH, ':raw' if $doslike; $rsize = sysread(FH, $tbuf, $filesize) if $filesize; close FH; $rsize == $filesize or $errorMsg = "cannot read the contents of $filename,$!", return 0; } # reading url or regular file my $bincount = $tbuf =~ y/\0\x80-\xff/\0\x80-\xff/; if($bincount*4 - 10 < $filesize) { # A text file - remove crlf in the dos world. $tbuf =~ s/\r\n/\n/g if $doslike; } elsif(! ($fmode&$binmode)) { # If it wasn't before, it is now a binary file. print "binary data\n"; $fmode |= $binmode; } $rc &= addTextToSession(\$tbuf); return $rc; } # readFile # Write a range into a file. # Pass the mode and filename. sub writeFile($$) { my ($mode, $filename) = @_; $errorMsg = "cannot write to a url", return 0 if is_url($filename); $dol or $errorMsg = "writing an empty file", return 0; open FH, "$mode$filename" or $errorMsg = "cannot create $filename, $!", return 0; $filesize = 0; binmode FH, ':raw' if $doslike and $fmode&$binmode; if($startRange) { foreach my $i ($startRange..$endRange) { my $nl = ($fmode&$nlmode && $i == $dol ? "" : "\n"); my $suf = dirSuffix($i); my $outline = fetchLine($i, 1).$suf.$nl; print FH $outline or $errorMsg = "cannot write to $filename, $!", close FH, return 0; $filesize += length $outline; } # loop over range } # nonempty file close FH; # This is not an undoable operation, nor does it change data. # In fact the data is "no longer modified" if we have written all of it. $fmode &= ~$changemode if $dol == 0 or $startRange == 1 and $endRange == $dol; return 1; } # writeFile # Read from another context. # Pass the context number. sub readContext($) { my $cx = shift; cxCompare($cx) and cxActive($cx) or return 0; my $dolcx = $dol[$cx]; $filesize = 0; if($dolcx) { return 0 if lineLimit $dolcx; $fmode &= ~$nlmode if $endRange == $dol; my $newpiece = ""; foreach my $i (1..$dolcx) { my $inline = fetchLineContext($i, 1, $cx); my $suf = ""; if($fmode[$cx] & $dirmode) { $suf = substr($labels[$cx], $dirSufStart + 2*$i, 2); $suf =~ s/ +$//; } $inline .= $suf; push @text, $inline; $newpiece .= sprintf $lnformat, $#text; $filesize += length($inline) + 1; } # end loop copying lines addToMap($newpiece, $endRange); if($fmode[$cx]&$nlmode) { --$filesize; $fmode |= $nlmode if $endRange == $dol; } $fmode |= $binmode, print "binary data\n" if $fmode[$cx]&$binmode and ! ($fmode&$binmode); } # nonempty buffer return 1; } # readContext # Write to another context. # Pass the context number. sub writeContext($) { my $cx = shift; my $dolcx = $endRange - $startRange + 1; $dolcx = 0 if ! $startRange; return 0 if ! cxCompare($cx) or !cxReset($cx, 1) or lineLimit $dolcx; my $mapcx = $lnspace; $filesize = 0; if($startRange) { foreach my $i ($startRange..$endRange) { $outline = fetchLine($i, 0); $outline .= dirSuffix($i); push @text, $outline; $mapcx .= sprintf $lnformat, $#text; $filesize += length($outline) + 1; } # end loop copying lines $fmode[$cx] = $fmode & ($binmode|$browsemode); $fmode[$cx] |= $nlmode, --$filesize if $fmode&$nlmode and $endRange == $dol; } # nonempty file $map[$cx] = $mapcx; $dot[$cx] = $dol[$cx] = $dolcx; $factive[$cx] = 1; $fname[$cx] = ""; $btags[$cx] = $btags; return 1; } # writeContext # Move or copy a block of text. sub moveCopy() { $dest++; # more convenient $endr1 = $endRange+1; # more convenient $dest <= $startRange or $dest >= $endr1 or $errorMsg = "destination lies inside the block to be moved or copied", return 0; if($cmd eq 'm' and ($dest == $endr1 or $dest == $startRange)) { $errorMsg = "no change" if ! $inglob; return 0; } my $starti = $startRange*$lnwidth; my $endi = $endr1*$lnwidth; my $desti = $dest * $lnwidth; my $offset = $endr1 - $startRange; my ($i, $j); # The section of the map that represents the range. my $piece_r = substr $map, $starti, $endi-$starti; my $piece_n = ""; # the new line numbers, if the text is copied. if($cmd eq 't') { return 0 if lineLimit $offset; for($j=0; $j $dol; $fmode &= ~$nlmode if $endRange == $dol and $cmd eq 'm'; } # Now for the labels my ($lowcut, $highcut, $p2len); if($dest <= $startRange) { $lowcut = $dest; $highcut = $endr1; $p2len = $startRange - $dest; } else { $lowcut = $startRange; $highcut = $dest; $p2len = $dest - $endr1; } foreach $i (0..25) { my $ln = substr($labels, $i*$lnwidth, $lnwidth); # line number next if $ln eq $lnspace or $ln < $lowcut; if($ln >= $highcut) { $ln += $offset if $cmd eq 't'; } elsif($ln >= $startRange and $ln <= $endRange) { $ln += ($dest < $startRange ? -$p2len : $p2len) if $cmd eq 'm'; $ln += $offset if $cmd eq 't' and $dest < $startRange; } elsif($dest < $startRange) { $ln += $offset; } else { $ln -= $offset if $cmd eq 'm'; } substr($labels, $i*$lnwidth, $lnwidth) = sprintf $lnformat, $ln; } # loop over labels $dol += $offset if $cmd eq 't'; $dot = $endRange; $dot += ($dest < $startRange ? -$p2len : $p2len) if $cmd eq 'm'; $dot = $dest + $offset - 1 if $cmd eq 't'; $fmode |= $changemode|$firstopmode; $ubackup = 1; return 1; } # moveCopy # Delete a block of text. # Pass the range to delete. sub delText($$) { my ($sr, $er) = @_; # local start and end range my ($i, $j); $fmode &= ~$nlmode if $er == $dol; $j = $er - $sr + 1; substr($map, $sr*$lnwidth, $j*$lnwidth) = ""; # Move the labels. foreach $i (0..25) { my $ln = substr($labels, $i*$lnwidth, $lnwidth); # line number next if $ln eq $lnspace or $ln < $sr; substr($labels, $i*$lnwidth, $lnwidth) = ($ln <= $er ? $lnspace : (sprintf $lnformat, $ln - $j)); } # loop over labels $dol -= $j; $dot = $sr; --$dot if $dot > $dol; $fmode |= $changemode|$firstopmode; $ubackup = 1; return 1; } # delText # Delete files from a directory as you delete lines. # It actually moves them to your trash bin. sub delFiles() { $dw or $errorMsg = "directories are readonly, type dw to enable directory writes", return 0; $dw == 2 or length $rbin or $errorMsg = "could not create .trash under your home directory, to hold the deleted files", return 0; my $ln = $startRange; my $cnt = $endRange - $startRange + 1; while($cnt--) { my $f = fetchLine($ln, 0); if($dw == 2 or dirSuffix($ln) =~ /^@/) { unlink "$dirname/$f" or $errorMsg = "could not remove $f, $!", return 0; } else { rename "$dirname/$f", "$rbin/$f" or $errorMsg = "Could not move $f to the trash bin, $!, set dx mode to actually remove the file", return 0; } delText($ln, $ln); substr($labels, $dirSufStart + 2*$ln, 2) = ""; } return 1; } # delFiles # Join lines from startRange to endRange. sub joinText() { $errorMsg = "cannot join one line", return 0 if $startRange == $endRange; return 0 if lineLimit 1; my ($i, $line); $line = ""; foreach $i ($startRange..$endRange) { $line .= ' ' if $cmd eq 'J' and $i > $startRange; $line .= fetchLine($i, 0); } push @text, $line; substr($map, $startRange*$lnwidth, $lnwidth) = sprintf $lnformat, $#text; delText($startRange+1, $endRange); $dot = $startRange; return 1; } # joinText # Substitute text on the lines in $startRange through $endRange. # We could be changing the text in an input field. # If so, we'll call infReplace(). # Also, we might be indirectory mode, whence we must rename the file. sub substituteText($) { my $line = shift; my $whichlink = ""; $whichlink = $1 if $line =~ s/^(\d+)//; length $line or $errorMsg = "no regular expression after $icmd", return -1; if($fmode&$dirmode) { $dw or $errorMsg = "directories are readonly, type dw to enable directory writes", return -1; } my ($i, $j, $exp, $rhs, $qrhs, $lastSubst, @pieces, $blmode); if($line ne "bl") { $blmode = 0; @pieces = regexpCheck($line, 1); return -1 if $#pieces < 0; $exp = $pieces[0]; $line = $pieces[1]; length $line or $errorMsg = "missing delimiter", return -1; @pieces = regexpCheck($line, 0); return -1 if $#pieces < 0; $rhs = $pieces[0]; $line = $pieces[1]; } else { $blmode = 1, $lspace = 3; } my $gflag = ""; my $nflag = 0; my $iflag = ""; $iflag = "i" if $caseInsensitive; $subprint = 1; # default is to print the last line substituted $lastSubst = 0; if(! $blmode) { if(length $line) { $subprint = 0; # necessarily starts with the delimiter substr($line, 0, 1) = ""; while(length $line) { $gflag = 'g', next if $line =~ s/^g//; $subprint = 2, next if $line =~ s/^p//; $iflag = 'i', next if $line =~ s/^i//; if($line =~ s/^(\d+)//) { ! $nflag or $errorMsg = "multiple numbers after the third delimiter", return -1; $nflag = $1; $nflag > 0 and $nflag <= 999 or $errorMsg = "numeric suffix out of range, please use [1-999]", return -1; next; } # number $errorMsg = "unexpected substitution suffix after the third delimiter"; return -1; } # loop gathering suffix flags ! $gflag or ! $nflag or $errorMsg = "cannot use both a numeric suffix and the `g' suffix simultaneously", return -1; # s/x/y/1 is very inefficient. $nflag = 0 if $nflag == 1; } # closing delimiter $qrhs = $rhs; # quote-fixed right hand side if($rhs =~ /^[ul]c$/) { $qrhs = "$qrhs \$&"; $iflag .= 'e' if !$nflag; } elsif($rhs eq "ue") { $qrhs = "unescape \$&"; $iflag .= 'e' if !$nflag; } elsif($rhs eq "mc") { $qrhs = "mixCase \$&"; $iflag .= 'e' if !$nflag; } else { if($nflag) { $qrhs =~ s/"/\\"/g; $qrhs = '"'.$qrhs.'"'; } } # I don't understand it, but $&[x] means something to perl. # So when I replace j with &[x], becomeing $&[x], it blows up. # Thus I escape open brackets and braces in the rhs. # Hopefully you won't escape them on the command line - you have no reason to. # If you do they'll be doubly escaped, and that's bad. $qrhs =~ s/([\[{])/\\$1/g; # } } else { $subprint = 0; } # blmode or not # Substitute the input fields first. if($cmd eq 'I') { my $yesdot = 0; my $foundFields = 0; foreach $i ($startRange..$endRange) { my $rc = infIndex($i, $whichlink); next unless $rc; $foundFields = 1; $rc > 0 or $dot = $i, $inglob = 0, return -1; my $newinf = $inf; if(!$nflag) { eval '$rc = $newinf =~ ' . "s/$exp/$qrhs/$iflag$gflag; "; } else { $j = 0; eval '$newinf =~ ' . "s/$exp/++\$j == $nflag ? $qrhs : \$&/ge$iflag; "; $rc = ($j >= $nflag); } next unless $rc; $dot = $i; infReplace($newinf) or return -1; $yesdot = $dot; } # loop over lines if(! $yesdot) { if(!$inglob) { $errorMsg = "no match" if $foundFields; } return 0; } dispLine($yesdot) if $subprint == 2 or ! $inglob and $subprint == 1; return 1; } # input fields # Not an input field, just text, so replace it. # Once again, use the eval construct. # This time we might be substituting across an entire range. @pieces = (); $errorMsg = ""; eval ' for($i=$startRange; $i<=$endRange; ++$i) { my $temp = fetchLine($i, 0);' . ($blmode ? 'my $subst = breakLine(\$temp);' : (!$nflag ? 'my $subst = $temp =~ ' . "s/$exp/$qrhs/o$iflag$gflag; " : 'my $subst = 0; my $k = 0; $temp =~ ' . "s/$exp/++\$k == $nflag ? $qrhs : \$&/oge$iflag; " . '$subst = ($k >= $nflag); ' )) . 'next unless $subst; if($fmode&$dirmode) { if($temp =~ m,[/\n],) { $errorMsg = "cannot embed slash or newline in a directory name"; $inglob = 0; last; } my $dest = "$dirname/$temp"; my $src = fetchLine($i, 0); $src = "$dirname/$src"; if($src ne $dest) { if(-e $dest or -l $dest) { $errorMsg = "destination file already exists"; $inglob = 0; last; } rename $src, $dest or $errorMsg = "cannot move file to $temp", $inglob = 0, last; } # source and dest are different } # directory @pieces = split "\n", $temp, -1; @pieces = ("") if $temp eq ""; last if lineLimit $#pieces+1; $j = $#text; push @text, @pieces; @pieces = (); substr($map, $i*$lnwidth, $lnwidth) = sprintf $lnformat, ++$j; if($j < $#text) { my $newpiece = ""; $newpiece .= sprintf $lnformat, $j while ++$j <= $#text; addToMap($newpiece, $i); $j = length($newpiece) / $lnwidth; $endRange += $j; $i += $j; } dispLine($i) if $subprint == 2; $lastSubst = $i; $fmode |= $changemode|$firstopmode; $ubackup = 1; last if $intFlag; } '; # eval string return 0 if length $errorMsg; if(! $lastSubst) { $errorMsg = ($blmode ? "no change" : "no match") if ! $inglob; return 0; } $dot = $lastSubst; dispLine($dot) if $subprint == 1 and ! $inglob; if($intFlag and ! $inglob) { $errorMsg = $intMsg, return 0; } return 1; } # substituteText # Follow a hyperlink to another web page. sub hyperlink($) { my $whichlink = shift; $cmd = 'b'; $errorMsg = "cannot use the g$whichlink command in directory mode", return 0 if $fmode&$dirmode; $startRange == $endRange or $errorMsg = "go command does not expect a range", return 0; my $h; # hyperlink tag my @links = (); # links on this line my @bref = (); # baseref values my ($j, $line, $href); if($fmode&$browsemode) { $line = fetchLine $endRange, 0; while($line =~ /\x80([\x85-\x8f]+){/g) { $j = revealNumber $1; $h = $$btags[$j]; $href = $$h{href}; $errorMsg = "hyperlink found without a url?? internal error", return 0 unless defined $href; push @links, $href; push @bref, $$h{bref}; } # loop } # browse mode if($#links < 0) { $line = fetchLine $endRange, 1; stripWhite \$line; $line =~ s/[\s"']+/ /g; if(length $line) { while($line =~ /([^ ]+)/g) { $href = $1; $href =~ s/^[^\w]+//; $href =~ s/[^\w]+$//; if(is_url $href) { push @links, $href; } else { $href =~ s/^mailto://i; push @links, "mailto:$href" if $href =~ /^[\w.,-]+@[\w,-]+\.[\w,.-]+$/; } } } # loop over words } # looking for url in text mode $j = $#links + 1; $j or $errorMsg = "no links present", return 0; length $whichlink or $j == 1 or $errorMsg = "multiple links, please use g [1,$j]", return 0; $whichlink = 1 if ! length $whichlink; if($whichlink == 0 or $whichlink > $j) { $errorMsg = $j > 1 ? "invalid link, please use g [1,$j]" : "this line only has one link"; return 0; } --$whichlink; $href = $links[$whichlink]; if($href =~ s/^mailto://i) { $cmd = 'e'; return 1, "\x80mail\x80$href"; } # mailto $href =~ /^javascript:/i and $errorMsg = "sorry, this link calls a javascript function", return 0; return 1, $href if $href =~ /^#/; $line = resolveUrl(($#bref >= 0 ? $bref[$whichlink] : ""), $href); print "* $line\n"; return 1, $line; } # hyperlink # Follow an internal link to a section of the document. sub findSection($) { my $section = shift; foreach my $i (1..$dol) { my $t = fetchLine $i, 0; while($t =~ /\x80([\x85-\x8f]+)\*/g) { my $j = revealNumber $1; my $h = $$btags[$j]; return $i if $$h{name} eq $section; } } return 0; } # findSection # Return the number of unbalanced punctuation marks at the start and end of the line. sub unbalanced($$$) { my ($c, $d, $ln) = @_; my $curline = fetchLine($ln, 1); # Escape these characters, so we know they are literal. $c = "\\$c"; $d = "\\$d"; while($curline =~ s/$c[^$c$d]*$d//) { ; } my $forward = $curline =~ s/$c//g; $forward = 0 if $forward eq ""; my $backward = $curline =~ s/$d//g; $backward = 0 if $backward eq ""; return $backward, $forward; } # unbalanced # Find the line that balances the unbalanced punctuation. sub balanceLine($) { my $line = shift; my ($c, $d); # balancing characters my $openlist = "{([<`"; my $closelist = "})]>'"; my $alllist = "{}()[]<>`'"; my $level = 0; my ($i, $direction, $forward, $backward); if(length $line) { $line =~ /^[\{\}\(\)\[\]<>`']$/ or $errorMsg = "you must specify exactly one of $alllist after the B command", return 0; $c = $line; if(index($openlist, $c) >= 0) { $d = substr $closelist, index($openlist, $c), 1; $direction = 1; } else { $d = $c; $c = substr $openlist, index($closelist, $d), 1; $direction = -1; } ($backward, $forward) = unbalanced($c, $d, $endRange); if($direction > 0) { ($level = $forward) or $errorMsg = "line does not contain an open $c", return 0; } else { ($level = $backward) or $errorMsg = "line does not contain an open $d", return 0; } } else { # character specified by the user or not? # Look for anything unbalanced, probably a brace. foreach $i (0..2) { $c = substr $openlist, $i, 1; $d = substr $closelist, $i, 1; ($backward, $forward) = unbalanced($c, $d, $endRange); ! $backward or ! $forward or $errorMsg = "both $c and $d are unbalanced on this line, try B$c or B$d", return 0; ($level = $backward + $forward) or next; $direction = 1; $direction = -1 if $backward; last; } $level or $errorMsg = "line does not contain an unbalanced brace, parenthesis, or bracket", return 0; } # explicit character passed in, or look for one my $selected = ($direction > 0) ? $c : $d; # Now search for the balancing line. $i = $endRange; while(($i += $direction) > 0 and $i <= $dol) { ($backward, $forward) = unbalanced($c, $d, $i); if($direction > 0 and $backward >= $level or $direction < 0 and $forward >= $level) { $dot = $i; dispLine($dot); return 1; } $level += ($forward-$backward) * $direction; } # loop over lines $errorMsg = "cannot find the line that balances $selected"; return 0; } # balanceLine # Apply a regular expression to each line, and then execute # a command for each matching, or nonmatching, line. # This is the global feature, g/re/p, which gives us the word grep. sub doGlobal($) { my $line = shift; my ($i, $j, $exp, @pieces); length $line or $errorMsg = "no regular expression after $icmd", return 0; @pieces = regexpCheck($line, 1); return 0 if $#pieces < 0; $exp = $pieces[0]; $line = $pieces[1]; length $line or $errorMsg = "missing delimiter", return 0; $line =~ s/^.(i?)\s*//; my $iflag = $1; $iflag = "i" if $caseInsensitive; # Clean up any previous stars. substr($map, $_*$lnwidth+$lnwidth1, 1) = ' ' foreach (1.. $dol); # Find the lines that match the pattern. my $gcnt = 0; # global count eval ' for($i=$startRange, $j=$i*$lnwidth+$lnwidth1; $i<=$endRange; ++$i, $j+=$lnwidth) { substr($map, $j, 1) = "*", ++$gcnt if fetchLine($i, 1)' . ($cmd eq 'g' ? ' =~ ' : ' !~ ') . "/$exp/o$iflag; }"; $gcnt or $errorMsg = ($cmd eq 'g' ? "no lines match the g pattern" : "all lines match the v pattern"), return 0; # Now apply $line to every line with a * $inglob = 1; $errorMsg = ""; $line = 'p' if ! length $line; my $origdot = $dot; my $yesdot = 0; my $nodot = 0; my $stars = 1; global:while($gcnt and $stars) { $stars = 0; for($i=1; $i<=$dol; ++$i) { last global if $intFlag; next unless substr($map, $i*$lnwidth+$lnwidth1, 1) eq '*'; $stars = 1,--$gcnt; substr($map, $i*$lnwidth+$lnwidth1, 1) = ' '; $dot = $i; # ready to run the command if(evaluate($line)) { $yesdot = $dot; --$i if $ubackup; # try this line again, in case we deleted or moved it } else { # Subcommand might turn global flag off. $nodot = $dot, $yesdot = 0, last global if ! $inglob; } } } $inglob = 0; # yesdot could be 0, even upon success, if all lines are deleted via g/re/d if($yesdot or ! $dol) { $dot = $yesdot; dispLine($dot) if ($cmd eq 's' or $cmd eq 'I') and $subprint == 1; } elsif($nodot) { $dot = $nodot; } else { $dot = $origdot; $errorMsg = "none of the marked lines were successfully modified" if $errorMsg eq ""; } $errorMsg = $intMsg if $errorMsg eq "" and $intFlag; return ! length $errorMsg; } # doGlobal # Reveal the links to other web pages, or the email links. sub showLinks() { my ($i, $j, $h, $href, $line); my $addrtext = ""; if($fmode&$browsemode) { $line = fetchLine $endRange, 0; while($line =~ /\x80([\x85-\x8f]+){(.*?)}/g) { $j = revealNumber $1; $i = $2; $h = $$btags[$j]; $href = $$h{href}; $href = "" unless defined $href; if($href =~ s/^mailto://i) { $addrtext .= "$i:$href\n"; } else { $href = resolveUrl($$h{bref}, $href); $addrtext .= "\n$i\n\n"; } } # loop } # browse mode if(! length $addrtext) { length $fname or $errorMsg = "no file name", return 0; if(is_url($fname)) { $href = $fname; $href =~ s/\.browse$//; $j = $href; $j =~ s,^https?://,,i; $j =~ s,.*/,,; $addrtext = "\n$j\n\n"; } else { $addrtext = $fname."\n"; } } $addrtext =~ s/\n$//; $j = $#text; push @text, split "\n", $addrtext, -1; $#text = $j, return 0 if lineLimit 0; $h = cxPack(); cxReset($context, 0) or return 0; $$h{backup} = $backup if defined $backup; $backup = $h; print((length($addrtext)+1)."\n"); $dot = $dol = $#text - $j; my $newpiece = $lnspace; $newpiece .= sprintf($lnformat, $j) while ++$j <= $#text; $map = $newpiece; return 1; } # showLinks # All other editors let you stack and undo hundreds of operations. # If I'm writing a new editor, why don't I do that? # I don't know; guess I don't have the time. # And in my 20 years experience, I have rarely felt the need # to undo several operations. # I'm usually undoing the last typo, and that's it. # So I allow you to undo the last operation, only. # Get ready for a possible undo command. sub readyUndo() { return if $fmode & $dirmode; $savedot = $dot, $savedol = $dol; $savemap = $map, $savelabels = $labels; } # readyUndo sub goUndo() { # swap, so we can undo our undo. I do this alot. my $temp; $temp = $ dot, $dot = $lastdot, $lastdot = $temp; $temp = $ dol, $dol = $lastdol, $lastdol = $temp; $temp = $ map, $map = $lastmap, $lastmap = $temp; $temp = $ labels, $labels = $lastlabels, $lastlabels = $temp; } # goUndo # Replace labels with their lines in shell escapes. sub expandLabeledLine($) { my$x = shift; my $n = ord($x) - ord('a'); my $ln = substr $labels, $n*$lnwidth, $lnwidth; $ln ne $lnspace or $errorMsg = "label $x not set", return ""; return fetchLine($ln, 1); } # expandLabeledLine # Run a shell escape sub shellEscape($) { my $line = shift; # Expand 'a through 'z labels $errorMsg = ""; $line =~ s/\B'([a-z])\b/expandLabeledLine($1)/ge; return 0 if length $errorMsg; $line =~ s/'_/$fname/g; $line =~ s/'\./fetchLine($dot,1)/ge; if($doslike) { # Just run system and hope for the best. system $line; } else { # Unix has a concept of shells. my $shell = $ENV{SHELL}; $shell = "/bin/sh" if ! defined $shell; if(length $line) { system $shell, "-c", $line; } else { system $shell; } } # dos or unix print "ok\n"; return 1; } # shellEscape # Implement various two letter commands. # Most of these set and clear modes. sub twoLetter($) { my $line = shift; my ($i, $j); if($line eq "qt") { exit 0; } if($line =~ s/^cd\s+// or $line =~ s/^cd$//) { $cmd = 'e'; # so error messages are printed if(length $line) { my $temppath = `pwd`; chomp $temppath; if($line eq "-") { $errorMsg = "you have no previous directory", return 0 unless defined $oldpath; chdir $oldpath or $errorMsg = "cannot change to previous directory $oldpath", return 0; } else { $line = envFile($line); return 0 if length $errorMsg; chdir $line or $errorMsg = "invalid directory", return 0; } $oldpath = $temppath; } print `pwd`; return 1; } if($line eq "rf") { $cmd = 'e'; if($fmode & $browsemode) { $cmd = 'b'; $fname =~ s/.browse$//; } length $fname or $errorMsg = "no file name", return 0; $nostack = 1; return -1, "$cmd $fname"; } if($line eq "et") { $cmd = 'e'; $fmode&$browsemode or $errorMsg = $nobrowse, return 0; foreach $i (1..$dol) { $text[substr($map, $i*$lnwidth, $lnwidth1)] = fetchLine($i,1); } $fmode &= ~($browsemode|$firstopmode|$changemode); $btags = []; # don't need those any more. print "editing as pure text\n" if $helpall; return 1; } if($line eq "ub") { $fmode&$browsemode or $errorMsg = $nobrowse, return 0; dropEmptyBuffers(); # Backing out. $map = $$btags[0]{map1}; $fname = $$btags[0]{fname}; $fmode = $$btags[0]{fmode}; $labels = $$btags[0]{labels}; $dot = $$btags[0]{dot}; $dol = $$btags[0]{dol1}; apparentSize(); return 1; } # reverse browse if($line eq "f/" or $line eq "w/") { $i = $fname; $i =~ s,.*/,, or $errorMsg = "filename does not contain a slash", return 0; print "$i\n" if $helpall; substr($line, 1, 1) = " $i"; return -1, $line; } if($line =~ /^f[dkt]$/) { $fmode&$browsemode or $errorMsg = $nobrowse, return 0; my $key = "title"; $key = "keywords" if $line eq "fk"; $key = "description" if $line eq "fd"; my $val = $$btags[0]{$key}; if(defined $val) { print "$val\n"; } else { print "no $key\n"; } return 1; } if($line =~ /^sm(\d*)$/) { $cmd = 'e'; $smMail = $1; $altattach = 0; $j = sendMailCurrent(); $j and print "ok\n"; return $j; } # simple commands if($line eq "sg") { $global_lhs_rhs = 1; print "substitutions global\n" if $helpall; return 1; } if($line eq "sl") { $global_lhs_rhs = 0; print "substitutions local\n" if $helpall; return 1; } if($line eq "ci") { $caseInsensitive = 1; print "case insensitive\n" if $helpall; return 1; } if($line eq "cs") { $caseInsensitive = 0; print "case sensitive\n" if $helpall; return 1; } if($line eq "dr") { $dw = 0; print "directories readonly\n" if $helpall; return 1; } if($line eq "dw") { $dw = 1; print "directories writable\n" if $helpall; return 1; } if($line eq "dx") { $dw = 2; print "directories writable with delete\n" if $helpall; return 1; } if($line eq "dp") { $delprint ^= 1; print ($delprint ? "delete print\n" : "delete quiet\n"); return 1; } if($line eq "rh") { $reroute ^= 1; print ($reroute ? "redirect html\n" : "do not redirect html\n"); return 1; } if($line eq "pm") { $passive ^= 1; print ($passive ? "passive ftp\n" : "active ftp\n"); return 1; } if($line eq "ph") { $pdf_convert ^= 1; print ($pdf_convert ? "pdf to html conversion\n" : "pdf raw\n"); return 1; } if($line eq "vs") { $ssl_verify ^= 1; print ($ssl_verify ? "verify ssl connections\n" : "do not verify ssl connections (less secure)\n"); return 1; } if($line eq "ac") { $allowCookies ^= 1; print ($allowCookies ? "accept cookies\n" : "reject cookies\n"); return 1; } if($line eq "sr") { $allowReferer ^= 1; print ($allowReferer ? "send refering web page\n" : "don't send refering web page\n"); return 1; } if($line =~ s/^db *//) { if($line =~ /^\d$/) { $debug = $line, return 1; } else { $errorMsg = "please set debug level, 0 through 7", return 0; } } if($line =~ s/^ua *//) { if($line =~ /^\d+$/) { $errorMsg = "Agent number $line is not defined", return 0 if ! defined$agents[$line]; $agent = $agents[$line], return 1; } else { $errorMsg = "please set user agent, 0 through ".$#agents, return 0; } } # ua number if($line eq "ff") { $fetchFrames ^= 1; print ($fetchFrames ? "fetch frames\n" : "do not fetch frames\n"); return 1; } if($line eq "tn") { $textAreaCR ^= 1; print ($textAreaCR ? "dos newlines on text areas\n" : "unix newlines on text areas\n"); return 1; } if($line eq "eo") { $endmarks = 0; print "end markers off\n" if $helpall; return 1; } if($line eq "el") { $endmarks = 1; print "end markers list\n" if $helpall; return 1; } if($line eq "ep") { $endmarks = 2; print "end markers on\n" if $helpall; return 1; } return -1,"^".length($1) if $line =~ /^(\^+)$/; return stripChild() if $line eq "ws"; return unstripChild() if $line eq "us"; return -1, $line; # no change } # twoLetter # Evaluate the entered command. # This is indirectly recursive, as in g/z/ s/x/y/ # Pass the command line, and return success or failure. sub evaluate($) { my $line = shift; my ($i, $j, @pieces, $h, $href); my $postspace = 0; my $postBrowse; my $nsuf = -1; # numeric suffix my $cx; # context specified -- always $nsuf - 1 my $section = ""; # section within a document my $post = ""; # for post cgi method $nostack = 0; # suppress stacking of edit sessions $referer = ""; $referer = $fname if $allowReferer; $referer =~ s/\.browse$//; $cmd = ""; # We'll allow whitespace at the start of an entered command. $line =~ s/^\s*//; # Watch for successive q commands. $lastq = $lastqq, $lastqq = -1; if(!$inglob) { # We'll allow comments in an edbrowse script return 1 if $line =~ /^#/; return shellEscape $line if $line =~ s/^!\s*//; # Web express shortcuts if($line =~ s/^@ *//) { if(! length $line) { my @shortList = (); foreach $i (sort keys %shortcut) { $j = $i; my ($desc, $sort); defined ($desc = $shortcut{$i}{desc}) and $j .= " = $desc"; $j = "|$j"; defined ($sort = $shortcut{$i}{sort}) and $j = "$sort$j"; $j .= "\n"; push @shortList, $j; } # loop over shortcuts foreach (sort @shortList) { s/^.*?\|//; print $_; } return 1; } $cmd = '@'; ($j, $line, $postBrowse) = webExpress($line); return 0 unless $j; $line =~ s%^%b http://%; if($line =~ /\*/) { $post = $line; $post =~ s/.*\*/*/; $line =~ s/\*.*//; } } # Predefined command sets. if($line =~ s/^< *//) { if(!length $line) { foreach $i (sort keys %commandList) { print "$i\n"; } return 1; } $i = $commandList{$line}; defined $i or $errorMsg = "command set $line is not recognized", return 0; return evaluateSequence($i, $commandCheck{$line}); } # command set # Two letter commands. ($j, $line) = twoLetter($line); return $j if $j >= 0; } # not in global $startRange = $endRange = $dot; # default, if no range given $line = '+' if ! length $line; $line = ($dol ? 1 : 0) . $line if substr($line, 0, 1) eq ','; if($line =~ /^j/i) { $endRange = $dot + 1; $errorMsg = "line number too large", return "" if $endRange > $dol; } elsif(substr($line, 0, 1) eq '=') { $startRange = $endRange = $dol; } elsif($line =~ /^[wgv]/ and $line !~ /^g\s*\d*$/) { $startRange = 1, $endRange = $dol; $startRange = 0 if ! $dol; } elsif($line =~ s/^;//) { $endRange = $dol; } else { @pieces = getRangePart($line); $inglob = 0, return 0 if $#pieces < 0; $startRange = $endRange = $pieces[0]; $line = $pieces[1]; if($line =~ s/^,//) { $endRange = $dol; # new default if($line =~ /^[-'.\$+\d\/?]/) { @pieces = getRangePart($line); $inglob = 0, return 0 if $#pieces < 0; $endRange = $pieces[0]; $line = $pieces[1]; } # second address } # comma present } # end standard range processing # lc lower case, uc upper case $line =~ s:^([lmu]c|ue)$:s/.*/$1/:; if($line eq "bl") { # break the line dirBrowseCheck("break line") or return 0; $line = "sbl"; } $cmd = substr($line, 0, 1); if(length $cmd) { $line = substr($line, 1); } else { $cmd = 'p'; } $icmd = $cmd; $startRange <= $endRange or $errorMsg = "bad range", return 0; index($valid_cmd, $cmd) >= 0 or $errorMsg = "unknown command $cmd", $inglob = 0, return 0; # Change some of the command codes, depending on context $cmd = 'I' if $cmd eq 'i' and $line =~ /^[$valid_delim\d<*]/o; $cmd = 'I' if $cmd eq 's' and $fmode&$browsemode; $cmd = 's' if $cmd eq 'S'; my $writeMode = ">"; if($cmd eq "w" and substr($line, 0, 1) eq "+") { $writeMode = ">>"; $line =~ s/^.//; } !($fmode&$dirmode) or index($dir_cmd, $cmd) >= 0 or $errorMsg = "$icmd $nixdir", $inglob = 0, return 0; !($fmode&$browsemode) or index($browse_cmd, $cmd) >= 0 or $errorMsg = "$icmd $nixbrowse", $inglob = 0, return 0; $startRange > 0 or index($zero_cmd, $cmd) >= 0 or $errorMsg = "zero line number", return 0; $postspace = 1 if $line =~ s/^\s+//; if(index($spaceplus_cmd, $cmd) >= 0 and ! $postspace and length $line and $line !~ /^\d+$/) { $errorMsg = "no space after command"; return 0; } # env variable and wild card expansion if(index("brewf", $cmd) >= 0 and length $line) { $line = envFile($line); return 0 if length $errorMsg; } if($cmd eq 'B') { return balanceLine($line); } if($cmd eq 'z') { $startRange = $endRange + 1; $endRange = $startRange; $startRange <= $dol or $errorMsg = "line number too large", return 0; $cmd = 'p'; $line = $last_z if ! length $line; if($line =~ /^(\d+)\s*$/) { $last_z = $1; $last_z = 1 if $last_z == 0; $endRange += $last_z - 1; $endRange = $dol if $endRange > $dol; } else { $errorMsg = "z command should be followed by a number", return 0; } $line = ""; } # move/copy destination, the third address if($cmd eq 'm' or $cmd eq 't') { length $line or $errorMsg = "no move/copy destination", $inglob = 0, return 0; $line =~ /^[-'.\$+\d\/?]/ or $errorMsg = "invalid move/copy destination", $inglob = 0, return 0; @pieces = getRangePart($line); $inglob = 0, return 0 if $#pieces < 0; $dest = $pieces[0]; $line = $pieces[1]; $line =~ s/^\s*//; } # move copy destination if($cmd eq 'a') { ($line eq "+") ? ($line = "") : ($linePending = undef); } else { $linePending = undef; } ! length $line or index($nofollow_cmd, $cmd) < 0 or $errorMsg = "unexpected text after the $icmd command", $inglob = 0, return 0; # We don't need trailing whitespace, except for substitute or global substitute. index("sgvI", $cmd) >= 0 or $line =~ s/\s*$//; ! $inglob or index($global_cmd, $cmd) >= 0 or $errorMsg = "the $icmd command cannot be applied globally", $inglob = 0, return 0; if($cmd eq 'h') { $errorMsg = "no errors" if ! length $errorMsg; print $errorMsg,"\n"; return 1; } if($cmd eq 'H') { $helpall ^= 1; print "help messages on\n" if $helpall; return 1; } # H if(index("lpn", $cmd) >= 0) { foreach $i ($startRange..$endRange) { dispLine($i); $dot = $i; last if $intFlag; } return 1; } if($cmd eq '=') { print $endRange,"\n"; return 1; } if($cmd eq 'u') { $fmode&$firstopmode or $errorMsg = "nothing to undo", return 0; goUndo(); return 1; } # u if($cmd eq 'k') { $line =~ /^[a-z]$/ or $errorMsg = "please enter k[a-z]", return 0; $startRange == $endRange or $errorMsg = "cannot label an entire range", return 0; substr($labels, (ord($line) - ord('a'))*$lnwidth, $lnwidth) = sprintf $lnformat, $endRange; return 1; } $nsuf = $line if $line =~ /^\d+$/ and ! $postspace; $cx = $nsuf - 1; if($cmd eq 'f') { if($nsuf >= 0) { (cxCompare($cx) and cxActive($cx)) or return 0; $j = $fname[$cx]; print(length($j) ? $j : "no file"); print " [binary]" if $fmode[$cx]&$binmode; print "\n"; return 1; } if(length $line) { $errorMsg = "cannot change the name of a directory", return 0 if $fmode&$dirmode; $fname = $line; } else { print(length($fname) ? $fname : "no file"); print " [binary]" if $fmode&$binmode; print "\n"; } return 1; } # f if($cmd eq 'q') { $nsuf < 0 or (cxCompare($cx) and cxActive($cx)) or return 0; if($nsuf < 0) { $cx = $context; $errorMsg = "unexpected text after the $icmd command", return 0 if length $line; } cxReset($cx, 1) or return 0; return 1 if $cx != $context; # look around for another active session while(1) { $cx = 0 if ++$cx > $#factive; exit 0 if $cx == $context; next if ! defined $factive[$cx]; cxSwitch($cx, 1); return 1; } } # q if($cmd eq 'w') { if($nsuf >= 0) { $writeMode eq ">" or $errorMsg = "sorry, append to buffer not yet implemented", return 0; return writeContext($cx) } $line = $fname if ! length $line; if($fmode&$dirmode and $line eq $fname) { $errorMsg = "cannot write to the directory; files are modified as you go"; return 0; } return writeFile($writeMode, $line) if length $line; $errorMsg = "no file specified"; return 0; } # w # goto a file in a directory if($fmode&$dirmode and $cmd eq 'g' and ! length $line) { $cmd = 'e'; $line = $dirname . '/' . fetchLine($endRange, 0); } if($cmd eq 'e') { return (cxCompare($cx) and cxSwitch($cx, 1)) if $nsuf >= 0; if(!length $line) { $j = $context + 1; print "session $j\n"; return 1; } } # e if($cmd eq 'g' and $line =~ /^\d*$/) { ($j, $line) = hyperlink($line); return 0 unless $j; # Go on to browse the file. } # goto link if($cmd eq '^') { ! length $line or $nsuf >= 0 or $errorMsg = "unexpected text after the ^ command", return 0; $nsuf = 1 if $nsuf < 0; while($nsuf) { $errorMsg = "no previous text", return 0 if ! defined $backup; cxReset($context, 2) or return 0; $h = $backup; $backup = $$h{backup}; cxUnpack($h); --$nsuf; } # Should this print be inside or outside the loop? if($dot) { dispLine($dot); } else { print "empty file\n"; } return 1; } # ^ if($cmd eq 'A') { return showLinks(); } # A if($icmd eq 's' or $icmd eq 'S') { # A few shorthand notations. if($line =~ /^([,.;:!?)"-])(\d?)$/) { my $suffix = $2; $line = "$1 +"; # We have to escape the question mark and period $line =~ s/^([?.])/\\$1/; $line = "/$line/$1\\n"; $line .= "/$suffix" if length $suffix; } } # original command was s readyUndo if ! $inglob; if($cmd eq 'g' or $cmd eq 'v') { return doGlobal($line); } # global if($cmd eq 'I') { $fmode&$browsemode or $errorMsg = $nobrowse, $inglob = 0, return 0; if($line =~ /^\d*\?/) { # status $inglob and $errorMsg = $inoglobal, $inglob = 0, return 0; $startRange == $endRange or $errorMsg = $inorange, return 0; infIndex($endRange, $line) > 0 or return 0; infStatus($line); return 1; } # get info on input field if($line =~ /^\d*([=<])/) { my $asg = $1; $subprint = 1; my $yesdot = 0; my $t = $line; $t =~ s/^\d*[=<]//; if($asg eq '<') { if($t =~ /^\d+$/) { my $cx = $t-1; cxCompare($cx) and cxActive($cx) or $inglob = 0, return 0; my $dolcx = $dol[$cx]; $dolcx == 1 or $errorMsg = "session $t should contain exactly one line", $inglob = 0, return 0; $t = fetchLineContext(1, 1, $cx); } else { $errorMsg = ""; $t = envFile $t; length($errorMsg) and $inglob = 0, return 0; open FH, $t or $errorMsg = "cannot open $t, $!", $inglob = 0, return 0; $t = ; defined $t or $errorMsg = "empty file", $inglob = 0, return 0; if(defined ) { close FH; $errorMsg = "file contains more than one line"; $inglob = 0; return 0; } close FH; $t =~ s/[\r\n]+$//; } } # I 0 and infReplace($t) or $inglob = 0, return 0; $yesdot = $dot; } # loop over lines if($yesdot) { dispLine($yesdot) if ! $inglob; return 1; } $errorMsg = "no input fields present" if ! $inglob; return 0; } # i= if($line =~ /^\d*\*$/) { $inglob and $errorMsg = $inoglobal, $inglob = 0, return 0; $startRange == $endRange or $errorMsg = $inorange, return 0; infIndex($endRange, $line) > 0 or return 0; ($j, $line, $post) = infPush(); # return code of -1 means there's more to do. return $j unless $j < 0; } elsif( $line !~ m&^\d*[$valid_delim]&o) { $errorMsg = "unknown input field directive, please use I? or I= or I/text/replacement/"; return 0; } } # input field # Pull section indicator off of a url. $section = $1 if $cmd eq 'b' and $line =~ s/(#.*)//; if(($cmd eq 'b' or $cmd eq 'e') and length $line) { $h = undef; $h = cxPack() if $dol and ! $nostack; cxReset($context, 0) or return 0; $startRange = $endRange = 0; $changeFname = ""; if($line =~ /^\x80mail\x80(.*)$/) { # special code for sendmail link $href = $1; my $subj = urlSubject(\$href); $subj = "Comments" unless length $subj; if(lineLimit 2) { $i = 0; } else { $i = 1; push @text, "To: $href"; $map .= sprintf($lnformat, $#text); push @text, "Subject: $subj"; $map .= sprintf($lnformat, $#text); $dot = $dol = 2; print "SendMail link. Compose your mail, type sm to send, then ^ to get back.\n"; apparentSize(); } } else { $fname = $line; $i = readFile($fname, $post); $fmode &= ~($changemode|$firstopmode); } $filesize = -1, cxUnpack($h), return 0 if !$i and ! $dol and is_url($fname); if(defined $h) { $$h{backup} = $backup if defined $backup; $backup = $h; } return 0 if ! $i; $fname = $changeFname if length $changeFname; $cmd = 'e' if $fmode&$binmode or ! $dol; return 1 if $cmd eq 'e'; } if($cmd eq 'b') { if(! ($fmode&$browsemode)) { readyUndo(); print("$filesize\n"), $filesize = -1 if $filesize >= 0; render() or return 0; if(defined $postBrowse) { $$btags[0]{pb} = $postBrowse; evaluateSequence($postBrowse, 0); if($$btags[0]{dol2} > $dol) { $fmode &= ~($changemode|$firstopmode); apparentSize(); } } } else { $errorMsg = "already browsing", return 0 if ! length $section; } return 1 if ! length $section; $section =~ s/^#//; $j = findSection($section); $errorMsg = "cannot locate section #$section", return 0 unless $j; $dot = $j; dispLine($dot); return 1; } # b if($cmd eq 'm' or $cmd eq 't') { return moveCopy(); } if($cmd eq 'i') { $cmd = 'a'; --$startRange, --$endRange; } if($cmd eq 'c') { delText($startRange, $endRange) or return 0; $endRange = --$startRange; $cmd = 'a'; } if($cmd eq 'a') { return readLines(); } if($cmd eq 'd') { $i = ($endRange == $dol); if($fmode & $dirmode) { $j = delFiles(); } else { $j = delText($startRange, $endRange); } $inglob = 0 if ! $j; if($j and $delprint and ! $inglob) { $i ? print "end of file\n" : dispLine($dot); } return $j; } # d if($cmd eq 'j' or $cmd eq 'J') { return joinText(); } # j if($cmd eq 'r') { return readContext($cx) if $nsuf >= 0; return readFile($line, "") if length $line; $errorMsg = "no file specified"; return 0; } # r if($cmd eq 's' or $cmd eq 'I') { $j = substituteText($line); $inglob = $j = 0 if $j < 0; return $j; } # substitute $errorMsg = "command $icmd not yet implemented"; $inglob = 0; return 0; } # evaluate sub evaluateSequence($$) { my $commands = shift; my $check = shift; foreach my $go (@$commands) { $inglob = 0; $intFlag = 0; $filesize = -1; my $rc = evaluate($go); print "$filesize\n" if $filesize >= 0; $rc or ! $check or return 0; } return 1; } # evaluateSequence # Hash to map html tags onto their English descriptions. # For instance, P maps to "paragraph". # Most of the tags, such as FONT, map to nothing, # whence they are thrown away. # The first two characters are not part of the description. # It forms a number that describes the nestability of the tag. # Bit 1 means the tag should be nested, like parentheses. # In fact all the bit1 tags should nest amongst eachother, unlike #
      (nesting error). # Bit 2 means a tag may appear inside itself, like nested lists. # Bit 4 means the tag implies a paragraph break. # Bit 8 means we retain attributes on the positive tag. # bit 16 means to close an open anchor *before* applying this tag %tagdesc = ( sub => "11a subscript", font => " 3a font", center => " 3centered text", sup => "11a superscript", title => "17the title", head => "17the html header information", body => "27the html body", bgsound => "24background music", meta => " 8a meta tag", base => " 8base reference for relative URLs", img => " 8an image", br => " 0a line break", p => "20a paragraph", blockquote => "20a quoted paragraph", div => "20a divided section", h => "21a header", dt => "20a term", dd => "20a definition", hr => "16a horizontal line", ul => "23a bullet list", ol => "23a numbered list", dl => "23a definition list", li => "16a list item", form => "25a form", input => "24an input item", a => "25an anchor", frame => "28a frame", map => "28An image map", area => "24an image map area", # I've seen tables nested inside tables -- I don't know why! table => "31a table", tr => "19a table row", td => "19a table entry", th => "19a table heading", pre => " 5a preformatted section", xmp => " 5a preformatted section", address => " 5a preformatted section", script => " 1a script", style => " 1a style block", noframes => " 1noframe section", select => "25an option list", textarea => "25an input text area", option => "24a select option", # The following tags convey formatting information that is eventually # discarded, but I'll track them for a while, # just to verify nestability. em => " 1a block of emphasized text", strong => " 1a block of emphasized text", b => " 1a block of bold text", i => " 1a block of italicized text", code => " 1a block of sample code", samp => " 1a block of sample code", ); # We encode tags in a @tag attribute=value attribute=value ...@ format, # though of course we don't use the @ sign. # We use \x80, which should not appear in international text. # I simply hard code it - it makes things simpler. # Support routine, to encode a tag. # Run from within a global substitute. # Pas the name of the tag, slash, and tag arguments sub processTag($$$) { my ($tag, $slash, $attributes) = @_; my $nlcount = $attributes =~ y/\n/\n/; # newline count my $doat = 0; # do attributes $tag = lc $tag; my $desc = $tagdesc{$tag}; if(defined $desc) { $doat = (substr($desc, 0, 2) & 8); } else { $tag = "z"; } # Do we need to gather up the attributes? if(!$doat or $slash eq "/") { # Guess not, just return the tag. return "" if $tag eq "z" and ! $nlcount; return "\x80$tag$slash$nlcount\x80"; } # Process each whitespace separated chunk, taking quotes into account. # note that name="foo"size="1" is suppose to be two separate tags; # God help us! # Borrow a global variable, even though this may not be an input tag. $itag = {tag => $tag}; push @$btags, $itag; $attributes =~ s/( # replace the entire matched text \w+ # attribute name (?>\s*=\s* # as in name=value (?> # a sequence of choices [^\s"']+ # regular printable characters | "[^"]*" # double quoted string | '[^']*' # single quoted string ) # one of three formats )? # =value )/processAttr($1)/xsge; # Capture description and keywords. if($tag eq "meta") { my $val = $$itag{name}; if(defined $val) { $val = lc $val; if($val eq "description" or $val eq "keywords") { my $content = $$itag{content}; if(defined $content) { stripWhite \$content; $$btags[0]{$val} = $content if length $content; } # content } # description or keywords } # name= pop @$btags; return "" unless $nlcount; return "\x80z$nlcount\x80"; } # meta tag my $tagnum = $#$btags; return "\x80$tag$nlcount,$tagnum\x80"; } # processTag # Support routine, to crack attribute=value. sub processAttr($) { my $line = shift; # Get rid of spaces around first equals. $line =~ s/^([^=\s]*)\s*=\s*/$1=/; # Get rid of the quotes. $line =~ s/("[^"]*"|'[^']*')/substr($1,1,-1)/sge; my $attr = lc $line; $attr =~ s/\s*=.*//s; return "" unless $attr =~ /^\w+$/; $line =~ s/^[^=]*=//s or $line = ""; $line =~ s/&([a-zA-Z]+|#\d+);/metaChar($1)/ge; $$itag{$attr} = $line; return ""; } # processAttr # Support routine, to encode a bang tag. # Run from within a global substitute. sub processBangtag($) { my $item = shift; if($item eq "'" or $item eq '"') { return (length $bangtag ? " " : $item); } if(substr($item, 0, 1) eq '<') { return "" if length $bangtag; return $item if $item eq "<"; $bangtag = substr $item, 1; return " my $l = length($bangtag) - 1; $l &= ~1; # back down to an even number return " " if $l and ! length $item; # lone > inside a comment $bangtag = ""; return ">"; } # processBangtag # Turn <>'" in javascript into spaces, as we did above. sub processScript($) { my $item = shift; if(length($item) < 5) { return ($inscript ? " " : $item); } # now $item is