3
# edbrowse: line editor/browser
13
248-524-1004 (during regular business hours)
14
http://www.eklhad.net/linux/app
16
=head1 Copyright Notice
18
This program is copyright (C) (C) Karl Dahlke, 2000-2003.
19
It is made available, by the author, under the terms of the General Public License (GPL),
20
as articulated by the Free Software Foundation.
21
It may be used for any purpose, and redistributed,
22
provided this copyright notice is included.
26
This program, and its associated documentation, are becoming quite large.
27
Therefore the documentation has been moved to a separate html file.
30
http://www.eklhad.net/linux/app/edbdoc.html
32
If you have lynx on hand, you can run:
34
lynx -dump http://www.eklhad.net/linux/app/edbdoc.html > edbdoc.txt
36
If you are using lynx to download the actual program, do this:
38
lynx -source www.eklhad.net/linux/app/edbrowse >edbrowse
44
@agents = ("edbrowse/$version");
48
# It's tempting to let perl establish the global variables as you go.
49
# Let's try not to do this.
50
# That's where all the side effects are - that's where the bugs come in.
51
# Below are the global variables, with some explanations.
53
$debug = 0; # general debugging
55
$ismc = 0; # is mail client
56
$zapmail = 0; # just get rid of the mail
57
$maxfile = 40000000; # Max size of an editable file.
58
$eol = "\r\n"; # end-of-line, as far as http is concerned
59
$doslike = 0; # Is it a Dos-like OS?
60
$doslike = 1 if $^O =~ /^(dos|win|mswin)/i;
61
$errorMsg = ""; # Set this if the last operation produced an error.
62
$inglob = 0; # Are we in global mode, under a g// operation?
64
$inscript = 0; # plowing through javascript
65
$filesize = 0; # size of file just read or written
66
$global_lhs_rhs = 0; # remember lhs and rhs across sessions
68
# Do we send crnl or nl after the lines in a text buffer?
69
# What is the standard - I think it's DOS newlines.
71
$pdf_convert = 1; # convert pdf to html
72
$fetchFrames = 1; # fetch the frames into a web page
73
$allsub = 0; # enclose all superscripts and subscripts
74
$allowCookies = 1; # allow all cookies.
75
%cookies = (); # the in-memory cookie jar
76
%authHist = (); # authorization strings by domain
77
$authAttempt = 0; # count authorization attempts for this page
78
$ssl_verify = 1; # By default we verify all certs.
79
$ssl = undef; # ssl connection
80
$ctx = undef; # ssl certificate
81
$allowReferer = 1; # Allow referer header by default.
82
$referer = ""; # refering web page
83
$reroute = 1; # follow http redirections to find the actual web page
84
$rerouteCount = 0; # but prevent infinite loops
85
%didFrame = (); # which frames have we fetched already
86
$passive = 1; # ftp passive mode on by default.
87
$nostack = 0; # suppress stacking of edit sessions
88
$last_z = 1; # line count for the z command
89
$endmarks = 0; # do we print ^ $ at the start and end of lines?
90
$subprint = 0; # print lines after substitutions?
91
$delprint = 0; # print line after delete
92
$dw = 0; # directory write enabled
93
$altattach = 0; # attachments are really alternative presentations of the same email
94
$do_input = 0; # waiting for the next input from the tty
95
$intFlag = 0; # control c was hit
96
$intMsg = "operation interrupted";
98
# Interrupt handler, for control C.
99
# Close file handle if we were reading from disk or socket.
104
print "\ninterrupt, type qt to quit completely\n";
107
# Reading from an http server.
108
close FH if defined FH;
109
# Kill ftp data connection if open.
110
close FDFH if defined FDFH;
111
# and mail connection or ftp control connection
112
close SERVER_FH if defined SERVER_FH;
113
# And listening ftp socket.
114
close FLFH if defined FLFH;
118
$SIG{INT} = \&intHandler;
120
# A quieter form of die, without the edbrowse line number, which just confuses people.
124
print "fatal: $msg\n";
128
@weekDaysShort = ("Sun","Mon","Tue","Wed","Thu","Fri","Sat");
129
@monthsShort = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");
132
my ($ss, $nn, $hh, $dd, $mm, $yy, $wd) = localtime time;
133
my $wds = $weekDaysShort[$wd];
134
my $mths = $monthsShort[$mm];
135
return sprintf "%s, %02d %s %d %02d:%02d:%02d",
136
$wds, $dd, $mths, $yy+1900, $hh, $nn, $ss;
139
# ubackup is set when the command has changed something.
140
# The previous text, stored in the save_* variables,
141
# is copied into the last* variables. If you type u,
142
# the last* variables and current variables are swapped.
145
# Did we successfully read the edbrowse config file?
146
# If so, set some variables.
147
$myname = $annoyFile = $junkFile = $addressFile = "";
150
@inmailserver = (); # list of pop3 servers
153
$whichMail = 0; # which account to use
155
$naccounts = 0; # number of pop accounts
156
$outmailserver = ""; # smtp
157
$smtplogin = ""; # smtp login
164
$serverLine = ""; # line received from mail or ftp server
166
# web express configuration variables and arrays.
170
$currentShortcut = "";
171
$currentCommandList = "";
173
# Specify the start and end of a range for an operation.
174
# 1,3m5 will set these variables to 1, 3, and 5.
175
$startRange = $endRange = $dest = 0;
177
# The input command, but only the one-letter commands.
179
# Now the command that is actually executed is in $cmd.
180
# This is usually the same as $icmd, but not always.
181
# 8i becomes 7a, for instance.
183
# The valid edbrowse commands.
184
$valid_cmd = "aAbBcdefghHiIjJklmnpqrsStuvwz=^@<";
185
# Commands that can be done in browse mode.
186
$browse_cmd = "AbBdefghHIjJklmnpqsuvwz=^@<";
187
# Commands for directory mode.
188
$dir_cmd = "AbdefghHklnpqsvwz=^@<";
189
# Commands that work at line number 0, in an empty file.
190
$zero_cmd = "aAbefhHqruw=^@<";
191
# Commands that expect a space afterward.
192
$spaceplus_cmd = "befrw";
193
# Commands that should have no text after them.
194
$nofollow_cmd = "aAcdhHijlmnptu=";
195
# Commands that can be done after a g// global directive.
196
$global_cmd = "dIjJlmnpst";
197
# Show the error message, not just the question mark, after these commands.
198
$showerror_cmd = "Abefqrw^@";
199
$helpall = 0; # show the error message all the time
201
# Remember that two successive q's will quit the session without changes.
202
# here we must track which session, by number, you were trying to quit.
203
$lastq = $lastqq = -1;
205
# For any variable x, there are usually multiple copies of x, one per session.
206
# These are housed in an array @x.
207
# In contrast, the variable $x holds $x[$context],
208
# according to the current context.
209
# I hope this isn't too confusing.
212
# dot and dol, current and last line numbers.
217
@factive = (1); # which sessions are active
218
# Retain file names, and whether the text has been modified.
221
$baseref = ""; # usually the same as $fname
222
@fmode = (0); # file modes
224
$binmode = 1; # binary file
225
$nlmode = 2; # newline apended
226
$browsemode = 4; # browsing html text
227
$changemode = 8; # something has changed in this file
228
$dirmode = 16; # directory mode
229
$firstopmode = 32; # first operation issued - undo is possible
230
$nobrowse = "not in browse mode"; # common error message
231
$nixbrowse = "command not available in browse mode";
232
$nixdir = "command not available in directory mode";
234
sub dirBrowseCheck($)
237
$fmode&$browsemode and $errorMsg = "$cmd $nixbrowse", $inglob = 0, return 0;
238
$fmode&$dirmode and $errorMsg = "$cmd $nixdir", $inglob = 0, return 0;
242
# retain base directory name when scanning a directory
244
$dirname = $dirname[0];
246
# Remember substitution strings.
247
@savelhs = (); # save left hand side
248
$savelhs = $savelhs[0];
249
@saverhs = (); # save right hand side
250
$saverhs = $saverhs[0];
252
# month hash, to encode dates.
254
(jan => 1, feb => 2, mar => 3, apr => 4, may => 5, jun => 6,
255
jul => 7, aug => 8, sep => 9, oct => 10, nov => 11, dec => 12);
258
defined $home and length $home or
259
dieq 'home directory not defined by $HOME.';
261
dieq "$home is not a directory.";
263
# Establish the recycle bin, for deleted files.
264
$rbin = "$home/.recycle";
266
$rbin = "" unless mkdir $rbin, 0700;
268
# Config file for this browser.
269
# Sample file is available at http://www.eklhad.net/linux/app/sample.ebrc
270
$rcFile = "$home/.ebrc";
271
# Last http header, normally deleted before you read the web page.
272
$ebhttp = "$rbin/eb.http";
274
# When we need a temp file.
275
$ebtmp = "$rbin/eb.tmp";
276
# A file containing SSL certificates in PEM format, concatinated together.
277
# This will be used for certificate verification.
278
$ebcerts = "$home/.ssl-certs";
279
# file for persistant cookies.
280
$ebcooks = "$home/.cookies";
282
fillJar(); # fill up that cooky jar
284
# Let's see if we can read the config file?
285
if(open FH, $rcFile) {
292
my ($server, $login, $passwd, $retpath, $key, $value);
293
if(/^([^:\s]+)\s*:\s*([^:\s]+)\s*:\s*([^:\s]+)\s*:\s*([^:\s]+)\s*:\s*([^:\s]*)/) {
294
($server, $login, $passwd, $retpath) = ($1, $2, $3, $4);
296
if($server =~ s/^\*\s*//) {
297
dieq "multiple accounts are marked as local, with a star." if $localMail >= 0;
298
$localMail = $naccounts;
299
$smtpbox = $server unless length $smtpbox;
300
$outmailserver = $smtpbox;
303
$inmailserver[$naccounts] = $server;
304
$pop3login[$naccounts] = $login;
305
$pop3password[$naccounts] = $passwd;
306
$replyAddress[$naccounts] = $retpath;
309
} # describing a mail server
311
# Now look form keyword = string.
312
# Initial < is shorthand for cmd =
314
if(/^([^=]+)=\s*(.+)/) {
318
$myname = $value, next if $key eq "fullname";
319
$addressFile = $value, next if $key eq "addressbook";
320
$junkFile = $value, next if $key eq "junkfile";
321
$annoyFile = $value, next if $key eq "annoyfile";
322
$mailDir = $value, next if $key eq "cd";
325
if($value =~ /^\s*([^\s>]+)\s*>\s*(.+)$/) {
326
push @fromSource, lc $1;
330
dieq "from filter \"$value\" does not look like \"emailAddress > file\".";
333
if($key eq "agent") {
334
push @agents, $value;
338
# web express keywords
339
if($key eq "shortcut") {
340
if(length $currentShortcut and ! defined $shortcut{$currentShortcut}{url}) {
341
dieq "shortcut $currentShortcut has not been assigned a url";
343
$value =~ /^[\w-]+$/ or dieq "the name of a shortcut must consist of letters digits or dashes, $value is invalid";
344
$currentShortcut = $value;
345
# Start out with no post processing commands.
346
$shortcut{$value}{after} = [];
347
$shortcut{$value}{sort} = sprintf "%04d", $sort;
349
$currentCommandList = "";
352
if($key eq "cmdlist") {
353
if(length $currentShortcut and ! defined $shortcut{$currentShortcut}{url}) {
354
dieq "shortcut $currentShortcut has not been assigned a url";
356
$currentShortcut = "";
358
$check = 1 if $value =~ s/^\+//;
359
$value =~ /^[\w-]+$/ or dieq "the name of a command list must consist of letters digits or dashes, $value is invalid.";
360
$currentCommandList = $value;
361
$commandList{$value} = [];
362
$commandCheck{$value} = $check;
366
length $currentShortcut or length $currentCommandList or
367
dieq "postprocessing command is not part of a command list or shortcut";
368
my $cref; # command reference
369
$cref = $shortcut{$currentShortcut}{after} if length $currentShortcut;
370
$cref = $commandList{$currentCommandList} if length $currentCommandList;
371
# is this a command list?
372
if($value =~ /^[a-zA-Z_-]+$/ and defined $commandList{$value}) {
373
my $cpush = $commandList{$value};
374
push @$cref, @$cpush;
381
length $currentShortcut or dieq "$key command without a current shortcut";
382
$shortcut{$currentShortcut}{url} = $value;
386
length $currentShortcut or dieq "$key command without a current shortcut";
387
$shortcut{$currentShortcut}{desc} = $value;
391
dieq "Unrecognized keyword <$key> in config file.";
394
dieq "garbled line <$_> in config file.";
395
} # loop over lines in config file
398
if(length $currentShortcut and ! defined $shortcut{$currentShortcut}{url}) {
399
dieq "shortcut $currentShortcut has not been assigned a url";
403
$localMail = 0 if $naccounts == 1;
404
dieq "None of the pop3 accounts is marked as local." if $localMail < 0;
405
dieq "fullname not specified in the config file." if ! length $myname;
409
# One array holds all the lines of text (without the newlines)
410
# for all the files in all the sessions.
411
# Within a given session, the actual file is represented by a list of numbers,
412
# indexes into this large array.
413
# Note that when text is copied, we actually copy the strings in the array.
414
# I could just have different lines use the same index, thus pointing to the
415
# same string, and there would be no need to copy that string,
416
# but then I'd have to maintain reference counts on all these strings,
417
# and that would make the program very messy!
420
# If a file has 30 lines, it is represented by 30 numbers,
421
# indexes into @text above.
422
# Should we use an array of numbers, or a string of numbers
423
# represented by decimal digits?
424
# Both are painful, in different ways.
425
# Consider inserting a block of text, a very common operation.
426
# In a list, we would have to slide all the following numbers down.
427
# Granted, that's better than copying all those lines of text down,
428
# but it's still a pain to program, and somewhat inefficient.
429
# If we use strings, we take the original string of numbers,
430
# break it at the insert point, and make a new string
431
# by concatenating these two pieces with the new block.
432
# The same issues arise when deleting text near the top of a file.
433
# This and other considerations push me towards strings.
434
# I currently use 6 characters for a line number, and a seventh for the g// flag.
435
$lnwidth = 7; # width of a line number field in $map
436
$lnwidth1 = $lnwidth - 1;
438
$lnspace = ' ' x $lnwidth;
440
# Note that line 0 never maps to anything in @text.
443
# The 26 labels, corresponding to the lower case letters.
444
# These are stored in a packed string, like $map above.
445
# labels also holds the filetype suffixes when in directory mode.
446
@labels = ($lnspace x 26);
447
$labels = $labels[0];
448
# offset into $labels, where directory suffixes begin.
449
$dirSufStart = 26 * $lnwidth;
451
# The anchor/form/input tags, for browsing.
452
# The browse tags are in an array of hashes.
453
# Each hash has tag=tagname,
454
# and attrib=value for each attrib=value in the tag.
455
# Be advised that certain special tags, such as those defining
456
# title and description and keywords, are placed in btag[0].
460
# When we focus on an input field, for edit or manipulation,
461
# we need its type, size, and list of options.
462
$inf = ""; # current text displayed by this input field.
463
$itype = ""; # Type of the input field.
464
$isize = 0; # size of the input field.
465
$iopt = {}; # hash of input options in a discrete list.
466
$irows = $icols = 0; # for a text area window.
467
$iwrap = ""; # Can we scroll beyond this window?
468
$itag = undef; # the input tag from which the previous variables were derived.
469
$iline = 0; # line where this input field was found.
470
$ifield = 0; # field number, within the line, the nth input field on the line.
471
$itagnum = 0; # tag number for this input field.
472
$inorange = "this input directive cannot be applied to a range of lines";
473
$inoglobal = "this input directive cannot be applied globally";
475
# last* and save* variables mirror the variables that define your session.
476
# This supports the undo command.
477
$lastdot = $savedot = $lastdol = $savedol = 0;
478
$lastmap = $savemap = $lastlabels = $savelabels = "";
480
# Variables to format text, i.e. break lines at sentence/phrase boundaries.
481
$refbuf = ""; # The new, reformatted buffer.
482
$lineno = $colno = 0; # line/column number
483
$optimalLine = 80; # optimal line length
484
$cutLineAfter = 36; # cut sentence or phrase after this column
485
$paraLine = 120; # longer lines are assumed to be self-contained paragraphs
486
$longcut = 0; # last cut of a long line
487
$lspace = 3; # last space value, 3 = paragraph
488
$lperiod = $lcomma = $lright = $lany = 0; # columns for various punctuations
489
$idxperiod = $idxcomma = $idxright = $idxany = 0;
491
# Push the entire edit session onto a stack, for the back key.
492
# A hash will hold all the variables that make a session,
493
# such as $map, $fname, $btags, etc.
495
$backup = $backup[0];
497
$hexChars = "0123456789abcdefABCDEF";
499
# Valid delimiters for search/substitute.
500
# note that \ is conspicuously absent, not a valid delimiter.
501
# I alsso avoid nestable delimiters such as parentheses.
502
# And no alphanumerics please -- too confusing.
503
$valid_delim = "-_=!|#*;:`\"',./?+@";
505
# $linePending holds a line of text that you accidentally typed in
506
# while edbrowse was in command mode.
507
# When you see the question mark, immediately type a+ to recover the line.
508
$linePending = undef;
511
# That's it for the globals, here comes the code.
512
# First a few support routines.
513
# Strip white space from either side.
521
# Is a filename a URL?
522
# If it is, return the transport protocol, e.g. http.
526
return 'http' if $line =~ m,^http://[^\s],i;
527
return 'https' if $line =~ m,^https://[^\s],i;
528
return 'gopher' if $line =~ m,^gopher://[^\s],i;
529
return 'telnet' if $line =~ m,^telnet://[^\s],i;
530
return 'ftp' if $line =~ m,^ftp://[^\s],i;
531
# I assume that the following will be regular http.
532
# Strip off the ?this=that stuff
534
# Strip off the file name and .browse suffix.
536
$line =~ s/\.browse$//;
538
return 0 if $line !~ /\w\.\w.*\w\.\w/; # we need at least two internal dots
539
# Look for an ip address, four numbers and three dots.
540
return 'http' if $line =~ /^\d+\.\d+\.\d+\.\d+$/;
542
return 'http' if index(".com.biz.info.net.org.gov.edu.us.uk.au.ca.de.jp.be.nz.sg.", ".$line.") >= 0;
545
# Apply a (possibly) relative path to a preexisting url.
546
# The new url is returned.
547
# resolveUrl("http://www.eklhad.net/linux/index.html", "app") returns
548
# "http://www.eklhad.net/linux/app"
551
my ($line, $href) = @_;
553
$line = "" unless defined $line;
554
$line =~ s/\.browse$//;
555
# debug print - this is a very subtle routine.
556
print "resolve($line, $href)\n" if $debug >= 2;
557
# Some people, or generators, actually write http://../whatever.html
558
$href =~ s/^http:(\.+)/$1/i;
559
$href =~ s,^http://(\.*/),$1,i;
560
return $href unless length $href and length $line and ! is_url($href);
561
if(substr($href, 0, 1) ne '/') {
562
$line =~ s/\?.*//; # hope this is right
563
if(substr($href, 0, 1) ne '?') {
564
if($line =~ s,^/[^/]*$,, or
565
$line =~ s,([^/])/[^/]*$,$1,) {
566
# We stripped off the last directory
569
if($scheme = is_url $line) {
574
} # stripping off last directory
575
} # doesn't start with ?
576
} elsif($scheme = is_url $line) {
577
# Keep the scheme and server, lose the filename
578
$line =~ s/\?.*//; # hope this is right
579
$line =~ s,^($scheme://[^/]*)/.*,$1,i;
586
# Prepare a string for http transmition.
587
# No, I really don't know which characters to encode.
588
# I'm probably encoding more than I need to -- hope that's ok.
592
s/([^-\w .@])/sprintf('%%%02X',ord($1))/ge;
601
s/%([0-9a-fA-F]{2})/chr hex "$1"/ge;
605
# Pull the subject out of a sendmail url.
609
if($$href =~ s/\?(.*)$//) {
610
my @pieces = split '&', $1;
611
foreach my $j (@pieces) {
612
next unless $j =~ s/^subject=//i;
613
my $subj = urlDecode $j;
617
} # attributes after the email
621
# Get raw text ready for html display.
625
return unless length $$tbuf;
626
$$tbuf =~ s/&/&/g;
627
$$tbuf =~ s/</</g;
628
$$tbuf =~ s/>/>/g;
629
$$tbuf =~ s/^/<P><PRE>/;
630
$$tbuf =~ s/$/<\/PRE><P>\n/;
633
# Derive the alt description for an image or hyperlink.
639
$alt = "" unless defined $alt;
641
# Some alt descriptions are flat-out useless.
642
$alt =~ s/^[^\w]+$//;
643
return $alt if length $alt;
646
$href = "" unless defined $href;
649
$alt =~ s/^javascript.*$//i;
653
$alt =~ s/\.[^.]*$//;
658
# Pull the reference out of a javascript openWindow() call.
662
my $jc = shift; # java call
665
$page = $1 if $jc =~ /(?:open|location|window)[\w.]* *[(=] *["']([\w._\/:,=@&?+-]+)["']/i;
666
return $page if length $page;
667
return "submit" if $jc =~ /\bsubmit *\(/i;
668
while($jc =~ /(\w+) *\(/g) {
670
my $href = $$btags[0]{fw}{$f};
680
# Try to find the Java functions
684
my $flc = 0; # function line count
685
my $f; # java function
686
while($$tbuf =~ /(.+)/g) {
688
if($line =~ /function *(\w+)\(/) {
690
print "java function $f\n" if $debug >= 6;
693
my $win = javaWindow $line;
696
if(not defined $$btags[0]{fw}{$f}) {
697
$$btags[0]{fw}{$f} = "*$win";
698
print "$f: $win\n" if $debug >= 3;
700
} elsif($win ne "submit") {
703
$attrhidden = hideNumber($#$btags);
704
$$h{ofs1} = length $refbuf;
705
my $alt = deriveAlt($h, $win);
706
$alt = "relocate" unless length $alt;
707
createHyperLink($h, $win, $alt);
712
$flc = 0 if $flc == 12;
720
$w =~ s/\b([a-z])/uc $1/ge;
721
# special McDonald code
722
$w =~ s/Mc([a-z])/"Mc".uc $1/ge;
726
# Create a hyperlink where there was none before.
727
sub createHyperLink($$$)
729
my ($h, $href, $desc) = @_;
731
$$h{bref} = $baseref;
733
$refbuf .= "\x80$attrhidden" . "{$desc}";
734
$colno += 2 + length $desc;
735
$$h{ofs2} = length $refbuf;
739
# meta html characters.
740
# There's lots more -- this is just a starter.
742
# Normal ascii symbols
743
gt => '>', lt => '<', quot => '"',
744
plus => '+', minus => '-', colon => ':',
745
apos => '`', star => '*', comma => ',',
746
period => '.', dot => ".",
747
dollar => '$', percnt => '%', amp => '&',
748
# International letters
749
ntilde => "\xf1", Ntilde => "\xd1",
750
agrave => "\xe0", Agrave => "\xc0",
751
egrave => "\xe8", Egrave => "\xc8",
752
igrave => "\xec", Igrave => "\xcc",
753
ograve => "\xf2", Ograve => "\xd2",
754
ugrave => "\xf9", Ugrave => "\xd9",
755
auml => "\xe4", Auml => "\xc4",
756
euml => "\xeb", Euml => "\xcb",
757
iuml => "\xef", Iuml => "\xcf",
758
ouml => "\xf6", Ouml => "\xd6",
759
uuml => "\xfc", Uuml => "\xdc",
760
yuml => "\xff", Yuml => 'Y',
761
aacute => "\xe1", Aacute => "\xc1",
762
eacute => "\xe9", Eacute => "\xc9",
763
iacute => "\xed", Iacute => "\xcd",
764
oacute => "\xf3", Oacute => "\xd3",
765
uacute => "\xfa", Uacute => "\xda",
766
yacute => "\xfd", Yacute => "\xdd",
767
atilde => "\xe3", Atilde => "\xc3",
768
itilde => 'i', Itilde => 'I',
769
otilde => "\xf5", Otilde => "\xd5",
770
utilde => 'u', Utilde => 'U',
771
acirc => "\xe2", Acirc => "\xc2",
772
ecirc => "\xea", Ecirc => "\xca",
773
icirc => "\xee", Icirc => "\xce",
774
ocirc => "\xf4", Ocirc => "\xd4",
775
ucirc => "\xfb", Ucirc => "\xdb",
776
# Other 8-bit symbols.
777
# I turn these into their 8 bit equivalents,
778
# then a follow-on routine turns them into words for easy reading.
779
# Some speech adapters do this as well, saying "cents" for the cents sign,
780
# but yours may not, so I do some of these translations for you.
781
# But not here, because some people put the 8-bit cents sign in directly,
782
# rather then ¢, so I've got to do that translation later.
783
pound => "\xa3", cent => "\xa2",
826
177 => "8177", # kludge!! I made up 8177
854
# map certain font=symbol characters to words
911
8177 => "+-", # kludge!! I made up 8177
914
8592 => "left arrow",
916
8660 => "double arrow",
921
8713 => "not a member of",
928
8773 => "congruent to",
930
8804 => "less equal",
931
8805 => "greater equal",
932
8834 => "proper subset of",
933
8835 => "proper superset of",
934
8836 => "not a subset of",
936
8839 => "superset of",
939
# Map an html meta character using the above hashes.
940
# Usually run from within a global substitute.
944
if($meta =~ /^#(\d+)$/) {
945
return chr $1 if $1 <= 255;
946
return "'" if $1 == 8217;
947
return "\x82$1#" if $symbolWord{$1};
950
my $real = $charmap{$meta};
951
defined $real or $real = "?";
955
# Translate <font face=symbol>number</font>.
956
# This is highly specific to my web pages - doesn't work in general!
962
my $real = $symbolmap{$meta};
963
return "?" unless $real;
967
# replace VAR with $VAR, as defined by the environment.
971
my $newvar = $ENV{$var};
972
if(defined $newvar) {
973
# There shouldn't be any whitespace at the front or back.
975
return $newvar if length $newvar;
978
$errorMsg = "environment variable $var not set";
982
# Replace the variables in a line, using the above.
987
# $errorMsg will be set if something goes wrong.
988
$line =~ s,^~/,\$HOME/,;
989
$line =~ s/\$([a-zA-Z]\w*)/envVar($1)/ge;
993
# The filename can be specified using environment variables,
994
# and shell meta characters such as *.
995
# But not if it's a url.
998
my $filename = shift;
1000
if(! is_url($filename)) {
1001
$filename = envLine($filename);
1002
return if length $errorMsg;
1004
# This is real kludgy - I just don't understand how glob works.
1005
if($filename =~ / / and $filename !~ /"/) {
1006
@filelist = glob '"'.$filename.'"';
1008
@filelist = glob $filename;
1010
$filelist[0] = $filename if $#filelist < 0;
1011
$errorMsg = "wild card expansion produces multiple files" if $#filelist;
1012
$filename = $filelist[0];
1017
# Drop any active edit sessions that have no text, and no associated file.
1018
# This housecleaning routine is run on every quit or backup command.
1019
sub dropEmptyBuffers()
1021
foreach my $cx (0..$#factive) {
1022
next if $cx == $context;
1023
next unless $factive[$cx];
1024
next if length $fname[$cx];
1026
$factive[$cx] = undef;
1028
} # dropEmptyBuffers
1030
# Several small functions to switch between contexts, i.e. editing sessions.
1031
# In all these functions, we have to map between our context numbers,
1032
# that start with 0, and the user's session numbers, that start with 1.
1033
# C and fortran programmers will be use to this problem.
1034
# Is a context different from the currently running context?
1038
$errorMsg = "session 0 is invalid", return 0 if $cx < 0;
1039
return 1 if $cx != $context; # ok
1041
$errorMsg = "you are already in session $cx";
1045
# Is a context active?
1049
return 1 if $factive[$cx];
1051
$errorMsg = "session $cx is not active";
1055
# Switch to another editing session.
1056
# This assumes cxCompare has succeeded - we're moving to a different context.
1057
# Pass the context number and an interactive flag.
1061
# Put the variables in a known start state if this is a virgin session.
1062
cxReset($cx, 0) if ! defined $factive[$cx];
1063
$dot[$context] = $dot, $dot = $dot[$cx];
1064
$dol[$context] = $dol, $dol = $dol[$cx];
1065
$fname[$context] = $fname, $fname = $fname[$cx];
1066
$dirname[$context] = $dirname, $dirname = $dirname[$cx];
1067
$map[$context] = $map, $map = $map[$cx];
1068
$labels[$context] = $labels, $labels = $labels[$cx];
1069
$btags = $btags[$cx];
1070
$backup[$context] = $backup, $backup = $backup[$cx];
1071
if(!$global_lhs_rhs) {
1072
$savelhs[$context] = $savelhs, $savelhs = $savelhs[$cx];
1073
$saverhs[$context] = $saverhs, $saverhs = $saverhs[$cx];
1075
$fmode[$context] = $fmode, $fmode = $fmode[$cx];
1076
# But we don't replicate the last* variables per context,
1077
# so your ability to undo is destroyed if you switch contexts.
1078
$fmode &= ~$firstopmode;
1080
if(defined $factive[$cx]) {
1081
print ((length($fname[$cx]) ? $fname[$cx] : "no file")."\n");
1083
print "new session\n";
1091
# Can we trash the data in a context?
1092
# If so, trash it, and reset all the variables.
1093
# The second parameter is a close directive.
1094
# If nonzero, we clear out empty buffers associated with
1095
# text areas in the fill-out forms (browse mode).
1096
# A value of 1, as opposed to 2, means close down the entire session.
1099
my ($cx, $close) = @_;
1101
if(defined $factive[$cx]) {
1102
# We might be trashing data, make sure that's ok.
1103
$fname[$cx] = $fname, $fmode[$cx] = $fmode if $cx == $context;
1104
if($fmode[$cx]&$changemode and
1105
!( $fmode[$cx]&$dirmode) and
1107
length $fname[$cx] and
1108
! is_url($fname[$cx])) {
1109
$errorMsg = "expecting `w'";
1111
if($cx != $context) {
1113
$errorMsg .= " on session $cx";
1121
# And we're closing this session.
1122
$factive[$cx] = undef;
1123
$backup[$cx] = undef;
1126
} # session was active
1128
# reset the variables
1129
$dot[$cx] = $dol[$cx] = 0;
1130
$map[$cx] = $lnspace;
1133
$labels[$cx] = $lnspace x 26;
1135
$savelhs[$cx] = $saverhs[$cx] = undef;
1137
if($cx == $context) {
1141
$labels = $labels[$cx];
1142
$btags = $btags[$cx];
1143
$global_lhs_rhs or $savelhs = $saverhs = undef;
1150
# Pack all the information about the current context into a hash.
1151
# This will be pushed onto a virtual stack.
1152
# When you enter the back key, it all gets unpacked again,
1153
# to restore your session.
1157
dot =>$dot, dol => $dol, map => $map, labels => $labels,
1158
lastdot =>$lastdot, lastdol => $lastdol, lastmap => $lastmap, lastlabels => $lastlabels,
1159
fname => $fname, dirname => $dirname,
1160
fmode => $fmode&~$changemode,
1161
savelhs => $savelhs, saverhs => $saverhs,
1170
return if ! defined $h;
1172
$lastdot = $$h{lastdot};
1174
$lastdol = $$h{lastdol};
1176
$lastmap = $$h{lastmap};
1177
$labels = $$h{labels};
1178
$lastlabels = $$h{lastlabels};
1179
$fmode = $$h{fmode};
1180
$fname = $$h{fname};
1181
$dirname = $$h{dirname};
1182
if(!$global_lhs_rhs) {
1183
$savelhs = $$h{savelhs};
1184
$saverhs = $$h{saverhs};
1186
$btags[$context] = $btags = $$h{btags};
1189
# find an available session and load it with some initial data.
1190
# Returns the context number.
1193
my ($text_ptr, $filename) = @_;
1194
# Look for an unused buffer
1196
for($cx=0; $cx<=$#factive; ++$cx) {
1197
last unless defined $factive[$cx];
1201
$fname[$cx] = $filename;
1202
my $bincount = $$text_ptr =~ y/\0\x80-\xff/\0\x80-\xff/;
1203
if($bincount*4 - 10 < length $$text_ptr) {
1204
# A text file - remove crlf in the dos world.
1205
$$text_ptr =~ s/\r\n/\n/g if $doslike;
1207
$fmode[$cx] |= $binmode;
1209
$fmode[$cx] |= $nlmode unless $$text_ptr =~ s/\n$//;
1211
if(length $$text_ptr) {
1212
push @text, split "\n", $$text_ptr, -1;
1215
my $newpiece = $lnspace;
1216
++$dol[$cx], $newpiece .= sprintf($lnformat, $j) while ++$j <= $#text;
1217
$map[$cx] = $newpiece;
1218
$dot[$cx] = $dol[$cx];
1225
# See if @text is too big.
1226
# Pass the number of lines we will be adding.
1230
return 0 if $#text + $more <= $lnmax;
1231
$errorMsg = "Your limit of 1 million lines has been reached.\nSave your files, then exit and restart this program.";
1235
# Hide and reveal numbers that are internal to the line.
1236
# These numbers indicate links and input fields, and are not displayed by the next routine.
1240
$n =~ y/0-9/\x85-\x8f/;
1247
$n =~ y/\x85-\x8f/0-9/;
1251
sub removeHiddenNumbers($)
1254
$$t =~ s/\x80[\x85-\x8f]+([<>{])/$1/g;
1255
$$t =~ s/\x80[\x85-\x8f]+\*//g;
1256
} # removeHiddenNumbers
1258
# Small helper function to retrieve the text for line number n.
1259
# If the second parameter is set, hidden numbers are left in place;
1260
# otherwise they are stripped out via removeHiddenNumbers().
1265
return "" unless $n; # should never happen
1266
my $t = $text[substr($map, $n*$lnwidth, $lnwidth1)];
1267
removeHiddenNumbers(\$t) if $show and $fmode&$browsemode;
1271
# Here's the same function, but for another context.
1272
sub fetchLineContext($$$)
1277
$t = $text[substr($map[$cx], $n*$lnwidth, $lnwidth1)];
1278
removeHiddenNumbers(\$t) if $show and $fmode&$browsemode;
1280
} # fetchLineContext
1282
# Print size of the text in buffer.
1286
$j += length(fetchLine($_, 1)) + 1 foreach (1..$dol);
1287
--$j if $fmode&$nlmode;
1291
# Read a line from stdin.
1292
# Could be a command, could be text going into the buffer.
1295
my ($i, $j, $c, $d, $line);
1301
redo getline if $intFlag and ! defined $line; # interrupt
1304
exit 0 unless defined $line; # EOF
1306
# A bug in my keyboard causes nulls to be entered from time to time.
1308
return $line if $line !~ /~/; # shortcut
1309
# We have to process it, character by character.
1311
for($i=0; $i<length($line); $line2 .= $c, ++$i) {
1312
$c = substr $line, $i, 1;
1314
next if $i == length($line) - 1;
1315
$d = substr $line, $i+1, 1;
1316
++$i, next if $d eq '~';
1317
next if $i == length($line) - 2;
1318
$j = index $hexChars, $d;
1320
$j -= 6 if $j >= 16;
1322
$d = substr $line, $i+2, 1;
1323
$j = index $hexChars, $d;
1325
$j -= 6 if $j >= 16;
1327
# We don't use this mechanism to enter normal ascii characters.
1328
next if $val >= 32 and $val < 127;
1329
# And don't stick a newline in the middle of an entered line.
1333
} # loop over input chars
1337
# Read a block of lines into the buffer.
1341
# Put the pending line in first, if it's there.
1342
my $line = $linePending;
1343
$line = readLine() unless defined $line;
1344
while($line ne ".") {
1347
} # loop gathering input lines
1348
return addTextToSession(\$tbuf) if length $tbuf;
1350
$dot = 1 if $dot == 0 and $dol;
1354
# Display a line. Show line number if $cmd is n.
1355
# Expand binary characters if $cmd is l.
1356
# Pass the line number.
1360
print "$ln " if $cmd eq 'n';
1361
my $line = fetchLine($ln, 1);
1362
# Truncate, if the line is pathologically long.
1363
$line = substr($line, 0, 500) . "..." if length($line) > 500;
1364
print '^' if $endmarks and ($endmarks == 2 or $cmd eq 'l');
1366
$line =~ y/\10\11/<>/;
1367
$line =~ s/([\0-\x1f\x80-\xff])/sprintf("~%02x",ord($1))/ge;
1369
# But we always remap return, null, and escape
1370
$line =~ s/(\00|\r|\x1b)/sprintf("~%02x",ord($1))/ge;
1373
print dirSuffix($ln);
1374
print '$' if $endmarks and ($endmarks == 2 or $cmd eq 'l');
1378
# If we've printed a line in directory mode, and the entry isn't
1379
# a regular file, we've got to find and print the special character at the end.
1380
# / means directory, for example.
1381
# This is used by the previous routine, among others.
1386
if($fmode&$dirmode) {
1387
$suf = substr($labels, $dirSufStart + 2*$ln, 2);
1393
# Routines to help format a string, i.e. cut at sentence boundaries.
1394
# This isn't real smart; it will happily split Mr. Flintstone.
1395
sub appendWhiteSpace($$)
1397
my($chunk, $breakable) = @_;
1398
my $nlc = $chunk =~ y/\n//d; # newline count
1400
# Don't interrogate the last few characters of a huge string -- that's inefficient.
1401
my $short = substr $refbuf, -2;
1402
my $l = length $refbuf;
1403
$lperiod = $colno, $idxperiod = $l if $short =~ /[.!?:][)"|}]?$/;
1404
$lcomma = $colno, $idxcomma = $l if $short =~ /[-,;][)"|]?$/;
1405
$lright = $colno, $idxright = $l if $short =~ /[)"|]$/;
1406
$lany = $colno, $idxany = $l;
1407
# Tack short fragment onto previous long line.
1408
if($longcut and ($nlc or $lperiod == $colno) and $colno <= 14) {
1409
substr($refbuf, $longcut, 1) = " ";
1410
$chunk = "", $nlc = 1 unless $nlc;
1411
} # pasting small fragment onto previous line
1412
} # allowing line breaks
1413
$nlc = 0 if $lspace == 3;
1415
$nlc = 1 if $lspace == 2;
1417
$refbuf .= "\n" if $nlc > 1;
1419
$longcut = $lperiod = $lcomma = $lright = $lany = 0;
1420
$lspace = 3 if $lspace >= 2 or $nlc > 1;
1421
$lspace = 2 if $lspace < 2;
1424
$lspace = 1 if length $chunk;
1425
$colno += $chunk =~ y/ / /;
1426
$colno += 4 * ($chunk =~ y/\t/\t/);
1427
} # appendWhiteSpace
1429
sub appendPrintable($)
1433
$colno += length $chunk;
1435
return if $colno <= $optimalLine;
1436
# Oops, line is getting long. Let's see where we can cut it.
1437
my ($i, $j) = (0, 0);
1438
if($lperiod > $cutLineAfter) { $i = $lperiod, $j = $idxperiod;
1439
} elsif($lcomma > $cutLineAfter) { $i = $lcomma, $j = $idxcomma;
1440
} elsif($lright > $cutLineAfter) { $i = $lright, $j = $idxright;
1441
} elsif($lany > $cutLineAfter) { $i = $lany, $j = $idxany;
1443
return unless $j; # nothing we can do about it
1445
$longcut = $j if $i != $lperiod;
1446
substr($refbuf, $j, 1) = "\n";
1454
# Break up a line using the above routines.
1458
my $ud = $$t =~ s/\r$//;
1459
if($lspace eq "2l") {
1460
$$t =~ s/^/\r/ if length $$t;
1463
$$t =~ s/^/\r/ if length $$t > $paraLine;
1464
my $rc = $$t =~ y/\r/\n/;
1465
$ud |= $$t =~ s/[ \t]+$//gm;
1466
$ud |= $$t =~ s/([^ \t\n])[ \t]{2,}/$1 /g;
1467
$ud |= $$t =~ s/([^ \t\n])\t/$1 /g;
1468
$ud |= $$t =~ s/ +\t/\t/g;
1469
$lspace = 2 if $lspace < 2; # should never happen
1470
$lspace = 3 unless length $$t;
1471
return $ud if ! $rc and length $$t < $optimalLine;
1473
# The following 120 comes from $paraLine.
1474
$$t =~ s/(\n.{120})/\n$1/g;
1475
$$t =~ s/(.{120,}\n)/$1\n/g;
1478
$longcut = $lperiod = $lcomma = $lright = $lany = 0;
1479
while($$t =~ /(\s+|[^\s]+)/g) {
1481
if($chunk =~ /\s/) { appendWhiteSpace($chunk, 1); } else { appendPrintable($chunk); }
1483
if($lspace < 2) { # line didn't have a \r at the end
1484
# We might want to paste the last word back on.
1485
appendWhiteSpace("\n", 1);
1488
$rc = 1 if $refbuf =~ /\n/;
1489
return 0 unless $rc;
1491
$lspace = "2l" if length $refbuf > $paraLine;
1495
# Check the syntax of a regular expression, before we pass it to perl.
1496
# If perl doesn't like it, it dies, and you've lost your edits.
1497
# The first char is the delimiter -- we stop at the next delimiter.
1498
# The regexp, up to the second delimiter, is returned,
1499
# along with the remainder of the string in the second return variable.
1500
# return (regexp, remainder), or return () if there is a problem.
1501
# As usual, $errorMsg will be set.
1502
# Pass the line containing the regexp, and a flag indicating
1503
# left or right side of a substitute.
1506
my ($line, $isleft) = @_;
1508
# We wouldn't be here if the line was empty.
1509
my $delim = substr $line, 0, 1;
1510
index($valid_delim, $delim) >= 0 or
1511
$errorMsg = "invalid delimiter $delim", return ();
1512
$line = substr $line, 1; # remove lead delimiter
1513
# Remember whether a character is "on deck", ready to be modified by * etc.
1517
my $cc = 0; # in character class
1518
my $paren = 0; # nested parentheses
1520
while(length $line) {
1521
$c = substr $line, 0, 1;
1523
$errorMsg = "line ends in backslash", return () if length($line) == 1;
1524
$d = substr $line, 1, 1;
1527
# I can't think of any reason to remove the escape \ from any character,
1528
# except ()|, where we reverse the sense of escape,
1529
# and \& on the right, which becomes &.
1530
if(index("()|", $d) >= 0 and ! $cc and $isleft) {
1531
$ondeck = 0, ++$paren if $c eq '(';
1532
--$paren if $c eq ')';
1533
$errorMsg = "Unexpected closing )", return () if $paren < 0;
1536
$c = '' if $d eq '&' and ! $isleft;
1538
$line = substr $line, 2;
1540
} # escape character
1542
# Break out if you've hit the delimiter
1543
$paren or $c ne $delim or last;
1545
# Not the delimiter, I'll assume I can copy it over to $exp.
1546
# But I have to watch out for slash, which is *my* delimiter.
1547
$exp .= '\\' if $c eq '/';
1548
# Then there's ()|, which I am reversing the sense of escape.
1549
$exp .= '\\' if index("()|", $c) >= 0 and $isleft;
1550
# Sometimes $ is interpolated when I don't want it to be.
1551
# Even if there is no alphanumeric following, a bare $ seems to cause trouble.
1552
# Escape it, unless followed by delimiter, or digit (rhs).
1554
$exp .= '\\' if $isleft and
1555
length($line) > 1 and substr($line, 1, 1) ne $delim;
1556
$exp .= '\\' if ! $isleft and
1560
$exp .= '\\' if $isleft and $cc != length $exp;
1562
# And we have to escape every @, to avoid interpolation.
1563
# Good thing we don't have to escape %,
1564
# or it might mess up our % remembered rhs logic.
1565
$exp .= '\\' if $c eq '@';
1567
$exp .= '$' if $c eq '&' and ! $isleft;
1568
# Finally push the character.
1570
$line = substr $line, 1;
1572
# Are there any syntax checks I need to make on the rhs?
1576
if($cc) { # character class
1577
# All that matters here is the ]
1578
$cc = 0 if $c eq ']';
1582
# Modifiers must have a preceding character.
1583
# Except ? which can reduce the greediness of the others.
1584
if($c eq '?' and $offdeck ne '?') {
1590
if(index("?+*", $c) >= 0 or
1591
$c eq '{' and $line =~ s/^(\d+,?\d*})//) {
1592
my $mod = ( $c eq '{' ? "{$1" : $c);
1593
$errorMsg = "$mod modifier has no preceding character", return () if ! $ondeck;
1596
$exp .= "$1" if $c eq '{';
1602
$cc = length $exp if $c eq '[';
1603
} # loop over chars in the pattern
1606
$errorMsg = "no closing ]", return ();
1608
$errorMsg = "no closing )", return ();
1609
if(! length $exp and $isleft) {
1611
$errorMsg = "no remembered search string", return () if ! defined $exp;
1613
$savelhs = $exp if $isleft;
1617
$errorMsg = "no remembered replacement string", return () if ! defined $exp;
1618
} elsif($exp eq '\\%') {
1624
return ($exp, $line);
1627
# Get the start or end of a range.
1628
# Pass the line containing the address.
1633
if($line =~ s/^(\d+)//) {
1635
} elsif($line =~ s/^\.//) {
1636
# $ln is already set to dot
1637
} elsif($line =~ s/^\$//) {
1639
} elsif($line =~ s/^'([a-z])//) {
1640
$ln = substr $labels, (ord($1) - ord('a'))*$lnwidth, $lnwidth;
1641
$errorMsg = "label $1 not set", return () if $ln eq $lnspace;
1642
} elsif($line =~ m:^([/?]):) {
1643
$errorMsg = "search string not found", return () if $dot == 0;
1645
my @pieces = regexpCheck($line, 1);
1646
return () if $#pieces < 0;
1647
my $exp = $pieces[0];
1649
my $icase = ""; # case independent
1650
$icase = "i" if $caseInsensitive;
1651
if($delim eq substr $line, 0, 1) {
1652
$line = substr $line, 1;
1653
if('i' eq substr $line, 0, 1) {
1654
$line = substr $line, 1;
1658
my $incr = ($delim eq '/' ? 1 : -1);
1659
# Recompile the regexp after each command, but don't compile it on every line.
1660
# Is there a better way to do this, besides using eval?
1665
$ln = 1 if $ln > $dol;
1666
$ln = $dol if $ln == 0;
1667
last if fetchLine($ln, 1) =~ ' .
1669
'$notfound = 1, last if $ln == $dot;
1670
} # looking for match
1671
'; # end evaluated string
1672
$errorMsg = "search string not found", return () if $notfound;
1674
# Now add or subtract from this base line number
1675
while($line =~ s/^([+-])(\d*)//) {
1676
my $add = ($2 eq "" ? 1 : $2);
1677
$ln += ($1 eq '+' ? $add : -$add);
1679
$errorMsg = "line number too large", return ()
1681
$errorMsg = "negative line number", return ()
1683
return ($ln, $line);
1686
# Read the data as a string from a url.
1687
# Data is retrieved using http, https, or ftp.
1688
# Parameters: url, post data, result buffer.
1689
# You can return 0 (failure) and leave text and the buffer,
1690
# and I'll report the error, and still assimilate the buffer.
1693
my ($filename, $post, $tbuf) = @_;
1694
my $rc = 1; # return code, success
1695
$lfsz = 0; # local file size
1696
my $rsize = 0; # size read
1701
my %url_desc = (); # Description of the current URL
1703
# I don't know if we need a full url encode or what??
1704
# This is a major kludge! I just don't understand this.
1705
$filename =~ s/ /%20/g;
1706
$filename =~ s/[\t\r\n]//g;
1707
# I don't know what http://foo@this.that.com/file.htm means,
1708
# but I see it all the time.
1709
$filename =~ s,^http://[^/]*@,http://,i;
1711
$$tbuf = ""; # start with a clear buffer
1712
$errorMsg = "too many nested frames", return 0 unless $rerouteCount;
1715
# split into machine, file, and post parameters
1717
my $oldname = $filename; # remember where we started
1718
my $authinfo = ""; # login password for web sites that return error 401
1720
$scheme = is_url $filename; # scheme could have changed
1722
$weburl = 1 if $scheme =~ /^https?$/;
1723
if(!length $post and $filename =~ s/^(.*)(\?[^\s]*)$/$1/ ) {
1726
my $postfilename = "";
1727
# We assume $post starts with ? or *, if it is present at all.
1729
my $postapplic = "";
1730
if(substr($post, 0, 1) eq '*') {
1733
$postfilename = $post;
1735
print "$meth: $post\n" if $debug >= 2;
1737
$filename =~ s,^$scheme://,,i;
1738
my $serverPort = 80;
1739
$serverPort = 443 if $scheme eq 'https';
1740
$serverPort = 21 if $scheme eq 'ftp';
1741
$serverPort = 23 if $scheme eq 'telnet';
1742
my $serverPortString = "";
1743
my $server = $filename;
1745
# Sometimes we need to do this -- got me hanging!
1746
$server =~ s/%([0-9a-fA-F]{2})/chr hex "$1"/ge;
1747
if($server =~ s/:(\d+)$//) {
1750
# If a server is on port 443, assume it speaks SSL.
1751
# This is a real bastardization of the html standard,
1752
# but it's the explorer standard. Need I say more?
1753
$scheme = 'https' if$serverPort == 443;
1754
$serverPortString = ":$serverPort" if $serverPort != 80;
1755
$filename =~ s,^[^/]*,,;
1757
# Lots of http servers can't handle /./ or /../ or //
1758
$filename =~ s:/{2,}:/:g;
1759
# Oops, put internal http:// back the way it was.
1760
# The bug is caused by a line like this.
1761
# <form method=post action=server/file?this=that&return=http://someOtherServer/blah>
1762
# Because it's post, the get parameters after the ? are still here.
1763
# And I just turned http:// into http:/
1764
# This is very rare, but it happened to me, so I'm trying to fix it.
1765
$filename =~ s,http:/,http://,gi;
1766
$filename =~ s,ftp:/,ftp://,gi;
1767
$filename =~ s:^/(\.{1,2}/)+:/:;
1768
$filename =~ s:/(\./)+:/:g;
1769
1 while $filename =~ s:/[^/]+/\.\./:/:;
1770
$filename =~ s:^/(\.\./)+:/:;
1772
# Ok, create some more variables so we either fetch this file
1773
# or convert it if it's pdf.
1774
# Too bad I did all this work, and the pdf converter doesn't work for crap.
1775
# Probably because pdf is irreparably inaccessible.
1776
# Thanks a lot adobe!
1777
my $go_server = $server;
1778
my $go_port = $serverPort;
1779
my $go_portString = $serverPortString;
1780
my $go_file = $filename;
1781
my $go_post = $post;
1782
my $go_postfilename = $postfilename;
1783
my $go_meth = $meth;
1785
if($filename =~ /\.pdf$/ and $pdf_convert) {
1786
($meth eq "GET" and $scheme eq "http") or
1787
$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;
1788
$go_server="access.adobe.com";
1790
$go_portString = "";
1791
$go_file = "/perl/convertPDF.pl";
1792
# It would be simpler if this bloody form wer get, but it's post.
1794
$go_post = "http://$server$serverPortString$filename$postfilename";
1795
$go_post = "*submit=submit&url=" . urlEncode($go_post);
1796
$go_postfilename = "";
1797
} # redirecting to adobe to convert pdf
1799
if($go_meth eq "POST") {
1801
"Pragma: no-cache$eol" .
1802
"Cache-Control: no-cache$eol" .
1803
"Content-Type: application/x-www-form-urlencoded$eol" .
1804
"Content-Length: " . (length($go_post)-1) . $eol;
1814
$go_file = "/" if ! length $go_file;
1815
%url_desc = (SCHEME => $scheme, SERVER => $go_server, PORT => $go_port, PATH => $go_file, method => $go_meth);
1816
$url_desc{content} = substr($go_post, 1) if length $go_post; # Kinda silly.
1817
# If you're using digest authentication with the POST method,
1818
# the content needs to be digestified.
1819
# This is for message integrity checking, when that option is used.
1820
# Consider completely replacing $go_x variables with elements of the %url_desc
1821
# hash? There is massive redundancy here.
1822
my $domainCookies = "";
1823
$domainCookies = fetchCookies(\%url_desc) if $allowCookies; # Grab the cookies.
1824
my $send_server = # Send this to the http server - maybe via SSL
1825
"$go_meth $go_file$go_postfilename HTTP/1.0$eol" .
1826
# Do we need $go_portString here???
1827
# If I put it in, paypal doesn't work.
1828
"Host: $go_server$eol" .
1829
(length $referer ? "Referer: $referer$eol" : "") .
1832
"Accept: text/*, audio/*, image/*, application/*, message/*$eol" .
1833
"Accept: audio-file, postscript-file, mail-file, default, */*;q=0.01$eol" .
1834
"Accept-Encoding: gzip, compress$eol" .
1835
"Accept-Language: en$eol" .
1836
"User-Agent: $agent$eol" .
1838
$eol; # blank line at the end
1840
# send data after if post method
1841
$send_server .= substr($go_post, 1) if $go_meth eq "POST";
1844
my $temp_server = $send_server;
1845
$temp_server =~ y/\r//d;
1849
if($scheme eq 'http') {
1850
# Connect to the http server.
1851
my $iaddr = inet_aton($go_server) or
1852
$errorMsg = "cannot identify $go_server on the network", return 0;
1853
my $paddr = sockaddr_in($go_port, $iaddr);
1854
my $proto = getprotobyname('tcp');
1855
socket(FH, PF_INET, SOCK_STREAM, $proto) or
1856
$errorMsg = "cannot allocate a socket", return 0;
1857
connect(FH, $paddr) or
1858
$errorMsg = "cannot connect to $go_server", return 0;
1861
print FH $send_server; # Send the HTTP request message
1863
# Now retrieve the page and update the user after every 100K of data.
1865
STDOUT->autoflush(1) if ! $doslike;
1866
while(defined($rsize = sysread FH, $chunk, 100000)) {
1867
print "sockread $rsize\n" if $debug >= 5;
1870
last if $rsize == 0;
1871
my $fk = int($lfsz/100000);
1872
if($fk > $last_fk) {
1876
last if $lfsz >= $maxfile;
1879
print "\n" if $last_fk;
1880
STDOUT->autoflush(0) if ! $doslike;
1881
$lfsz <= $maxfile or
1882
$errorMsg = "file is too large, limit 40MB", return 0;
1884
$$tbuf = "", $errorMsg = "error reading data from the socket", return 0;
1886
} elsif ($scheme eq 'https') {
1887
$lfsz = do_ssl($go_server, $go_port, $send_server, $tbuf);
1888
Net::SSLeay::free($ssl) if defined $ssl;
1889
Net::SSLeay::CTX_free($ctx) if defined $ctx;
1890
return 0 unless $lfsz;
1892
} elsif ($scheme eq 'ftp') {
1893
$lfsz = ftp_connect($go_server, $go_port, $go_file, $tbuf);
1894
return 0 unless $lfsz;
1896
} elsif ($scheme eq "telnet") {
1897
if($go_server =~ s/^([^:@]*):([^:@]*)@//) {
1898
print "This URL gives a suggested username of $1 and password of $2\n" .
1899
"to be used with the telnet connection you are about to establish.\n";
1900
# See RFC 1738, section 3.8. The username and password in a telnet URL
1901
# are advisory. There is no standard method of logging into telnet services.
1902
# I guess this is especially useful for public services, which offer guest accounts and such.
1904
print "Starting telnet.\n";
1905
system("telnet $go_server $go_port");
1909
$errorMsg = "this browser cannot access $scheme URLs.", return 0;
1912
# We got the web page.
1913
# But it might be a redirection to another url.
1914
if($weburl and $$tbuf =~ /^http\/[\d.]+ 30[12]/i) {
1915
if($$tbuf =~ /\nlocation:[ \t]+([^\s]+)[ \t\r]*\n/i) {
1917
print "relocate $newname\n" if $debug >= 2;
1921
! length $newname and
1922
# Some web sites serve up pages with no headers at all!
1923
# aspace.whizy.com/forum/ultimate.cgi
1924
$$tbuf =~ /^http/i and
1925
$$tbuf =~ /^http\/[\d.]+ 404 /i) {
1926
$errorMsg = "file not found on the remote server";
1930
# there is yet another way to redirect to a url
1931
if($rc and $$tbuf =~ /<meta +http-equiv=["']?refresh[^<>]*(url=|\d+;)['"]?([^'">\s]+)/i) {
1933
print "refresh $newname\n" if $debug >= 2;
1934
# This is almost always an absolute url, even without the http prefix,
1935
# but sometimes it's relative. Got me hanging!
1936
# Here's a looser criterion for web url.
1937
if($newname =~ /^[\w,-]+\.[\w,-]+\.[\w,-]/) {
1938
$newname = "http://$newname";
1942
# Extract information from the http header - primarily cookies.
1943
$encoding = $pagetype = "";
1944
if($$tbuf =~ s/^(http\/\d.*?\r?\n\r?\n)//si) {
1946
my @lines = split "\n", $header;
1947
open BFH, ">>$ebhttp";
1953
while(my $hline = shift @lines) {
1955
print "$hline\n" if $debug >= 4;
1956
setCookies($hline, \%url_desc) if $hline =~ /^Set-Cookie:/i and $allowCookies;
1957
$authinfo = parseWWWAuth($hline, \%url_desc) if $hline =~ /^WWW-Authenticate/i;
1958
return 0 if $authinfo eq "x";
1959
# I shouldn't really discard things like charset=blablabla,
1960
# but I don't really know what to do with it anyways.
1962
$encoding = lc $1 if $hline =~ /^content-encoding:\s+['"]?(\w+)['"]?\s*$/i;
1963
$pagetype = lc $1 if $hline =~ /^content-type:\s+['"]?([^\s'"]+)['"]?\s*$/i;
1965
++$authAttempt, redo makeconnect if length $authinfo;
1966
} else { # http header extracted
1967
if($scheme =~ /^https?$/) {
1968
$errorMsg = "http response doesn't have a head-body structure";
1971
# For now, this means ftp.
1972
# We could have retrieved an html page via ftp, but probably not.
1973
# Turn off browse command.
1974
$cmd = 'e' unless $$tbuf =~ /^<[hH!]/;
1979
# cookies that are set via http-equiv
1980
# The content of the cookie must be quoted.
1981
while($$tbuf =~ /<meta +http-equiv=["']?set-cookie['" ]+content="([^"]*)"/gi) {
1982
setCookies($1, \%url_desc);
1984
while($$tbuf =~ /<meta +http-equiv=["']?set-cookie['" ]+content='([^']*)'/gi) {
1985
setCookies($1, \%url_desc);
1988
if($rc and $reroute and length $newname) {
1989
$newname = resolveUrl("$scheme://$server$serverPortString$filename", $newname);
1990
print "becomes $newname\n" if $debug >= 2;
1991
if($newname ne $oldname) {
1992
# It's not really diferent if one has :80 and the other doesn't.
1993
# I wouldn't code this up if it didn't really happen. See www.claritin.com
1994
$oldname =~ s,^HTTP://,http://,;
1995
$oldname =~ s,^(http://)?([^/]*):80/,$1$2/,;
1996
$oldname =~ s,^(http://)?([^/]*):80$,$1$2,;
1997
$newname =~ s,^HTTP://,http://,;
1998
$newname =~ s,^(http://)?([^/]*):80/,$1$2/,;
1999
$newname =~ s,^(http://)?([^/]*):80$,$1$2,;
2000
if($oldname ne $newname) {
2001
if(--$rerouteCount) {
2002
print "$newname\n" if $debug >= 1;
2003
# Post method becomes get after redirection, I think.
2004
# $post = "" if length $post and $newname =~ /\?[^\/]*$/;
2006
$filename = $newname;
2009
$errorMsg = "too many url redirections";
2011
}}} # automatic url redirection
2013
$changeFname = "$scheme://$server$serverPortString$filename$postfilename";
2016
# Check for complressed data.
2017
if($rc and $lfsz and length $encoding and $pagetype =~ /^text/i) {
2018
print "$lfsz\ndecoding $encoding\n" if $debug >= 2;
2020
my $csuf = ""; # compression suffix
2021
$program = "zcat", $csuf = "gz" if $encoding eq "gzip";
2022
$program = "zcat", $csuf = "Z" if $encoding eq "compress";
2024
$errorMsg = "unrecognized compression method", return 0;
2025
$cfn = "$ebtmp.$csuf"; # compressed file name
2027
$errorMsg = "cannot create temp file $cfn", return 0;
2028
binmode FH, ':raw' if $doslike;
2030
$errorMsg = "cannot write to temp file $cfn", return 0;
2033
if(! system "$program $ebtmp.$csuf >$ebtmp 2>/dev/null") {
2034
# There are web pages out there that are improperly compressed.
2035
# We'll call it good if we got any data at all.
2036
$errorMsg = "could not uncompress the data", return 0 unless (stat($ebtmp))[7];
2039
# Read in the uncompressed data.
2042
$errorMsg = "cannot open the uncompressed file $ebtmp", return 0;
2043
$lfsz = (stat(FH))[7];
2044
$lfsz <= $maxfile or
2045
$errorMsg = "uncompressed file is too large, limit 40MB", close FH, return 0;
2046
binmode FH, ':raw' if $doslike;
2047
$rsize = sysread FH, $$tbuf, $lfsz;
2049
$rsize and $rsize == $lfsz or
2050
$errorMsg = "cannot read the uncompressed data from $ebtmp", return 0;
2054
if($rc and $fetchFrames) {
2056
# This really isn't right - to do this here I mean.
2057
# If a line of javascript happens to contain a frame tag
2058
# I'm going to fetch that frame and put it in right here.
2059
# Hopefully that won't happen.
2060
# Note that the entire frame tag must be on one line.
2061
$$tbuf =~ s/(<frame\b[^<>\0\x80-\xff]+>)/readFrame($1)/gei;
2062
$rc = 0 if length $errorMsg;
2063
} # looking for frames
2072
my $saveFname = $changeFname;
2073
my($tc, $fbuf, $src, $name);
2075
$tag =~ s/\bsrc *= */src=/gi;
2076
$tag =~ s/\bname *= */name=/gi;
2078
if($tc =~ s/^.*\bsrc=//s) {
2082
$src =~ s/['"]?>?$//;
2084
print "fetch frame $src\n" if $debug >= 1;
2085
$src = resolveUrl($saveFname, $src);
2086
if($didFrame{$src}) {
2087
print "already fetched\n" if $debug >= 2;
2088
$changeFname = $saveFname;
2091
$didFrame{$src} = 1;
2092
print "* $src\n" if $debug >= 1;
2096
if($tc =~ s/^.*\bname=//s) {
2099
$tc =~ s/['"]?>?$//;
2100
$name = urlDecode $tc if length $tc;
2103
if(readUrl($src, "", \$fbuf)) {
2104
# Replace the tag with the data, and some stuff prepended.
2105
$name = " $name" if length $name;
2106
$tag = "<H2> Frame$name: </H2>\n<base href=" .
2107
urlEncode($changeFname) . ">\n";
2108
$changeFname = $saveFname;
2110
} # frame read successfully
2111
}} # src attribute present
2113
$changeFname = $saveFname;
2117
# Adjust the map of line numbers -- we have inserted text.
2118
# Also shift the downstream labels.
2119
# Pass the string containing the new line numbers, and the dest line number.
2122
my ($newpiece, $dln) = @_;
2123
my $offset = length($newpiece)/$lnwidth;
2125
die "negative offset in addToMap";
2127
foreach $i (0..25) {
2128
my $ln = substr($labels, $i*$lnwidth, $lnwidth); # line number
2129
next if $ln eq $lnspace or $ln <= $dln;
2130
substr($labels, $i*$lnwidth, $lnwidth) =
2131
sprintf($lnformat, $ln + $offset);
2132
} # loop over 26 labels
2133
$j = ($dln+1) * $lnwidth;
2134
substr($map, $j, 0) = $newpiece;
2135
$dot = $dln + $offset;
2137
$fmode |= $changemode|$firstopmode;
2141
# Fold in the text buffer (parameter) at $endRange (global variable).
2142
# Assumes the text has the last newline on it.
2143
sub addTextToSession($)
2145
my $tbuf = shift; # text buffer
2146
return 1 unless length $$tbuf;
2147
$fmode &= ~$nlmode if $endRange == $dol;
2148
if(not $$tbuf =~ s/\n$// and
2149
$endRange == $dol) {
2151
print "no trailing newline\n" if ! ($fmode&$binmode) and $cmd ne 'b';
2155
# At this point $tbuf could be empty, whence split doesn't work properly.
2156
# This only happens when reading a file containing one blank line.
2158
push @text, split "\n", $$tbuf, -1;
2162
$#text = $j, return 0 if lineLimit 0;
2163
$newpiece .= sprintf($lnformat, $j) while ++$j <= $#text;
2164
addToMap($newpiece, $endRange);
2166
} # addTextToSession
2168
# Read a file into memory.
2169
# As described earlier, the lines are appended to @text.
2170
# Then the indexes for those lines are pasted into $map,
2172
# Check to see if the data is binary, and set $fmode accordingly.
2173
# Parameters are the filename or URL, and the post data (for URLs).
2176
my ($filename, $post) = @_;
2177
my $tbuf; # text buffer
2178
my $rc = 1; # return code, success
2180
my $rsize = 0; # size read
2183
if(is_url $filename) {
2186
$rc = readUrl($filename, $post, \$tbuf);
2187
$filesize = length $tbuf;
2188
return 0 unless $rc + $filesize;
2189
} else { # url or file
2191
open FH, "<$filename" or
2192
$errorMsg = "cannot open $filename, $!", return 0;
2194
# Check for directory here
2202
@dirlist = glob '"'.$j.'"';
2212
$dirname =~ s/..$//; # get rid of /*
2213
return 0 if lineLimit($#dirlist + 1);
2217
substr($labels, $j, 2) = " ";
2218
foreach (@dirlist) {
2220
$entry =~ s,.*/,,; # leave only the file
2221
$entry =~ s/\n/\t/g;
2230
} # not a regular file
2231
$filesize += length($entry) + length($suf) + 1;
2237
substr($labels, $j, 2) = substr($suf, 0, 2);
2239
$tbuf .= "$entry\n";
2241
$dol or $fmode = $dirmode, print "directory mode\n";
2242
return addTextToSession(\$tbuf);
2245
-f FH or $errorMsg = "$filename is not a regular file", close FH, return 0;
2246
$filesize = (stat(FH))[7];
2253
$filesize <= $maxfile or
2254
$errorMsg = "file is too large, limit 40MB", close FH, return 0;
2255
binmode FH, ':raw' if $doslike;
2256
$rsize = sysread(FH, $tbuf, $filesize) if $filesize;
2258
$rsize == $filesize or
2259
$errorMsg = "cannot read the contents of $filename,$!", return 0;
2260
} # reading url or regular file
2262
my $bincount = $tbuf =~ y/\0\x80-\xff/\0\x80-\xff/;
2263
if($bincount*4 - 10 < $filesize) {
2264
# A text file - remove crlf in the dos world.
2265
$tbuf =~ s/\r\n/\n/g if $doslike;
2266
} elsif(! ($fmode&$binmode)) {
2267
# If it wasn't before, it is now a binary file.
2268
print "binary data\n";
2272
$rc &= addTextToSession(\$tbuf);
2276
# Write a range into a file.
2277
# Pass the mode and filename.
2280
my ($mode, $filename) = @_;
2281
$errorMsg = "cannot write to a url", return 0 if is_url($filename);
2282
$dol or $errorMsg = "writing an empty file", return 0;
2283
open FH, "$mode$filename" or
2284
$errorMsg = "cannot create $filename, $!", return 0;
2286
binmode FH, ':raw' if $doslike and $fmode&$binmode;
2288
foreach my $i ($startRange..$endRange) {
2289
my $nl = ($fmode&$nlmode && $i == $dol ? "" : "\n");
2290
my $suf = dirSuffix($i);
2291
my $outline = fetchLine($i, 1).$suf.$nl;
2292
print FH $outline or
2293
$errorMsg = "cannot write to $filename, $!", close FH, return 0;
2294
$filesize += length $outline;
2298
# This is not an undoable operation, nor does it change data.
2299
# In fact the data is "no longer modified" if we have written all of it.
2300
$fmode &= ~$changemode if $dol == 0 or $startRange == 1 and $endRange == $dol;
2304
# Read from another context.
2305
# Pass the context number.
2309
cxCompare($cx) and cxActive($cx) or return 0;
2310
my $dolcx = $dol[$cx];
2313
return 0 if lineLimit $dolcx;
2314
$fmode &= ~$nlmode if $endRange == $dol;
2316
foreach my $i (1..$dolcx) {
2317
my $inline = fetchLineContext($i, 1, $cx);
2319
if($fmode[$cx] & $dirmode) {
2320
$suf = substr($labels[$cx], $dirSufStart + 2*$i, 2);
2324
push @text, $inline;
2325
$newpiece .= sprintf $lnformat, $#text;
2326
$filesize += length($inline) + 1;
2327
} # end loop copying lines
2328
addToMap($newpiece, $endRange);
2329
if($fmode[$cx]&$nlmode) {
2331
$fmode |= $nlmode if $endRange == $dol;
2333
$fmode |= $binmode, print "binary data\n"
2334
if $fmode[$cx]&$binmode and ! ($fmode&$binmode);
2339
# Write to another context.
2340
# Pass the context number.
2344
my $dolcx = $endRange - $startRange + 1;
2345
$dolcx = 0 if ! $startRange;
2346
return 0 if ! cxCompare($cx) or !cxReset($cx, 1) or lineLimit $dolcx;
2347
my $mapcx = $lnspace;
2350
foreach my $i ($startRange..$endRange) {
2351
$outline = fetchLine($i, 0);
2352
$outline .= dirSuffix($i);
2353
push @text, $outline;
2354
$mapcx .= sprintf $lnformat, $#text;
2355
$filesize += length($outline) + 1;
2356
} # end loop copying lines
2357
$fmode[$cx] = $fmode & ($binmode|$browsemode);
2358
$fmode[$cx] |= $nlmode, --$filesize
2359
if $fmode&$nlmode and $endRange == $dol;
2362
$dot[$cx] = $dol[$cx] = $dolcx;
2365
$btags[$cx] = $btags;
2369
# Move or copy a block of text.
2372
$dest++; # more convenient
2373
$endr1 = $endRange+1; # more convenient
2374
$dest <= $startRange or
2376
$errorMsg = "destination lies inside the block to be moved or copied", return 0;
2378
($dest == $endr1 or $dest == $startRange)) {
2379
$errorMsg = "no change" if ! $inglob;
2382
my $starti = $startRange*$lnwidth;
2383
my $endi = $endr1*$lnwidth;
2384
my $desti = $dest * $lnwidth;
2385
my $offset = $endr1 - $startRange;
2387
# The section of the map that represents the range.
2388
my $piece_r = substr $map, $starti, $endi-$starti;
2389
my $piece_n = ""; # the new line numbers, if the text is copied.
2391
return 0 if lineLimit $offset;
2392
for($j=0; $j<length($piece_r); $j+=$lnwidth) {
2394
$text[substr($piece_r, $j, $lnwidth1)];
2395
$piece_n .= sprintf $lnformat, $#text;
2397
substr($map, $desti, 0) = $piece_n;
2398
} elsif($dest < $startRange) {
2399
substr($map, $starti, $endi-$starti) = "";
2400
substr($map, $desti, 0) = $piece_r;
2402
substr($map, $desti, 0) = $piece_r;
2403
substr($map, $starti, $endi-$starti) = "";
2405
if($fmode&$nlmode) {
2406
$fmode &= ~$nlmode if $dest > $dol;
2407
$fmode &= ~$nlmode if $endRange == $dol and $cmd eq 'm';
2409
# Now for the labels
2410
my ($lowcut, $highcut, $p2len);
2411
if($dest <= $startRange) {
2414
$p2len = $startRange - $dest;
2416
$lowcut = $startRange;
2418
$p2len = $dest - $endr1;
2420
foreach $i (0..25) {
2421
my $ln = substr($labels, $i*$lnwidth, $lnwidth); # line number
2422
next if $ln eq $lnspace or $ln < $lowcut;
2423
if($ln >= $highcut) {
2424
$ln += $offset if $cmd eq 't';
2425
} elsif($ln >= $startRange and $ln <= $endRange) {
2426
$ln += ($dest < $startRange ? -$p2len : $p2len) if $cmd eq 'm';
2427
$ln += $offset if $cmd eq 't' and $dest < $startRange;
2428
} elsif($dest < $startRange) {
2431
$ln -= $offset if $cmd eq 'm';
2433
substr($labels, $i*$lnwidth, $lnwidth) = sprintf $lnformat, $ln;
2434
} # loop over labels
2435
$dol += $offset if $cmd eq 't';
2437
$dot += ($dest < $startRange ? -$p2len : $p2len) if $cmd eq 'm';
2438
$dot = $dest + $offset - 1 if $cmd eq 't';
2439
$fmode |= $changemode|$firstopmode;
2444
# Delete a block of text.
2445
# Pass the range to delete.
2448
my ($sr, $er) = @_; # local start and end range
2450
$fmode &= ~$nlmode if $er == $dol;
2452
substr($map, $sr*$lnwidth, $j*$lnwidth) = "";
2454
foreach $i (0..25) {
2455
my $ln = substr($labels, $i*$lnwidth, $lnwidth); # line number
2456
next if $ln eq $lnspace or $ln < $sr;
2457
substr($labels, $i*$lnwidth, $lnwidth) =
2458
($ln <= $er ? $lnspace : (sprintf $lnformat, $ln - $j));
2459
} # loop over labels
2462
--$dot if $dot > $dol;
2463
$fmode |= $changemode|$firstopmode;
2468
# Delete files from a directory as you delete lines.
2469
# It actually moves them to your recycle bin.
2472
$dw or $errorMsg = "directories are readonly, type dw to enable directory writes", return 0;
2473
$dw == 2 or length $rbin or
2474
$errorMsg = "could not create .recycle under your home directory, to hold the deleted files", return 0;
2475
my $ln = $startRange;
2476
my $cnt = $endRange - $startRange + 1;
2478
my $f = fetchLine($ln, 0);
2479
if($dw == 2 or dirSuffix($ln) =~ /^@/) {
2480
unlink "$dirname/$f" or
2481
$errorMsg = "could not remove $f, $!", return 0;
2483
rename "$dirname/$f", "$rbin/$f" or
2484
$errorMsg = "Could not move $f to the recycle bin, $!, set dx mode to actually remove the file", return 0;
2487
substr($labels, $dirSufStart + 2*$ln, 2) = "";
2492
# Join lines from startRange to endRange.
2495
$errorMsg = "cannot join one line", return 0 if $startRange == $endRange;
2496
return 0 if lineLimit 1;
2499
foreach $i ($startRange..$endRange) {
2500
$line .= ' ' if $cmd eq 'J' and $i > $startRange;
2501
$line .= fetchLine($i, 0);
2504
substr($map, $startRange*$lnwidth, $lnwidth) = sprintf $lnformat, $#text;
2505
delText($startRange+1, $endRange);
2510
# Substitute text on the lines in $startRange through $endRange.
2511
# We could be changing the text in an input field.
2512
# If so, we'll call infReplace().
2513
# Also, we might be indirectory mode, whence we must rename the file.
2514
sub substituteText($)
2518
$whichlink = $1 if $line =~ s/^(\d+)//;
2520
$errorMsg = "no regular expression after $icmd", return -1;
2521
if($fmode&$dirmode) {
2522
$dw or $errorMsg = "directories are readonly, type dw to enable directory writes", return -1;
2524
my ($i, $j, $exp, $rhs, $qrhs, $lastSubst, @pieces, $blmode);
2528
@pieces = regexpCheck($line, 1);
2529
return -1 if $#pieces < 0;
2532
length $line or $errorMsg = "missing delimiter", return -1;
2533
@pieces = regexpCheck($line, 0);
2534
return -1 if $#pieces < 0;
2537
} else { $blmode = 1, $lspace = 3; }
2542
$iflag = "i" if $caseInsensitive;
2543
$subprint = 1; # default is to print the last line substituted
2549
# necessarily starts with the delimiter
2550
substr($line, 0, 1) = "";
2551
while(length $line) {
2552
$gflag = 'g', next if $line =~ s/^g//;
2553
$subprint = 2, next if $line =~ s/^p//;
2554
$iflag = 'i', next if $line =~ s/^i//;
2555
if($line =~ s/^(\d+)//) {
2556
! $nflag or $errorMsg = "multiple numbers after the third delimiter", return -1;
2558
$nflag > 0 and $nflag <= 999 or
2559
$errorMsg = "numeric suffix out of range, please use [1-999]", return -1;
2562
$errorMsg = "unexpected substitution suffix after the third delimiter";
2564
} # loop gathering suffix flags
2565
! $gflag or ! $nflag or
2566
$errorMsg = "cannot use both a numeric suffix and the `g' suffix simultaneously", return -1;
2567
# s/x/y/1 is very inefficient.
2568
$nflag = 0 if $nflag == 1;
2569
} # closing delimiter
2571
$qrhs = $rhs; # quote-fixed right hand side
2572
if($rhs =~ /^[ul]c$/) {
2573
$qrhs = "$qrhs \$&";
2574
$iflag .= 'e' if !$nflag;
2575
} elsif($rhs eq "mc") {
2576
$qrhs = "mixCase \$&";
2577
$iflag .= 'e' if !$nflag;
2581
$qrhs = '"'.$qrhs.'"';
2585
# I don't understand it, but $&[x] means something to perl.
2586
# So when I replace j with &[x], becomeing $&[x], it blows up.
2587
# Thus I escape open brackets and braces in the rhs.
2588
# Hopefully you won't escape them on the command line - you have no reason to.
2589
# If you do they'll be doubly escaped, and that's bad.
2590
$qrhs =~ s/([\[{])/\\$1/g; # }
2595
# Substitute the input fields first.
2598
my $foundFields = 0;
2599
foreach $i ($startRange..$endRange) {
2600
my $rc = infIndex($i, $whichlink);
2603
$rc > 0 or $dot = $i, $inglob = 0, return -1;
2606
eval '$rc = $newinf =~ ' .
2607
"s/$exp/$qrhs/$iflag$gflag; ";
2610
eval '$newinf =~ ' .
2611
"s/$exp/++\$j == $nflag ? $qrhs : \$&/ge$iflag; ";
2612
$rc = ($j >= $nflag);
2616
infReplace($newinf) or return -1;
2621
$errorMsg = "no match" if $foundFields;
2625
dispLine($yesdot) if $subprint == 2 or ! $inglob and $subprint == 1;
2629
# Not an input field, just text, so replace it.
2630
# Once again, use the eval construct.
2631
# This time we might be substituting across an entire range.
2635
for($i=$startRange; $i<=$endRange; ++$i) {
2636
my $temp = fetchLine($i, 0);' .
2637
($blmode ? 'my $subst = breakLine(\$temp);' :
2639
'my $subst = $temp =~ ' .
2640
"s/$exp/$qrhs/o$iflag$gflag; "
2645
"s/$exp/++\$k == $nflag ? $qrhs : \$&/oge$iflag; " .
2646
'$subst = ($k >= $nflag); '
2648
'next unless $subst;
2649
if($fmode&$dirmode) {
2650
if($temp =~ m,[/\n],) {
2651
$errorMsg = "cannot embed slash or newline in a directory name";
2655
my $dest = "$dirname/$temp";
2656
my $src = fetchLine($i, 0);
2657
$src = "$dirname/$src";
2659
if(-e $dest or -l $dest) {
2660
$errorMsg = "destination file already exists";
2664
rename $src, $dest or
2665
$errorMsg = "cannot move file to $temp", $inglob = 0, last;
2666
} # source and dest are different
2668
@pieces = split "\n", $temp, -1;
2669
@pieces = ("") if $temp eq "";
2670
last if lineLimit $#pieces+1;
2672
push @text, @pieces;
2674
substr($map, $i*$lnwidth, $lnwidth) = sprintf $lnformat, ++$j;
2677
$newpiece .= sprintf $lnformat, $j while ++$j <= $#text;
2678
addToMap($newpiece, $i);
2679
$j = length($newpiece) / $lnwidth;
2683
dispLine($i) if $subprint == 2;
2685
$fmode |= $changemode|$firstopmode;
2690
return 0 if length $errorMsg;
2692
$errorMsg = ($blmode ? "no change" : "no match") if ! $inglob;
2696
dispLine($dot) if $subprint == 1 and ! $inglob;
2697
if($intFlag and ! $inglob) {
2698
$errorMsg = $intMsg, return 0;
2703
# Follow a hyperlink to another web page.
2706
my $whichlink = shift;
2708
$errorMsg = "cannot use the g$whichlink command in directory mode", return 0 if $fmode&$dirmode;
2709
$startRange == $endRange or
2710
$errorMsg = "go command does not expect a range", return 0;
2712
my $h; # hyperlink tag
2713
my @links = (); # links on this line
2714
my @bref = (); # baseref values
2715
my ($j, $line, $href);
2717
if($fmode&$browsemode) {
2718
$line = fetchLine $endRange, 0;
2719
while($line =~ /\x80([\x85-\x8f]+){/g) {
2720
$j = revealNumber $1;
2723
$errorMsg = "hyperlink found without a url?? internal error", return 0 unless defined $href;
2725
push @bref, $$h{bref};
2730
$line = fetchLine $endRange, 1;
2732
$line =~ s/[\s"']+/ /g;
2734
while($line =~ /([^ ]+)/g) {
2736
$href =~ s/^[^\w]+//;
2737
$href =~ s/[^\w]+$//;
2741
$href =~ s/^mailto://i;
2742
push @links, "mailto:$href" if $href =~ /^[\w.,-]+@[\w,-]+\.[\w,.-]+$/;
2746
} # looking for url in text mode
2749
$j or $errorMsg = "no links present", return 0;
2750
length $whichlink or $j == 1 or
2751
$errorMsg = "multiple links, please use g [1,$j]", return 0;
2752
$whichlink = 1 if ! length $whichlink;
2753
if($whichlink == 0 or $whichlink > $j) {
2754
$errorMsg = $j > 1 ?
2755
"invalid link, please use g [1,$j]" :
2756
"this line only has one link";
2760
$href = $links[$whichlink];
2761
if($href =~ s/^mailto://i) {
2763
return 1, "\x80mail\x80$href";
2765
$href =~ /^javascript:/i and
2766
$errorMsg = "sorry, this link calls a javascript function", return 0;
2767
return 1, $href if $href =~ /^#/;
2768
$line = resolveUrl(($#bref >= 0 ? $bref[$whichlink] : ""), $href);
2773
# Follow an internal link to a section of the document.
2776
my $section = shift;
2777
foreach my $i (1..$dol) {
2778
my $t = fetchLine $i, 0;
2779
while($t =~ /\x80([\x85-\x8f]+)\*/g) {
2780
my $j = revealNumber $1;
2781
my $h = $$btags[$j];
2782
return $i if $$h{name} eq $section;
2788
# Return the number of unbalanced punctuation marks at the start and end of the line.
2791
my ($c, $d, $ln) = @_;
2792
my $curline = fetchLine($ln, 1);
2793
# Escape these characters, so we know they are literal.
2796
while($curline =~ s/$c[^$c$d]*$d//) { ; }
2797
my $forward = $curline =~ s/$c//g;
2798
$forward = 0 if $forward eq "";
2799
my $backward = $curline =~ s/$d//g;
2800
$backward = 0 if $backward eq "";
2801
return $backward, $forward;
2804
# Find the line that balances the unbalanced punctuation.
2808
my ($c, $d); # balancing characters
2809
my $openlist = "{([<`";
2810
my $closelist = "})]>'";
2811
my $alllist = "{}()[]<>`'";
2813
my ($i, $direction, $forward, $backward);
2816
$line =~ /^[\{\}\(\)\[\]<>`']$/ or
2817
$errorMsg = "you must specify exactly one of $alllist after the B command", return 0;
2819
if(index($openlist, $c) >= 0) {
2820
$d = substr $closelist, index($openlist, $c), 1;
2824
$c = substr $openlist, index($closelist, $d), 1;
2827
($backward, $forward) = unbalanced($c, $d, $endRange);
2828
if($direction > 0) {
2829
($level = $forward) or
2830
$errorMsg = "line does not contain an open $c", return 0;
2832
($level = $backward) or
2833
$errorMsg = "line does not contain an open $d", return 0;
2835
} else { # character specified by the user or not?
2836
# Look for anything unbalanced, probably a brace.
2838
$c = substr $openlist, $i, 1;
2839
$d = substr $closelist, $i, 1;
2840
($backward, $forward) = unbalanced($c, $d, $endRange);
2841
! $backward or ! $forward or
2842
$errorMsg = "both $c and $d are unbalanced on this line, try B$c or B$d", return 0;
2843
($level = $backward + $forward) or next;
2845
$direction = -1 if $backward;
2849
$errorMsg = "line does not contain an unbalanced brace, parenthesis, or bracket", return 0;
2850
} # explicit character passed in, or look for one
2852
my $selected = ($direction > 0) ? $c : $d;
2854
# Now search for the balancing line.
2856
while(($i += $direction) > 0 and $i <= $dol) {
2857
($backward, $forward) = unbalanced($c, $d, $i);
2858
if($direction > 0 and $backward >= $level or
2859
$direction < 0 and $forward >= $level) {
2864
$level += ($forward-$backward) * $direction;
2867
$errorMsg = "cannot find the line that balances $selected";
2871
# Apply a regular expression to each line, and then execute
2872
# a command for each matching, or nonmatching, line.
2873
# This is the global feature, g/re/p, which gives us the word grep.
2877
my ($i, $j, $exp, @pieces);
2880
$errorMsg = "no regular expression after $icmd", return 0;
2881
@pieces = regexpCheck($line, 1);
2882
return 0 if $#pieces < 0;
2886
$errorMsg = "missing delimiter", return 0;
2887
$line =~ s/^.(i?)\s*//;
2889
$iflag = "i" if $caseInsensitive;
2891
# Clean up any previous stars.
2892
substr($map, $_*$lnwidth+$lnwidth1, 1) = ' ' foreach (1.. $dol);
2894
# Find the lines that match the pattern.
2895
my $gcnt = 0; # global count
2897
for($i=$startRange, $j=$i*$lnwidth+$lnwidth1; $i<=$endRange; ++$i, $j+=$lnwidth) {
2898
substr($map, $j, 1) = "*", ++$gcnt if
2900
($cmd eq 'g' ? ' =~ ' : ' !~ ') .
2902
$gcnt or $errorMsg =
2903
($cmd eq 'g' ? "no lines match the g pattern" : "all lines match the v pattern"),
2906
# Now apply $line to every line with a *
2909
$line = 'p' if ! length $line;
2914
global:while($gcnt and $stars) {
2916
for($i=1; $i<=$dol; ++$i) {
2917
last global if $intFlag;
2918
next unless substr($map, $i*$lnwidth+$lnwidth1, 1) eq '*';
2920
substr($map, $i*$lnwidth+$lnwidth1, 1) = ' ';
2921
$dot = $i; # ready to run the command
2922
if(evaluate($line)) {
2924
--$i if $ubackup; # try this line again, in case we deleted or moved it
2926
# Subcommand might turn global flag off.
2927
$nodot = $dot, $yesdot = 0, last global if ! $inglob;
2932
# yesdot could be 0, even upon success, if all lines are deleted via g/re/d
2933
if($yesdot or ! $dol) {
2935
dispLine($dot) if ($cmd eq 's' or $cmd eq 'I') and $subprint == 1;
2940
$errorMsg = "none of the marked lines were successfully modified" if $errorMsg eq "";
2942
$errorMsg = $intMsg if $errorMsg eq "" and $intFlag;
2943
return ! length $errorMsg;
2946
# Reveal the links to other web pages, or the email links.
2949
my ($i, $j, $h, $href, $line);
2951
if($fmode&$browsemode) {
2952
$line = fetchLine $endRange, 0;
2953
while($line =~ /\x80([\x85-\x8f]+){(.*?)}/g) {
2954
$j = revealNumber $1;
2958
$href = "" unless defined $href;
2959
if($href =~ s/^mailto://i) {
2960
$addrtext .= "$i:$href\n";
2962
$href = resolveUrl($$h{bref}, $href);
2963
$addrtext .= "<A HREF=$href>\n$i\n</A>\n";
2967
if(! length $addrtext) {
2968
length $fname or $errorMsg = "no file name", return 0;
2969
if(is_url($fname)) {
2971
$href =~ s/\.browse$//;
2973
$j =~ s,^https?://,,i;
2975
$addrtext = "<A HREF=$href>\n$j\n</A>\n";
2977
$addrtext = $fname."\n";
2980
$addrtext =~ s/\n$//;
2982
push @text, split "\n", $addrtext, -1;
2983
$#text = $j, return 0 if lineLimit 0;
2985
cxReset($context, 0) or return 0;
2986
$$h{backup} = $backup if defined $backup;
2988
print((length($addrtext)+1)."\n");
2989
$dot = $dol = $#text - $j;
2990
my $newpiece = $lnspace;
2991
$newpiece .= sprintf($lnformat, $j) while ++$j <= $#text;
2996
# All other editors let you stack and undo hundreds of operations.
2997
# If I'm writing a new editor, why don't I do that?
2998
# I don't know; guess I don't have the time.
2999
# And in my 20 years experience, I have rarely felt the need
3000
# to undo several operations.
3001
# I'm usually undoing the last typo, and that's it.
3002
# So I allow you to undo the last operation, only.
3003
# Get ready for a possible undo command.
3006
return if $fmode & $dirmode;
3007
$savedot = $dot, $savedol = $dol;
3008
$savemap = $map, $savelabels = $labels;
3013
# swap, so we can undo our undo. I do this alot.
3015
$temp = $ dot, $dot = $lastdot, $lastdot = $temp;
3016
$temp = $ dol, $dol = $lastdol, $lastdol = $temp;
3017
$temp = $ map, $map = $lastmap, $lastmap = $temp;
3018
$temp = $ labels, $labels = $lastlabels, $lastlabels = $temp;
3021
# Replace labels with their lines in shell escapes.
3022
sub expandLabeledLine($)
3025
my $n = ord($x) - ord('a');
3026
my $ln = substr $labels, $n*$lnwidth, $lnwidth;
3028
$errorMsg = "label $x not set", return "";
3029
return fetchLine($ln, 1);
3030
} # expandLabeledLine
3032
# Run a shell escape
3036
# Expand 'a through 'z labels
3038
$line =~ s/\B'([a-z])\b/expandLabeledLine($1)/ge;
3039
return 0 if length $errorMsg;
3040
$line =~ s/'_/$fname/g;
3041
$line =~ s/'\./fetchLine($dot,1)/ge;
3043
# Just run system and hope for the best.
3046
# Unix has a concept of shells.
3047
my $shell = $ENV{SHELL};
3048
$shell = "/bin/sh" if ! defined $shell;
3050
system $shell, "-c", $line;
3059
# Implement various two letter commands.
3060
# Most of these set and clear modes.
3066
if($line eq "qt") { exit 0; }
3068
if($line =~ s/^cd\s+// or $line =~ s/^cd$//) {
3069
$cmd = 'e'; # so error messages are printed
3071
my $temppath = `pwd`;
3074
$errorMsg = "you have no previous directory", return 0 unless defined $oldpath;
3075
chdir $oldpath or $errorMsg = "cannot change to previous directory $oldpath", return 0;
3077
$line = envFile($line);
3078
return 0 if length $errorMsg;
3079
chdir $line or $errorMsg = "invalid directory", return 0;
3081
$oldpath = $temppath;
3089
if($fmode & $browsemode) {
3091
$fname =~ s/.browse$//;
3093
length $fname or $errorMsg = "no file name", return 0;
3095
return -1, "$cmd $fname";
3100
$fmode&$browsemode or
3101
$errorMsg = $nobrowse, return 0;
3102
foreach $i (1..$dol) {
3103
$text[substr($map, $i*$lnwidth, $lnwidth1)] = fetchLine($i,1);
3105
$fmode &= ~($browsemode|$firstopmode|$changemode);
3106
$btags = []; # don't need those any more.
3107
print "editing as pure text\n" if $helpall;
3112
$fmode&$browsemode or
3113
$errorMsg = $nobrowse, return 0;
3116
$map = $$btags[0]{map1};
3117
$fname = $$btags[0]{fname};
3118
$fmode = $$btags[0]{fmode};
3119
$labels = $$btags[0]{labels};
3120
$dot = $$btags[0]{dot};
3121
$dol = $$btags[0]{dol1};
3126
if($line eq "f/" or $line eq "w/") {
3129
$errorMsg = "filename does not contain a slash", return 0;
3130
print "$i\n" if $helpall;
3131
substr($line, 1, 1) = " $i";
3135
if($line =~ /^f[dkt]$/) {
3136
$fmode&$browsemode or
3137
$errorMsg = $nobrowse, return 0;
3139
$key = "keywords" if $line eq "fk";
3140
$key = "description" if $line eq "fd";
3141
my $val = $$btags[0]{$key};
3150
if($line =~ /^sm(\d*)$/) {
3154
$j = sendMailCurrent();
3155
$j and print "ok\n";
3160
if($line eq "sg") { $global_lhs_rhs = 1; print "substitutions global\n" if $helpall; return 1; }
3161
if($line eq "sl") { $global_lhs_rhs = 0; print "substitutions local\n" if $helpall; return 1; }
3162
if($line eq "ci") { $caseInsensitive = 1; print "case insensitive\n" if $helpall; return 1; }
3163
if($line eq "cs") { $caseInsensitive = 0; print "case sensitive\n" if $helpall; return 1; }
3164
if($line eq "dr") { $dw = 0; print "directories readonly\n" if $helpall; return 1; }
3165
if($line eq "dw") { $dw = 1; print "directories writable\n" if $helpall; return 1; }
3166
if($line eq "dx") { $dw = 2; print "directories writable with delete\n" if $helpall; return 1; }
3167
if($line eq "dp") { $delprint ^= 1; print ($delprint ? "delete print\n" : "delete quiet\n"); return 1; }
3168
if($line eq "rh") { $reroute ^= 1; print ($reroute ? "redirect html\n" : "do not redirect html\n"); return 1; }
3169
if($line eq "pm") { $passive ^= 1; print ($passive ? "passive ftp\n" : "active ftp\n"); return 1; }
3170
if($line eq "ph") { $pdf_convert ^= 1; print ($pdf_convert ? "pdf to html conversion\n" : "pdf raw\n"); return 1; }
3171
if($line eq "vs") { $ssl_verify ^= 1; print ($ssl_verify ? "verify ssl connections\n" : "do not verify ssl connections (less secure)\n"); return 1; }
3172
if($line eq "ac") { $allowCookies ^= 1; print ($allowCookies ? "accept cookies\n" : "reject cookies\n"); return 1; }
3173
if($line eq "sr") { $allowReferer ^= 1; print ($allowReferer ? "send refering web page\n" : "don't send refering web page\n"); return 1; }
3174
if($line =~ s/^db *//) {
3175
if($line =~ /^\d$/) {
3176
$debug = $line, return 1;
3178
$errorMsg = "please set debug level, 0 through 7", return 0;
3181
if($line =~ s/^ua *//) {
3182
if($line =~ /^\d+$/) {
3183
$errorMsg = "Agent number $line is not defined", return 0 if ! defined$agents[$line];
3184
$agent = $agents[$line], return 1;
3186
$errorMsg = "please set user agent, 0 through ".$#agents, return 0;
3189
if($line eq "ff") { $fetchFrames ^= 1; print ($fetchFrames ? "fetch frames\n" : "do not fetch frames\n"); return 1; }
3190
if($line eq "tn") { $textAreaCR ^= 1; print ($textAreaCR ? "dos newlines on text areas\n" : "unix newlines on text areas\n"); return 1; }
3191
if($line eq "eo") { $endmarks = 0; print "end markers off\n" if $helpall; return 1; }
3192
if($line eq "el") { $endmarks = 1; print "end markers list\n" if $helpall; return 1; }
3193
if($line eq "ep") { $endmarks = 2; print "end markers on\n" if $helpall; return 1; }
3194
return -1,"^".length($1) if $line =~ /^(\^+)$/;
3195
return stripChild() if $line eq "ws";
3196
return unstripChild() if $line eq "us";
3198
return -1, $line; # no change
3201
# Evaluate the entered command.
3202
# This is indirectly recursive, as in g/z/ s/x/y/
3203
# Pass the command line, and return success or failure.
3207
my ($i, $j, @pieces, $h, $href);
3210
my $nsuf = -1; # numeric suffix
3211
my $cx; # context specified -- always $nsuf - 1
3212
my $section = ""; # section within a document
3213
my $post = ""; # for post cgi method
3214
$nostack = 0; # suppress stacking of edit sessions
3217
$referer = $fname if $allowReferer;
3218
$referer =~ s/\.browse$//;
3221
# We'll allow whitespace at the start of an entered command.
3223
# Watch for successive q commands.
3224
$lastq = $lastqq, $lastqq = -1;
3227
# We'll allow comments in an edbrowse script
3228
return 1 if $line =~ /^#/;
3230
return shellEscape $line if $line =~ s/^!\s*//;
3232
# Web express shortcuts
3233
if($line =~ s/^@ *//) {
3234
if(! length $line) {
3236
foreach $i (sort keys %shortcut) {
3239
defined ($desc = $shortcut{$i}{desc}) and
3242
defined ($sort = $shortcut{$i}{sort}) and
3245
push @shortList, $j;
3246
} # loop over shortcuts
3247
foreach (sort @shortList) {
3254
($j, $line, $postBrowse) = webExpress($line);
3256
$line =~ s%^%b http://%;
3264
# Predefined command sets.
3265
if($line =~ s/^< *//) {
3267
foreach $i (sort keys %commandList) {
3272
$i = $commandList{$line};
3273
defined $i or $errorMsg = "command set $line is not recognized", return 0;
3274
return evaluateSequence($i, $commandCheck{$line});
3277
# Two letter commands.
3278
($j, $line) = twoLetter($line);
3279
return $j if $j >= 0;
3282
$startRange = $endRange = $dot; # default, if no range given
3283
$line = '+' if ! length $line;
3284
$line = ($dol ? 1 : 0) . $line if substr($line, 0, 1) eq ',';
3285
if($line =~ /^j/i) {
3286
$endRange = $dot + 1;
3287
$errorMsg = "line number too large", return "" if $endRange > $dol;
3288
} elsif(substr($line, 0, 1) eq '=') {
3289
$startRange = $endRange = $dol;
3290
} elsif($line =~ /^[wgv]/ and $line !~ /^g\s*\d*$/) {
3291
$startRange = 1, $endRange = $dol;
3292
$startRange = 0 if ! $dol;
3293
} elsif($line =~ s/^;//) {
3296
@pieces = getRangePart($line);
3297
$inglob = 0, return 0 if $#pieces < 0;
3298
$startRange = $endRange = $pieces[0];
3300
if($line =~ s/^,//) {
3301
$endRange = $dol; # new default
3302
if($line =~ /^[-'.\$+\d\/?]/) {
3303
@pieces = getRangePart($line);
3304
$inglob = 0, return 0 if $#pieces < 0;
3305
$endRange = $pieces[0];
3309
} # end standard range processing
3311
# lc lower case, uc upper case
3312
$line =~ s:^([lmu]c)$:s/.*/$1/:;
3313
if($line eq "bl") { # break the line
3314
dirBrowseCheck("break line") or return 0;
3318
$cmd = substr($line, 0, 1);
3319
if(length $cmd) { $line = substr($line, 1); } else { $cmd = 'p'; }
3321
$startRange <= $endRange or
3322
$errorMsg = "bad range", return 0;
3323
index($valid_cmd, $cmd) >= 0 or
3324
$errorMsg = "unknown command $cmd", $inglob = 0, return 0;
3326
# Change some of the command codes, depending on context
3327
$cmd = 'I' if $cmd eq 'i' and $line =~ /^[$valid_delim\d<*]/o;
3328
$cmd = 'I' if $cmd eq 's' and $fmode&$browsemode;
3329
$cmd = 's' if $cmd eq 'S';
3330
my $writeMode = ">";
3331
if($cmd eq "w" and substr($line, 0, 1) eq "+") {
3336
!($fmode&$dirmode) or index($dir_cmd, $cmd) >= 0 or
3337
$errorMsg = "$icmd $nixdir", $inglob = 0, return 0;
3338
!($fmode&$browsemode) or index($browse_cmd, $cmd) >= 0 or
3339
$errorMsg = "$icmd $nixbrowse", $inglob = 0, return 0;
3340
$startRange > 0 or index($zero_cmd, $cmd) >= 0 or
3341
$errorMsg = "zero line number", return 0;
3342
$postspace = 1 if $line =~ s/^\s+//;
3343
if(index($spaceplus_cmd, $cmd) >= 0 and
3344
! $postspace and length $line and
3346
$errorMsg = "no space after command";
3350
# env variable and wild card expansion
3351
if(index("brewf", $cmd) >= 0 and length $line) {
3352
$line = envFile($line);
3353
return 0 if length $errorMsg;
3357
return balanceLine($line);
3361
$startRange = $endRange + 1;
3362
$endRange = $startRange;
3363
$startRange <= $dol or
3364
$errorMsg = "line number too large", return 0;
3366
$line = $last_z if ! length $line;
3367
if($line =~ /^(\d+)\s*$/) {
3369
$last_z = 1 if $last_z == 0;
3370
$endRange += $last_z - 1;
3371
$endRange = $dol if $endRange > $dol;
3373
$errorMsg = "z command should be followed by a number", return 0;
3378
# move/copy destination, the third address
3379
if($cmd eq 'm' or $cmd eq 't') {
3381
$errorMsg = "no move/copy destination", $inglob = 0, return 0;
3382
$line =~ /^[-'.\$+\d\/?]/ or
3383
$errorMsg = "invalid move/copy destination", $inglob = 0, return 0;
3384
@pieces = getRangePart($line);
3385
$inglob = 0, return 0 if $#pieces < 0;
3389
} # move copy destination
3391
($line eq "+") ? ($line = "") : ($linePending = undef);
3393
$linePending = undef;
3395
! length $line or index($nofollow_cmd, $cmd) < 0 or
3396
$errorMsg = "unexpected text after the $icmd command", $inglob = 0, return 0;
3398
# We don't need trailing whitespace, except for substitute or global substitute.
3399
index("sgvI", $cmd) >= 0 or
3403
index($global_cmd, $cmd) >= 0 or
3404
$errorMsg = "the $icmd command cannot be applied globally", $inglob = 0, return 0;
3407
$errorMsg = "no errors" if ! length $errorMsg;
3408
print $errorMsg,"\n";
3414
print "help messages on\n" if $helpall;
3418
if(index("lpn", $cmd) >= 0) {
3419
foreach $i ($startRange..$endRange) {
3428
print $endRange,"\n";
3433
$fmode&$firstopmode or
3434
$errorMsg = "nothing to undo", return 0;
3440
$line =~ /^[a-z]$/ or
3441
$errorMsg = "please enter k[a-z]", return 0;
3442
$startRange == $endRange or
3443
$errorMsg = "cannot label an entire range", return 0;
3444
substr($labels, (ord($line) - ord('a'))*$lnwidth, $lnwidth) =
3445
sprintf $lnformat, $endRange;
3449
$nsuf = $line if $line =~ /^\d+$/ and ! $postspace;
3454
(cxCompare($cx) and cxActive($cx)) or return 0;
3456
print(length($j) ? $j : "no file");
3457
print " [binary]" if $fmode[$cx]&$binmode;
3462
$errorMsg = "cannot change the name of a directory", return 0 if $fmode&$dirmode;
3465
print(length($fname) ? $fname : "no file");
3466
print " [binary]" if $fmode&$binmode;
3473
$nsuf < 0 or (cxCompare($cx) and cxActive($cx)) or return 0;
3476
$errorMsg = "unexpected text after the $icmd command", return 0 if length $line;
3478
cxReset($cx, 1) or return 0;
3479
return 1 if $cx != $context;
3480
# look around for another active session
3482
$cx = 0 if ++$cx > $#factive;
3483
exit 0 if $cx == $context;
3484
next if ! defined $factive[$cx];
3492
$writeMode eq ">" or
3493
$errorMsg = "sorry, append to buffer not yet implemented", return 0;
3494
return writeContext($cx)
3496
$line = $fname if ! length $line;
3497
if($fmode&$dirmode and $line eq $fname) {
3498
$errorMsg = "cannot write to the directory; files are modified as you go";
3501
return writeFile($writeMode, $line) if length $line;
3502
$errorMsg = "no file specified";
3506
# goto a file in a directory
3507
if($fmode&$dirmode and $cmd eq 'g' and ! length $line) {
3509
$line = $dirname . '/' . fetchLine($endRange, 0);
3513
return (cxCompare($cx) and cxSwitch($cx, 1)) if $nsuf >= 0;
3516
print "session $j\n";
3521
if($cmd eq 'g' and $line =~ /^\d*$/) {
3522
($j, $line) = hyperlink($line);
3524
# Go on to browse the file.
3528
! length $line or $nsuf >= 0 or
3529
$errorMsg = "unexpected text after the ^ command", return 0;
3530
$nsuf = 1 if $nsuf < 0;
3532
$errorMsg = "no previous text", return 0 if ! defined $backup;
3533
cxReset($context, 2) or return 0;
3535
$backup = $$h{backup};
3539
# Should this print be inside or outside the loop?
3540
if($dot) { dispLine($dot); } else { print "empty file\n"; }
3548
if($icmd eq 's' or $icmd eq 'S') {
3549
# A few shorthand notations.
3550
if($line =~ /^([,.;:!?)"-])(\d?)$/) {
3553
# We have to escape the question mark and period
3554
$line =~ s/^([?.])/\\$1/;
3555
$line = "/$line/$1\\n";
3556
$line .= "/$suffix" if length $suffix;
3558
} # original command was s
3560
readyUndo if ! $inglob;
3562
if($cmd eq 'g' or $cmd eq 'v') {
3563
return doGlobal($line);
3567
$fmode&$browsemode or $errorMsg = $nobrowse, $inglob = 0, return 0;
3569
if($line =~ /^\d*\?/) { # status
3570
$inglob and $errorMsg = $inoglobal, $inglob = 0, return 0;
3571
$startRange == $endRange or $errorMsg = $inorange, return 0;
3572
infIndex($endRange, $line) > 0 or return 0;
3575
} # get info on input field
3577
if($line =~ /^\d*([=<])/) {
3586
cxCompare($cx) and cxActive($cx) or $inglob = 0, return 0;
3587
my $dolcx = $dol[$cx];
3588
$dolcx == 1 or $errorMsg = "session $t should contain exactly one line", $inglob = 0, return 0;
3589
$t = fetchLineContext(1, 1, $cx);
3593
length($errorMsg) and $inglob = 0, return 0;
3594
open FH, $t or $errorMsg = "cannot open $t, $!", $inglob = 0, return 0;
3596
defined $t or $errorMsg = "empty file", $inglob = 0, return 0;
3599
$errorMsg = "file contains more than one line";
3607
foreach $i ($startRange..$endRange) {
3608
my $rc = infIndex($i, $line);
3611
$rc > 0 and infReplace($t) or $inglob = 0, return 0;
3615
dispLine($yesdot) if ! $inglob;
3618
$errorMsg = "no input fields present" if ! $inglob;
3622
if($line =~ /^\d*\*$/) {
3623
$inglob and $errorMsg = $inoglobal, $inglob = 0, return 0;
3624
$startRange == $endRange or $errorMsg = $inorange, return 0;
3625
infIndex($endRange, $line) > 0 or return 0;
3626
($j, $line, $post) = infPush();
3627
# return code of -1 means there's more to do.
3628
return $j unless $j < 0;
3629
} elsif( $line !~ m&^\d*[$valid_delim]&o) {
3630
$errorMsg = "unknown input field directive, please use I? or I= or I/text/replacement/";
3635
# Pull section indicator off of a url.
3636
$section = $1 if $cmd eq 'b' and $line =~ s/(#.*)//;
3638
if(($cmd eq 'b' or $cmd eq 'e') and length $line) {
3640
$h = cxPack() if $dol and ! $nostack;
3641
cxReset($context, 0) or return 0;
3642
$startRange = $endRange = 0;
3644
if($line =~ /^\x80mail\x80(.*)$/) { # special code for sendmail link
3646
my $subj = urlSubject(\$href);
3647
$subj = "Comments" unless length $subj;
3652
push @text, "To: $href";
3653
$map .= sprintf($lnformat, $#text);
3654
push @text, "Subject: $subj";
3655
$map .= sprintf($lnformat, $#text);
3657
print "SendMail link. Compose your mail, type sm to send, then ^ to get back.\n";
3662
$i = readFile($fname, $post);
3663
$fmode &= ~($changemode|$firstopmode);
3665
$filesize = -1, cxUnpack($h), return 0 if !$i and ! $dol and is_url($fname);
3667
$$h{backup} = $backup if defined $backup;
3671
$fname = $changeFname if length $changeFname;
3672
$cmd = 'e' if $fmode&$binmode or ! $dol;
3673
return 1 if $cmd eq 'e';
3677
if(! ($fmode&$browsemode)) {
3679
print("$filesize\n"), $filesize = -1 if $filesize >= 0;
3680
render() or return 0;
3681
if(defined $postBrowse) {
3682
$$btags[0]{pb} = $postBrowse;
3683
evaluateSequence($postBrowse, 0);
3684
if($$btags[0]{dol2} > $dol) {
3685
$fmode &= ~($changemode|$firstopmode);
3690
$errorMsg = "already browsing", return 0 if ! length $section;
3692
return 1 if ! length $section;
3694
$j = findSection($section);
3695
$errorMsg = "cannot locate section #$section", return 0 unless $j;
3701
if($cmd eq 'm' or $cmd eq 't') {
3707
--$startRange, --$endRange;
3711
delText($startRange, $endRange) or return 0;
3712
$endRange = --$startRange;
3721
$i = ($endRange == $dol);
3722
if($fmode & $dirmode) {
3725
$j = delText($startRange, $endRange);
3727
$inglob = 0 if ! $j;
3728
if($j and $delprint and ! $inglob) {
3729
$i ? print "end of file\n" : dispLine($dot);
3734
if($cmd eq 'j' or $cmd eq 'J') {
3739
return readContext($cx) if $nsuf >= 0;
3740
return readFile($line, "") if length $line;
3741
$errorMsg = "no file specified";
3745
if($cmd eq 's' or $cmd eq 'I') {
3746
$j = substituteText($line);
3747
$inglob = $j = 0 if $j < 0;
3751
$errorMsg = "command $icmd not yet implemented";
3756
sub evaluateSequence($$)
3758
my $commands = shift;
3760
foreach my $go (@$commands) {
3764
my $rc = evaluate($go);
3765
print "$filesize\n" if $filesize >= 0;
3770
} # evaluateSequence
3772
# Hash to map html tags onto their English descriptions.
3773
# For instance, P maps to "paragraph".
3774
# Most of the tags, such as FONT, map to nothing,
3775
# whence they are thrown away.
3776
# The first two characters are not part of the description.
3777
# It forms a number that describes the nestability of the tag.
3778
# Bit 1 means the tag should be nested, like parentheses.
3779
# In fact all the bit1 tags should nest amongst eachother, unlike
3780
# <UL> <TABLE> </UL> </TABLE> (nesting error).
3781
# Bit 2 means a tag may appear inside itself, like nested lists.
3782
# Bit 4 means the tag implies a paragraph break.
3783
# Bit 8 means we retain attributes on the positive tag.
3784
# bit 16 means to close an open anchor *before* applying this tag
3786
sub => "11a subscript",
3788
center => " 3centered text",
3789
sup => "11a superscript",
3790
title => "17the title",
3791
head => "17the html header information",
3792
body => "27the html body",
3793
bgsound => "24background music",
3794
meta => " 8a meta tag",
3795
base => " 8base reference for relative URLs",
3796
img => " 8an image",
3797
br => " 0a line break",
3798
p => "20a paragraph",
3799
blockquote => "20a quoted paragraph",
3800
div => "20a divided section",
3803
dd => "20a definition",
3804
hr => "16a horizontal line",
3805
ul => "23a bullet list",
3806
ol => "23a numbered list",
3807
dl => "23a definition list",
3808
li => "16a list item",
3810
input => "24an input item",
3812
frame => "28a frame",
3813
map => "28An image map",
3814
area => "24an image map area",
3815
# I've seen tables nested inside tables -- I don't know why!
3816
table => "31a table",
3817
tr => "19a table row",
3818
td => "19a table entry",
3819
th => "19a table heading",
3820
pre => " 5a preformatted section",
3821
xmp => " 5a preformatted section",
3822
address => " 5a preformatted section",
3823
script => " 1a script",
3824
style => " 1a style block",
3825
noframes => " 1noframe section",
3826
select => "25an option list",
3827
textarea => "25an input text area",
3828
option => "24a select option",
3829
# The following tags convey formatting information that is eventually
3830
# discarded, but I'll track them for a while,
3831
# just to verify nestability.
3832
em => " 1a block of emphasized text",
3833
strong => " 1a block of emphasized text",
3834
b => " 1a block of bold text",
3835
i => " 1a block of italicized text",
3836
code => " 1a block of sample code",
3837
samp => " 1a block of sample code",
3840
# We encode tags in a @tag attribute=value attribute=value ...@ format,
3841
# though of course we don't use the @ sign.
3842
# We use \x80, which should not appear in international text.
3843
# I simply hard code it - it makes things simpler.
3845
# Support routine, to encode a tag.
3846
# Run from within a global substitute.
3847
# Pas the name of the tag, slash, and tag arguments
3850
my ($tag, $slash, $attributes) = @_;
3851
my $nlcount = $attributes =~ y/\n/\n/; # newline count
3852
my $doat = 0; # do attributes
3854
my $desc = $tagdesc{$tag};
3856
$doat = (substr($desc, 0, 2) & 8);
3860
# Do we need to gather up the attributes?
3861
if(!$doat or $slash eq "/") {
3862
# Guess not, just return the tag.
3863
return "" if $tag eq "z" and ! $nlcount;
3864
return "\x80$tag$slash$nlcount\x80";
3866
# Process each whitespace separated chunk, taking quotes into account.
3867
# note that name="foo"size="1" is suppose to be two separate tags;
3869
# Borrow a global variable, even though this may not be an input tag.
3870
$itag = {tag => $tag};
3871
push @$btags, $itag;
3872
$attributes =~ s/( # replace the entire matched text
3873
\w+ # attribute name
3874
(?>\s*=\s* # as in name=value
3875
(?> # a sequence of choices
3876
[^\s"']+ # regular printable characters
3878
"[^"]*" # double quoted string
3880
'[^']*' # single quoted string
3881
) # one of three formats
3883
)/processAttr($1)/xsge;
3884
# Capture description and keywords.
3885
if($tag eq "meta") {
3886
my $val = $$itag{name};
3889
if($val eq "description" or $val eq "keywords") {
3890
my $content = $$itag{content};
3891
if(defined $content) {
3892
stripWhite \$content;
3893
$$btags[0]{$val} = $content if length $content;
3895
} # description or keywords
3898
return "" unless $nlcount;
3899
return "\x80z$nlcount\x80";
3901
my $tagnum = $#$btags;
3902
return "\x80$tag$nlcount,$tagnum\x80";
3905
# Support routine, to crack attribute=value.
3909
# Get rid of spaces around first equals.
3910
$line =~ s/^([^=\s]*)\s*=\s*/$1=/;
3911
# Get rid of the quotes.
3912
$line =~ s/("[^"]*"|'[^']*')/substr($1,1,-1)/sge;
3913
my $attr = lc $line;
3914
$attr =~ s/\s*=.*//s;
3915
return "" unless $attr =~ /^\w+$/;
3916
$line =~ s/^[^=]*=//s
3918
$line =~ s/&([a-zA-Z]+|#\d+);/metaChar($1)/ge;
3919
$$itag{$attr} = $line;
3923
# Support routine, to encode a bang tag.
3924
# Run from within a global substitute.
3925
sub processBangtag($)
3928
if($item eq "'" or $item eq '"') {
3929
return (length $bangtag ? " " : $item);
3931
if(substr($item, 0, 1) eq '<') {
3932
return "" if length $bangtag;
3933
return $item if $item eq "<";
3934
$bangtag = substr $item, 1;
3937
return $item unless length $bangtag;
3938
# dashes at the front require dashes at the end.
3939
# But (apparently) they don't have to be the same number of dashes.
3940
# I really don't understand this syntax at all!
3941
# It is suppose to follow the rules in
3942
# http://www.htmlhelp.com/reference/wilbur/misc/comment.html
3943
# but real web pages hardly ever follow these rules!
3944
substr($item, -1) = ""; # don't need that last >
3945
my $l = length($bangtag) - 1;
3946
$l &= ~1; # back down to an even number
3947
return " " if $l and ! length $item; # lone > inside a comment
3952
# Turn <>'" in javascript into spaces, as we did above.
3953
sub processScript($)
3956
if(length($item) < 5) {
3957
return ($inscript ? " " : $item);
3959
# now $item is <script or </script
3960
# Try to guard against Java code that looks like
3961
# document_write("<script bla bla bla>\n";
3962
# There's a lot of this going around.
3964
$prequote = 1 if $item =~ s/^\( *['"]//;
3965
return ' ' if $inscript and $prequote;
3966
if(substr($item, 1, 1) eq '/') {
3967
--$inscript if $inscript;
3974
sub backOverSpaces($)
3977
my $j = length($refbuf) - 1;
3978
--$j while $j >= 0 and substr($refbuf, $j, 1) =~ /[ \t]/;
3980
substr($refbuf, $j) = "" if $trunc;
3984
# Recompute space value, after the buffer has been cropped.
3985
# 0 = word, 1 = spaces, 2 = newline, 3 = paragraph.
3988
return 3 if ! length $refbuf;
3989
my $last = substr $refbuf, -1;
3990
return 0 if $last !~ /\s/;
3991
return 1 if $last ne "\n";
3992
return 2 if substr($refbuf, -2) ne "\n\n";
3996
# Here are the common keywords for mail header lines.
3997
# These are in alphabetical order, so you can stick more in as you find them.
3998
# The more words we have, the more accurate the test.
3999
# Value = 1 means it might be just a "NextPart" mime header,
4000
# rather than a full-blown email header.
4001
# Value = 2 means it could be part of an English form.
4002
# Value = 4 means it's almost certainly a line in a mail header.
4005
"arrival-date" => 4,
4006
"content-transfer-encoding" => 1,
4007
"content-type" => 1,
4009
"delivered-to" => 4,
4011
"final-recipient" => 4,
4014
"last-attempt-date" => 4,
4016
"mailing-list" => 4,
4018
"mime-version" => 4,
4023
"reporting-mta" => 4,
4032
"x-mailman-version" => 4,
4034
"x-ms-tnef-correlator" => 4,
4035
"x-msmail-priority" => 4,
4040
# Get a filename from the user.
4043
my $startName = shift;
4047
print "[$startName] " if defined $startName;
4049
exit 0 unless defined $line;
4052
redo input if ! defined $startName;
4056
$line = envLine $line;
4057
print("$errorMsg\n"), redo input if length $errorMsg;
4059
if($isnew and -e $line) {
4060
print "Sorry, file $line already exists.\n";
4068
# Get a character from the tty, raw mode.
4069
# For some reason hitting ^c in this routine doesn't leave the tty
4070
# screwed up. I don't know why not.
4073
my $choices = shift;
4075
# Too bad there isn't a perl in-built for this.
4076
# I don't know how to do this in Windows. Help anybody?
4077
system "stty", "-icanon", "-echo";
4079
system "stty", "icanon", "echo";
4080
if(defined $choices and index($choices, $c) < 0) {
4081
STDOUT->autoflush(1);
4083
STDOUT->autoflush(0);
4090
# Encode html page or mail message.
4091
# No args, the html is stored in @text, as indicated by $map.
4094
$dol or $errorMsg = "empty file", return 0;
4095
$errorMsg = "binary file", return 0 if $fmode&$binmode;
4096
$errorMsg = "cannot render a directory", return 0 if $fmode&$dirmode;
4098
my ($i, $j, $k, $rc);
4100
$btags[$context] = $btags = [];
4101
$$btags[0] = {tag => "special", fw => {} };
4103
# If it starts with html, head, or comment, we'll call it html.
4104
my $tbuf = fetchLine 1, 0;
4105
if($tbuf =~ /^\s*<(?:!-|html|head|meta)/i) {
4109
if(! length $type) {
4110
# Check for mail header.
4111
# There might be html tags inside the mail message, so we need to
4112
# look for mail headers first.
4113
# This is a very simple test - hopefully not too simple.
4114
# The first 20 non-indented lines have to look like mail header lines,
4115
# with at least half the keywords recognized.
4118
my $line = fetchLine $i, 0;
4119
last unless length $line;
4120
next if $line =~ /^[ \t]/; # indented
4122
next unless $line =~ /^([\w-]+):/;
4124
my $v = $mhWords{$word};
4126
if($k >= 4 and $k*2 >= $j) {
4134
if($type ne "mail") {
4135
# Put the lines together into one long string.
4136
# This is necessary to check for, and render, html.
4138
$tbuf .= fetchLine($_, 0) . "\n" foreach (2..$dol);
4141
if(! length $type) {
4142
# Count the simple html tags, we need at least two per kilabyte.
4144
$j = $tbuf =~ s/(<\/?[a-zA-Z]{1,7}\d?[>\s])/$1/g;
4146
$type = "html" if $j * 500 >= $i;
4149
if(! length $type) {
4150
$errorMsg = "this doesn't look like browsable text";
4155
$badHtml = 1 if is_url($fname);
4156
$rc = renderMail(\$tbuf) if $type eq "mail";
4157
$rc = renderHtml(\$tbuf) if $type eq "html";
4158
return 0 unless $rc;
4160
pushRenderedText(\$tbuf) or return 0;
4161
if($type eq "mail") {
4162
$fmode &= ~$browsemode; # so I can run the next command
4166
$fmode &= ~$changemode;
4167
$fmode |= $browsemode;
4172
if($type eq "mail" and $nat) {
4173
print "$nat attachments.\n";
4175
foreach $curPart (@mimeParts) {
4176
next unless $$curPart{isattach};
4178
print "Attachment $j\n";
4179
my $filename = getFileName($$curPart{filename}, 1);
4180
next if $filename eq "x";
4181
if($filename eq "e") {
4182
print "session " . (cxCreate(\$$curPart{data}, $$curPart{filename})+1) . "\n";
4185
if(open FH, ">$filename") {
4186
binmode FH, ':raw' if $doslike;
4187
print FH $$curPart{data}
4188
or dieq "Cannot write to attachment file $filename, $!.";
4191
print "Cannot create attachment file $filename.\n";
4193
} # loop over attachments
4194
print "attachments complete.\n";
4195
} # attachments present
4200
# Pass the reformatted text, without its last newline.
4201
sub pushRenderedText($)
4205
# Replace common nonascii symbols
4206
# I don't know what this pair of bytes is for!
4207
$$tbuf =~ s/\xe2\x81//g;
4209
# Transliterate alternate forms of quote, apostrophe, etc.
4210
# We replace escape too, cuz it shouldn't be there anyways, and it messes up
4211
# some terminals, and some adapters.
4212
# Warning!! Don't change anything in the range \x80-\x8f.
4213
# These codes are for internal use, and mus carry through.
4214
$$tbuf =~ y/\x1b\x95\x99\x9c\x9d\x92\x93\x94\xa0\xad\x96\x97/_*'`''`' \55\55\55/;
4216
# Sometimes the bullet list indicator is falsely separated from the subsequent text.
4217
$$tbuf =~ s/\n\n\*\n\n/\n\n* /g;
4219
# Turn nonascii math symbols into our encoded versions of math symbols,
4220
# to be handled like Greek letters etc, in a consistent manner,
4221
# by the next block of code.
4222
$$tbuf =~ s/\xb0/\x82176#/; # degrees
4223
$$tbuf =~ s/\xbc/\x82188#/; # 1 fourth
4224
$$tbuf =~ s/\xbd/\x82189#/; # 1 half
4225
$$tbuf =~ s/\xbe/\x82190#/; # 3 fourths
4226
$$tbuf =~ s/\xd7/\x82215#/; # times
4227
$$tbuf =~ s/\xf7/\x82247#/; # divided by
4229
if($$tbuf =~ /\x82\d+#/) { # we have codes to expand.
4230
# These symbols are going to become words -
4231
# put spaces on either side, if the neighbors are also words.
4232
$$tbuf =~ s/#\x82/# \x82/g;
4233
$$tbuf =~ s/([a-zA-Z\d])(\x82\d+#)/$1 $2/g;
4234
$$tbuf =~ s/(\x82\d+#)([a-zA-Z\d])/$1 $2/g;
4235
$$tbuf =~ s/\x82(\d+)#/$symbolWord{$1}/ge;
4238
# Now push into lines, for the editor.
4241
push @text, split "\n", $$tbuf, -1;
4245
$#text = $j, return 0 if lineLimit 0;
4247
$$btags[0]{map1} = $map;
4248
$$btags[0]{dot} = $dot;
4249
$$btags[0]{dol1} = $dol;
4250
$dot = $dol = $#text - $j;
4251
$$btags[0]{dol2} = $dol;
4253
$map .= sprintf($lnformat, $j) while ++$j <= $#text;
4254
$$btags[0]{map2} = $map;
4255
$fmode &= ~$firstopmode;
4256
$$btags[0]{fname} = $fname;
4257
$$btags[0]{fmode} = $fmode;
4258
$$btags[0]{labels} = $labels;
4259
$fmode &= $changemode; # only the change bit retains its significance
4260
$fmode |= $browsemode;
4261
$labels = $lnspace x 26;
4262
$fname .= ".browse" if length $fname;
4264
} # pushRenderedText
4266
# Pass in the text to be rendered, by reference.
4267
# The text is *replaced* with the rendered text.
4271
my ($i, $j, $ofs1, $ofs2, $h); # variables
4275
# Ok, here's a real kludge.
4276
# The utility that converts pdf to html,
4277
# access.adobe.com/simple_form.html, has a few quirks.
4278
# One of the common problems in the translation is
4279
# the following meaningless string, that appears over and over again.
4280
# I'm removing it here.
4281
$$tbuf =~ s/Had\strouble\sresolving\sdest\snear\sword\s(<[\w_;:\/'"().,-]+>\s)?action\stype\sis\sGoToR?//g;
4283
# I don't expect any overstrikes, but just in case ...
4284
$$tbuf =~ s/[^<>"'&\n]\10//g;
4285
# Get rid of any other backspaces.
4286
$$tbuf =~ y/\10/ /d;
4288
# Make sure there aren't any \x80 characters to begin with.
4289
$$tbuf =~ y/\x80/\x81/;
4291
# As far as I can tell, href=// means href=http://
4292
# Is this documented anywhere??
4293
$$tbuf =~ s,\bhref=(["']?)//\b,HREF=$1http://,ig;
4295
# Find the simple window javascript functions
4297
$lineno = $colno = 1;
4299
javaFunctions($tbuf);
4301
# Before we do the standard tags, get rid of the <!-- .. --> tags.
4302
# I turn them into <z ... > tags,
4303
# which will be disposed of later, along with all the
4304
# other unrecognized tags.
4305
# This is not a perfect implementation.
4306
# It will glom onto the <! inside <A HREF="xyz<!stuff">,
4307
# and it shouldn't; but niehter should you be writing such a perverse string!
4308
$$tbuf =~ s/<!-*>//g;
4310
$$tbuf =~ s/(['"]|<(!-*)?|-*>)/processBangtag($1)/ge;
4311
print "comments stripped\n" if $debug >= 6;
4313
$errorMsg = $intMsg, return 0 if $intFlag;
4315
# A good web page encloses its javascript in comments <!-- ... -->,
4316
# But some don't, and the (sometimes quoted) < > characters
4317
# really mess us up. Let's try to strip the javascript,
4318
# or any other script for that matter.
4320
$$tbuf =~ s/((?>(\( *['"])?<(\/?script[^>]*>)?|[>"']))/processScript($1)/gei;
4321
print "javascript stripped\n" if $debug >= 6;
4323
$errorMsg = $intMsg, return 0 if $intFlag;
4325
# I'm about to crack html tags with one regexp,
4326
# and that would be entirely doable, if people and web tools didn't
4327
# generate crappy html.
4328
# The biggest problem is unbalanced quotes, whence the open quote
4329
# swallows the rest of the document in one tag.
4330
# I'm goint to *try*, emphasis on try, to develop a few heuristics
4331
# that will detect some of the common misquotings.
4332
# This stuff should be written in C, a complex procedural algorithm.
4333
# But I don't have the time or inclination to translate this mess into C,
4334
# and perl is not the write language to write an algorithm like that.
4335
# I've seen examples of all of these syntactical nightmares on the web,
4336
# and others that I can't possibly code around.
4337
# Only one quote in the tag; get rid of it. Tag is on one line.
4338
$$tbuf =~ s/<(\/?[a-zA-Z][^<>'"]*)['"]([^<>'"]*)>/<$1$2>/g;
4339
# Two quotes before the last >, but not ="">, which would be ok.
4340
$$tbuf =~ s/([^= <>])"">/$1">/g;
4341
$$tbuf =~ s/([^= <>])''>/$1'>/g;
4342
# Missing quote before the last > "word>
4343
# It's usually the last > where things screw up.
4344
$$tbuf =~ s/["'](\w+)>/$1>/g;
4345
#   is suppose to have a semi after it - it often doesn't.
4346
$$tbuf =~ s/ $/ /gi;
4347
$$tbuf =~ s/ ([^;])/ $1/gi;
4348
# Well that's all I can manage right now.
4350
# Encode <font face=symbol> number characters.
4351
# This is kludgy as hell, but I want to be able to read my own math pages.
4352
$$tbuf =~ s/<font +face=['"]?symbol['"]?> *([a-zA-Z]|&#\d+;) *<\/font>/metaSymbol($1)/gei;
4354
# Now let's encode the tags.
4355
# Thanks to perl, we can do it in one regexp.
4356
$$tbuf =~ s/< # start the tag
4357
(\/?) # leading slash turns off the tag
4358
([a-zA-Z]+) # name of the tag
4359
( # remember the attributes
4360
(?> # fix each subexpression as you find it
4361
[^>"']+ # unquoted stuff inside the tag
4363
"[^"]*" # stuff in double quotes
4365
'[^']*' # stuff in single quotes
4366
)* # as many of these chunks as you need
4367
) # return the set in $3
4368
> # close the html tag
4369
/processTag($2, $1, $3)/xsge;
4370
print "tags encoded\n" if $debug >= 6;
4372
$errorMsg = $intMsg, return 0 if $intFlag;
4374
# Now we can crunch the meta chars without fear.
4375
$$tbuf =~ s/&([a-zA-Z]+|#\d+);/metaChar($1)/ge;
4376
print "meta chars translated\n" if $debug >= 6;
4379
$longcut = $lperiod = $lcomma = $lright = $lany = 0;
4381
my @olcount = (); # Where are we in each nested number list?
4382
my @dlcount = (); # definition lists
4383
my $tagnest = "."; # Stack the nestable html tags, such as <LI> </LI>
4384
my $tagLock = 0; # other tags are locked out, semantically, until this one is done
4385
my $tagStart; # location of the tag currently under lock
4386
# Locking tags are currently: title, select, textarea
4387
my $inhref = 0; # in anchor reference
4390
my $inta = 0; # text area
4391
my $optStart; # start location of option
4392
my $opt; # hash of options
4393
my $optCount; # count of options
4394
my $optSel; # options selected
4395
my $optSize; # size of longest option
4396
my $lastopt; # last option, waiting for next <option>
4397
my $premode = 0; # preformatted mode
4401
my $intable = 0; # in table
4402
my $intabhead = 0; # in table header
4403
my $intabrow = 0; # in table row
4404
my $inform = 0; # in form
4406
# Global substitute is mighty powerful, but at this point
4407
# we really need to proceed token by token.
4408
# Going by chunks is better than shifting each character.
4409
# Extract a contiguous sequence of non-whitespace characters,
4410
# or whitespace, or a tag.
4412
while($$tbuf =~ /(\s+|\x80[\w\/,]+\x80|[^\s\x80]+)/gs) {
4413
$errorMsg = $intMsg, return 0 if $intFlag;
4416
# Should we ignore line breaks in table headers?
4417
$chunk = ' ' if ($intabhead|$inhref) and $chunk =~ /^\x80br\/?0/;
4419
if($chunk =~ /^\s/) { # whitespace
4420
$j = $chunk =~ y/\n/\n/; # count newlines
4422
next reformat if $inscript;
4423
if(!$premode or $tagLock) {
4424
next reformat if $lspace;
4426
$chunk = "\n" if $j and substr($refbuf, -4) =~ / [a-zA-Z]\.$/;
4427
appendWhiteSpace($chunk, !($inhref + $tagLock));
4428
# Switch { and space, it looks prettier.
4429
# Hopefully this will never happen accept at the beginning of a link.
4430
$inhref and $lspace == 1 and $refbuf =~ s/(\x80[\x85-\x8f]+{) $/ $1/;
4432
} # not preformatted
4434
# Formfeed is a paragraph break.
4435
$j = 2 if $chunk =~ s/\f/\n\n/g;
4437
# Keep the whitespace after nl, it's preformatted.
4438
$chunk =~ s/.*\n//s;
4439
# Note that we make no effort to track colno or lperiod etc in preformat mode.
4446
$chunk = "\n\n".$chunk if $lspace < 2;
4447
$chunk = "\n".$chunk if $lspace == 2;
4455
# Now j = 1 and lspace < 3
4457
$refbuf .= "\n$chunk";
4458
$lspace = 1 if ! $lspace;
4463
if(substr($chunk, 0, 1) ne "\x80") {
4464
next reformat if $inscript;
4465
$chunk =~ y/{}/[]/ if $inhref;
4466
$inhref = 2 if $inhref;
4467
appendPrintable($chunk);
4472
my ($tag, $slash, $nlcount, $attrnum) =
4473
$chunk =~ /^.([a-z]+)(\/?)(\d+)(?:,(\d+))?.$/;
4474
# Unless we hear otherwise, the tag is assumed to contribute no visible
4475
# characters to the finished document.
4478
my $desc = $tagdesc{$tag};
4479
$desc = " 0an unknown construct" if ! defined $desc;
4480
my $nest = substr $desc, 0, 2;
4481
$chunk = "\n\n" if $nest & 4;
4482
substr($desc, 0, 2) = "";
4483
# Equivalent tags, as far as we're concerned
4484
$tag = "script" if $tag eq "style";
4485
$tag = "script" if $tag eq "noframes";
4486
$tag = "pre" if $tag eq "xmp";
4487
$tag = "pre" if $tag eq "address";
4488
my $tag1 = ".$tag.";
4489
my $tagplus = "$tag$slash";
4490
$lineno += $nlcount, next reformat if $inscript and $tagplus ne "script/";
4491
$attrnum = 0 if ! defined $attrnum;
4492
# A hidden version of the attribute number, to embed in the text.
4493
$attrhidden = hideNumber $attrnum;
4496
$h = $$btags[$attrnum];
4497
$$h{lineno} = $lineno; # source line number
4502
my $closeAnchor = 0;
4503
$closeAnchor = 1 if $inhref and ($tag eq "a" or $tag eq "area" or $tag eq "frame" or $tag eq "input");
4504
$closeAnchor = 1 if $inhref == 2 and $nest&16;
4506
# Make sure we open and close things in order.
4509
errorConvert("$desc begins in the middle of $desc")
4510
if index($tagnest, $tag1) >= 0 and !($nest&2);
4511
$tagnest = ".$tag.$attrnum" . $tagnest;
4512
push @olcount, 0 if $tag eq "ol";
4513
push @dlcount, 0 if $tag eq "dl";
4515
$j = index $tagnest, $tag1;
4517
errorConvert("an unexpected closure of $desc, which was never opened");
4520
my $opendesc = substr $tagnest, 1;
4521
$opendesc =~ s/\..*//;
4522
$opendesc = $tagdesc{$opendesc};
4523
substr($opendesc, 0, 2) = "";
4524
errorConvert("$desc is closed inside $opendesc");
4527
substr($tagnest, $j, length($tag)+1) = "";
4528
$openattr = substr $tagnest, $j;
4529
$openattr =~ s/\..*//;
4530
substr($tagnest, $j, length($openattr)+1) = "";
4531
$openattrhidden = hideNumber $openattr;
4532
if($openattr) { # record the offset of </tag>
4533
$ofs2 = backOverSpaces(0);
4534
$openTag = $$btags[$openattr];
4535
# Tweak offset for the } on the anchor
4536
++$ofs2 if $closeAnchor and $inhref == 2;
4537
$ofs2 = $tagStart if $tagLock;
4538
$$openTag{ofs2} = $ofs2;
4540
pop @olcount if $tag eq "ol";
4541
pop @dlcount if $tag eq "dl";
4542
} # was this construct open or not
4546
# retain the start and end of any tag worthy of attributes
4548
$ofs1 = backOverSpaces(0);
4549
$ofs1 = $tagStart if $tagLock;
4556
if($inhref == 1) { # no text in the hyperlink
4557
if($refbuf =~ s/( *\x80[\x85-\x8f]+{[\s|]*)$//s) {
4559
$colno -= $j =~ y/ {/ {/;
4561
warn "couldn't strip off the open anchor at line $lineno <" .
4562
substr($refbuf, -10) . ">.";
4564
$$hrefTag{tag} = "z"; # trash the anchor
4567
$refbuf =~ s/([ \n])}$/}$1/;
4569
$j = $$hrefTag{href};
4570
my $onc = $$hrefTag{onclick};
4571
if($j =~ /^javascript/i or $onc) {
4572
# Let the onclick take precedence.
4573
$j = $onc if defined $onc and length $onc;
4574
# See if this is a javascript function we can recognize and circumvent.
4576
if($$hrefTag{form} and $i eq "submit") {
4577
# I'll assume this is a check and submit function.
4578
# If it only validates fields, we're ok.
4579
# If it reformats the data, we're screwed!
4580
$i = $$hrefTag{ofs1};
4581
$inf = substr $refbuf, $i;
4584
$inf =~ s/}$/ js\x80\x8f>/;
4585
$$hrefTag{$ofs2} += 5;
4586
if($$inform{action}) {
4587
my $actscheme = is_url $$inform{action};
4588
$actscheme = is_url $baseref unless $actscheme;
4589
if($actscheme eq "https") {
4590
$inf =~ s/ js/& secure/;
4591
$$hrefTag{$ofs2} += 7;
4593
if($$inform{action} =~ /^mailto:/i) {
4594
$inf =~ s/ js/& mailform/;
4595
$$hrefTag{$ofs2} += 9;
4598
substr($refbuf, $i) = $inf;
4599
# change it to an input field
4600
$$hrefTag{tag} = "input";
4601
$$hrefTag{type} = "submit";
4602
$$hrefTag{value} = "submit";
4603
$$inform{nnh}++; # another non hidden field
4605
$$inform{lnh} = $h; # last non hidden field
4607
# Is this just opening a new window, then calling the link?
4608
elsif(length $i and $i ne "submit") {
4609
# Ok, I'll assume it's a new window with hyperlink
4610
$$hrefTag{href} = $i;
4612
print "unknown javascript ref $j\n" if $debug >= 3;
4616
$lspace = computeSpace();
4618
last switch if $tagplus eq "a/";
4619
} # close the open anchor
4621
if($tagplus eq "sup") {
4626
if($tagplus eq "sup/" and defined $openTag) {
4627
$ofs1 = $$openTag{ofs1};
4628
++$ofs1; # skip past ^
4629
$j = substr $refbuf, $ofs1;
4631
last switch unless length $j;
4632
if($j =~ /^th|st|rd|nd$/i and
4633
substr($refbuf, $ofs1-2) =~ /\d/) {
4635
substr($refbuf, $ofs1, 1) = "";
4638
last switch if $j =~ /^(\d+|\*)$/;
4640
last switch if $j =~ /^[a-zA-Z](?:\d{1,2})?$/;
4642
(substr $refbuf, $ofs1) = "($j)";
4646
if($tagplus eq "sub/" and defined $openTag) {
4647
$ofs1 = $$openTag{ofs1};
4648
$j = substr $refbuf, $ofs1;
4650
last switch unless length $j;
4652
last switch if $j =~ /^\d{1,2}$/;
4654
(substr $refbuf, $ofs1) = "[$j]";
4658
if($tagplus eq "title" and ! $tagLock and ! $intitle) {
4659
$tagStart = length $refbuf;
4660
$tagLock = $intitle = 1;
4664
if($tagplus eq "title/" and $intitle) {
4665
$i = substr $refbuf, $tagStart;
4666
substr($refbuf, $tagStart) = "";
4667
$lspace = computeSpace();
4670
if(! defined $$btags[0]{title}) {
4672
$$btags[0]{title} = $i if length $i;
4679
if($tagplus eq "li") {
4680
$i = index $tagnest, ".ol.";
4681
$j = index $tagnest, ".ul.";
4683
if($j >= 0 and $j < $i) {
4686
$j = ++$olcount[$#olcount];
4693
errorConvert("$desc appears outside of a list context");
4698
if($tagplus eq "dt" or $tagplus eq "dd") {
4699
if(($i = $#dlcount) >= 0) {
4700
$j = ($tag eq "dd" ? 1 : 0);
4701
errorConvert("improper term->definition sequence") if $j != $dlcount[$i];
4704
errorConvert("$desc is not contained in a definition list");
4709
# The only thing good about an image is its alt description.
4710
if($tagplus eq "img") {
4711
$hrefFile = "" unless $inhref;
4712
$j = deriveAlt($h, $hrefFile);
4713
$j = "?" if $inhref and length($j) == 0;
4716
$inhref = 2 if $inhref;
4722
if($tagplus eq "body") {
4723
my $onl = $$h{onload}; # popup
4724
$onl = $$h{onunload} unless $onl; # popunder
4726
if($onl =~ /submit[.\w]* *\(/i) {
4730
$j = javaWindow $onl;
4731
if(length $j and $j ne "submit") {
4732
createHyperLink($h, $j, "onload");
4735
} # open another window
4738
if($tagplus eq "bgsound") {
4740
if(defined $j and length $j) {
4741
# Someday we'll let you play this right from edbrowse, spawning playmidi
4742
# or mpg123 or whatever. For now I'll let you grab the file yourself.
4743
# Maybe that's better anyways.
4744
createHyperLink($h, $j, "Background music");
4748
} # background music
4750
if($tag eq "base") {
4752
$baseref = urlDecode $href if $href;
4756
if($tagplus eq "a") {
4757
if(defined $$h{name}) {
4758
$refbuf .= "\x80$attrhidden*";
4760
if(defined($hrefFile = $$h{href})) {
4761
$$h{form} = $inform;
4764
$$h{bref} = $baseref;
4765
# We preserve $lspace, despite pushing visible characters.
4766
$refbuf .= "\x80$attrhidden".'{';
4772
if($tagplus eq "area") {
4774
if(defined($href = $$h{href})) {
4775
$j = javaWindow($href);
4776
$href = $j if length $j and $j ne "submit";
4777
$alt = deriveAlt($h, "");
4778
$alt = $foundFunc if length $foundFunc and not defined $$h{alt};
4779
$alt = "area" unless length $alt;
4780
createHyperLink($h, $href, $alt);
4784
if($tagplus eq "frame") {
4785
my $name = $$h{name};
4788
$name = "" if ! defined $name;
4792
$$h{ofs1} = backOverSpaces(1);
4793
$name = "???" if ! length $name;
4794
$name =~ y/{}\n/[] / if $inhref;
4795
$refbuf .= "frame ";
4797
createHyperLink($h, $src, $name);
4798
}} # frame becomes hyperlink
4802
$premode = 1, last switch if $tagplus eq "pre";
4803
$premode = 0, last switch if $tagplus eq "pre/";
4804
$inscript = 1, last switch if $tagplus eq "script";
4805
$inscript = 0, last switch if $tagplus eq "script/";
4806
$intable++, last switch if $tagplus eq "table";
4807
$intable--, last switch if $tagplus eq "table/" and $intable;
4810
$chunk = "\n\n" if $lspace >= 2;
4813
$chunk = "\n--------------------------------------------------------------------------------\n\n", last switch if $tagplus eq "hr";
4816
errorConvert("$desc not inside a table") if ! $intable;
4817
$slash ? do { --$intabrow if $intabrow } : ++$intabrow;
4822
if($tag eq "td" or $tag eq "th") {
4823
errorConvert("$desc not inside a table row") if ! $intabrow;
4825
$intabhead = 1 - length $slash if $tag eq "th";
4827
substr($refbuf, -1) = "" if $lspace == 1;
4834
if($tagplus eq "form" and ! ($inform + $tagLock)) {
4836
$$h{bref} = $baseref;
4837
$j = lc $$h{method};
4838
$j = "get" if ! defined $j or ! length $j; # default
4839
if($j ne "post" and $j ne "get") {
4840
errorConvert("form method $j not supported");
4844
$$h{nnh} = 0; # number of non hidden fields
4845
$$h{nif} = 0; # number of input fields
4849
if($tagplus eq "form/" and $inform) {
4850
# Handle the case with only one visible input field.
4852
($$inform{action} or $$inform{onchange}) and
4853
$$inform{nnh} <= 1 and $$inform{nif} and (
4854
$$inform{nnh} == 0 or
4855
($h = $$inform{lnh}) and
4856
$$h{type} ne "submit")) {
4857
$refbuf .= " " if $lspace == 0;
4858
$itag = {tag => "input",
4859
type => "submit", form => $inform,
4860
size => 2, value => "Go"};
4861
push @$btags, $itag;
4862
$j = hideNumber $#$btags;
4863
$refbuf .= "\x80$j<Go\x80\x8f>";
4866
} # submit button created out of thin air
4871
my $noform = "$desc is not inside a form";
4872
if($tagplus eq "select" and ! $tagLock) {
4873
errorConvert($noform) if ! $inform;
4875
$$inform{onchange} = 1 if $inform and $$h{onchange};
4877
$tagStart = length $refbuf;
4878
$optCount = $optSel = $optSize = 0;
4880
$$h{opt} = $opt = {};
4884
if(($tagplus eq "select/" or $tagplus eq "option") and $inselect) {
4885
if(defined $lastopt) {
4886
$j = substr $refbuf, $optStart;
4889
$lastopt =~ s/NoOptValue$/$j/;
4890
$$opt{$j} = $lastopt;
4891
if($optCount < 999) {
4894
errorConvert("too many options, limit 999");
4896
++$optSel if substr($lastopt, 3, 1) eq '+';
4898
$optSize = $j if $j > $optSize;
4901
if($tagplus eq "select/") {
4904
substr($refbuf, $tagStart) = "";
4905
$lspace = computeSpace();
4907
$optCount or errorConvert("no options in the select statement");
4908
my $mult = 0; # multiple select
4909
$mult = 1 if defined $$openTag{multiple};
4910
my $mse = 0; # multiple select error
4911
$optSel <= 1 or $mult or
4912
$mse = 1, errorConvert("multiple options preselected");
4913
$$inform{nnh}++; # another non hidden field
4915
$$inform{lnh} = $openTag; # last non hidden field
4916
# Display selected item(s)
4917
$refbuf .= "\x80$openattrhidden<";
4918
my $buflen = length $refbuf;
4922
$j = $_, next if $i;
4923
if($mult and $j =~ /,/) {
4924
errorConvert("sorry, option string cannot contain a comma");
4925
$$opt{$j} = ""; # can't delete from hash
4928
substr($_, 3, 1) = '-' if $mse;
4929
next unless substr($_, 3, 1) eq '+';
4930
$refbuf .= ',' unless substr($refbuf, -1) eq '<';
4933
# This is really an input tag.
4934
$$openTag{tag} = "input";
4935
$$openTag{type} = "select";
4936
$$openTag{size} = ($mult ? 0 : $optSize);
4937
$$openTag{value} = substr $refbuf, $buflen;
4938
$$openTag{form} = $inform if $inform;
4939
$refbuf .= "\x80\x8f>";
4941
$$openTag{ofs2} = length $refbuf;
4945
if($tagplus eq "option") {
4947
errorConvert("$desc is not inside a select statement")
4949
$lastopt = $$h{value};
4950
$lastopt = "NoOptValue" unless defined $lastopt;
4951
$lastopt = (defined $$h{selected} ? "+" : "-") . $lastopt;
4952
$lastopt = sprintf("%03d", $optCount) . $lastopt;
4953
$optStart = length $refbuf;
4955
} # in select or not
4959
if($tagplus eq "textarea" and ! $tagLock) {
4960
errorConvert($noform) if ! $inform;
4963
$tagStart = length $refbuf;
4967
if($tagplus eq "textarea/" and $inta) {
4968
# Gather up the original, unformatted text.
4970
foreach $j ($$inta{lineno}..$lineno) {
4971
$i .= fetchLine($j, 0);
4974
# Strip off textarea tags.
4975
# I'm not using the s suffix, textarea tags should not cover multiple lines.
4976
$i =~ s/^.*<textarea[^<>]*>\n?//i;
4977
$i =~ s/<\/textarea.*\n$//i;
4978
$i .= "\n" if length $i and substr($i, -1) ne "\n";
4981
substr($refbuf, $tagStart) = "";
4982
my $cx = cxCreate(\$i, "");
4984
$$openTag{cx} = $cx;
4986
$$inform{nnh}++; # another non hidden field
4988
$$inform{lnh} = $openTag; # last non hidden field
4989
$refbuf .= "\x80$openattrhidden<buffer $cx\x80\x8f>";
4991
# This is really an input tag.
4992
$$openTag{tag} = "input";
4993
$$openTag{ofs1} = $tagStart;
4994
$$openTag{ofs2} = length($refbuf);
4995
$$openTag{type} = "area";
4996
$$openTag{form} = $inform if $inform;
4997
$j = $$openTag{rows};
4998
$j = 0 if ! defined $j;
4999
$j = 0 unless $j =~ /^\d+$/;
5000
$$openTag{rows} = $j;
5001
$j = $$openTag{cols};
5002
$j = 0 if ! defined $j;
5003
$j = 0 unless $j =~ /^\d+$/;
5004
$$openTag{cols} = $j;
5008
if($tagplus eq "input") {
5009
errorConvert($noform) if ! $inform;
5011
$i = "text" unless defined $i and length $i;
5012
$i = "text" if $i eq "password";
5013
# I should verify that the input is a number,
5014
# but I'm too busy right now to implement that.
5015
$i = "text" if $i eq "number";
5016
# Be on the lookout for new, advanced types.
5017
index(".text.checkbox.radio.submit.image.button.reset.hidden.", ".$i.") >= 0 or
5018
errorConvert("unknown input type $i");
5020
$j = "" unless defined $j;
5022
if($i eq "radio" or $i eq "checkbox") {
5023
$j = (defined $$h{checked} ? '+' : '-');
5028
length $j or $j = deriveAlt($h, "submit");
5029
} # submit button is represented by an icon
5030
if($i ne "hidden") {
5031
# I don't think there should be newlines in the value field.
5034
if($i eq "button") {
5035
# Hopefully we can turn this into a hyperlink.
5036
# If not, it's no use to us.
5037
my $onc = $$h{onclick};
5038
my $page = javaWindow $onc;
5039
$j = "button" unless length $j; # alt=button
5040
if(not length $page and
5041
$onc =~ /self\.location *= *['"]([\w._\/:,=@&?+-]+)["'] *(\+?)/i) {
5044
$i = $page, $page = "" if $page eq "submit";
5046
createHyperLink($h, $page, $j);
5053
if($i ne "hidden") {
5054
$$inform{nnh}++; # another non hidden field
5055
$$inform{lnh} = $h; # last non hidden field
5056
$refbuf .= "\x80$attrhidden<$j";
5057
if($i eq "submit") {
5058
$refbuf .= " js" if $$inform{onsubmit} or $$h{onclick};
5059
if($$inform{action}) {
5060
my $actscheme = is_url $$inform{action};
5061
$actscheme = is_url $baseref unless $actscheme;
5062
$refbuf .= " secure" if $actscheme eq "https";
5063
$refbuf .= " mailform" if $$inform{action} =~ /^mailto:/i;
5066
$refbuf .= "\x80\x8f>";
5068
$j = $$h{maxlength};
5069
$j = 0 unless defined $j and $j =~ /^\d+$/;
5070
$j = 1 if $i eq "checkbox" or $i eq "radio";
5073
if($inform and ! $tagLock) {
5074
$$h{form} = $inform;
5075
$$h{ofs2} = length $refbuf;
5082
$lineno += $nlcount;
5083
next reformat unless length $chunk;
5085
# Apparently the tag has forced a line break or paragraph break.
5086
# I've decided to honor this, even in preformat mode,
5087
# because that's what lynx does.
5089
$longcut = $lperiod = $lcomma = $lright = $lany = 0;
5092
# Get rid of a previous line of pipes.
5093
# This is usually a table or table row of images -- of no use to me.
5094
if($intable and $lspace < 2) {
5095
$j = length($refbuf) - 1;
5096
while($j >= 0 and substr($refbuf, $j, 1) =~ /[|\s]/) {
5097
last if $j > 0 and substr($refbuf, $j-1, 2) eq "\n\n";
5101
if($j < length $refbuf) {
5102
substr($refbuf, $j) = "";
5103
$lspace = computeSpace();
5106
} # end of line tag inside a table
5108
if($chunk eq "\n\n") {
5109
next reformat if $lspace == 3;
5110
$chunk = "\n" if $lspace == 2;
5116
# It's a line break.
5117
substr($chunk, 0, 1) = "" if $lspace > 1;
5118
$lspace = 2 if $lspace < 2;
5119
next reformat unless length $chunk;
5122
next reformat unless length $chunk;
5123
# It's either a list item indicator or a horizontal line
5124
$inhref = 2 if $inhref;
5125
if($chunk =~ /^--/) {
5126
# Again I'm following the lynx convention.
5127
# hr implies a line break before, and aparagraph break after.
5130
$colno += length $chunk;
5133
} # loop over tokens in the buffer
5136
print "tags rendered\n" if $debug >= 6;
5138
if(length($tagnest) > 1 and $tagnest ne ".body.0.") {
5139
my $opendesc = substr $tagnest, 1;
5140
$opendesc =~ s/\..*//;
5141
$opendesc = $tagdesc{$opendesc};
5142
substr($opendesc, 0, 2) = "";
5144
errorConvert("$opendesc is not closed at EOF");
5147
$errorMsg = $intMsg, return 0 if $intFlag;
5149
$refbuf =~ s/\s+$//; # don't need trailing blank lines
5151
# In order to fit all the links on one screen, many web sites place
5152
# several links on a line. Sometimes they are separated
5153
# by whitespace, sometimes commas, sometimes hyphens.
5154
# Sometimes they are arranged in a table, and thanks to the
5155
# table rendering software in this program, they will be pipe separated.
5156
# In any case, there is no advantage in having multiple
5157
# links on a line, and it's downright inconvenient when you want to use
5158
# the g or A command. We introduce line breaks between links.
5159
# We use alphanum [punctuation] right brace to locate the end of a link.
5160
# We use { optional '" alphanum for the start of a link.
5161
# These aren't guaranteed to be right, but they probably are most of the time.
5162
# Let's start with link space link or link separater link
5163
$refbuf =~ s/} ?[-,|]? ?(\x80[\x85-\x8f]+{['"]?\w)/}\n$1/g;
5164
# Separating punctuation at the end of the line.
5165
$refbuf =~ s/^({[^{}]+} ?),$/$1/mg;
5166
# Delimiter at the start of line, before the first link.
5167
$refbuf =~ s/\n[-,|] ?(\x80[\x85-\x8f]+{['"]?\w)/\n$1/g;
5168
# word delimiting punctuation space link.
5169
$refbuf =~ s/([a-zA-Z]{2,}[-:|]) (\x80[\x85-\x8f]+{['"]?\w)/$1\n$2/g;
5170
# Link terminating punctuation words
5171
$refbuf =~ s/(\w['"!]?}) ?[-|:] ?(\w\w)/$1\n$2/g;
5172
print "links rearranged\n" if $debug >= 6;
5175
# Verify internal links.
5177
foreach $h (@$btags) {
5179
next unless $tag eq "a";
5181
next if ! defined $j;
5182
next unless $j =~ s/^#//;
5183
$refbuf .= ""; # reset match position. Isn't there a better way??
5184
while($refbuf =~ /\x80([\x85-\x8f]+)\*/g) {
5185
$i = revealNumber $1;
5186
next intlink if $$btags[$i]{name} eq $j;
5188
$lineno = $$h{lineno};
5189
errorConvert("internal link #$j not found");
5192
print "internal links verified\n" if $debug >= 6;
5195
# Find the uncalled javascript functions.
5196
my $fw = $$btags[0]{fw}; # pointer to function window hash
5198
foreach $i (keys %$fw) {
5200
next unless $j =~ s/^\*//;
5201
$orphans = 1, $refbuf .= "\n" unless $orphans;
5202
print "orphan java function $i\n" if $debug >= 3;
5203
$itag = {tag => "a", href => $j, bref => $baseref};
5204
push @$btags, $itag;
5205
my $hn = hideNumber $#$btags;
5206
$refbuf .= "\n jf: $i\x80$hn" . "{$j}";
5207
# I don't think we need to mess with ofs1 and ofs2?
5208
$$itag{ofs2} = length $refbuf;
5211
$$tbuf = $refbuf; # replace
5215
# Report the first html syntax error.
5216
# $lineno tracks the line number, where text is being processed.
5219
$badHtml and return;
5221
# Look at the following print statement, and you'll see the little things
5222
# I try to anticipate when I write software for the blind.
5223
# The first physical line of output is for the sighted user, or the
5224
# new blind user -- but the experienced blind user doesn't need to read it.
5225
# He can read the last line of output, one keystroke in my adaptive software,
5226
# and hear exactly what he want to know.
5227
print "The html text contains syntax errors. The first one is at line\n$lineno: $msg.\n";
5228
# Put the bad line number in label e.
5229
substr($labels, 4*$lnwidth, $lnwidth) =
5230
sprintf $lnformat, $lineno;
5234
# Strip redundent stuff off the start and end of a web page,
5235
# relative to its parent.
5238
$fmode&$browsemode or $errorMsg = $nobrowse, return 0;
5239
defined $backup or $errorMsg = "no previous web page", return 0;
5240
my $p_fmode = $$backup{fmode};
5241
$p_fmode&$browsemode or $errorMsg = "no previous web page", return 0;
5242
# Parent and child file names should come from the same server.
5243
my $p_fname = $$backup{fname};
5244
my $c_fname = $fname;
5245
is_url($p_fname) and is_url($c_fname) or $errorMsg = "web pages do not come from web servers", return 0;
5246
$p_fname =~ s,^https?://,,i;
5247
$c_fname =~ s,^https?://,,i;
5248
$p_fname =~ s,/.*,,;
5249
$c_fname =~ s,/.*,,;
5250
$p_fname =~ s/\.browse$//;
5251
$c_fname =~ s/\.browse$//;
5252
$p_fname eq $c_fname or $errorMsg = "parent web page comes from a different server", return 0;
5253
$$btags[0]{dol2} == $dol or $errorMsg = "web page already stripped or modified", return 0;
5254
my $p_dol = $$backup{btags}[0]{dol2};
5256
if($p_dol > 10 and $c_dol > 10) {
5257
my $pb = $$backup{btags}[0]{pb};
5259
evaluateSequence($pb, 0);
5260
if($$btags[0]{dol2} > $dol) {
5261
$fmode &= ~($changemode|$firstopmode);
5263
$$btags[0]{pb} = $pb;
5265
} # successful post browse from the parent page
5266
} # attempting post browse from the parent page
5267
my $p_map = $$backup{btags}[0]{map2};
5271
while($start <= $p_dol and $start <= $c_dol) {
5272
if(!sameChildLine(\$p_map, $start, \$c_map, $start)) {
5278
$start = $oneout if $oneout and $start < $oneout + 5;
5279
my $delcount = --$start;
5282
while($p_end > $start and $c_end > $start) {
5283
last unless sameChildLine(\$p_map, $p_end, \$c_map, $c_end);
5287
if($delcount == $dol) {
5288
my $ln = substr($map, $lnwidth, $lnwidth1);
5289
$text[$ln] = "This web page contains no new information - you've seen it all before.";
5292
$labels = $lnspace x 26;
5293
$fmode &= ~$firstopmode;
5298
delText($c_end, $dol) if $c_end <= $dol;
5299
delText(1, $start) if $start;
5300
$labels = $lnspace x 26;
5301
$fmode &= ~($changemode|$firstopmode);
5306
$errorMsg = "nothing to strip";
5310
sub sameChildLine($$$$)
5312
my ($m1, $l1, $m2, $l2) = @_;
5313
my $t1 = $text[substr($$m1, $l1*$lnwidth, $lnwidth1)];
5314
my $t2 = $text[substr($$m2, $l2*$lnwidth, $lnwidth1)];
5315
removeHiddenNumbers \$t1;
5316
removeHiddenNumbers \$t2;
5317
$t1 =~ y/a-zA-Z0-9//cd;
5318
$t2 =~ y/a-zA-Z0-9//cd;
5319
return ($t1 eq $t2);
5324
$fmode&$browsemode or
5325
$errorMsg = $nobrowse, return 0;
5326
my $dol2 = $$btags[0]{dol2};
5327
$dol2 > $dol or $errorMsg = "nothing stripped from this web page", return 0;
5329
$map = $$btags[0]{map2};
5330
$fmode &= ~$firstopmode;
5331
$labels = $lnspace x 26;
5338
# Returns the index of the input field to be modified.
5339
# Sets $inf to the text of that field.
5340
# Sets $itag, $isize, and the other globals that establish an input field.
5341
# Returns 0 for no input fields on the line, -1 for some other error.
5344
my ($ln, $line) = @_;
5348
# Here's some machinery to remember the index if there's only one
5349
# input field of the desired type.
5351
my $t = fetchLine $ln, 0;
5352
# Bug in perl mandates the use of the no-op (?=) below.
5353
# You'll see this other places in the code too.
5354
# This bug was fixed in September 2001, patch 12120.
5355
while($t =~ /\x80([\x85-\x8f]+)<(.*?)(?=)\x80\x8f>/g) {
5356
$j = revealNumber $1;
5359
push @fieldtext, $i;
5360
$itag = $$btags[$j];
5361
$itype = $$itag{type};
5362
next if $itype eq "area";
5363
if($line =~ /^\d*\*/) {
5364
if($itype eq "submit" or $itype eq "reset") {
5365
$holdInput = -1 if $holdInput > 0;
5366
$holdInput = $#fields+1 if $holdInput == 0;
5369
if($itype ne "submit" and $itype ne "reset") {
5370
$holdInput = -1 if $holdInput > 0;
5371
$holdInput = $#fields+1 if $holdInput == 0;
5377
$errorMsg = "no input fields present" if ! $inglob;
5381
$idx = $1 if $line =~ /^(\d+)/;
5382
$idx = $holdInput if $holdInput > 0 and $idx < 0;
5383
$idx >= 0 or $j == 1 or
5384
$errorMsg = "multiple input fields, please use $icmd [1,$j]", return -1;
5385
$idx = 1 if $idx < 0;
5386
if($idx == 0 or $idx > $j) {
5387
$errorMsg = $j > 1 ?
5388
"invalid field, please use $icmd [1,$j]" :
5389
"line only has one input field";
5392
$j = $fields[$idx-1];
5393
$inf = $fieldtext[$idx-1];
5396
$itag = $$btags[$j];
5398
$itype = $$itag{type};
5399
$isize = $$itag{size};
5400
$irows = $$itag{rows};
5401
$icols = $$itag{cols};
5402
$iwrap = $$itag{wrap};
5403
$iwrap = "" if ! defined $iwrap;
5405
$iopt = $$itag{opt};
5409
# Get status on an input field, including its options.
5413
$line =~ s/^\d*\?//;
5416
print "[$isize]" if $isize;
5417
if($itype eq "area" and $irows and $icols) {
5418
print "[${irows}x$icols";
5419
print " recommended" if $iwrap eq "virtual";
5422
print " many" if defined $$itag{multiple};
5424
my $name = $$itag{name};
5425
print " [$name]" if defined $name and length $name;
5427
return unless $itype eq "select";
5429
# Display the options in a pick list.
5430
# If a string is given, display only those options containing the string.
5434
foreach my $v (%{$iopt}) {
5436
$j = $v, next if $i;
5438
next unless s/^(...)[-+]//;
5439
next if length $line and index(lc $j, $line) < 0;
5440
push @pieces, "$1$j\n";
5443
print(length($line) ? "No options contain the string \"$line\"\n" :
5444
"No options found\n");
5447
foreach (sort @pieces) {
5448
print((substr($_, 0, 3) + 1) . ": " . substr($_, 3));
5453
# Replace an input field with new text.
5456
my $newtext = shift;
5457
my ($i, $j, $k, $t);
5459
# Sanity checks on the input.
5460
$itype ne "submit" and $itype ne "reset" or
5461
$errorMsg = "field is a $itype button, use * to push the button", return 0;
5463
$errorMsg = "field is a text area, you must edit it from another session", return 0;
5464
not defined $$itag{readonly} or
5465
$errorMsg = "readonly field", return 0;
5466
$newtext =~ /\n/ and
5467
$errorMsg = "input field cannot contain a newline character", return 0;
5468
return 0 if lineLimit 2;
5471
my $newlen = length $newtext;
5472
! $isize or $newlen <= $isize or
5473
$errorMsg = "input field too long, limit $isize", return 0;
5475
if($itype eq "checkbox" or $itype eq "radio") {
5476
$newtext eq "+" or $newtext eq "-" or
5477
$errorMsg = "field requires + (active) or - (inactive)", return 0;
5478
$itype eq "checkbox" or $newtext eq '+' or $inf eq '-' or
5479
$errorMsg = "at least one radio button must be set", return 0;
5481
} # not from reset button
5483
if($itype eq "select") {
5484
my @opts = $newtext;
5485
@opts = split(',', $newtext) if defined $$itag{multiple};
5488
foreach my $newopt (@opts) {
5489
$newtext .= "," if length $newtext;
5490
$j = $$iopt{$newopt};
5491
# If you type in the option exactly, that's grand.
5492
$newtext .= $newopt, next if defined $j and length $j;
5493
# Maybe it's a menu number.
5494
if($newopt =~ /^\d+$/) {
5495
$j = sprintf("%03d", $newopt-1);
5496
# reverse hash lookup.
5499
foreach (%{$iopt}) {
5501
$revkey = $_, next if $revcnt;
5502
next unless substr($_, 0, 3) eq $j;
5503
$newtext .= $revkey;
5506
} else { # menu number conversion
5507
# See if this text is a piece of one and only one option.
5508
# Or if it is exactly one and only one option.
5513
foreach $k (keys %{$iopt}) {
5514
my $klow = lc $k; # k lower case
5515
next unless index($klow, $j) >= 0;
5517
$matchCount = 0, $matchLevel = 2 if $matchLevel < 2;
5521
next if $matchLevel == 2;
5522
$matchCount = 0, $matchLevel = 1 unless $matchLevel;
5527
$newtext .= $bestopt, next option if $matchCount == 1;
5528
$errorMsg = "$j matches more than one entry in the list", return 0 if $matchCount > 1;
5530
$errorMsg = "$newopt is not an option, type i$ifield? for the list";
5532
} # loop over options in the new list
5535
# Definitely making a change.
5536
$fmode |= $firstopmode;
5540
return 1 if $newtext eq $inf; # no change
5542
# Find and replace the text.
5543
$t = fetchLine $iline, 0;
5544
my $itaghidden = hideNumber $itagnum;
5545
$t =~ s/\x80$itaghidden<.*?(?=)\x80\x8f>/\x80$itaghidden<$newtext\x80\x8f>/;
5547
substr($map, $iline*$lnwidth, $lnwidth) =
5548
sprintf $lnformat, $#text;
5550
if($itype eq "radio") { # find and undo the other radio button
5551
my $radioname = $$itag{name};
5552
if(defined $radioname and length $radioname) {
5553
my $form = $$itag{form};
5555
foreach $k (1..$dol) {
5556
$t = fetchLine $k, 0;
5557
while($t =~ /\x80([\x85-\x8f]+)<\+\x80\x8f>/g) {
5559
$j = revealNumber $1;
5560
next if $j == $itagnum; # already changed this one
5561
my $h = $$btags[$j];
5562
next unless $$h{form} eq $form;
5563
# Input field is part of our form.
5564
next unless $$h{type} eq "radio";
5565
my $name = $$h{name};
5566
next unless defined $name and $name eq $radioname;
5567
# It's another radio button in our set.
5568
$t =~ s/\x80$jh<\+\x80\x8f>/\x80$jh<-\x80\x8f>/;
5570
substr($map, $k*$lnwidth, $lnwidth) =
5571
sprintf $lnformat, $#text;
5573
} # loop over input fields on this line
5575
} # radio button has a name
5581
# Push the submit or reset button.
5585
my $buttontype = $itype;
5586
$buttontype eq "submit" or $buttontype eq "reset" or
5587
$errorMsg = "this is not a submit or reset button", return 0;
5588
$cmd = 'b'; # this has become a browse command
5589
my $formh = $$itag{form};
5591
$errorMsg = "field is not part of a form", return 0;
5592
my $buttonvalue = $inf;
5593
$buttonvalue =~ s/ secure$//;
5594
$buttonvalue =~ s/ mailform$//;
5595
$buttonvalue =~ s/ js$//;
5596
my $domail = 0; # sendmail link
5598
my $bref = $$formh{bref};
5599
my $action = $$formh{action};
5600
if(! defined $action or ! length $action) {
5601
# If no form program is specified, the default is the current url.
5603
$action =~ s/\?.*//;
5605
$domail = 1 if $action =~ s/^mailto://i;
5606
# We should check for $form{encoding}.
5608
my ($name, $val, $i, $j, $cx, $h, @pieces);
5612
# Loop over all tags, keeping those in the input form.
5614
foreach $h (@$btags) {
5616
next unless $$h{tag} eq "input";
5617
# Overwrite the global input variables, so infReplace will work properly.
5618
# $itagnum is already set.
5622
next unless defined $j and $j eq $formh;
5623
# Input field is part of our form.
5627
if($itag eq $button and $itype eq "submit") {
5628
$name = $$button{name};
5629
if(defined $name and length $name) {
5631
$post .= "\n" if length $post;
5633
$post .= '&' if length $post;
5634
$name = urlEncode $name;
5636
if($$button{image}) {
5638
"$name.x=\n0\n\n$name.y=\n0\n" :
5639
"$name.x=0&$name.y=0";
5641
if(defined $buttonvalue and length $buttonvalue) {
5643
$post .= "$name=\n$buttonvalue\n";
5645
$buttonvalue = urlEncode $buttonvalue;
5646
$post .= "$name=$buttonvalue";
5650
"$name=\nSubmit\n" :
5657
next if $itype eq "reset" or $itype eq "submit";
5659
if($itype eq "hidden") {
5661
$iline = $ifield = 0;
5663
# Establish the line number, field number, and field value.
5664
# This is crude and inefficient, but it doesn't happen very often.
5666
for($iline=1; $iline<=$dol; ++$iline) {
5667
$j = fetchLine $iline, 0;
5669
while($j =~ /\x80([\x85-\x8f]+)<(.*?)(?=)\x80\x8f>/g) {
5670
$i = revealNumber $1;
5673
last findField if $i == $itagnum;
5676
$iline <= $dol or $errorMsg = "input field $itagnum is lost", return 0;
5679
if($buttontype eq "submit") {
5680
if($itype eq "area") {
5683
if(defined $factive[$cx] and $dol[$cx]) {
5684
# Send all the lines of text in the secondary buffer.
5685
for(my $ln=1; $ln<=$dol[$cx]; ++$ln) {
5686
$val .= fetchLineContext($ln, 1, $cx);
5687
next if $ln == $dol[$cx];
5688
$val .= ($textAreaCR ? $eol : "\n");
5691
} else { # text area or field
5693
if($itype eq "radio" or $itype eq "checkbox") {
5694
next if $val eq '-';
5695
$val = $$h{saveval};
5696
# I thought it had to say "on"; now I'm not sure.
5697
$val = "on" if $itype eq "checkbox" and ! length $val;
5699
} # text area or input field
5700
# Turn option descriptions into option codes for transmission
5701
if($itype eq "select") {
5703
@pieces = split ',', $val if defined $$h{multiple};
5706
$val .= "," if length $val;
5707
my $code = $$iopt{$_};
5709
$code = substr($code, 4);
5714
} # loop over options
5718
defined $name or $name = "";
5720
# Encode punctuation marks for http transmition
5721
$name = urlEncode($name);
5723
$val = urlEncode($val);
5725
if($itype eq "select" and defined $$h{multiple}) {
5726
# This is kludgy as hell.
5727
# comma has been turned into %2C
5728
@pieces = split '%2C', $val;
5729
foreach $val (@pieces) {
5730
$post .= ($domail ? "\n" : '&') if length $post;
5732
$post .= "\n" if $domail and length $name;
5734
$post .= "\n" if $domail and length $val;
5737
$post .= ($domail ? "\n" : '&') if length $post;
5739
$post .= "\n" if $domail and length $name;
5741
$post .= "\n" if $domail and length $val;
5744
} else { # submit or reset
5746
next if $itype eq "hidden";
5747
if($itype eq "area") {
5752
$ifield = 0; # zero skips some of the field checks in infReplace
5755
} # field or text area
5760
$dot = $origdot, return 1 if $buttontype eq "reset";
5761
print "submit: $post\n" if $debug >= 2;
5764
$errorMsg = "form does not specify a program to run", return 0;
5767
my $subj = urlSubject(\$action);
5768
$subj = "html form" unless length $subj;
5769
$post = "Subject: $subj\n\n$post";
5771
my @tolist = ($action);
5773
$mailToSend = "form";
5775
$whichMail = $localMail;
5776
sendMail(\@tolist, \$post, \@atlist) or return 0;
5777
print "Form has been mailed, watch for a reply.\n";
5781
$line = resolveUrl($bref, $action);
5783
$post = ($$formh{method} eq "get" ? '?' : '*') . $post;
5784
return -1, $line, $post;
5790
$badenc = $bad64 = 0;
5792
$nat = 0; # number of attachments
5795
# Copy lines into @msg.
5796
# The original cleanMail routine was built upon @msg,
5797
# And when I folded it into edbrowse, I was too lazy to change it.
5799
push @msg, fetchLine($_, 0) foreach (1..$dol);
5801
findHeaders(0, $#msg);
5804
--$#msg while $#msg >= 0 and
5805
$msg[$#msg] !~ /[a-zA-Z0-9]/;
5807
# Last chance to interrupt a browse operation
5808
$errorMsg = $intMsg, return 0 if $intFlag;
5811
$$tbuf .= "$_\n" foreach (@msg);
5812
chomp $$tbuf if length $$tbuf;
5813
$$tbuf =~ y/\x92\x93\x94\xa0\xad/'`' -/;
5817
# Insert this text line between mail headers.
5818
$mailBreak = "{NextMailHeader}";
5819
# Insert this text line between mime headers.
5820
$mimeBreak = "{NextMimeSection}, Level";
5821
# Max lines in a "to unsubscribe" trailer?
5824
# Hash the annoying commercials.
5826
if(length $annoyFile) {
5828
or dieq "Cannot open file of annoying commercials $annoyFile.";
5831
$annoy{lc $_} = "" if length $_;
5836
# Today timestamp, so old "junk" subjects can expire.
5837
$junkToday = int time / (60*60*24);
5842
# Now load the junk subjects, which we aren't interested in reading.
5843
if(length $junkFile) {
5845
or dieq "Cannot open file of junk subjects $junkFile.";
5847
s/\n$//; # don't need nl
5848
($jtime = $_) =~ s/:.*//;
5849
($jsubject = $_) =~ s/^\d+:\s*(.*)\s*$/$1/;
5850
if($jsubject =~ /^`/) {
5851
$junkSubjects{$jsubject} = $junkToday;
5853
$oldSubjects = 1, next if $jtime < $junkToday - $junkHorizon;
5854
$junkSubjects{$jsubject} = $jtime;
5860
# Add a subject to the junk list.
5861
# This updates the junk file.
5865
die "No subject to junk." if $s eq "";
5866
$junkSubjects{$s} = $junkToday;
5868
open FH, ">$junkFile"
5869
or dieq "Cannot rewrite file of junk subjects $junkFile.";
5871
foreach (%junkSubjects) {
5874
print FH "$_:$savekey\n";
5878
open FH, ">>$junkFile"
5879
or dieq "Cannot add to file of junk subjects $junkFile.";
5880
print FH "$junkToday:$s\n";
5885
# Build an array for base64 decoding.
5889
$b64_map[ord $c] = $j, ++$c, ++$j until $j == 26;
5891
$b64_map[ord $c] = $j, ++$c, ++$j until $j == 52;
5893
$b64_map[ord $c] = $j, ++$c, ++$j until $j == 62;
5894
$b64_map[ord '+'] = $j++;
5895
$b64_map[ord '/'] = $j++;
5898
# The following routine decodes the Quoted-Printable mime standard.
5899
# If one line ends in an equals sign, it must be joined to the next.
5900
# All other =xx sequences become the 8-bit value defined by hex xx.
5901
# Pass the start and end offsets -- what do you want to dequote?
5902
# Also pass the boundary, if any, and stop there.
5903
sub qp_lowlevel($$$)
5905
my ($start, $end, $boundary) = @_;
5906
print "qp $start-$end<$boundary\n" if $debug >= 6;
5907
return if $end < $start;
5910
foreach my $i ($start..$end) {
5911
if(length $boundary) {
5912
my $line = $msg[$i];
5913
$line =~ s/^[ \t>]*-*//;
5915
$end = $i-1, last if $line eq $boundary;
5917
$msg[$i] =~ s/[ \t]+$//;
5918
$tbuf .= $msg[$i]."\n";
5921
print "qp ends at $end, length " . length($tbuf) . "\n" if $debug >= 6;
5923
# Now undo quoted-printable encoding.
5924
# Use global substitutions on the concatenated texts, it's faster.
5927
$join = 0 unless $join;
5928
print "qp joins $join lines\n" if $debug >= 6;
5929
$tbuf =~ s/=([0-9a-fA-F]{2})/chr hex "$1"/ge;
5930
# Split the text back into lines, and back into @msg.
5933
@msg[$start.. $end] = split("\n", $tbuf, $end-$start+1);
5934
} else { # split problem
5935
$msg[$start] = ""; # probably was "" already
5937
# Fill the empty spaces with blank lines
5938
$msg[++$end] = '' while $join--;
5941
# If the message includes any lines with leading > signs,
5942
# break paragraphs whenever the nesting level changes.
5943
# A change in the number of > symbols indicates a different speaker,
5944
# hence a new paragraph.
5946
# Some mail intermediaries cut long lines,
5947
# leaving a dangling fragment without a > nesting level.
5948
# This fragment does not represent a new paragraph;
5949
# it is part of the previous sentence.
5950
# But watch watch out!
5951
# Some people deliberately interject short comments, as in:
5953
# > I really think the Tigers are the hotest baseball team ever, really great,
5955
# > a team to look up to.
5957
# There is virtually no way to distinguish between the two cases.
5958
# Most of the time a short fragment in the midst of indented text
5959
# actually belongs to the previous line,
5960
# so I treat it as such and hope for the best.
5963
my (@nestlev, @newmsg, $i, $j, $state, $temp);
5964
my $lastsubject = "";
5966
# Push some blank lines, to avoid eof conditions.
5967
push @msg, '', '', '';
5969
# Establish the nest level of each line.
5973
$temp =~ s/^([ \t>]*).*/$1/;
5974
$j = $temp =~ y/>/>/;
5980
for($i=0; $i<=$#msg; ++$i) {
5981
$newlev = $nestlev[$i];
5983
# Let's get right at the tricky part, a drop in level.
5984
# It's a fragment if the next line, or line after,
5985
# has the same nest level as the previous line.
5986
if($newlev < $lastlev and
5987
(($nextlev = $nestlev[$i+1]) == $lastlev or
5988
$nextlev < $lastlev && $nestlev[$i+2] == $lastlev)) {
5990
$temp =~ s/^[ \t>]*//;
5991
if($j = length $temp) {
5992
if($nextlev == $lastlev) {
5993
$newmsg[$#newmsg] .= " $temp";
5996
# It must be that the line after next has the previous nest level.
5997
my $temp2 = $msg[$i+1];
5998
$temp2 =~ s/^[ \t>]*//;
5999
if($j = length $temp2) {
6000
$newmsg[$#newmsg] .= " $temp $temp2";
6003
} # next line is nonempty
6004
} # this line is nonempty
6006
$newlev = $lastlev if $j == 0 and $nextlev == $lastlev;
6007
} # bracketed between larger nest levels
6009
if($msg[$i] =~ /^$mailBreak/o) {
6010
$newlev = $ lastlev = 0;
6011
push @newmsg, $mailBreak;
6012
my ($subject, $from, $date, $reply);
6014
($subject = $temp) =~ s/\n.*//s if $temp =~ s/.*\nSubject: //s;
6015
($from = $temp) =~ s/\n.*//s if $temp =~ s/.*\nFrom: //s;
6016
($date = $temp) =~ s/\n.*//s if $temp =~ s/.*\nDate: //s;
6017
($reply = $temp) =~ s/\n.*//s if $temp =~ s/.*\nReply-to: //s;
6018
if(defined $subject or defined $from or defined $date or defined $reply) {
6019
if($#newmsg == 0) { # Read the first header differently.
6020
push @newmsg, "Subject: $subject" if defined $subject;
6021
push @newmsg, "From $from" if defined $from;
6025
$temp .= " from $from,";
6026
if(defined $subject) {
6028
$subject eq $lastsubject ?
6030
" with subject, $subject.");
6032
$temp .= " with no subject.";
6035
if(defined $subject) {
6037
$subject eq $lastsubject ?
6038
" with the same subject." :
6039
" with subject, $subject.");
6041
$temp .= " with no subject.";
6043
} # from line or not
6044
push @newmsg, $temp;
6045
} # top header or internal
6046
push @newmsg, "Mail sent $date" if defined $date;
6047
push @newmsg, "Reply to $reply" if defined $reply;
6048
push @newmsg, "" if $#newmsg;
6050
$subject = "" if ! defined $subject;
6051
$lastsubject = $subject;
6055
if($newlev != $lastlev) {
6056
push @newmsg, "", "Indent $newlev.";
6059
# Strip off leading >
6061
$temp =~ s/^[ \t]*>[ \t>]*//;
6062
push @newmsg, $temp;
6066
# Push a mime separater on, to make the unsubscribe test work.
6067
push @newmsg, "$mimeBreak 1";
6069
# Now put the lines back into @msg, compressing blank lines.
6070
# Also, Try to remove any "unsubscribe" trailers.
6078
foreach my $line (@newmsg) {
6079
# Check for "to unsubscribe"
6080
if($line =~ /^ *to unsubscribe/i) {
6081
$unslast = $j if $unslast < 0 or $unscount > $unsHorizon;
6083
} # unsubscribe line
6085
# Check for mime/mail separater.
6089
if($line =~ /^$mimeBreak \d/o or
6090
$line eq $mailBreak or
6091
defined $annoy{$temp} or
6092
$temp =~ /^-+\s*original message\s*-+\s*$/) {
6093
$line = ""; # no need to read that
6097
$unstest = 1 if $line =~ /^$mailBreak/o;
6099
if($line =~ /^Indent \d/) {
6101
if($j > 0 and $msg[$j-1] =~ /^Indent \d/) {
6104
} # sequential indents
6107
if($unstest and $unslast >= 0 and $unscount <= $unsHorizon) {
6108
# Remove unsubscribe section
6111
--$j while $j >= 0 and
6112
$msg[$j] !~ /[a-zA-Z0-9]/;
6117
} # crunching unsubscribe
6120
++$unscount if $line =~ /[a-zA-Z0-9]/;
6123
$state = 1 if $line =~ /^Indent \d/;
6131
--$j if $j >= 0 and $state;
6135
# No need to read vacuous forwardings.
6136
sub nullForwarding()
6138
my $lf = -1; # last forwarding
6140
foreach my $line (@msg) {
6141
if($line =~ /^Message/) {
6142
$j = $lf if $lf >= 0 and $j - $lf <= 4;
6151
# Decide whether a line, and eventually a paragraph, is an email header.
6152
# Realize that these headers might be pasted in almost anywhere.
6153
# They don't always appear at the top of the message,
6154
# or even the top of a mime section.
6155
# They may even be indented, or prepended with leading greater than signs,
6156
# if a mail message is manually forwarded, or pasted inside a larger
6158
# We willhowever assume that a header block, once begun,
6159
# continues until we reach a blank line.
6160
# If you've manually pasted a header and body together, sorry,
6161
# but the body is going to get thrown away.
6162
# This routine is recursive, so make sure the appropriate variables are auto.
6163
# Pass the start and end offsets -- a sub-message inside the entire message.
6164
sub findHeaders($$) ;
6167
my ($start, $end) = @_;
6170
my $boundaryCut = "";
6171
my ($i, $j, $temp, $line, $state);
6172
my ($reply, $from, $subject, $date);
6173
my ($boundary, $content, $encoding, $encfile);
6176
$line = $msg[$start];
6177
if($line =~ s/^$mailBreak.*\nboundary=//so) {
6179
$boundaryCut = $line;
6182
print "findheaders$fhLevel $start-$end<$boundaryCut\n" if $debug >= 6;
6184
foreach $i ($start..$end) {
6187
# Strip away whitespace and leading greater than signs.
6188
$line =~ s/^[ \t>]+//;
6191
# Are we expanding binary data?
6193
$expand64 = 0 if $line eq "";
6194
if(length $boundaryCut and $expand64) {
6198
$expand64 = 0 if $temp eq $boundaryCut;
6201
my ($c, $leftover, $rem);
6202
# We don't really need the padding equals to run the algorithm properly.
6203
# Sometimes it ends in =9 I don't know what that means!
6205
if($line =~ y;+/a-zA-Z0-9;;cd && !$bad64) {
6206
warn "Invalid base64 encoding at line $i";
6209
for($j=0; $j < length $line; ++$j) {
6210
$c = $b64_map[ord substr($line,$j,1)];
6214
} elsif($rem == 1) {
6215
$$curPart{data} .= chr($leftover | ($c>>4));
6216
$leftover = ($c & 0xf) <<4;
6217
} elsif($rem == 2) {
6218
$$curPart{data} .= chr($leftover | ($c>>2));
6219
$leftover = ($c & 3) <<6;
6221
$$curPart{data} .= chr($leftover | $c);
6229
# Look for mailKeyWord:
6230
# We check for a header until we have established a boundary,
6231
# and then we only crack the header at the start of each section.
6232
if(($startLine >= 0 or # inside a header
6233
! length $boundaryCut or # no boundary yet
6234
$msg[$i-1] eq "$mimeBreak $fhLevel") # top of the mime section
6236
$line =~ /^\$?[a-zA-Z][\w-]*:/) { # keyword:
6237
if($startLine < 0) {
6240
$reply = $from = $subject = $date = "";
6241
$boundary = $content = $encoding = $encfile = "";
6243
($headKey = $line) =~ s/:.*//;
6244
$headKey = lc $headKey;
6245
($headVal = $line) =~ s/^[^:]+:\s*//;
6246
my $headKeyType = $mhWords{$headKey};
6247
$state |= $headKeyType if defined $headKeyType;
6248
if($headVal ne "") {
6249
$from = $headVal if $headKey eq "from";
6250
$reply = $headVal if $headKey eq "reply-to";
6251
$subject = $headVal if $headKey eq "subject";
6252
$date = $headVal if $headKey eq "date";
6253
$date = $headVal if $headKey eq "sent";
6254
if($headKey eq "content-transfer-encoding") {
6255
$encoding = lc $headVal;
6257
if($headKey eq "content-type") {
6258
$content = lc $headVal;
6259
$content =~ s/;.*//;
6261
} # something after keyword:
6262
} # keyword: mail/mime header line
6264
if($startLine >= 0) {
6265
# boundary= is a special attribute within a mail header
6267
if($temp =~ s/.*boundary *= *//i) {
6269
($temp =~ s/".*//) :
6272
$boundary =~ s/^-+//;
6273
$boundary =~ s/-+$//;
6274
$boundaryCut = $boundary if length $boundary and ! length $boundaryCut;
6275
} # boundary keyword detected
6276
# filename is similarly set.
6278
if($temp =~ s/.*(?:file)?name *= *//i) {
6280
($temp =~ s/".*//) :
6287
next if ! length $boundaryCut;
6288
# Strip away leading and trailing hyphens -- helps us look for boundary
6291
next if $line ne $boundaryCut;
6292
$msg[$i] = "$mimeBreak $fhLevel";
6296
# Now we know we're inside a mail header.
6297
next if length $line;
6299
# We've got a blank line -- that ends the header.
6300
# But it's not really a header if we've just got English keywords.
6303
if(length $boundary) {
6304
# Skip the preamble.
6305
foreach $j ($i+1..$#msg) {
6309
last if $temp eq $boundary;
6314
# Handle the various encodings.
6315
$encoding = "" if length $boundary and $startLine == $start;
6316
$encoding = "" if $encoding eq "8bit" or $encoding eq "7bit" or $encoding eq "binary";
6317
if($encoding eq "quoted-printable") {
6318
qp_lowlevel($i+1, $end, $boundaryCut);
6321
if($encoding eq "base64") { # binary attachment
6323
$curPart = { data => "", filename => $encfile, isattach => 1};
6324
push @mimeParts, $curPart;
6328
if($encoding and !$badenc) {
6329
warn "Unknown encoding at line $i $encoding";
6334
if($state & 4 or length $boundary) {
6335
# Process from/reply lines.
6336
$reply = $from if ! length $reply;
6337
$from = $reply if ! length $from;
6338
$from =~ s/".*// if $from =~ s/^"//;
6339
$from =~ s/\s*<.*>.*$//;
6340
$reply =~ s/^.*<(.*)>.*$/$1/;
6342
if length $reply and (
6343
$reply =~ /[\s<>]/ or $reply !~ /\w@\w/ or $reply !~ /\w\.\w/);
6344
# Strip away re: and fwd:
6345
while($subject =~ s/^(re|fd|fwd)[,:]\s*//i) { }
6347
$mailSubject = $subject,
6349
$mailReply = $reply,
6351
if $startLine == 0; # top of the message
6353
# Consolodate the header.
6354
$line = "$mailBreak\n";
6355
$line .= "Subject: $subject\n" if length $subject;
6356
$line .= "From: $from\n" if length $from;
6357
$line .= "Date: $date\n" if length $date;
6358
$line .= "Reply-to: $reply\n" if length $reply;
6359
$line .= "boundary=$boundary\n" if length $boundary;
6362
$msg[$j++] = "" while $j <= $i;
6364
# Decode html, if specified in the header.
6365
# Or turn it into an attachment, if anything other than plain text.
6366
if(length $content and ! $expand64) {
6367
if($content eq "text/html" or length $encfile) {
6368
mailHtml($i+1, $end, $startLine-1, $boundaryCut, $encfile);
6369
$content = "text/plain";
6373
} # mail or mime header
6376
} # loop over lines in the message
6378
if(length $boundaryCut) {
6379
# Still more work to do.
6380
# Reprocess each section.
6381
$boundary = "$mimeBreak $fhLevel";
6383
foreach $i ($start..$end) {
6384
next unless $msg[$i] eq $boundary;
6385
findHeaders($j+1, $i-1) if $j >= 0;
6388
} # bounhdary encountered
6393
# process an html mime section within a mail message.
6396
my ($start, $end, $breakLine, $boundary, $filename) = @_;
6397
return if $end < $start; # should never happen
6402
foreach $i ($start..$end) {
6404
$line =~ s/^[ \t>]*//;
6406
# boundary may end this section.
6407
if(length $boundary) {
6411
$end = $i-1, last if $temp eq $boundary;
6418
if(length $filename) { # present as attachment
6419
$curPart = { data => $tbuf, filename => $filename, isattach => 1};
6420
push @mimeParts, $curPart;
6425
my $cx = cxCreate(\$tbuf, $filename);
6426
my $precx = $context;
6429
# $tbuf still holds the html attachment
6431
renderHtml(\$tbuf) and
6432
pushRenderedText(\$tbuf);
6436
print "switch to session $cx for the html version of this mail\n" unless $ismc;
6439
# Connect to the mail server.
6444
my $iaddr = inet_aton($remote) or
6445
$errorMsg = "cannot locate the mail server $remote", return 0;
6446
my $paddr = sockaddr_in($port, $iaddr);
6447
my $proto = getprotobyname('tcp');
6448
socket(SERVER_FH, PF_INET, SOCK_STREAM, $proto) or
6449
$errorMsg = "Cannot establish TCP socket", return 0;
6450
connect(SERVER_FH, $paddr) or
6451
$errorMsg = "Cannot connect to mail server $remote", return 0;
6452
SERVER_FH->autoflush(1);
6456
# Put and get lines from the mail server.
6457
sub serverPutLine ($)
6465
print SERVER_FH $line.$eol or
6466
$errorMsg = "Could not write to the mail socket", return 0;
6472
defined($serverLine = <SERVER_FH>)
6473
or $errorMsg = "could not read from the mail socket", return 0;
6474
# strip trailing newline indicator(s)
6475
$serverLine =~ s/[\r\n]+$//;
6476
print "< $serverLine\n" if $debug >= 7;
6483
# Should we make $scheme global instead of passing it around?
6484
if($scheme =~ /(smtp|pop3)/i) {
6485
serverPutLine("quit");
6486
} elsif ($scheme =~ /ftp/i) {
6487
serverPutLine "abor${eol}quit";
6488
# Nope, abor is not a typo.
6489
my @disposeOf = <SERVER_FH>;
6490
close FDFH if defined FDFH;
6491
close FLFH if defined FLFH;
6497
# This subroutine was taken from MIME::Base64 by Gisle Aas.
6498
sub encodeBase64($$$)
6500
my($in, $eol, $out) = @_;
6501
my $inl = length $$in;
6502
# uuencode is pretty close
6503
$$out = pack 'u', $$in;
6504
# get rid of first and last char
6507
# Get rid of newlines inside
6509
# Over to base 64 char set
6510
$$out =~ tr|` -_|AA-Za-z0-9+/|;
6511
# fix padding at the end
6512
my $padding = (3 - $inl%3) % 3;
6513
$$out =~ s/.{$padding}$/'=' x $padding/e if $padding;
6514
# break encoded string into lines of no more than 76 characters each
6516
$$out =~ s/(.{1,72})/$1$eol/g;
6520
# Read the file into memory, mime encode it,
6521
# and return the type of encoding and the encoded data.
6522
# Last three parameters are result parameters.
6523
sub encodeAttachment($$$$$)
6525
my ($atfile, $isMail, $res_enc, $res_type, $res_data) = @_;
6526
my ($subline, $buffer, $fsize, $rsize);
6529
if($atfile =~ /^\d+$/) { # edbrowse session
6530
my $cx = $atfile - 1;
6532
for(my $ln=1; $ln<=$dol[$cx]; ++$ln) {
6533
$buffer .= fetchLineContext($ln, 1, $cx);
6534
$buffer .= "\n" if $ln < $dol[$cx];
6536
$fsize = $rsize = length $buffer;
6539
$errorMsg = "cannot open attachment file $atfile,$!", return 0;
6540
binmode FH, ':raw' if $doslike;
6541
$fsize = (stat(FH))[7];
6544
$rsize = sysread(FH, $buffer, $fsize) if $fsize;
6547
$errorMsg = "cannot read the contents of $atfile,$!", return 0;
6551
# We just made a copy of the mail to send; hope it wasn't too big.
6552
$atfile = $mailToSend;
6553
$fsize = $rsize = length $buffer;
6554
$buffer =~ s/^\s*subject\s*:\s*/Subject: /i or
6555
$errorMsg = "$atfile does not begin with a line `Subject: subject of your mail'", return 0;
6556
$buffer =~ s/\r\n/\n/g;
6557
$buffer .= "\n" if substr($buffer, -1) ne "\n";
6558
$buffer .= ':'; # temporary
6559
# Extra blank line after subject.
6560
$buffer =~ s/^(.*\n)(.)/$1\n$2/;
6561
$buffer =~ /^(.*)\n/;
6563
substr($buffer, -1) = ""; # get rid of :
6564
length $subline < 90 or
6565
$errorMsg = "subject line too long, limit 80 characters", return 0;
6566
} # primary mail message
6569
my ($c, $col, $j, $ctype, $enc);
6571
# Count nonascii characters.
6572
my $nacount = $buffer =~ y/\x80-\xff/\x80-\xff/;
6573
# Count null characters.
6574
my $nullcount = $buffer =~ y/\0/\0/;
6575
$nacount += $nullcount;
6577
if($nacount*5 > $fsize and $fsize > 20) {
6579
$errorMsg = "cannot mail the binary file $atfile - perhaps this should be an attachment?", return 0;
6581
encodeBase64(\$buffer, "\n", \$newbuf);
6583
$ctype = "application/octet-stream";
6584
$ctype = "application/PostScript" if $atfile =~ /\.ps$/i;
6585
$ctype = "image/jpeg" if $atfile =~ /\.jpeg$/i;
6586
$ctype = "image/gif" if $atfile =~ /\.gif$/i;
6587
$ctype = "audio/basic" if $atfile =~ /\.wav$/i;
6588
$ctype = "video/mpeg" if $atfile =~ /\.mpeg$/i;
6590
$$res_type = $ctype;
6592
$$res_data = $newbuf;
6596
# Use the filename of the edbrowse session to determine type.
6597
if($atfile =~ /^\d+$/) {
6598
$atfile = $fname[$atfile-1];
6600
$ctype = "text/plain";
6601
$ctype = "text/html" if $atfile =~ /\.(htm|html|shtml|asp)$/i;
6602
$ctype = "text/richtext" if $atfile =~ /\.rtf$/i;
6604
# Switch to unix newlines - we'll switch back to dos later.
6605
$buffer =~ s/\r\n/\n/g;
6606
$fsize = length $buffer;
6608
if($nacount*20 < $fsize) {
6609
# Looks like it's almost all ascii, but we still have to switch to qp
6610
# if the lines are too long.
6612
for($j =0; $j < $fsize; ++$j) {
6613
$c = substr $buffer, $j, 1;
6614
$col = 0, next if $c eq "\n";
6616
$nacount = $fsize, last if $col > 500 or $col > 120 and ! $isMail;
6620
if($nullcount or $nacount*20 >= $fsize) {
6621
$buffer =~ s/([^\t\n-<>-~])/sprintf("=%02X", ord $1)/ge;
6622
$buffer =~ s/ $/=20/m;
6623
$buffer =~ s/\t$/=09/m;
6624
# Cut long lines, preferably after a space, but wherever we can.
6625
$fsize = length $buffer;
6628
for($j =0; $j < $fsize; ++$j) {
6629
$c = substr $buffer, $j, 1;
6631
if($c eq "\n") { # new line, column 0
6632
$spaceCol = $col = 0;
6636
if($c eq " " || $c eq "\t") {
6637
$spaceCol = length $newbuf;
6640
# Don't break an = triplet.
6642
next if substr($newbuf, -2, 1) eq '=';
6643
# If we're near the end, don't worry about it.
6644
next if $j == $fsize - 1;
6645
# If newline's coming up anyways, don't force another one.
6646
$c = substr $buffer, $j+1, 1;
6648
# Ok, it's a long line, we need to cut it.
6649
$spaceCol = length $newbuf if ! $spaceCol;
6650
substr($newbuf, $spaceCol, 0) = "=\n";
6652
$col = length($newbuf) - $spaceCol;
6657
# Don't qp the subject.
6658
$newbuf =~ s/^.*/$subline/;
6661
$enc = "quoted-printable";
6662
$$res_type = $ctype;
6664
$$res_data = $newbuf;
6668
# Almost all ascii, short lines, no problems.
6669
$enc = ($nacount ? "8bit" : "7bit");
6670
$$res_type = $ctype;
6672
$$res_data = $buffer;
6674
} # encodeAttachment
6675
# Don't forget to turn lf into crlf before you send this on to smtp.
6677
# Send mail to the smtp server.
6678
# sendMail(recipients, mailtext, attachments)
6679
# Everything passed by reference.
6682
my ($tolist, $main, $atlist) = @_;
6683
length $outmailserver or
6684
$errorMsg = "No mail server specified - check your $home/.ebrc file", return 0;
6687
my $reply = $replyAddress[$whichMail];
6688
$altattach == 0 or $altattach == $#$atlist+1 or
6689
$errorMsg = 'either none or all of the attachments must be declared "alternative"', return 0;
6691
# Read and/or refresh the address book.
6692
if(length $addressFile and -e $addressFile) {
6693
my $newtime = (stat($addressFile))[9];
6694
if($newtime > $adbooktime) {
6696
$adbooktime = $newtime;
6697
my ($alias, $email);
6698
open FH, $addressFile or
6699
$errorMsg = "Cannot open address book $addressFile.", return 0;
6701
s/\n$//; # don't need nl
6702
next if /^\s*#/; # comment line
6703
next if /^\s*$/; # blank line
6704
($alias = $_) =~ s/:.*//;
6705
($email = $_) =~ s/^[^:]*:([^:]*).*/$1/;
6706
$adbook{$alias} = $email;
6712
# Resolve recipients against address book.
6713
foreach my $who (@$tolist) {
6714
next if $who =~ /@/;
6715
my $real = $adbook{$who};
6716
if(defined $real and length $real) {
6717
# Remember that $who is a by reference variable, being in the for loop.
6721
length $addressFile or
6722
$errorMsg = "No address book specified - check your $home/.ebrc file", return 0;
6723
$errorMsg = "alias $who not found in your address book";
6727
# Verify attachments are readable.
6728
foreach my $f (@$atlist) {
6731
cxCompare($cx) or return 0;
6732
defined $factive[$cx] and $dol[$cx] or
6733
$errorMsg = "session $f is empty - cannot atach", return 0;
6736
$errorMsg = "cannot access attachment $f", return 0;
6740
my $mustmime = $#$atlist + 1;
6741
my ($sendEnc, $sendType, $sendData);
6742
encodeAttachment($main, 1, \$sendEnc, \$sendType, \$sendData) or return 0;
6743
$mustmime = 1 if $sendEnc =~ /^q/;
6745
# Boundary, for sending attachments.
6746
my $sendBound = rand;
6747
$sendBound =~ s/^0./nextpart-domail/;
6749
# Looks good - let's get going.
6750
pop3connect($outmailserver, 25) or return 0;
6753
serverGetLine() or last normal;
6754
while($serverLine =~ /^220-/) {
6755
serverGetLine() or last normal;
6757
$serverLine =~ /^220 / or
6758
$errorMsg = "Unexpected prompt <$serverLine> at the start of the sendmail session", last normal;
6760
serverPutLine "helo $smtplogin" or last normal;
6761
serverGetLine() or last normal;
6762
$serverLine =~ /^250 / or
6763
$errorMsg = "The mail server doesn't recognize $smtplogin", last normal;
6765
serverPutLine "mail from: $reply" or last normal;
6766
serverGetLine() or last normal;
6767
$serverLine =~ /^250 / or
6768
$errorMsg = "mail server rejected $reply <$serverLine>", last normal;
6770
my $reclist = ""; # list of recipients
6771
my $reccount = 0; # count recipients
6772
foreach my $f (@$tolist) {
6773
$f = "\"$f\"" if $f =~ /[^\w,.@=_-]/;
6774
$reclist .= ", " if $reccount;
6777
serverPutLine "rcpt to: $f" or last normal;
6778
serverGetLine() or last normal;
6779
$serverLine =~ /^250 / or
6780
$errorMsg = "mail server rejected $f <$serverLine>", last normal;
6781
} # loop over recipients
6783
serverPutLine "data" or last normal;
6784
serverGetLine() or last normal;
6785
$serverLine =~ /^354 / or
6786
$errorMsg = "The mail server is not ready to accept email data <$serverLine>", last normal;
6787
serverPutLine "To: $reclist$eol" .
6788
"From: $myname <$reply>$eol" .
6789
"Reply-To: $myname <$reply>$eol" .
6790
"Date: " . mailTimeString() . $eol .
6791
"Mime-Version: 1.0" or last normal;
6793
# dot alone tells smtp we're done.
6794
# Make sure there isn't a dot line in the middle of the mail.
6795
$sendData =~ s/^\.$/ ./gm;
6796
# serverPutLine() routine already adds the last newline.
6797
substr($sendData, -1) = "" if substr($sendData, -1) eq "\n";
6798
# smtp requires crlf.
6799
$sendData =~ s/\n/\r\n/g;
6802
serverPutLine "Content-Type: $sendType$eol" .
6803
"Content-Transfer-Encoding: $sendEnc" or last normal;
6805
$sendData =~ s/^(.*\r\n)// or
6806
$errorMsg = "could not pull subject line out of sendData", last normal;
6808
serverPutLine $subline .
6809
"Content-Type: multipart/" .
6810
($altattach ? "alternative" : "mixed") .
6811
"; boundary=$sendBound$eol" .
6812
"Content-Transfer-Encoding: 7bit$eol" .
6814
"This message is in MIME format. Since your mail reader does not understand$eol" .
6815
"this format, some or all of this message may not be legible.$eol" .
6817
"--$sendBound$eol" .
6818
"Content-Type: $sendType$eol" .
6819
"Content-Transfer-Encoding: $sendEnc" or last normal;
6821
serverPutLine $sendData or last normal;
6824
foreach my $f (@$atlist) {
6825
encodeAttachment($f, 0, \$sendEnc, \$sendType, \$sendData) or last normal;
6826
serverPutLine "$eol--$sendBound$eol" .
6827
"Content-Type: $sendType" .
6828
# If the filename has a quote in it, forget it.
6829
# Also, suppress filename if this is an alternative presentation.
6830
# Also, suppress filename if you pulled it out of an edbrowse session.
6831
(($altattach or $f =~ /"/ or $f =~ /^\d+$/) ?
6832
"" : "; name=\"$f\"") . $eol .
6833
"Content-Transfer-Encoding: $sendEnc$eol" or last normal;
6835
$sendData =~ s/^\.$/ ./gm;
6836
substr($sendData, -1) = "" if substr($sendData, -1) eq "\n";
6837
$sendData =~ s/\n/\r\n/g;
6838
serverPutLine $sendData or last normal;
6839
} # loop over attachments
6841
serverPutLine "$eol--$sendBound--" or last normal;
6844
serverPutLine "." or last normal;
6845
serverGetLine() or last normal;
6846
$serverLine =~ /message (accepted|received)/i or
6847
$serverLine =~ /^250/ or
6848
$errorMsg = "Could not send mail message <$serverLine>", last normal;
6849
serverClose($proto);
6851
} # normal processing
6857
# Send the current session as outgoing mail.
6858
sub sendMailCurrent()
6860
dirBrowseCheck("send mail") or return 0;
6861
$fmode&$binmode and $errorMsg = "cannot mail a binary file - should this be an attachment?", return 0;
6862
$dol or $errorMsg = "cannot mail an empty file", return 0;
6863
$whichMail = $localMail;
6865
# Gather recipients and attachments, until we reach subject:
6870
for($ln=1; $ln<=$dol; ++$ln) {
6871
$t = fetchLine $ln, 0;
6872
$t =~ s/^reply[ -]to:* /to:/i;
6873
$t =~ s/^mailto:/to:/i;
6874
push(@tolist, $1), next if $t =~ /^to\s*:\s*(.*?)[ \t]*$/i;
6875
if($t =~ /^(attach|alt)\s*:\s*(.*?)[ \t]*$/i) {
6876
$altattach++ if lc($1) eq "alt";
6880
$whichMail = $1, next if $t =~ /^account\s*:\s*(\d+)[ \t]*$/i;
6881
$subject = 1 if $t =~ /^subject\s*:/i;
6884
$whichMail = $smMail if length $smMail;
6885
$subject or $errorMsg = "line $ln, should begin with to: attach: or subject:", return 0;
6886
$#tolist >= 0 or $errorMsg = "no recipients specified - place `To: emailAddress' at the top of your file", return 0;
6887
$whichMail <= $#inmailserver or $errorMsg = "account $whichMail is out of range", return 0;
6890
$tbuf .= fetchLine($_, 0) . "\n" foreach ($ln..$dol);
6891
$mailToSend = "buffer";
6892
return sendMail(\@tolist, \$tbuf, \@atlist);
6896
# runtime code starts here.
6897
# Think of this code as being inside main(){}
6900
# Buffered I/O messes me up when this runs on NT, over telnet.
6901
STDOUT->autoflush(1);
6902
# The shell doesn't expand wild cards, let's do it here.
6904
push @arglist, glob($_) foreach (@ARGV);
6908
if($#ARGV >= 0 and $ARGV[0] eq "-v") {
6914
if($#ARGV >= 0 and $ARGV[0] =~ /^-d(\d*)$/) {
6915
$debug = (length $1 ? $1 : 4);
6920
if($#ARGV >= 0 and $ARGV[0] eq '-e') {
6925
# -m is a special flag; run as a mail client.
6926
if($#ARGV >= 0 and $ARGV[0] =~ /^-(u?)m(\d+)$/) {
6927
$ismc = 1; # running as a mail client
6928
my $unformat = length $1;
6931
$#inmailserver >= 0 or
6932
dieq "there are no mail accounts in your .ebrc config file.";
6933
$account <= $#inmailserver or
6934
dieq "account designator $account is out of range.";
6935
$whichMail = $account;
6939
if($#ARGV == 0 and $ARGV[0] eq "-Zap") {
6945
my $arg = pop @ARGV;
6946
if($arg =~ s/^([-+])//) {
6947
++$altattach if $1 eq '-';
6949
dieq "cannot access attachment $arg.";
6951
unshift @atfiles, $arg;
6954
open FH, $mailToSend
6955
or dieq "Cannot access send file $mailToSend.";
6956
dieq "Send file $mailToSend has zero size." if -z FH;
6957
binmode FH, ':raw' if $doslike;
6958
my $fsize = (stat(FH))[7];
6959
my $rsize = sysread(FH, $mailBuf, $fsize);
6962
dieq "cannot read the contents of $mailToSend,$!";
6965
} # loop looking for files to transmit
6967
if(length $mailToSend or $#atfiles >= 0) {
6968
# Mail client is in send mode.
6969
length $mailToSend or
6970
dieq "all arguments are attachments - you must include a plain send file.";
6971
$#ARGV >= 0 or dieq "No recipients specified.";
6972
sendMail(\@ARGV, \$mailBuf, \@atfiles) or dieq $errorMsg;
6976
# Move to the mail directory.
6977
length $mailDir or dieq "mailbox directory not specified in your .ebrc file.";
6978
chdir $mailDir or dieq "Cannot change directory to $mailDir.";
6980
# Now fetch the mail and process it,
6981
# and ask the user what to do with it.
6982
# Begin with the pop3 login/password sequence.
6984
pop3connect($inmailserver[$whichMail], 110) or dieq $errorMsg;
6986
$serverLine =~ /^\+OK /
6987
or dieq "Unexpected pop3 introduction <$serverLine>.";
6988
my $login = $pop3login[$whichMail];
6989
my $password = $pop3password[$whichMail];
6990
serverPutLine("user $login");
6992
# perhaps we require a password?
6994
serverPutLine("pass $password");
6996
} # sending password
6997
$serverLine =~ /^\+OK/
6998
or dieq "Could not complete the pop3 login/password sequence <$serverLine>.";
7000
# determine number of messages
7001
serverPutLine("stat");
7003
$serverLine =~ /^\+OK /
7004
or dieq "Could not obtain status information on your mailbox <$serverLine>.";
7005
my $nmsgs = substr($serverLine, 4);
7010
serverClose($proto);
7014
my $mailHuge = "Mail message consumes more than a million lines; you won't be able to use this client.";
7015
print "$nmsgs messages\n";
7017
$nmsgs = 300 if $nmsgs > 300;
7020
# Iterate over messages.
7021
foreach my $m (1..$nmsgs) {
7022
my ($filename, $j, $curpart, $rendered);
7023
# Is this mail automatically going somewhere else?
7031
# Clear out the editor before we read in the next message.
7032
foreach $j (0..$#factive) {
7035
$context = 0; # probably not necessary
7036
$factive[0] = 1; # mail goes into session 0
7039
$text[1] = "--------------------------------------------------------------------------------";
7041
# retrieve the entire mth message from the server.
7042
serverPutLine("retr $m");
7043
my $exact_msg = ""; # an exact copy of the email
7044
# Throw first line away, it's from the pop3 server, not part of the mail.
7048
while($serverLine ne ".") {
7049
$exact_msg .= "$serverLine\n";
7050
lineLimit 1 and dieq $mailHuge;
7051
push @text, $serverLine;
7053
$map .= sprintf($lnformat, $j);
7059
# Browse the mail message for display.
7060
$btags[0] = $btags = [];
7061
$$btags[0] = {tag => "special", fw => {} };
7063
$mailSubject = $mailFrom = $mailReply = $mailDate = "";
7064
renderMail(\$rendered) and pushRenderedText(\$rendered) or
7066
$rendered = undef; # don't need it any more
7068
# Break the lines in the buffer.
7069
$fmode &= ~$browsemode; # so I can run the next command
7073
$fmode |= $browsemode;
7075
# Let user know about attachments.
7076
my $unat = 0; # unnamed attachments
7077
my $exat = 0; # attachment already exists
7079
print "$nat attachments.\n";
7081
foreach $curPart (@mimeParts) {
7082
next unless $$curPart{isattach};
7084
$filename = $$curPart{filename};
7085
++$unat, next unless length $filename;
7086
print "$j = $filename";
7095
# Paste on the html segments.
7096
foreach $j (1..$#factive) {
7097
next unless $factive[$j];
7098
next unless $dol[$j];
7099
$map .= sprintf($lnformat, 0) if $dol;
7101
$map .= sprintf($lnformat, 1);
7102
$map .= sprintf($lnformat, 0);
7104
$map .= substr($map[$j], $lnwidth);
7105
$dot = $dol = length($map)/$lnwidth - 1;
7107
foreach my $t (@text) {
7108
removeHiddenNumbers \$t;
7111
# See if the mail is redirected.
7112
if(length $mailReply and $#fromSource >= 0) {
7113
my $lowReply = lc $mailReply;
7114
foreach my $j (0..$#fromSource) {
7115
next unless index($lowReply, $fromSource[$j]) >= 0;
7116
$redirect = $fromDest[$j];
7121
# I'm not going to redirect mail if there are unamed or existing attachments.
7122
$redirect = "" if $redirect ne "x" and $unat + $exat;
7123
} # formatting the mail message
7126
if(length $redirect) {
7128
# Replace % date/time fields.
7129
if($redirect =~ /%[ymdhns]{2,}/) {
7130
my ($ss, $nn, $hh, $dd, $mm, $yy) = localtime time;
7133
$redirect =~ s/%yyyy/sprintf "%4d", $yy/ge;
7134
$redirect =~ s/%yy/sprintf "%02d", $yy%100/ge;
7135
$redirect =~ s/%mm/sprintf "%02d", $mm/ge;
7136
$redirect =~ s/%dd/sprintf "%02d", $dd/ge;
7137
$redirect =~ s/%hh/sprintf "%02d", $hh/ge;
7138
$redirect =~ s/%nn/sprintf "%02d", $nn/ge;
7139
$redirect =~ s/%ss/sprintf "%02d", $ss/ge;
7141
print "$mailReply > $redirect\n";
7144
# display the next page of mail and get an input character.
7147
print("skipped\n"), $delFlag = 1, last if ! $unformat and length $mailSubject and defined $junkSubjects{$mailSubject};
7148
foreach $j (keys %junkSubjects) {
7149
next unless $j =~ /^`/;
7152
next unless index($exact_msg, $trash) >= 0;
7153
print("trash\n"), $delFlag = 1, last dispInput;
7155
if($dispLine <= $dol) {
7156
foreach $j (1..20) {
7157
last if $dispLine > $dol;
7158
my $line = fetchLine $dispLine, 0;
7159
# Don't print date and return address, but they will be recorded,
7160
# if you save the file.
7161
next if $line =~ /^Mail sent /;
7162
next if $line =~ /^Reply to /;
7164
} continue { ++$dispLine; }
7165
} # display next page
7166
} # not being deleted
7171
last if $redirect eq "x";
7174
# Interactive prompt depends on whether there is more text or not.
7175
STDOUT->autoflush(1);
7176
print ($dispLine > $dol ? "? " : "* ");
7177
STDOUT->autoflush(0);
7179
$key = userChar("qx? nwkuJdA");
7182
exit 0 if $key eq 'x';
7183
print("quit\n"), serverClose($proto), exit 0 if $key eq 'q';
7184
print("next\n"), last dispInput if $key eq 'n';
7185
print("delete\n"), $delFlag = 1, last dispInput if $key eq 'd';
7188
print "End of message\n" if $dispLine > $dol;
7193
print "?\tprint this help message.
7194
q\tquit this program.
7195
x\texit without changing anything on the mail server.
7196
space\tread more of this mail message.
7197
n\tmove on to the next mail message.
7198
A\tadd the sender to your address book.
7199
d\tdelete this message.
7200
J\tjunk this subject, and delete any mail with this subject.
7201
w\twrite this message to a file and delete it.
7202
k\tkeep this message in a file, but don't delete it.
7203
u\twrite this message unformatted to a file, and delete it.\n";
7208
print "No subject to junk\n", redo if $mailSubject eq "";
7209
print "No junkfile specified in your .ebrc file\n", redo unless length $junkFile;
7211
markSubject($mailSubject);
7217
print "No addressbook specified in your .ebrc file\n", redo unless length $addressFile;
7218
print "Cannot establish sender's name and/or email address.", redo unless length $mailFrom and length $mailReply;
7219
open FH, ">>$addressFile"
7220
or dieq "Cannot append to $addressFile.";
7223
print "$_:$mailReply\n";
7224
print FH "$_:$mailReply\n";
7230
# At this point we're saving the mail somewhere.
7231
$delFlag = 1 if $key ne 'k';
7233
if(length $redirect) {
7234
$filename = $redirect;
7236
$filename = getFileName(undef, 0);
7238
if($filename ne "x") {
7239
my $append = (-e $filename);
7240
open FH, ">>$filename"
7241
or dieq "Cannot create mail file $filename."; # should not happen
7243
if($key eq 'u'or $unformat) {
7245
or dieq "Cannot write to mail file $filename.";
7246
$fsize = length $exact_msg;
7248
foreach $j (1..$dol) {
7249
my $line = fetchLine $j, 0;
7251
or dieq "Cannot write to mail file $filename.";
7252
$fsize += length($line) + 1;
7256
print "mail saved, $fsize bytes";
7257
print " appended" if $append;
7261
if($key ne 'u' and $redirect ne 'x') {
7262
# Ask the user about any attachments.
7264
foreach $curPart (@mimeParts) {
7265
next unless $$curPart{isattach};
7267
$filename = $$curPart{filename};
7268
if(length $redirect) {
7269
print "attach $filename\n";
7271
print "Attachment $j ";
7272
$filename = getFileName($filename, 1);
7273
next if $filename eq "x";
7275
open FH, ">$filename"
7276
or dieq "Cannot create attachment file $filename.";
7277
binmode FH, ':raw' if $doslike;
7278
print FH $$curPart{data}
7279
or dieq "Cannot write to attachment file $filename.";
7281
} # loop over attachments
7282
} # key other than 'u'
7285
} # display and input
7286
} # interactive or zap
7288
if($delFlag) { # Delete the message.
7289
# Remember, it isn't really gone until you quit the session.
7290
# So if you didn't want to delete, type x to exit abruptly,
7291
# then fetch your mail again.
7292
serverPutLine("dele $m");
7294
$serverLine =~ /^\+OK/
7295
or dieq "Unable to delete message <$serverLine>.";
7298
} # loop over messages
7300
print "$nmsgs\n" if $zapmail;
7302
serverClose($proto); # that's all folks!
7306
# Initial set of commands.
7307
if($commandList{init}) {
7308
evaluateSequence($commandList{init}, $commandCheck{init});
7311
# Process the command line arguments.
7312
foreach my $cx (0..$#ARGV) {
7313
my $file = $ARGV[$cx];
7314
cxSwitch($cx, 0) if $cx;
7316
my $rc = readFile($file, "");
7317
print "$filesize\n";
7318
$rc or print $errorMsg,"\n";
7320
$fname = $changeFname if length $changeFname;
7321
$fmode &= ~($changemode|$firstopmode);
7322
if($rc and $filesize and is_url($fname)) {
7323
# Go ahead and browse it.
7324
$inglob = $intFlag = 0;
7326
$rc = evaluate("b");
7327
print "$filesize\n" if $filesize >= 0;
7328
$rc or print "$errorMsg\n";
7330
} # loop over args on the command line
7331
cxSwitch(0, 0) if $context;
7332
print "edbrowse ready\n" if ! length $fname;
7334
# get user commands.
7336
my $line = readLine();
7337
my $saveLine = $line;
7341
my $rc = evaluate($line);
7342
print "$filesize\n" if $filesize >= 0;
7344
print ((($helpall or $cmd =~ /[$showerror_cmd]/o) ? $errorMsg : "?"), "\n");
7345
exit 1 if $errorExit;
7347
$linePending = $saveLine;
7349
$lastdot = $savedot, $lastdol = $savedol;
7350
$lastmap = $savemap, $lastlabels = $savelabels;
7355
#*********************************************************************
7356
# The following code is written and maintained by Chris Brannon,
7357
# cbrannon@wilnet1.com
7358
# It manages secure http and ftp connections.
7359
#*********************************************************************
7363
# Do the SSL thing. This takes four arguments: server, port, message,
7364
# and buffer reference.
7365
# <message> is a scalar containing http headers. <buffer reference> is
7366
# a reference to a scalar. We tack each chunk of received data onto that
7367
# scalar. Thusly, we don't have to return a variable containing twenty
7369
# I borrow heavily from Karl's plain http connection code.
7370
unless(eval { require Net::SSLeay }) {
7371
$errorMsg = "you must have the Net::SSLeay module and OpenSSL toolkit to speak https", return 0;
7373
# Should I error-check these values? I don't know. Probably.
7376
my $message = shift;
7378
my $iaddr = inet_aton($server) or
7379
$errorMsg = "Cannot identify $server on the network", return 0;
7380
my $paddr = sockaddr_in($port, $iaddr);
7381
my $proto = getprotobyname('tcp');
7382
socket(FH, PF_INET, SOCK_STREAM, $proto) or
7383
$errorMsg = "cannot allocate a socket", return 0;
7384
connect(FH, $paddr) or
7385
$errorMsg = "cannot connect to $server on $port: $!", return 0;
7386
Net::SSLeay::load_error_strings();
7387
Net::SSLeay::SSLeay_add_ssl_algorithms();
7388
Net::SSLeay::randomize();
7389
$ctx = Net::SSLeay::CTX_new();
7390
Net::SSLeay::CTX_set_options($ctx, &Net::SSLeay::OP_ALL);
7392
Net::SSLeay::CTX_load_verify_locations($ctx, $ebcerts, '') or
7393
$errorMsg = "Error opening certificate file $ebcerts: $!", return 0;
7394
Net::SSLeay::CTX_set_verify($ctx, &Net::SSLeay::VERIFY_PEER, 0);
7396
# Should the user be warned somehow when SSL certificate verification has
7397
# been turned off? Accepting unverifiable certificates can be a security
7398
# risk. But some servers, like https://listman.redhat.com can't be verified
7399
# with my certificate bundle. So I make verification the default, but optional.
7400
$ssl = Net::SSLeay::new($ctx);
7401
Net::SSLeay::set_fd($ssl, fileno(FH)) or
7402
$errorMsg = Net::SSLeay::ERR_error_string(Net::SSLeay::ERR_get_error()), return 0;
7403
if(Net::SSLeay::connect($ssl) == -1) {
7404
$errorMsg = Net::SSLeay::ERR_error_string(Net::SSLeay::ERR_get_error());
7407
Net::SSLeay::ssl_write_all($ssl, $message) or
7408
$errorMsg = &Net::SSLeay::ERR_error_string(&Net::SSLeay::ERR_get_error()), return 0;
7409
my ($chunk, $filesize, $rsize, $last_fk, $fk);
7411
STDOUT->autoflush(1) if ! $doslike;
7412
while($chunk = Net::SSLeay::ssl_read_all($ssl, 100000)) {
7413
$$bufref .= $chunk; # how cute!
7414
$rsize = length($chunk); # Is this computationally expensive??
7415
$filesize += $rsize;
7416
last if $rsize == 0;
7417
$fk = int($filesize / 100000);
7418
if($fk > $last_fk) {
7422
last if($filesize >= $maxfile);
7425
print "\n" if $last_fk > $fk;
7426
STDOUT->autoflush(0) if ! $doslike;
7427
$filesize <= $maxfile or
7428
$errorMsg = "file is too large, limit 40MB", return 0;
7430
$errorMsg = "error reading data from the socket", return 0;
7431
# There's no way to distinguish between a read error and reading a zero
7432
# length file. I guess that's ok.
7433
if(defined($filesize)) {
7440
sub ftp_connect($$$$)
7442
my($host, $port, $path, $bufref) = @_;
7444
my ($tempbuf, @disposeOf);
7446
my $login = "anonymous";
7447
my $password = 'some-user@edbrowse.net';
7449
$passive ? \&pasvOpen : \&ftpListen);
7450
if($host =~ s/^([^:@]*):([^:@]*)@//) {
7451
$login = $1, $password = $2;
7453
# Do an ftp connect, prompting for username & password.
7454
my $iaddr = inet_aton($host) or
7455
$errorMsg = "cannot identify $host on the network", return 0;
7456
my $paddr = sockaddr_in($port, $iaddr);
7457
socket(SERVER_FH, PF_INET, SOCK_STREAM, getprotobyname('tcp')) or
7458
$errorMsg = "cannot allocate a socket", return 0;
7459
connect(SERVER_FH, $paddr) or
7460
$errorMsg = "cannot connect to $host", return 0;
7461
SERVER_FH->autoflush(1);
7462
STDOUT->autoflush(1) if !$doslike;
7463
serverGetLine or serverClose($proto), return 0;
7464
serverClose($proto), return 0 if ftpError($serverLine, 220, "server sent \"$serverLine\" while attempting login");
7465
serverPutLine "user $login";
7467
serverGetLine or serverClose($proto), return 0;
7469
while($serverLine =~ /^220/);
7470
serverClose($proto), return 0 if ftpError($serverLine, 331, "invalid username: server sent $serverLine");
7471
serverPutLine "pass $password";
7472
serverGetLine or serverClose($proto), return 0;
7473
serverClose($proto), return 0 if ftpError($serverLine, 230, "bad password: server sent $serverLine");
7474
my $wmsg = ""; # welcome message
7475
if($serverLine =~ s/^\s*230\s*?-\s*//) {
7476
# We got a welcome message.
7477
$wmsg = "$serverLine\n";
7478
while(serverGetLine) {
7479
last if $serverLine =~ /^\s*230\s*?[^-]/;
7480
$serverLine =~ s/^\s*230\s*?-\s*//;
7481
$wmsg .= "$serverLine\n";
7484
$wmsg = "" unless $path eq "/"; # trash the welcome message, we're going somewhere else
7485
serverPutLine "CWD $path";
7486
serverGetLine or serverClose($proto), return 0;
7487
if($serverLine =~ /^\s*250\s*/) {
7488
if($serverLine =~ s/^\s*250\s*?-\s*//) {
7489
# Its a directory-specific greeting.
7490
$wmsg = "$serverLine\n";
7491
while(serverGetLine) {
7492
last if $serverLine =~ /^\s*250\s*?[^-]/;
7493
$serverLine =~ s/^\s*250\s*?-\s*//;
7494
$wmsg .= "$serverLine\n";
7497
serverPutLine "type a";
7498
serverGetLine or serverClose($proto), return 0;
7499
serverClose($proto), return 0 if ftpError($serverLine, 200, "ASCII transfers not supported by server: received \"$serverLine\"");
7501
serverClose($proto), return 0;
7502
serverPutLine "list";
7503
serverGetLine or serverClose($proto), return 0;
7504
serverClose($proto), return 0 if ftpError($serverLine, "150", "error retrieving directory listing");
7506
ftpRead(\$tempbuf) or
7507
serverClose($proto), return 0;
7509
serverPutLine "syst";
7510
serverGetLine or serverClose($proto), return 0;
7511
if($serverLine =~ /unix/i) {
7512
# Good. Let's try to htmlize this file listing.
7513
my $base_filename = "ftp://$host";
7514
$base_filename .= ":$port" if $port != 21;
7515
$base_filename .= $path;
7516
$base_filename .= '/' if $base_filename !~ m,/$,;
7517
# Yah, I know. That looks disgusting.
7519
$$bufref = "http/1.0 200 ok$eol$eol<html><head><title>Directory Listing</title>\n</head>\n<body>\n$wmsg<ul>\n";
7520
my @lines = split("$eol", $tempbuf);
7521
shift(@lines); # Ditch the "total: xxx" line from Unix ls
7522
foreach $line (@lines) {
7523
# Extract the filename and length from ls -l format
7524
my @listItems = split /\s+/, $line;
7525
my $mode = $listItems[0];
7526
my $extracted = $listItems[$#listItems];
7527
my $extlen = $listItems[$#listItems-4];
7528
$extlen = "/" if $mode =~ /^d/;
7529
$$bufref .= "<li><a href=\"$base_filename$extracted\">$extracted</a> $extlen\n";
7531
$$bufref .= "</ul>\n</body>\n</html>\n";
7532
$$bufref =~ s/<ul>\n<\/ul>/This ftp directory is empty./;
7533
$filesize = length($$bufref);
7535
$$bufref = $tempbuf; # Oh well...
7537
serverPutLine "quit";
7538
@disposeOf = <SERVER_FH>;
7540
return 0 if !$filesize;
7544
# Try to retr. If unable, the path was bogus.
7545
serverPutLine "type i";
7546
serverGetLine or serverClose($proto), return 0;
7547
serverClose($proto), return 0 if ftpError($serverLine, 200, "binary transfers unsupported by server: received \"$serverLine\"");
7549
serverClose($proto), return 0;
7550
serverPutLine "retr $path";
7551
serverGetLine or serverClose($proto), return 0;
7552
serverClose($proto), return 0 if ftpError($serverLine, "150",
7553
"the path you specified in this URL is neither a filename nor a directory");
7554
# Let's read our data.
7555
$filesize = ftpRead($bufref);
7556
serverClose($proto);
7557
# The problem is, the ftp server will get an extraneous abor command when
7558
# we close connection. I only want these sent after an error condition, to
7560
return 0 if !$filesize;
7567
# I don't like the fact that this subroutine returns 0 on error. Seems wrong.
7576
vec($check, fileno(FLFH), 1) = 1;
7577
select($check, undef, undef, 10) or
7578
$errorMsg = "ftp data connection timed out", $filesize = 0, goto Cleanup;
7579
socket(FDFH, PF_INET, SOCK_STREAM, getprotobyname('TCP')) or
7580
$errorMsg = "unable to allocate a socket", $filesize = 0, goto Cleanup;
7584
while(defined($rsize = sysread(FDFH, $chunk, 100000))) {
7585
print "sockread $rsize\n" if $debug >= 5;
7587
$filesize += $rsize;
7588
last if $rsize == 0;
7589
my $fk = int($filesize / 100000);
7590
if($fk > $last_fk) {
7594
last if $filesize >= $maxfile;
7597
serverGetLine or return 0;
7600
# ignore it; it should read 226 transfer complete
7601
print "\n" if $last_fk;
7603
$errorMsg = "error reading data from the socket", $filesize = 0, goto Cleanup;
7604
$filesize <= $maxfile or
7605
$errorMsg = "file to large: 4-1M limit", $filesize = 0, goto Cleanup;
7607
$errorMsg = "empty file", $filesize = 0, goto Cleanup;
7609
close FDFH if defined FDFH;
7610
close FLFH if defined FLFH;
7617
# This subroutine matches an ftp response against an status code. The code can
7618
# be specified as a regexp. So, 25[0-9] as the status code will let us match
7619
# any of the 25X status codes.
7620
# It returns 1 on error. This subroutine used to do cleanup, but
7621
# I'm leaving this job to the main ftp subroutine.
7622
my ($input, $statcode, $errmsg) = @_;
7623
$errorMsg = $errmsg, return 1 if($input !~ /^\s*$statcode/);
7629
my ($line, $packed_ftpaddr, $ipaddr, $port);
7630
serverPutLine "pasv";
7631
serverGetLine or return 0;
7632
return 0 if ftpError($serverLine, '227', "server doesn't support passive mode: received \"$serverLine\"");
7633
if($serverLine =~ /([0-9]+,[0-9]+,[0-9]+,[0-9]+,[0-9]+,[0-9]+)/) {
7634
$packed_ftpaddr = pack("C6", split(',', $1));
7636
$errorMsg = "cannot make ftp data connection: server sent \"$serverLine\"";
7639
$ipaddr = substr($packed_ftpaddr, 0, 4);
7640
$port = unpack("n", substr($packed_ftpaddr, 4, 2));
7641
# The address for ftp data connections is written this way:
7643
# We turn those decimal notations into a packed string of unsigned chars..
7644
# The first four characters are the IP address in network byte order. They
7645
# are fed directly to sockaddr_in. The last two are unpacked as an
7646
# unsigned short in NBO. The new decimal representation is fed to sockaddr_in.
7647
my $saddr = sockaddr_in($port, $ipaddr);
7648
socket(FDFH, PF_INET, SOCK_STREAM, getprotobyname('tcp')) or
7649
$errorMsg = "cannot allocate a socket", return 0;
7650
connect(FDFH, $saddr) or
7651
$errorMsg = "cannot open ftp data connection", return 0;
7652
shutdown(FDFH, 1); # Hmm. My server hangs if this doesn't happen...
7657
my $ctladdr = (sockaddr_in(getsockname(SERVER_FH)))[1];
7658
$errorMsg = "unable to obtain address of control connection; cannot initiate data connection",
7659
return 0 if !$ctladdr;
7660
my $port = int(rand(64510) + 1025);
7661
socket(FLFH, PF_INET, SOCK_STREAM, getprotobyname('tcp')) or
7662
$errorMsg = "unable to allocate a socket", return 0;
7663
my $saddr = sockaddr_in($port, $ctladdr);
7664
bind(FLFH, $saddr) or
7665
$errorMsg = "unable to bind socket: port $port $!", return 0;
7667
$errorMsg = "unable to listen on ftp data socket", return 0;
7668
serverPutLine sprintf("port %d,%d,%d,%d,%d,%d", unpack('C4', $ctladdr), $port >> 8, $port & 255);
7669
serverGetLine or return 0;
7670
return 0 if ftpError($serverLine, '200', "ftp server does not support port command, received \"$serverLine\"");
7680
# We only support Netscape-style cookies presently. The newer style will
7681
# be supported eventually. It offers some functionality that Netscape's
7684
print "incoming cookie: $cookie\n" if $debug >= 4;
7685
$cookie =~ s/^Set-Cookie:\s+//i;
7686
stripWhite \$cookie;
7687
return unless length $cookie;
7688
my $url_desc = shift;
7689
my @cook_array = split(';', $cookie);
7690
# We should have the cookie into its component parts.
7691
my($name, $value, $path, $domain, $expires, $secure);
7692
($name, $value) = split('=', shift(@cook_array), 2);
7693
$value = "" unless defined $value;
7695
while($crumb = shift(@cook_array)) {
7697
$crumb = "secure=" if $crumb =~ /^secure$/i;
7698
if($crumb =~ s/^domain=//i) {
7699
# Do some work on $crumb to protect us from general maliciousness/idiocy.
7700
my $workingserver = $$url_desc{SERVER};
7701
next unless $$url_desc{SERVER} =~ /\Q$crumb\E$/i;
7702
my $l = length $crumb;
7703
next if length($workingserver) > $l and substr($crumb, 0, 1) ne '.' and substr($workingserver, -$l-1, 1) ne '.';
7704
# We simply won't use a bogus domain attribute. We ignore it, and the domain
7705
# eventually is set to the default.
7706
# In other words, we don't want somebody sending us a cookie for all of .com.
7707
my $numfields = $crumb =~ y/././;
7708
++$numfields unless substr($crumb, 0, 1) eq '.';
7709
if($crumb =~ /\.(net|com|gov|edu|mil|org|int|tv|bus)$/i) {
7710
# One nasty regexp, oh well. Domain attributes from these domains may
7711
# have a minimum of two fields.
7712
next if $numfields < 2;
7714
# Everyone else needs three fields.
7715
next if $numfields < 3;
7718
} elsif($crumb =~ s/^path=//i) {
7720
} elsif($crumb =~ s/^expires=?\s*//i) {
7721
# Squeeze a time_t out of the date string, hopefully! If not, then "-1"
7722
# is used as the date, so the cookie will expire on quit.
7723
$expires = cookieDate($crumb);
7724
} elsif($crumb =~ s/^max-age=?\s*//i) {
7725
if($crumb =~ /^\d+$/ and not defined $expires) {
7726
$expires = time() + $crumb;
7728
} elsif($crumb =~ s/^secure=//i) {
7732
print STDERR "Error processing cookie with element $crumb\n"; # debugging statement
7735
$domain = $$url_desc{SERVER} if !defined $domain;
7736
# Here's what it should be, according to the standard.
7737
# $path = $$url_desc{PATH} if !defined $path;
7738
# Here's what some sites require, such as http://tdzk.net
7739
# This is apparently what Explorer does.
7740
# Oh well, who the hell needs standards;
7741
# when you're a monopoly you set the standards.
7742
$path = "/" if !defined $path;
7743
$expires = -1 if !defined $expires;
7744
$secure = 0 if !defined $secure; # For secure cookies, it will have been set to 1
7745
# Put the cookie into the master cookie jar.
7746
print "into jar: $domain $path $expires $name $value\n" if $debug >= 4;
7747
$cookies{$domain}{$path}{$name} =
7748
{value => $value, expires => $expires, secure => $secure};
7749
# If a server sends two cookies of the same path and name, with different values,
7750
# the former will be quashed by the latter. This is proper behavior.
7751
if($expires != -1) { # Persistent cookie.
7753
$chmodFlag = 1 unless -f $ebcooks;
7754
# Now, append to the cookie file.
7755
# I learned the format for Netscape's cookie file from lynx's source. Thank you, lynx team.
7756
if(!open(COOKFILE, ">>$ebcooks")) {
7757
warn "unable to open cookie jar for append: $!";
7759
chmod 0600, $ebcooks if $chmodFlag;
7760
print COOKFILE join("\t", $domain, 'FALSE', $path,
7761
$secure ? 'TRUE' : 'FALSE', $expires, $name, $value) . "\n";
7762
# A note. Lynx defines a field, "what". I don't know what its used
7763
# for. But all the Netscape cookie files I've seen have it set to "FALSE".
7765
# Maybe its proprietary to Netscape's browser.
7773
my $url_desc = shift;
7774
my $cur_scheme = $$url_desc{SCHEME};
7775
my $cur_domain = $$url_desc{SERVER};
7776
my $cur_path = $$url_desc{PATH};
7777
my ($domainm, $pathm, $cookiem); # The 'm' at the end stands for 'match'
7778
my @sendable = (); # Sendable cookie strings.
7779
foreach $domainm (keys(%cookies)) {
7780
next unless $cur_domain =~ /\Q$domainm\E$/i;
7781
my $l = length $domainm;
7782
next if length($cur_domain) > $l and substr($domainm, 0, 1) ne '.' and substr($cur_domain, -$l-1, 1) ne '.';
7783
foreach $pathm (keys(%{$cookies{$domainm}})) {
7784
next unless $cur_path =~ /^\Q$pathm\E/;
7785
foreach $cookiem (keys(%{$cookies{$domainm}{$pathm}})) {
7786
my $deref = $cookies{$domainm}{$pathm}{$cookiem};
7787
# $deref is a simple hash reference, containing the description of one cookie.
7788
# We can do the rest of our matching painlessly, without dereferencing
7789
# the whole nasty data structure every time.
7790
next if $$deref{secure} and ($cur_scheme !~ /https/);
7791
my $j = join('=', $cookiem, $$deref{value});
7794
print "outgoing cookie: $domainm $pathm $j\n" if $debug >= 4;
7798
return "" if $#sendable < 0; # no cookies
7799
my $outgoing = 'Cookie: ' . join("; ", @sendable);
7800
# Lynx prepends a cookie2: directive.
7801
# I don't know what it means or what it's for. Here it is.
7802
return "Cookie2: \$Version=1$eol$outgoing$eol";
7807
# This might become a general http date decoder, if we ever find
7808
# places where dates are useful.
7809
my $datestring = shift;
7810
stripWhite \$datestring;
7811
if($datestring =~ /^[a-z]{3,9},\s+(\d\d)[- ]([a-z]{3})[- ](\d\d(?:\d\d)?)\s+(\d\d):(\d\d):(\d\d)\s+GMT/i) {
7812
my ($day, $mon, $year, $hour, $min, $sec) = ($1, $2, $3, $4, $5, $6);
7813
if(($year < 100) and ($year > 0)) {
7817
} else { $year += 2000; }
7819
$mon = $monhash{lc($mon)} - 1;
7820
# We should probably range-check all the fields,
7821
# but year is definitely necessary.
7822
$year = 2035 if $year > 2035;
7823
$year = 1970 if $year < 1970;
7824
my $time = timegm($sec, $min, $hour, $day, $mon, $year);
7833
# Initialize the cookie jar.
7834
my $writeFlag = 0; # Write revised cookie file?
7835
open(COOKFILE, "+<$ebcooks") or return;
7838
while($inline = <COOKFILE>) {
7840
my ($domain, $what, $path, $secure, $expires, $name, $value) = split("\t", $inline);
7841
$writeFlag = 1 if exists $cookies{$domain}{$path}{$name};
7842
if($expires > $nowtime) {
7843
$cookies{$domain}{$path}{$name} =
7844
{value => $value, secure => $secure eq "TRUE" ? 1 : 0, expires => $expires}
7847
} # cookies expired.
7850
seek COOKFILE, 0, 0;
7851
truncate COOKFILE, 0;
7852
my ($odomain, $opath, $ocook); # o for out
7853
foreach $odomain (keys(%cookies)) {
7854
foreach $opath (keys(%{$cookies{$odomain}})) {
7855
foreach $ocook (keys(%{$cookies{$odomain}{$opath}})) {
7856
my %deref = %{$cookies{$odomain}{$opath}{$ocook}};
7857
print COOKFILE join("\t", $odomain, 'FALSE', $opath,
7858
$deref{secure} ? "TRUE" : "FALSE", $deref{expires}, $ocook,
7859
$deref{value}), "\n" if $deref{expires} > $nowtime;
7867
#*********************************************************************
7868
# Web Express features. For more on Web Express, visit
7869
# http://www.webexpresstech.com/WebXP/WebExpressTutorial.html
7870
#*********************************************************************
7880
defined $shortcut{$code} or
7881
$errorMsg = "shortcut $code is not recognized", return 0;
7882
my $newurl = $shortcut{$code}{url};
7884
# Step through $line and extract options, indicated by -
7885
# This isn't implemented yet.
7887
# Done with options, what remains is the search argument.
7888
my $arg = urlEncode $line;
7890
$errorMsg = "shortcut is given no search argument", return 0;
7892
# Put the argument into the url.
7893
$newurl =~ s/\$1/$arg/;
7895
return 1, $newurl, $shortcut{$code}{after};
7899
# return "x" if an error is encountered
7900
sub parseWWWAuth($$)
7902
my ($authline, $url_desc) = @_;
7903
my ($qop_auth, $qop_authint) = (0, 1); # this would be an enum in C
7904
my ($username, $pass);
7906
# parse the authorization request line
7907
my @challenges = ();
7908
my ($attribname, $value);
7909
stripWhite(\$authline);
7910
$authline =~ s/^WWW-Authenticate:\s*//i;
7911
while($authline =~ s/^\s*([^\s]+)\s+//) {
7912
my %challenge = (authscheme => $1);
7913
while($authline =~ s/^([^=]+)=//) {
7914
$attribname = lc($1);
7915
if($authline =~ s/^"//) {
7916
# value of attribute is a quoted string.
7917
$authline =~ s/^([^"]+)"((,\s*)|$)//;
7920
$authline =~ s/^([^,]+)((,\s*)|$)//;
7923
$challenge{$attribname} = $value;
7925
if($challenge{authscheme} =~ /^digest/i && defined($challenge{qop})) {
7926
my ($q, $newq) = undef;
7927
my @qop = split(/\s*,\s*/, $challenge{qop});
7929
$newq = $qop_authint, last if $q =~ /^auth-int$/i;
7931
if(!defined($newq)) {
7933
$newq = $qop_auth, last if $q =~ /^auth$/i;
7936
$errorMsg = "Server sent a bad qop value in digest authentication", return "x" unless defined $newq;
7937
$challenge{qop} = $newq;
7939
push(@challenges, {%challenge});
7941
my ($c, $used_challenge) = undef;
7942
# Server may have sent multiple challenges with multiple auth schemes.
7943
# Spec says that we use the strongest scheme supported by the server.
7944
foreach $c (@challenges) {
7945
$used_challenge = $c, last if $$c{authscheme} =~ /^Digest$/i;
7947
if(!defined($used_challenge)) {
7948
foreach $c (@challenges) {
7949
$used_challenge = $c if($$c{authscheme} =~ /Basic/);
7952
$errorMsg = "no usable challenges were found", return "x" unless defined $used_challenge;
7953
if($$used_challenge{authscheme} =~ /Basic/i) {
7954
($username, $pass) = getUserPass($$used_challenge{realm});
7955
return "x" if $username eq "x";
7956
my $do64x = "$username:$pass";
7958
encodeBase64(\$do64x, "", \$do64y);
7959
return "Authorization: Basic $do64y$eol";
7961
else { # Not Basic, must be Digest.
7962
unless(eval { require Digest::MD5 }) {
7963
$errorMsg = "You need to download the Digest::MD5 module from CPAN to do digest authentication.", return "x";
7965
$errorMsg = "Unsupported algorithm for digest authentication", return "x" if(defined($$used_challenge{algorithm}) && $$used_challenge{algorithm} !~ /^md5$/i);
7966
$errorMsg = "unable to perform digest authentication", return "x" if(!defined($$used_challenge{realm})
7967
|| !defined($$used_challenge{nonce}));
7968
($username, $pass) = getUserPass($$used_challenge{realm});
7969
return "x" if $username eq "x";
7971
my $nc = "00000001";
7972
my $cnonce = sprintf("%08x%08x", int(rand(0xffffffff)), int(rand(0xffffffff)));
7973
# pseudorandoms are fine here. The cnonce is used to thwart chosen plaintext
7974
# attacks when checking integrity of message content. Probably not much
7975
# of a threat for MD5. Maybe it will be someday, and when it is, I'll
7976
# dream up a better way to create a random cnonce.
7978
$a1 = "$username:$$used_challenge{realm}:$pass";
7979
if($$used_challenge{qop} == $qop_authint) {
7980
$a2 = $$url_desc{method} . ':' . $$url_desc{PATH} . Digest::MD5::md5_hex($$url_desc{content});
7982
$a2 = $$url_desc{method} . ':' . $$url_desc{PATH};
7985
if(defined($$used_challenge{qop})) {
7986
$response = Digest::MD5::md5_hex(Digest::MD5::md5_hex($a1) . ':' . $$used_challenge{nonce} . ':' .
7987
$nc . ':' . $cnonce . ':' .
7988
($$used_challenge{qop} == $qop_auth ? "auth" : "auth-int") . ':' . Digest::MD5::md5_hex($a2)) ;
7990
$response = Digest::MD5::md5_hex(Digest::MD5::md5_hex($a1) . ':' . $$used_challenge{nonce} . ':' . Digest::MD5::md5_hex($a2)) ;
7992
my $out = "Authorization: Digest username=\"$username\", realm=\"$$used_challenge{realm}\", " .
7993
"nonce=\"$$used_challenge{nonce}\", uri=\"$$url_desc{PATH}\", response=\"$response\"";
7994
$out .= ", opaque=\"$$used_challenge{opaque}\"" if defined($$used_challenge{opaque});
7995
$out .= ", algorithm=\"$$used_challenge{algorithm}\"" if defined($$used_challenge{algorithm});
7996
if(defined($$used_challenge{qop})) {
7998
$out .= "\"auth\"" if $$used_challenge{qop} == $qop_auth;
7999
$out .= "\"auth-int\"" if $$used_challenge{qop} == $qop_authint;
8000
$out .= ", nc=$nc, cnonce=\"$cnonce\"";
8010
my $abort = "login password sequence aborted";
8011
if(! $authAttempt and defined $authHist{$realm}) {
8012
return split ":", $authHist{$realm};
8014
print "Server requests authentication for $realm. (type x to abort)\n";
8016
my $username = <STDIN>;
8018
$errorMsg = $abort, return ("x","x") if $username eq "x";
8022
$errorMsg = $abort, return ("x","x") if $pass eq "x";
8023
$authHist{$realm} = "$username:$pass";
8024
return ($username, $pass);