]> git.treefish.org Git - fex.git/blobdiff - bin/fexsend
Original release 20160919
[fex.git] / bin / fexsend
index 7e498dce61f81160a31b8cd70732665eeb7dc1d1..fae77e9b79e977a1819e98b9345ea3e6fa54de3f 100755 (executable)
@@ -17,10 +17,10 @@ use IO::Handle;
 use IO::Socket::INET;
 use Getopt::Std;
 use File::Basename;
 use IO::Socket::INET;
 use Getopt::Std;
 use File::Basename;
-use Cwd qw'abs_path';
+use Cwd 'abs_path';
 use Fcntl qw':flock :mode';
 use Fcntl qw':flock :mode';
-use Digest::MD5 qw'md5_hex';  # encrypted ID / SID
-use Time::HiRes qw'time';
+use Digest::MD5 'md5_hex';  # encrypted ID / SID
+use Time::HiRes 'time';
 # use Smart::Comments;
 use constant k => 2**10;
 use constant M => 2**20;
 # use Smart::Comments;
 use constant k => 2**10;
 use constant M => 2**20;
@@ -37,7 +37,7 @@ our ($tpid,$frecipient);
 our ($FEXID,$FEXXX,$HOME);
 our (%alias);
 our $chunksize = 0;
 our ($FEXID,$FEXXX,$HOME);
 our (%alias);
 our $chunksize = 0;
-our $version = 20160104;
+our $version = 20160919;
 our $_0 = $0;
 our $DEBUG = $ENV{DEBUG};
 
 our $_0 = $0;
 our $DEBUG = $ENV{DEBUG};
 
