#!/usr/local/bin/perl5 # Uses glimpse to search my mail. # By Jamie Zawinski , 18-Jun-96. # # If invoked with argv[0] which ends in ".cgi", looks at the $QUERY_STRING # environment variable to decide what to do. Otherwise, passes command # line arguments to glimpse and returns output similar to grep (but with # mailbox URLs instead of file names and line numbers.) # # Todo: # # = Figure out a way to make it search the entire message, instead of # being limited to a line at a time (make it an option.) $glimpse = "/usr/local/bin/glimpse"; $extra_glimpse_args = "-H /u/jwz/nsmail/.glimpse"; ############################################################################ $ENV{'PATH'} = '/bin:/usr/bin'; $ENV{'SHELL'} = '/bin/sh' if defined $ENV{'SHELL'}; $ENV{'IFS'} = '' if defined $ENV{'IFS'}; sub url_quote { die "expected 1 arg to url_quote" unless @_ == 1; local $_ = shift; s|([^-a-zA-Z0-9.\@/_\r\n])|sprintf("%%%02X", ord($1))|ge; return $_; } sub url_unquote { die "expected 1 arg to url_quote" unless @_ == 1; local $_ = shift; s/[+]/ /g; s/%([a-z0-9]{2})/chr(hex($1))/ige; return $_; } sub html_quote { die "expected 1 arg to url_quote" unless @_ == 1; local $_ = shift; s/&/&/g; s//>/g; s/\"/"/g; return $_; } sub sh_quote { die "expected 1 arg to sh_quote" unless @_ == 1; local $_ = shift; s/([^-_.a-zA-Z0-9])/\\\1/g; return $_; } sub run_glimpse { die "expected 1 arg to run_glimpse" unless (@_ == 1 || @_ == 2); local ($_, $emit_html_p) = @_; local $command = $glimpse . " -b -y " . $extra_glimpse_args . " " . $_; if ($emit_html_p) { local $c2; ($c2 = $command) =~ s|^/?([^/ ]+/)*||; print "

", html_quote($c2); print "


\n"; } foreach $line ( `$command` ) { $line = hack_line($line); if ($line ne "") { if ($emit_html_p) { $line = quote_line($line); } print $line, "\n"; } } } # turns the output from glimpse into a mailbox: URL followed by the match. sub hack_line { die "expected 1 arg to hack_line" unless @_ == 1; local $_ = shift; local ($file, $byte, $match) = m/^([^:]+): *([^=]+)= *(.*)$/; $_ = find_mid("-b", $byte, $file); local ($msg_num, $msg_id) = m/^([0-9]+)[ \t\n]*]*)>?$/; local $result = "mailbox:" . url_quote($file); if ($msg_id ne "" && $msg_id ne 0) { $result = $result . "?id=" . url_quote($msg_id); } if ($msg_num ne "" && $msg_num ne 0) { $result = $result . (($msg_id eq "" || $msg_id eq 0) ? "?" : "&") . "number=" . url_quote($msg_num); } if (($msg_id eq "" || $msg_id eq 0) && ($msg_num eq "" || $msg_num eq 0)) { return ""; } return $result . "\t" . $match; } # turns the output from hack_line() into presentable HTML. sub quote_line { die "expected 1 arg to quote_line" unless @_ == 1; local $_ = shift; local ($url, $match) = m/^([^ \t]+)[ \t]+(.+)$/; $_ = $url; local ($file) = m/^[^:]+:([^?]+)/; local ($number) = m/number=([0-9]*)/; ($file) =~ s!.*?([^/]+)$!\1!; # return "" . # html_quote($file . "#" . $number) . "" . # "   " . # html_quote($match) . "
"; local $tag = $file . "#" . $number; local $result = "" . "
"; if ($tag eq $prev_quoted_tag) { #$result = $result . " "; } else { $result = $result . "" . html_quote($tag) . ""; $prev_quoted_tag = $tag; } $result = $result . "" . html_quote($match) . "
"; return $result; } sub run_glimpse_cgi { die "expected 0 args to run_glimpse_cgi" unless @_ == 0; local $_ = shift; local $search=""; local $files=""; local $errors=""; local $case_p=0; local $word_p=0; print "Content-Type: text/html\n\n"; # Since this junk has to run as setuid in order to read my mail indexes, # don't allow the CGI script to run unless it's being accessed directly # from my machine. (This assumes that logins on my machine are secured, # but that HTTP connections to it are not.) # if ( $ENV{"REMOTE_ADDR"} ne "127.0.0.1" && $ENV{"REMOTE_ADDR"} ne "209.157.133.130" && $ENV{"REMOTE_ADDR"} ne "209.157.133.131" && $ENV{"REMOTE_ADDR"} ne "209.157.133.132" ) { print "piss off", "", "", "
", "

Who invited you?
Go away.
", "

"; exit(-1); } $_ = $ENV{"QUERY_STRING"}; local $body_tag = ""; if ($_ eq "") { print "Mail Search", $body_tag; print "

Mail Search