@@ -45,6 +45,7 @@ my %SSL = (SSL_version => 'TLSv1');
 my $sigpipe;
 
 if ($Config{osname} =~ /^mswin/i) {
 my $sigpipe;
 
 if ($Config{osname} =~ /^mswin/i) {
+  # http://slu.livejournal.com/17395.html
   $windoof = $Config{osname};
   $HOME = $ENV{USERPROFILE};
   $fexhome = $ENV{FEXHOME} || $HOME.'\fex';
   $windoof = $Config{osname};
   $HOME = $ENV{USERPROFILE};
   $fexhome = $ENV{FEXHOME} || $HOME.'\fex';
@@ -55,8 +56,8 @@ if ($Config{osname} =~ /^mswin/i) {
                        $Config{osname},$Config{archname});
   $SSL{SSL_verify_mode} = 0;
 } elsif ($Config{osname} =~ /^darwin/i or $ENV{MACOS}) {
                        $Config{osname},$Config{archname});
   $SSL{SSL_verify_mode} = 0;
 } elsif ($Config{osname} =~ /^darwin/i or $ENV{MACOS}) {
-  $macos = $Config{osname};
   # http://stackoverflow.com/questions/989349/running-a-command-in-a-new-mac-os-x-terminal-window
   # http://stackoverflow.com/questions/989349/running-a-command-in-a-new-mac-os-x-terminal-window
+  $macos = $Config{osname};
   $HOME = (getpwuid($<))[7]||$ENV{HOME};
   $fexhome = $HOME.'/.fex';
   $tmpdir = $ENV{FEXTMP} || $ENV{TMPDIR} || "$fexhome/tmp";
   $HOME = (getpwuid($<))[7]||$ENV{HOME};
   $fexhome = $HOME.'/.fex';
   $tmpdir = $ENV{FEXTMP} || $ENV{TMPDIR} || "$fexhome/tmp";
@@ -142,10 +143,11 @@ special options: -I          initialize ID file or show ID
                  -d \#        delete file on fex server
                  -N \#        resend notification e-mail
                  -Q          check quotas
                  -d \#        delete file on fex server
                  -N \#        resend notification e-mail
                  -Q          check quotas
+                 -T up:down  test internet speed with up and down MBs
                  -A          edit server address book (aliases)
                  -S          show server/user settings and auth-ID
                  -H          show hints, examples and more options
                  -A          edit server address book (aliases)
                  -S          show server/user settings and auth-ID
                  -H          show hints, examples and more options
-                 -V          show version
+                 -V          show version and ask for upgrade
                  (# is a file number, see output from $0 -l)
 examples: $0 visualization.mpg framstag\@rus.uni-stuttgart.de
           $0 -a images.zip *.jpg webmaster\@flupp.org,metoo
                  (# is a file number, see output from $0 -l)
 examples: $0 visualization.mpg framstag\@rus.uni-stuttgart.de
           $0 -a images.zip *.jpg webmaster\@flupp.org,metoo
@@ -231,7 +233,7 @@ whereas archive types zip and 7z need a temporary archive file on local disk.
 With option -s you can send any data coming from a pipe (STDIN) as a file
 without wasting local disc space.
 
 With option -s you can send any data coming from a pipe (STDIN) as a file
 without wasting local disc space.
 
-With option -X you can specify any URL parameter, e.g.: 
+With option -X you can specify any URL parameter, e.g.:
 fexsend -X autodelete=yes ...
 fexsend -X 'autodelete=no&locale=german' ...
 
 fexsend -X autodelete=yes ...
 fexsend -X 'autodelete=no&locale=german' ...
 
@@ -246,6 +248,14 @@ Partner program xx is an internet clipboard. See: xx -h
 
 Partner program fexget is for downloading. See: fexget -h
 
 
 Partner program fexget is for downloading. See: fexget -h
 
+fexsend stores the login data (server, user and auth-ID) in the file
+$HOME/.fex/id
+The format of this file is ([data] is optional):
+
+server-URL[!proxy[:port[:chunk-size]]
+e-mail-address
+auth-ID
+
 For temporary usage of a HTTP proxy use:
   $0 -P your_proxy:port:chunksize_in_MB file recipient
 Example:
 For temporary usage of a HTTP proxy use:
   $0 -P your_proxy:port:chunksize_in_MB file recipient
 Example:
@@ -286,11 +296,22 @@ my @rcamel = (
  *=(  __  /
     \\\\/\\\\/
 ',
  *=(  __  /
     \\\\/\\\\/
 ',
-'\e[A    \\\\/\\\\/
+"\e[A    \\\\/\\\\/ \n",
+"\e[A   //\\\\//\\\\\n"
+);
+
+my @rrcamel = (
+'\e[A
+ (_*p _  _
+   \\\\/ \/ \\
+    \  __  )=*
+    //\\\\//\\\\
 ',
 ',
-'\e[A   //\\\\//\\\\
-');
+"\e[A     \\\\/\\\\/ \n",
+"\e[A    //\\\\//\\\\\n"
+);
 
 
+autoflush STDOUT;
 autoflush STDERR;
 
 if ($windoof and not @ARGV and not $ENV{PROMPT}) {
 autoflush STDERR;
 
 if ($windoof and not @ARGV and not $ENV{PROMPT}) {
@@ -312,7 +333,7 @@ my @_ARGV = @ARGV; # save arguments
 our ($opt_q,$opt_h,$opt_H,$opt_v,$opt_m,$opt_c,$opt_k,$opt_d,$opt_l,$opt_I,
      $opt_K,$opt_D,$opt_u,$opt_f,$opt_a,$opt_C,$opt_R,$opt_M,$opt_L,$opt_Q,
      $opt_A,$opt_i,$opt_z,$opt_Z,$opt_b,$opt_P,$opt_x,$opt_X,$opt_V,$opt_U,
 our ($opt_q,$opt_h,$opt_H,$opt_v,$opt_m,$opt_c,$opt_k,$opt_d,$opt_l,$opt_I,
      $opt_K,$opt_D,$opt_u,$opt_f,$opt_a,$opt_C,$opt_R,$opt_M,$opt_L,$opt_Q,
      $opt_A,$opt_i,$opt_z,$opt_Z,$opt_b,$opt_P,$opt_x,$opt_X,$opt_V,$opt_U,
-     $opt_s,$opt_o,$opt_g,$opt_F,$opt_n,$opt_r,$opt_S,$opt_N);
+     $opt_s,$opt_o,$opt_g,$opt_F,$opt_n,$opt_r,$opt_S,$opt_N,$opt_T);
 
 if ($xx) {
   $opt_q = 1 if @ARGV and $ARGV[-1] eq '--' and pop @ARGV or not -t STDOUT;
 
 if ($xx) {
   $opt_q = 1 if @ARGV and $ARGV[-1] eq '--' and pop @ARGV or not -t STDOUT;
@@ -331,9 +352,9 @@ if ($xx) {
   ${'opt_@'} = ${'opt_!'} = ${'opt_+'} = ${'opt_.'} = ${'opt_/'} = 0;
   ${'opt_='} = ${'opt_#'} = '';
   $opt_u = $opt_f = $opt_a = $opt_C = $opt_i = $opt_b = $opt_P = $opt_X = '';
   ${'opt_@'} = ${'opt_!'} = ${'opt_+'} = ${'opt_.'} = ${'opt_/'} = 0;
   ${'opt_='} = ${'opt_#'} = '';
   $opt_u = $opt_f = $opt_a = $opt_C = $opt_i = $opt_b = $opt_P = $opt_X = '';
-  $opt_s = $opt_r = '';
+  $opt_s = $opt_r = $opt_T = '';
   $_ = "$fexhome/config.pl"; require if -f;
   $_ = "$fexhome/config.pl"; require if -f;
-  getopts('hHvcdognVDKlILUARWMFzZqQS@!+./r:m:k:u:f:a:s:C:i:b:P:x:X:N:=:#:')
+  getopts('hHvcdognVDKlILUARWMFzZqQS@!+./r:m:k:u:f:a:s:C:i:b:P:x:X:N:T:=:#:')
     or die $usage;
 
   if ($opt_H) {
     or die $usage;
 
   if ($opt_H) {
@@ -343,6 +364,29 @@ if ($xx) {
 
   if ($opt_V) {
     print "Version: $version\n";
 
   if ($opt_V) {
     print "Version: $version\n";
+    unless (@ARGV) {
+      print "Upgrade fexsend? ";
+      $_ = <STDIN>||'';
+      if (/^y/i) {
+        my $new = `wget -nv -O- http://fex.belwue.de/download/fexsend`;
+        my $newversion = $1 if $new =~ /version = (\d+)/;
+        if ($new !~ /upgrade fexsend/ or not $newversion) {
+          die "$0: bad update\n";
+        }
+        if ($newversion <= $version) {
+          die "$0: no newer version\n";
+        }
+        $_0 = abs_path($_0);
+        system qw'rsync -a',$_0,$_0.'_old';
+        exit $? if $?;
+        open $_0,'>',$_0 or die "$0: cannot write $_0. - $!\n";
+        print {$_0} $new;
+        close $_0;
+        exec $_0,qw'-V .';
+      }
+    }
+    exit;
+    exit if "@ARGV" eq '.';
   }
 
   if ($opt_K and $opt_D) {
   }
 
   if ($opt_K and $opt_D) {
@@ -477,6 +521,29 @@ if ($opt_I) {
   exit;
 }
 
   exit;
 }
 
+if ($opt_T) {
+  my ($up,$down);
+
+  $usage = "usage: $0 -T MB_up[:MB_down] [fexserver]\n";
+  if ($opt_T =~ /^(\d+)$/) {
+    $up = $down = $1;
+  } elsif ($opt_T =~ /^(\d+):(\d+)$/) {
+    $up = $1;
+    $down = $2;
+  } else {
+    die $usage;
+  }
+
+  if (@ARGV) {
+    nettest($ARGV[0],$up,$down);
+  } elsif ($fexcgi) {
+    nettest($fexcgi,$up,$down);
+  } else {
+    nettest('fex.belwue.de',$up,$down);
+  }
+  exit;
+}
+
 if (@ARGV > 1 and $ARGV[-1] =~ /(^|\/)anonymous/) {
   $fexcgi = $1 if $ARGV[-1] =~ s:(.+)/::;
   die "usage: $0 [options] file FEXSERVER/anonymous\n" unless $fexcgi;
 if (@ARGV > 1 and $ARGV[-1] =~ /(^|\/)anonymous/) {
   $fexcgi = $1 if $ARGV[-1] =~ s:(.+)/::;
   die "usage: $0 [options] file FEXSERVER/anonymous\n" unless $fexcgi;
@@ -805,7 +872,7 @@ sub menu {
   my $key;
   my $new;
   local $_;
   my $key;
   my $new;
   local $_;
-  
+
   system 'clear';
   print "\n";
   print "fexsend-$version\n";
   system 'clear';
   print "\n";
   print "fexsend-$version\n";
@@ -824,7 +891,7 @@ sub menu {
   print "\n";
   print "$from on $fexcgi\n";
   print "\n";
   print "\n";
   print "$from on $fexcgi\n";
   print "\n";
-  
+
   for (;;) {
     print "\n";
     print "[s]  send a file or directory\n";
   for (;;) {
     print "\n";
     print "[s]  send a file or directory\n";
@@ -835,15 +902,15 @@ sub menu {
     print "\n";
     print "your choice: ";
     $key = ReadKey(0);
     print "\n";
     print "your choice: ";
     $key = ReadKey(0);
-    if ($key eq 'q') { 
+    if ($key eq 'q') {
       print "$key\n";
       print "\n";
       print "Type [Cmd]W to close this window.\n";
       exit;
     }
       print "$key\n";
       print "\n";
       print "Type [Cmd]W to close this window.\n";
       exit;
     }
-    if ($key eq 'h') { 
+    if ($key eq 'h') {
       print "$key\n";
       print "$key\n";
-      print 
+      print
         "\n".
         "With fexsend you can send files of any size to any e-mail address.\n".
         "\n".
         "\n".
         "With fexsend you can send files of any size to any e-mail address.\n".
         "\n".
@@ -855,10 +922,10 @@ sub menu {
         "\n".
         "Do not forget to terminate each input line with [RETURN].\n".
         "\n".
         "\n".
         "Do not forget to terminate each input line with [RETURN].\n".
         "\n".
-        "See http://fex.rus.uni-stuttgart.de/ for more informations.\n";
+        "See http://fex.rus.uni-stuttgart.de/ for more information.\n";
       next;
     }
       next;
     }
-    if ($key eq 'u') { 
+    if ($key eq 'u') {
       print "$key\n";
       if ($0 =~ m:(^/client/|/sw/):) {
         print "\n";
       print "$key\n";
       if ($0 =~ m:(^/client/|/sw/):) {
         print "\n";
@@ -878,13 +945,13 @@ sub menu {
       }
       next;
     }
       }
       next;
     }
-    if ($key eq 'l') { 
+    if ($key eq 'l') {
       print "$key\n";
       system 'clear';
       &set_ID;
       next;
     }
       print "$key\n";
       system 'clear';
       &set_ID;
       next;
     }
-    if ($key eq 's' or $key eq "\n") { 
+    if ($key eq 's' or $key eq "\n") {
       print "s\n";
       &ask_file;
       next;
       print "s\n";
       &ask_file;
       next;
@@ -900,9 +967,9 @@ sub ask_file {
   my @files;
   my $qfiles;
   local $_;
   my @files;
   my $qfiles;
   local $_;
-  
+
   system 'clear';
   system 'clear';
-  
+
   &set_ID unless -s $idf;
 
   print "\n";
   &set_ID unless -s $idf;
 
   print "\n";
@@ -1021,7 +1088,7 @@ sub ask_file {
 sub set_ID {
   my ($server,$port,$user,$logo);
   local $_;
 sub set_ID {
   my ($server,$port,$user,$logo);
   local $_;
-  
+
   print "\n";
   for (;;) {
     print "F*EX server URL: ";
   print "\n";
   for (;;) {
     print "F*EX server URL: ";
@@ -1071,7 +1138,6 @@ sub set_ID {
     sendheader(
       "$server:$port",
       "GET /logo.jpg HTTP/1.0",
     sendheader(
       "$server:$port",
       "GET /logo.jpg HTTP/1.0",
-      "User-Agent: $useragent",
       "Connection: close",
     );
     $_ = <$SH>||'';
       "Connection: close",
     );
     $_ = <$SH>||'';
@@ -1092,7 +1158,7 @@ sub set_ID {
     close $logo;
     last;
   }
     close $logo;
     last;
   }
-  
+
   for (;;) {
     last if $user;
     print "Your login (e-mail address): ";
   for (;;) {
     last if $user;
     print "Your login (e-mail address): ";
@@ -1103,14 +1169,14 @@ sub set_ID {
       next;
     }
   }
       next;
     }
   }
-  
+
   for (;;) {
     last if $id;
     print "Your auth-ID for this account: ";
     $id = <STDIN>;
     $id =~ s/[\s\n]//g;
   }
   for (;;) {
     last if $id;
     print "Your auth-ID for this account: ";
     $id = <STDIN>;
     $id =~ s/[\s\n]//g;
   }
-  
+
   open $idf,'>',$idf or die "$0: cannot write to $idf - $!\n";
   print {$idf} "$server\n",
                "$user\n",
   open $idf,'>',$idf or die "$0: cannot write to $idf - $!\n";
   print {$idf} "$server\n",
                "$user\n",
@@ -1131,11 +1197,111 @@ sub set_ID {
 }
 
 
 }
 
 
+
+sub nettest {
+  my $url = shift;
+  my $up = shift;
+  my $down = shift;
+  my $bs = 2**16;
+  my ($length,$t0,$t1,$t2,$tt,$tb,$tc,$B,$kBs,$bt);
+
+  my $nettest = $sid = 'nettest';
+
+  $port ||= 80;
+  if ($url =~ s:^https.//::) {
+    $https = $port = 443;
+  } else {
+    $url =~ s:^http.//::;
+    $port = $1 if $url =~ s/:(\d+)//;
+  }
+  $url =~ s/[\/:].*//;
+  $server = $url;
+
+  if ($up) {
+    serverconnect($server,$port);
+    checkrecipient($nettest,$nettest);
+    warn "$0: send to $server:$port\n";
+    formdatapost(
+      from     => $nettest,
+      to       => $nettest,
+      id       => $nettest,
+      file     => $nettest,
+      size     => $up*M,
+      comment  => 'NOSTORE',
+    );
+  }
+
+  if ($down) {
+    serverconnect($server,$port);
+    warn "$0: receive from $server:$port\n";
+    sendheader("$server:$port","GET $proxy_prefix/ddd/$down HTTP/1.0");
+    $_ = <$SH>;
+    die "$0: no response from fex server $server\n" unless $_;
+    s/\r//;
+
+    if (/^HTTP\/[\d.]+ 2/) {
+      warn "<-- $_" if $opt_v;
+      while (<$SH>) {
+        s/\r//;
+        print "<-- $_" if $opt_v;
+        last if /^$/;
+        $length = $1 if /^Content-Length:\s*(\d+)/i;
+      }
+    } else {
+      s/HTTP\/[\d.]+ \d+ //;
+      die "$0: bad server reply: $_";
+    }
+
+    unless ($length) {
+      die "$0: no Content-Length header in server reply\n";
+    }
+
+
+    if (${'opt_+'}) {
+      print $rrcamel[0];
+      $tc = 0;
+    }
+
+    $t0 = $t1 = $t2 = int(time);
+    $B = 0;
+    while ($B < $length) {
+      $b = read $SH,$_,$bs or die "$0: cannot read after $B bytes - $!\n";
+      # defined($_ = <$SH>) or die "$0: cannot read after $B bytes - $!\n";
+      # $b = length;
+      $B += $b;
+      $bt += $b;
+      $t2 = time;
+      if (${'opt_+'} and int($t2*10)>$tc) {
+        print $rrcamel[$tc%2+1];
+        $tc = int($t2*10);
+      }
+      if (int($t2) > $t1) {
+        $kBs = int($bt/k/($t2-$t1));
+        $t1 = $t2;
+        $bt = 0;
+        printf STDERR "nettest: %d MB (%d%%) %d kB/s        \r",
+          int($B/M),int(100*$B/$length),$kBs;
+      }
+    }
+    close $SH;
+
+    $tt = $t2-$t0;
+    $kBs = int($B/k/($tt||1));
+    if (${'opt_+'}) {
+      print $rrcamel[1];
+      print $rrcamel[2];
+    }
+    printf STDERR "nettest: %d MB in %d s = %d kB/s        \n",
+      int($B/M),$tt,$kBs;
+  }
+}
+
+
 # read one key from terminal in raw mode
 sub ReadKey {
   my $key;
   local $SIG{INT} = sub { stty('reset'); exit };
 # read one key from terminal in raw mode
 sub ReadKey {
   my $key;
   local $SIG{INT} = sub { stty('reset'); exit };
-  
+
   stty('raw');
   # loop necessary for ESXi support
   while (not defined $key) {
   stty('raw');
   # loop necessary for ESXi support
   while (not defined $key) {
@@ -1339,7 +1505,6 @@ sub list {
           sendheader(
             "$server:$port",
             "GET $proxy_prefix/fop/$2/$2?LIST HTTP/1.1",
           sendheader(
             "$server:$port",
             "GET $proxy_prefix/fop/$2/$2?LIST HTTP/1.1",
-            "User-Agent: $useragent",
           );
           $_ = <$SH>||'';
           s/\r//;
           );
           $_ = <$SH>||'';
           s/\r//;
@@ -1364,7 +1529,7 @@ sub list {
       die "$0: file \#$a not found in fexlist\n";
     }
   }
       die "$0: file \#$a not found in fexlist\n";
     }
   }
-   
+
   @r = formdatapost(
     from       => $from,
     to         => $opt_l ? '*' : $from,
   @r = formdatapost(
     from       => $from,
     to         => $opt_l ? '*' : $from,
@@ -1391,7 +1556,7 @@ sub list {
       s/&amp;/&/g;
       s/&quot;/\"/g;
       s/&lt;/</g;
       s/&amp;/&/g;
       s/&quot;/\"/g;
       s/&lt;/</g;
-      if (/^(to (.+) :)/) { 
+      if (/^(to (.+) :)/) {
         $s = $2 =~ /$a/;
         print "\n$_\n" if $s;
         print {$fexlist} "\n$_\n";
         $s = $2 =~ /$a/;
         print "\n$_\n" if $s;
         print {$fexlist} "\n$_\n";
@@ -1489,7 +1654,6 @@ sub delete_file_number {
         sendheader(
           "$server:$port",
           "GET $proxy_prefix/fop/$2/$2?DELETE HTTP/1.1",
         sendheader(
           "$server:$port",
           "GET $proxy_prefix/fop/$2/$2?DELETE HTTP/1.1",
-          "User-Agent: $useragent",
         );
         $_ = <$SH>||'';
         s/\r//;
         );
         $_ = <$SH>||'';
         s/\r//;
@@ -1528,14 +1692,13 @@ sub delete_file {
     serverconnect($server,$port);
     query_sid($server,$port) unless $anonymous;
   }
     serverconnect($server,$port);
     query_sid($server,$port) unless $anonymous;
   }
-  
+
   $file = urlencode($file);
   sendheader(
     "$server:$port",
     "GET $proxy_prefix/fop/$to/$from/$file?id=$sid&DELETE HTTP/1.1",
   $file = urlencode($file);
   sendheader(
     "$server:$port",
     "GET $proxy_prefix/fop/$to/$from/$file?id=$sid&DELETE HTTP/1.1",
-    "User-Agent: $useragent",
   );
   );
-  
+
   while (<$SH>) {
     s/\r//;
     printf "<-- $_"if $opt_v;
   while (<$SH>) {
     s/\r//;
     printf "<-- $_"if $opt_v;
@@ -1917,7 +2080,7 @@ sub send_fex {
         }
       }
       if ($from eq $to or $from =~ /^\Q$to\E@/i
         }
       }
       if ($from eq $to or $from =~ /^\Q$to\E@/i
-          or $nomail or $anonymous or $nonot) 
+          or $nomail or $anonymous or $nonot)
       {
         print "$recipient\n" if $recipient;
         print "$location\n"  if $location;
       {
         print "$recipient\n" if $recipient;
         print "$location\n"  if $location;
@@ -2174,10 +2337,10 @@ sub get_xx {
 
 sub formdatapost {
   my %P = @_;
 
 sub formdatapost {
   my %P = @_;
-  my ($boundary,$filename,$length,$buf,$file,$fpsize,$resume,$seek);
+  my ($boundary,$filename,$length,$buf,$file,$fpsize,$resume,$seek,$nettest);
   my ($flink);
   my (@hh,@hb,@r,@pv,$to);
   my ($flink);
   my (@hh,@hb,@r,@pv,$to);
-  my ($bytes,$t,$bt);
+  my ($bytes,$b,$t,$bt);
   my ($t0,$t1,$t2,$tt,$tc);
   my $bs = 2**16;        # blocksize for reading and sending file
   my $fileid = int(time);
   my ($t0,$t1,$t2,$tt,$tc);
   my $bs = 2**16;        # blocksize for reading and sending file
   my $fileid = int(time);
@@ -2285,7 +2448,7 @@ sub formdatapost {
       }
       # print "calculating archive size... ";
       debug("cd $dittodir;$ditto -");
       }
       # print "calculating archive size... ";
       debug("cd $dittodir;$ditto -");
-      open $ditto,"cd $dittodir;$ditto - 2>$error|" 
+      open $ditto,"cd $dittodir;$ditto - 2>$error|"
         or die "$0: cannot run ditto - $!\n";
       $t0 = int(time) if -t STDOUT;
       while ($b = read $ditto,$_,$bs) {
         or die "$0: cannot run ditto - $!\n";
       $t0 = int(time) if -t STDOUT;
       while ($b = read $ditto,$_,$bs) {
@@ -2315,6 +2478,12 @@ sub formdatapost {
       undef $SH; # force reconnect (timeout!)
     }
 
       undef $SH; # force reconnect (timeout!)
     }
 
+    elsif ($P{to} eq 'nettest') {
+      $filename = $nettest = 'nettest';
+      $filesize = $P{size};
+      $fileid = 0;
+    }
+
     # single file
     else {
       $filename = encode_utf8(${'opt_='} || $file);
     # single file
     else {
       $filename = encode_utf8(${'opt_='} || $file);
@@ -2334,7 +2503,7 @@ sub formdatapost {
 
     $filename .= '.gpg' if $opt_g;
 
 
     $filename .= '.gpg' if $opt_g;
 
-    unless ($opt_d) {
+    unless ($opt_d or $nettest) {
       if ($opt_g) {
         $filesize = -1;
         $fileid = int(time);
       if ($opt_g) {
         $filesize = -1;
         $fileid = int(time);
@@ -2363,7 +2532,7 @@ sub formdatapost {
 
   unless ($SH) {
     serverconnect($server,$port);
 
   unless ($SH) {
     serverconnect($server,$port);
-    query_sid($server,$port) unless $anonymous;
+    query_sid($server,$port) unless $anonymous or $nettest;
   }
 
   $P{id} = $sid; # ugly hack!
   }
 
   $P{id} = $sid; # ugly hack!
@@ -2371,7 +2540,7 @@ sub formdatapost {
   $filename =~ s/\\/_/g; # \ is a illegal character for fexsrv
 
   # ask server if this file has been already sent
   $filename =~ s/\\/_/g; # \ is a illegal character for fexsrv
 
   # ask server if this file has been already sent
-  if ($file and not $xx) {
+  if ($file and not $xx and not $nettest) {
     if (not $opt_d and $opt_o) {
       # delete before overwrite
       delete_file($from,$to,$filename);
     if (not $opt_d and $opt_o) {
       # delete before overwrite
       delete_file($from,$to,$filename);
@@ -2548,6 +2717,8 @@ sub formdatapost {
           print "Fast forward to byte $seek (resuming)\n";
           readahead($file,$seek);
         }
           print "Fast forward to byte $seek (resuming)\n";
           readahead($file,$seek);
         }
+      } elsif ($nettest) {
+        #
       } else {
         if ($opt_g) {
           my $fileq = quote($file);
       } else {
         if ($opt_g) {
           my $fileq = quote($file);
@@ -2564,8 +2735,17 @@ sub formdatapost {
 
       print $rcamel[0] if ${'opt_+'};
 
 
       print $rcamel[0] if ${'opt_+'};
 
+      $buf = '#' x $bs if $nettest;
+
       $SIG{ALRM} = sub { retry("timed out") };
       $SIG{ALRM} = sub { retry("timed out") };
-      while (my $b = read $file,$buf,$bs) {
+
+      while ($bytes < $fpsize) {
+        if ($nettest) {
+          $b = $bs;
+        } else {
+          $b = read $file,$buf,$bs;
+          last if $b == 0;
+        }
         alarm($timeout*2);
         if ($https) {
           print {$SH} $buf or &sigpipehandler;
         alarm($timeout*2);
         if ($https) {
           print {$SH} $buf or &sigpipehandler;
@@ -2574,7 +2754,7 @@ sub formdatapost {
         }
         alarm(0);
         $bytes += $b;
         }
         alarm(0);
         $bytes += $b;
-        if ($filesize > 0 and $bytes+$seek > $filesize) {
+        if (not $nettest and $filesize > 0 and $bytes+$seek > $filesize) {
           if ($tpid) {
             kill 9,$tpid;
             unlink $list;
           if ($tpid) {
             kill 9,$tpid;
             unlink $list;
@@ -2619,7 +2799,9 @@ sub formdatapost {
         last if $filesize > 0 and $bytes >= $fpsize;
         sleep 1 while ($opt_m and $bytes/k/(time-$t0||1) > $opt_m);
       }
         last if $filesize > 0 and $bytes >= $fpsize;
         sleep 1 while ($opt_m and $bytes/k/(time-$t0||1) > $opt_m);
       }
-      close $file; # or die "$0: error while reading $file - $!\n";
+
+      close $file unless $nettest;
+
       $tt = ($t2-$t0)||1;
 
       print $rcamel[2] if ${'opt_+'};
       $tt = ($t2-$t0)||1;
 
       print $rcamel[2] if ${'opt_+'};
@@ -2630,7 +2812,7 @@ sub formdatapost {
         kill 9,$tpid;
         unlink $list;
       }
         kill 9,$tpid;
         unlink $list;
       }
-      
+
       if ($fileid =~ /[a-z]/ and not ($opt_s or $opt_g)) {
         if ($opt_a) {
           if ($fileid ne md5_hex(fmd(@ARGV))) {
       if ($fileid =~ /[a-z]/ and not ($opt_s or $opt_g)) {
         if ($opt_a) {
           if ($fileid ne md5_hex(fmd(@ARGV))) {
@@ -2644,7 +2826,7 @@ sub formdatapost {
           }
         }
       }
           }
         }
       }
-      
+
       unless ($opt_q) {
         if (not $chunksize and $bytes+$seek < $filesize) {
           die "$0: \"$file\" filesize has shrunk while uploading\n";
       unless ($opt_q) {
         if (not $chunksize and $bytes+$seek < $filesize) {
           die "$0: \"$file\" filesize has shrunk while uploading\n";
@@ -2652,7 +2834,7 @@ sub formdatapost {
 
         if ($seek or $chunksize and $chunksize < $filesize) {
           if ($fpsize>2*M) {
 
         if ($seek or $chunksize and $chunksize < $filesize) {
           if ($fpsize>2*M) {
-            printf STDERR "%s: %d MB in %d s (%d kB/s)",
+            printf STDERR "%s: %d MB in %d s = %d kB/s",
                            $opt_s||$opt_a||$file,
                            int($bytes/M),
                            $tt,
                            $opt_s||$opt_a||$file,
                            int($bytes/M),
                            $tt,
@@ -2664,7 +2846,7 @@ sub formdatapost {
                             $chunk,int(($bytes+$seek)/M);
             }
           } else {
                             $chunk,int(($bytes+$seek)/M);
             }
           } else {
-            printf STDERR "%s: %d kB in %d s (%d kB/s)",
+            printf STDERR "%s: %d kB in %d s = %d kB/s",
                           $opt_s||$opt_a||$file,
                           int($bytes/k),
                           $tt,
                           $opt_s||$opt_a||$file,
                           int($bytes/k),
                           $tt,
@@ -2678,13 +2860,13 @@ sub formdatapost {
           }
         } else {
           if ($bytes>2*M) {
           }
         } else {
           if ($bytes>2*M) {
-            printf STDERR "%s: %d MB in %d s (%d kB/s)        \n",
+            printf STDERR "%s: %d MB in %d s = %d kB/s        \n",
                           $opt_s||$opt_a||$file,
                           int($bytes/M),
                           $tt,
                           int($bytes/k/$tt);
           } else {
                           $opt_s||$opt_a||$file,
                           int($bytes/M),
                           $tt,
                           int($bytes/k/$tt);
           } else {
-            printf STDERR "%s: %d kB in %d s (%d kB/s)        \n",
+            printf STDERR "%s: %d kB in %d s = %d kB/s        \n",
                           $opt_s||$opt_a||$file,
                           int($bytes/k),
                           $tt,
                           $opt_s||$opt_a||$file,
                           int($bytes/k),
                           $tt,
@@ -2692,7 +2874,7 @@ sub formdatapost {
           }
         }
 
           }
         }
 
-        if (-t STDOUT and not ($opt_s or $opt_g)) {
+        if (-t STDOUT and not ($opt_s or $opt_g or $nettest)) {
           print STDERR "waiting for server ok..."
         }
       }
           print STDERR "waiting for server ok..."
         }
       }
@@ -2700,6 +2882,7 @@ sub formdatapost {
 
     autoflush $SH 1;
     print {$SH} "\r\n--$boundary--\r\n";
 
     autoflush $SH 1;
     print {$SH} "\r\n--$boundary--\r\n";
+    # return if $nettest;
 
     # special handling of streaming file because of stunnel tcp shutdown bug
     if ($opt_s or $opt_g) {
 
     # special handling of streaming file because of stunnel tcp shutdown bug
     if ($opt_s or $opt_g) {
@@ -2860,8 +3043,7 @@ sub query_file {
   my ($response,$fexsrv,$cc);
   local $_;
 
   my ($response,$fexsrv,$cc);
   local $_;
 
-  $to =~ s/,.*//;
-  $to =~ s/:\w+=.*//;
+  $to =~ s/[,:].*//;
   $to = $AB{$to} if $AB{$to};
   $filename =~ s/([^_=:,;<>()+.\w\-])/'%'.uc(unpack("H2",$1))/ge; # urlencode
   if ($skey) {
   $to = $AB{$to} if $AB{$to};
   $filename =~ s/([^_=:,;<>()+.\w\-])/'%'.uc(unpack("H2",$1))/ge; # urlencode
   if ($skey) {
@@ -3038,14 +3220,15 @@ sub query_sid {
   $sid = $id;
 
   if ($port eq 443 or $proxy) {
   $sid = $id;
 
   if ($port eq 443 or $proxy) {
+    return if $opt_d;
     return if $features;    # early return if we know enough
     return if $features;    # early return if we know enough
-    $req = "OPTIONS /FEX HTTP/1.1";
-    $req = "HEAD / HTTP/1.1";
+    $req = "OPTIONS /FEX HTTP/1.1"; # does not work with (some) proxies
+    $req = "GET /SID HTTP/1.1";     # needed as FEATURES query
   } else {
     $req = "GET /SID HTTP/1.1";
   }
 
   } else {
     $req = "GET /SID HTTP/1.1";
   }
 
-  sendheader("$server:$port",$req,"User-Agent: $useragent");
+  sendheader("$server:$port",$req);
   $_ = <$SH>;
   unless (defined $_ and /\w/) {
     print "\n" if $opt_v;
   $_ = <$SH>;
   unless (defined $_ and /\w/) {
     print "\n" if $opt_v;
@@ -3059,7 +3242,7 @@ sub query_sid {
     close $SH;
     serverconnect($server,$port);
     $req = "GET /SID HTTP/1.0";
     close $SH;
     serverconnect($server,$port);
     $req = "GET /SID HTTP/1.0";
-    sendheader("$server:$port",$req,"User-Agent: $useragent");
+    sendheader("$server:$port",$req);
     $_ = <$SH>;
     unless (defined $_ and /\w/) {
       print "\n" if $opt_v;
     $_ = <$SH>;
     unless (defined $_ and /\w/) {
       print "\n" if $opt_v;
@@ -3129,7 +3312,7 @@ sub xxget {
   $xx =~ s:.*/::;
   $url = "$proxy_prefix/fop/$from/$from/$xx?ID=$id";
 
   $xx =~ s:.*/::;
   $url = "$proxy_prefix/fop/$from/$from/$xx?ID=$id";
 
-  sendheader("$server:$port","GET $url HTTP/1.0","User-Agent: $useragent");
+  sendheader("$server:$port","GET $url HTTP/1.0");
   http_response();
   while (<$SH>) {
     s/\r//;
   http_response();
   while (<$SH>) {
     s/\r//;
@@ -3218,6 +3401,7 @@ sub checkrecipient {
   $_ = shift @r or die "$0: no reply from server\n";
 
   if (/ 2\d\d /) {
   $_ = shift @r or die "$0: no reply from server\n";
 
   if (/ 2\d\d /) {
+    return if $to eq 'nettest';
     foreach (@r) {
       last if /^$/;
       if (s/X-(Recipient: .+)/$1\n/) {
     foreach (@r) {
       last if /^$/;
       if (s/X-(Recipient: .+)/$1\n/) {
@@ -3300,8 +3484,9 @@ sub readahead {
 
 sub fileid {
   my $file = shift;
 
 sub fileid {
   my $file = shift;
-  my @s = stat($file);
-  
+  my $dirmode = shift;
+  my @s = $dirmode ? lstat($file) : stat($file);
+
   if (@s) {
     return md5_hex($file.$s[0].$s[1].$s[7].$s[9]);
   } else {
   if (@s) {
     return md5_hex($file.$s[0].$s[1].$s[7].$s[9]);
   } else {
@@ -3314,9 +3499,10 @@ sub fileid {
 sub get_mutt_alias {
   my $to = shift;
   my $ma = $HOME.'/.mutt/aliases';
 sub get_mutt_alias {
   my $to = shift;
   my $ma = $HOME.'/.mutt/aliases';
-  my $alias;
+  my ($alias,$options);
   local $_;
 
   local $_;
 
+  $to =~ s/(:.+)// and $options = $1;
   open $ma,$ma or return $to;
   while (<$ma>) {
     if (/^alias \Q$to\E\s/i) {
   open $ma,$ma or return $to;
   while (<$ma>) {
     if (/^alias \Q$to\E\s/i) {
@@ -3333,11 +3519,13 @@ sub get_mutt_alias {
       if (/@/) {
         $alias = $_;
         warn "$0: found mutt alias $to = $alias\n";
       if (/@/) {
         $alias = $_;
         warn "$0: found mutt alias $to = $alias\n";
+        $alias .= $options if $options;
         last;
       }
     }
   }
   close $ma;
         last;
       }
     }
   }
   close $ma;
+  $to = "$to:$options" if $options;
   return ($alias||$to);
 }
 
   return ($alias||$to);
 }
 
@@ -3356,6 +3544,9 @@ sub fmd {
           next if $file eq '..';
           if ($file eq '.') {
             $fmd .= fileid($dir);
           next if $file eq '..';
           if ($file eq '.') {
             $fmd .= fileid($dir);
+          } elsif (-l "$dir/$file") {
+            # hack for dangling symlinks: do not raise an error
+            $fmd .= fileid("$dir/$file",'dirmode');
           } else {
             $fmd .= fmd("$dir/$file");
           }
           } else {
             $fmd .= fmd("$dir/$file");
           }
@@ -3623,8 +3814,10 @@ sub sendheader {
   my $head;
 
   push @head,"Host: $sp";
   my $head;
 
   push @head,"Host: $sp";
+  push @head,"User-Agent: $useragent";
 
   foreach $head (@head) {
 
   foreach $head (@head) {
+    chomp $head;
     print "--> $head\n" if $opt_v;
     print {$SH} $head,"\r\n";
   }
     print "--> $head\n" if $opt_v;
     print {$SH} $head,"\r\n";
   }