"; write_control_panel_html($search, $files, $errors, $case_p, $word_p); } else { foreach (split(/[?&;]/) ) { local($key, $value) = m/^([^=]+)=?(.*)$/; if ($key eq "search") { $search = url_unquote($value); } elsif ($key eq "files") { $files = url_unquote($value); } elsif ($key eq "errors") { $errors = url_unquote($value); } elsif ($key eq "case") { $case_p = ($value eq "true" || $value eq "TRUE"); } elsif ($key eq "words") { $word_p = ($value eq "true" || $value eq "TRUE"); } else { die "unknown key $key"; } } local $args = ""; if ($files) { $args = $args . " -F " . sh_quote($files); } if ($errors) { $args = $args . " -" . sh_quote($errors); } if (!$case_p) { $args = $args . " -i"; } if ($word_p) { $args = $args . " -w"; } if ($search) { $args = $args . " " . sh_quote($search); } print "Mail Search: ", html_quote($search), ""; print $body_tag; print "

Mail Search: ", html_quote($search), "

"; run_glimpse($args, 1); print "

"; write_control_panel_html($search, $files, $errors, $case_p, $word_p); } } sub write_control_panel_html { die "expected 5 args to write_control_panel_html" unless @_ == 5; ($search, $files, $errors, $case_p, $word_p) = @_; if ($errors eq "") { $errors = "0"; } print "

Search for:
In files:
Closeness: Case Sensitive: Word Search:

; between words indicates and
, between words indicates or
# matches 0 or more characters
<text> means text must match exactly.
  and if none of that matches, the text is interpreted as a regexp:
  |, *, and () work, but + does not, and the regexp is limited to
   around 30 characters.

\n"; } sub find_mid { die "expected 2-3 args to write_control_panel_html" unless (@_ == 2 || @_ == 3); local $bytes_p = 0; local $index; local $file; $index = shift; $_ = $index; if ( m/^-b$/ ) { $bytes_p = 1; $index = shift; } elsif ( m/^-l$/ ) { $bytes_p = 0; $index = shift; } elsif ( m/^-/ ) { die "unknown switch $_ in find_mid.\n"; } $file = shift; if (shift) { die("expected 2-3 args to write_control_panel_html"); } # printf STDERR "looking for %s in %s\n", $index, $file; sub find_mid_reset { $find_mid_name = 0; $find_mid_byte = 0; $find_mid_line = 0; $find_mid_id = 0; $find_mid_blank_p = 0; $find_mid_header_p = 1; $find_mid_moz = 0; $find_mid_msgnum = -1; # Set this to true to treat the separator as "\n\nFrom ". If # undefined, "\nFrom " is used (Mozilla bug compatibility.) $find_mid_strict_sep = 0; } if (!$find_mid_initted) { find_mid_reset(); $find_mid_initted = 1; } if ($file ne $find_mid_name || ($bytes_p ? ($find_mid_byte > $index) : ($find_mid_line > $index))) { if ($find_mid_name) { # printf STDERR "closing %s\n", $find_mid_name; close MIDS; find_mid_reset(); } open(MIDS, $file); $find_mid_name = $file; } local $line; EOF: while ($line = ) { $find_mid_line++; $find_mid_byte += length($line); $_ = $line; if ($line eq "\n") { # printf STDERR "line %d is blank\n", $find_mid_line; $find_mid_blank_p = 1; $find_mid_header_p = 0; next; } $_ = $line; if ((!$find_mid_strict_sep || $find_mid_blank_p) && m/^From / ) { $find_mid_id = 0; $find_mid_moz = 0; $find_mid_header_p = 1; $find_mid_msgnum++; # printf STDERR "line %d is a separator\n", $find_mid_line; } elsif ($find_mid_header_p && m/^Message-ID:[ \t]*(<[^>]+>)[ \t]*$/i) { ($find_mid_id) = m/^Message-ID:[ \t]*(<[^>]+>)[ \t]*$/i; # printf STDERR "line %d has message ID %s for msg #%d\n", # $find_mid_line, $find_mid_id, $find_mid_msgnum; } elsif ($find_mid_header_p && m/^X-Mozilla-Status:[ \t]*(.+)$/i) { ($find_mid_moz) = m/^X-Mozilla-Status:[ \t]*(.+)$/i; # printf STDERR "line %d has status %s for msg #%d\n", # $find_mid_line, $find_mid_moz, $find_mid_msgnum; } $find_mid_blank_p = 0; if ($bytes_p ? ($find_mid_byte >= $index) : ($find_mid_line >= $index)) { if ($find_mid_moz && (hex($find_mid_moz) & 8)) { # printf STDERR "msg #%d is deleted " . # "(%s => 0x%04X which has 0x0008 set)\n", # $find_mid_msgnum, $find_mid_moz, hex($find_mid_moz); return ""; } return sprintf("%d %s", $find_mid_msgnum, $find_mid_id); } } return ""; } sub main { # unbuffer all streams. select(STDIN); $| = 1; select(STDERR); $| = 1; select(STDOUT); $| = 1; $_ = $0; if ( m/\.cgi$/ ) { run_glimpse_cgi(); } else { local $args = ""; for (@ARGV) { if ($args ne "") { $args .= " " }; $args .= sh_quote($_); } if ($args eq "") { print STDERR "usage: $0 [ glimpse-args ... ]\n"; #system $glimpse; exit(1); } run_glimpse($args); } } main(); exit(0);