httpd-test-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Stas Bekman <s...@stason.org>
Subject Re: sticky preferences in Apache-Test
Date Wed, 07 Jan 2004 04:06:05 GMT
OK, here is a an extended version of Randy's original work to be able to save 
and reuse A-T config. I've added first time interactive configuration feature 
(similar to CPAN.pm). It took me a long time to polish it as there are so many 
possible cases on how this new feature can be used, it was pretty tricky. I 
haven't quite tested -save too. Also there could be some issues with win32, 
I'm sure Randy will post a fix. It's a pretty big patch, let me know if you 
want me to commit it and we will take it from there, making it easier to users 
to test it. We probably need to polish it somewhat.

Also notice that I did a few tweaks to mp2 source to support new A-T features. 
so you need to apply it from modperl-2.0, but you can and should test in 
Apache-Test/ as well.

I did various tests including:

1. 'make test' from Apache-Test.
        o delete blib
        o delete local lib/Apache/TestConfigData.pm
        o delete global Apache/TestConfigData.pm
1a - interactive config should kick in first time
1b - second time it should run without interactive config
1c - run 'make install',
      o delete blib
      o delete local lib/Apache/TestConfigData.pm
      'make test' should run without interactive config
1d - now remove the globally installed Apache/TestConfigData.pm,
      and run 't/TEST -httpd /path'
      interactive config shouldn't kick in

2. repeat 1 but replace 'make test' with 't/TEST'


3. 'make test' from modperl-2.0, first
        o delete local lib/Apache/TestConfigData.pm
        o delete global Apache/TestConfigData.pm
3a - interactive config shouldn't kick in first time,
      since we explicitly path -httpd behind the scenes
3b - 'make install' (which installs a custom config),
      now build mod_perl against a different httpd path, make sure
      that 'make test' uses that different httpd path and not the one
      from the custom config

4. custom config wasn't done during install of Apache-Test (e.g. rpm
    binary package).
        o delete global Apache/TestConfigData.pm
    pick some module which uses Apache-Test
4a. run 'make test' - interactive config should kick in
    make sure that it was saved in ~/.apache-test/Apache/TestConfigData.pm
    (because there is no global config)
4b. run 'make test' - interactive config should not kick in second time

5. -save option (test to be written) main issue is to avoid the situation
when 'Apache/TestConfigData.pm' was written to a root owned directory
and a non-root user is trying to -save (need to save into
~/.apache-test/Apache/TestConfigData.pm, if the custom_config_path
returns a path owned by root).


ok here it the patch:

Index: Apache-Test/Makefile.PL
===================================================================
RCS file: /home/cvs/httpd-test/perl-framework/Apache-Test/Makefile.PL,v
retrieving revision 1.15
diff -u -r1.15 Makefile.PL
--- Apache-Test/Makefile.PL	6 Jan 2004 19:17:59 -0000	1.15
+++ Apache-Test/Makefile.PL	7 Jan 2004 03:46:57 -0000
@@ -12,12 +12,14 @@

  use ExtUtils::MakeMaker;
  use Symbol;
+use File::Spec::Functions qw(catfile);
+use FindBin;

  use Apache::TestMM qw(test); #enable 'make test'
+use Apache::TestRun;

  my $VERSION;

-
  Apache::TestMM::filter_args();

  my @scripts = qw(t/TEST);
@@ -35,6 +37,8 @@
      add_Apache__test_target();
  }

+write_custom_config_file_stub();
+
  WriteMakefile(
      NAME      => 'Apache::Test',
      VERSION   => $VERSION,
@@ -99,3 +103,17 @@
          $string;
      };
  }
+
+# write the custom test file so it'll be copied to blib if updated
+# during 'make test' and then installed system-wide
+sub write_custom_config_file_stub {
+    # It doesn't matter whether it gets written under modperl-2.0/lib
+    # or Apache-Test/lib root, since Apache::TestRun uses the same
+    # logic and will update that file with real config data, which
+    # 'make install' will then pick and install system-wide
+    my $path = catfile $FindBin::Bin, "lib",
+        Apache::TestRun::CUSTOM_CONFIG_FILE;
+    # write an empty stub
+    Apache::TestRun::custom_config_write($path, '');
+}
+
Index: Apache-Test/lib/Apache/TestConfig.pm
===================================================================
RCS file: 
/home/cvs/httpd-test/perl-framework/Apache-Test/lib/Apache/TestConfig.pm,v
retrieving revision 1.192
diff -u -r1.192 TestConfig.pm
--- Apache-Test/lib/Apache/TestConfig.pm	5 Jan 2004 23:40:53 -0000	1.192
+++ Apache-Test/lib/Apache/TestConfig.pm	7 Jan 2004 03:46:58 -0000
@@ -17,6 +17,9 @@
  use constant IS_MOD_PERL_2_BUILD => IS_MOD_PERL_2 &&
      require Apache::Build && Apache::Build::IS_MOD_PERL_BUILD();

+use constant IS_APACHE_TEST_BUILD =>
+    grep { -e "$_/lib/Apache/TestConfig.pm" } qw(. ..);
+
  use Symbol ();
  use File::Copy ();
  use File::Find qw(finddepth);
@@ -305,20 +308,6 @@

      $vars->{target} ||= (WIN32 ? 'Apache.exe' : 'httpd');

-    unless ($vars->{httpd}) {
-        #sbindir should be bin/ with the default layout
-        #but its eaiser to workaround apxs than fix apxs
-        for my $dir (map { $vars->{$_} } qw(sbindir bindir)) {
-            next unless defined $dir;
-            my $httpd = catfile $dir, $vars->{target};
-            next unless -x $httpd;
-            $vars->{httpd} = $httpd;
-            last;
-        }
-
-        $vars->{httpd} ||= $self->default_httpd;
-    }
-
      if ($vars->{httpd}) {
          my @chunks = splitdir $vars->{httpd};
          #handle both $prefix/bin/httpd and $prefix/Apache.exe
@@ -1366,7 +1355,7 @@
          }
      }

-    my $exe = $vars->{apxs} || $vars->{httpd};
+    my $exe = $vars->{apxs} || $vars->{httpd} || '';
      # if httpd.conf is older than executable
      push @reasons,
          "$exe is newer than $vars->{t_conf_file}"
Index: Apache-Test/lib/Apache/TestRun.pm
===================================================================
RCS file: /home/cvs/httpd-test/perl-framework/Apache-Test/lib/Apache/TestRun.pm,v
retrieving revision 1.131
diff -u -r1.131 TestRun.pm
--- Apache-Test/lib/Apache/TestRun.pm	19 Dec 2003 09:16:14 -0000	1.131
+++ Apache-Test/lib/Apache/TestRun.pm	7 Jan 2004 03:46:58 -0000
@@ -11,20 +11,29 @@
  use Apache::TestHarness ();
  use Apache::TestTrace;

+use Cwd;
  use File::Find qw(finddepth);
-use File::Spec::Functions qw(catfile);
+use File::Spec::Functions qw(catfile catdir);
  use File::Basename qw(basename dirname);
  use Getopt::Long qw(GetOptions);
  use Config;

+use constant IS_APACHE_TEST_BUILD => Apache::TestConfig::IS_APACHE_TEST_BUILD;
+
  use constant STARTUP_TIMEOUT => 300; # secs (good for extreme debug cases)
+
+use constant CUSTOM_CONFIG_FILE => 'Apache/TestConfigData.pm';
+
  use subs qw(exit_shell exit_perl);

+my $original_command;
+my $orig_cwd;
+
  my %core_files  = ();
  my %original_t_perms = ();

  my @std_run      = qw(start-httpd run-tests stop-httpd);
-my @others       = qw(verbose configure clean help ssl http11 bugreport);
+my @others       = qw(verbose configure clean help ssl http11 bugreport save);
  my @flag_opts    = (@std_run, @others);
  my @string_opts  = qw(order trace);
  my @ostring_opts = qw(proxy ping);
@@ -57,9 +66,24 @@
     'ssl'             => 'run tests through ssl',
     'proxy'           => 'proxy requests (default proxy is localhost)',
     'trace=T'         => 'change tracing default to: warning, notice, info, 
debug, ...',
+   'save'            => 'save test paramaters into Apache::TestConfigData',
     (map { $_, "\U$_\E url" } @request_opts),
  );

+# variables stored in $Apache::TestConfigData::vars
+my @data_vars_must = qw(httpd apxs);
+my @data_vars_opt  = qw(user group port);
+# mapping from $Apache::TestConfigData::vars to $ENV settings
+my %vars_to_env = (
+    httpd => 'APACHE',
+    user  => 'APACHE_USER',
+    group => 'APACHE_GROUP',
+    apxs  => 'APXS',
+    port  => 'APACHE_PORT',
+);
+
+custom_config_load();
+
  sub fixup {
      #make sure we use an absolute path to perl
      #else Test::Harness uses the perl in our PATH
@@ -435,6 +459,19 @@
      $test_config->cmodules_configure;
      $test_config->generate_httpd_conf;
      $test_config->save;
+
+    # save if
+    # 1) requested to save
+    # 2) no saved config yet
+    # 3) A-T build (since it'll override the global file)
+    # the only side-effect of (3) is that if config is changed during
+    # A-T build it'll change the global custom config if that exists,
+    # but it shouldn't affect normal users who won't do it more than once
+    if ($self->{opts}->{save}
+        or !$self->custom_config_exists()
+        or IS_APACHE_TEST_BUILD) {
+        $self->custom_config_save();
+    }
  }

  sub try_exit_opts {
@@ -470,12 +507,17 @@
      my $test_config = $self->{test_config};

      unless ($test_config->{vars}->{httpd}) {
-        error "no test server configured, please specify an httpd or ".
-              ($test_config->{APXS} ?
-               "an apxs other than $test_config->{APXS}" : "apxs").
-               " or put either in your PATH. For example:\n" .
-               "$0 -httpd /path/to/bin/httpd";
-        exit_perl 0;
+        $self->opt_clean(1);
+        # this method restarts the whole program via exec
+        # so it never returns
+        $self->custom_config_first_time;
+    }
+
+    # if we have gotten that far we know at least about the location
+    # of httpd, so let's save it if we haven't saved any custom
+    # configs yet
+    unless ($self->custom_config_exists()) {
+        $self->custom_config_save();
      }

      my $opts = $self->{opts};
@@ -541,6 +583,10 @@

  sub new_test_config {
      my $self = shift;
+    for (@data_vars_must, @data_vars_opt) {
+        next unless $Apache::TestConfigData::vars->{$_};
+        $self->{conf_opts}->{$_} ||= $Apache::TestConfigData::vars->{$_};
+    }
      Apache::TestConfig->new($self->{conf_opts});
  }

@@ -563,12 +609,10 @@
      }
      close $sh;

-    # reconstruct argv, preserve multiwords args, eg 'PerlTrace all'
-    my $argv = join " ", map { /^-/ ? $_ : qq['$_'] } @ARGV;
-    my $command = "ulimit -c unlimited; $0 $argv";
-    warning "setting ulimit to allow core files\n$command";
-    exec $command;
-    die "exec $command has failed"; # shouldn't be reached
+    $original_command = "ulimit -c unlimited; $original_command";
+    warning "setting ulimit to allow core files\n$original_command";
+    exec $original_command;
+    die "exec $original_command has failed"; # shouldn't be reached
  }

  sub set_ulimit {
@@ -587,6 +631,10 @@
  sub run {
      my $self = shift;

+    # reconstruct argv, preserve multiwords args, eg 'PerlTrace all'
+    my $argv = join " ", map { /^-/ ? $_ : qq['$_'] } @ARGV;
+    $original_command = "$0 $argv";
+    $orig_cwd = Cwd::cwd();
      $self->set_ulimit;
      $self->set_env; #make sure these are always set

@@ -1087,6 +1135,324 @@
      CORE::exit $_[0];
  }

+# determine where the configuration file Apache/TestConfigData.pm
+# lives. The order searched is:
+# 1) $ENV{HOME}/.apache-test/
+# 2) in global @INC (if preconfigured already)
+# 3) in local lib (for build times)
+my $custom_config_path;
+sub custom_config_path {
+
+    return $custom_config_path if $custom_config_path;
+
+    my @global_inc = ();
+    my @build_inc  = ();
+    for (@INC) {
+        my $rel_dir = basename $_;
+        $rel_dir eq 'lib' ? push(@build_inc, $_) : push(@global_inc, $_);
+    }
+
+    # XXX $ENV{HOME} isn't propagated in mod_perl
+    unshift @global_inc, catdir $ENV{HOME}, '.apache-test' if $ENV{HOME};
+
+    for (@global_inc, @build_inc) {
+        my $candidate = File::Spec->rel2abs(catfile $_, CUSTOM_CONFIG_FILE);
+        next unless -e $candidate;
+        return $custom_config_path = $candidate;
+    }
+
+    return '';
+}
+
+sub custom_config_exists {
+    my $self = shift;
+    # custom config gets loaded via custom_config_load when this
+    # package is loaded. it's enough to check whether we have a custom
+    # config for 'httpd'.
+    my $httpd = $Apache::TestConfigData::vars->{httpd} || '';
+    return $httpd && -e $httpd ? 1 : 0;
+}
+
+sub custom_config_save {
+    my $self = shift;
+    my $vars = $self->{test_config}->{vars};
+    my $conf_opts = $self->{conf_opts};
+    my $config_dump = '';
+
+    # minimum httpd needs to be set
+    return 0 unless $vars->{httpd} or $Apache::TestConfigData::vars->{httpd};
+
+    # it doesn't matter how these vars were set (apxs may get set
+    # using the path to httpd, or the other way around)
+    for (@data_vars_must) {
+        next unless my $var = $vars->{$_} || $conf_opts->{$_};
+        $config_dump .= qq{    '$_' => '$var',\n};
+    }
+
+    # save these vars only if they were explicitly set via command line
+    # options. For example if someone builds A-T as user 'foo', then
+    # installs it as root and we save it, all users will now try to
+    # configure under that user 'foo' which won't quite work.
+    for (@data_vars_opt) {
+        next unless my $var = $conf_opts->{$_};
+        $config_dump .= qq{    '$_' => '$var',\n};
+    }
+
+    my $path;
+    # if A-T build always update the build file, regardless whether
+    # the global file already exists, since 'make install' will
+    # overwrite it
+    if (IS_APACHE_TEST_BUILD) {
+        $path = catfile $vars->{top_dir}, 'lib', CUSTOM_CONFIG_FILE;
+    }
+    elsif ($path = custom_config_path() ) {
+        # do nothing, the config file already exists (global)
+        debug "Found custom config '$path'";
+    }
+    else {
+        # next try a global location, as if it was configured before
+        # Apache::Test's 'make install'
+        my $base = dirname dirname $INC{"Apache/TestRun.pm"};
+        $path = catdir $base, CUSTOM_CONFIG_FILE;
+    }
+
+    # check whether we can write to the directory of the chosen path
+    if ($path) {
+        # first make sure that the file is writable if it exists
+        # already (it might be non-writable if installed via EU::MM)
+        if (-e $path) {
+            my $mode = (stat _)[2];
+            $mode |= 0200;
+            chmod $mode, $path; # it's ok if we fail
+        }
+
+        # try to open for append
+        my $fh = Symbol::gensym;
+        if (open $fh, ">>$path") {
+            close $fh;
+        }
+        else {
+            $path = ''; # can't use that path to write custom config
+        }
+    }
+
+    # if we have no path yet, try to use ~
+    if (!$path and $ENV{HOME}) {
+        $path = catfile $ENV{HOME}, '.apache-test', CUSTOM_CONFIG_FILE;
+    }
+
+    if ($path) {
+        custom_config_write($path, $config_dump);
+        return 1;
+    }
+
+    return 0; # failed to write config XXX: should we croak?
+}
+
+sub custom_config_write {
+    my($path, $config_dump) = @_;
+
+    my $pkg = << "EOC";
+package Apache::TestConfigData;
+
+use strict;
+use warnings;
+
+\$Apache::TestConfigData::vars = {
+$config_dump
+};
+
+1;
+
+=head1 NAME
+
+Apache::TestConfigData - Configuration file for Apache::Test
+
+=cut
+EOC
+
+    debug "Writing custom config $path";
+    require File::Path;
+    my $dir = dirname $path;
+    File::Path::mkpath($dir, 0, 0755) unless -e $dir;
+    my $fh = Symbol::gensym;
+    open $fh, ">$path" or die "Cannot open $path: $!";
+    print $fh $pkg;
+    close $fh;
+}
+
+sub custom_config_load {
+    debug "trying to load custom config data";
+
+    if (my $custom_config_path = custom_config_path()) {
+        debug "loading custom config path '$custom_config_path'";
+
+        require $custom_config_path;
+
+        # %ENV overloads any custom saved config (command-line option will
+        # override any of these settings), though only if assigned
+        # some value (this is because Makefile will pass empty env
+        # vars if none were assigned)
+        for (@data_vars_must, @data_vars_opt) {
+            my $value = $ENV{ $vars_to_env{$_} };
+            next unless defined $value and length $value;
+            $Apache::TestConfigData::vars->{$_} = $value;
+        }
+    }
+}
+
+sub custom_config_first_time {
+    my $self = shift;
+
+    my $test_config = $self->{test_config};
+    my $vars = $test_config->{vars};
+
+    require ExtUtils::MakeMaker;
+    require File::Spec;
+    local *which = \&Apache::TestConfig::which;
+
+    print qq[
+
+We are now going to configure the Apache-Test framework.
+This configuration process needs to be done only once.
+
+];
+
+    print qq[
+
+First we need to know where the 'httpd' executable is located.
+If you have more than one Apache server is installed, make sure
+you supply the path to the one you are going to use for testing.
+You can always override this setting at run time via the '-httpd'
+option. For example:
+
+  % t/TEST -httpd /path/to/alternative/httpd
+
+or via the environment variable APACHE. For example:
+
+  % APACHE=/path/to/alternative/httpd t/TEST
+
+];
+
+    {
+        my %choices = ();
+        for (grep defined $_,
+             map({ catfile $vars->{$_}, $vars->{target} } qw(sbindir bindir)),
+             $test_config->default_httpd, which($vars->{target}),
+             $ENV{APACHE},  which('apache'),  which('httpd'),
+             $ENV{APACHE2}, which('apache2'), which('httpd2')) {
+            $choices{$_}++ if -e $_ && -x _;
+        }
+        my $optional = 0;
+        my $wanted = 'httpd';
+        $test_config->{vars}->{$wanted} =
+            _custom_config_prompt_path($wanted, \%choices, $optional);
+    }
+
+    print qq[
+
+Next we need to know where the 'apxs' script is located. This script
+provides a lot of information about the apache installation, and makes
+it easier to find things. However it's not available on all platforms,
+therefore it's optional. If you don't have it installed it's not a
+problem. Notice that if you have Apache 2.x installed that script
+could be called as 'apxs2'.
+
+If you have more than one Apache server is installed, make sure you
+supply the path to the apxs script you are going to use for testing.
+You can always override this setting at run time via the '-apxs'
+option. For example:
+
+  % t/TEST -apxs /path/to/alternative/apxs
+
+or via the environment variable APACHE. For example:
+
+  % APXS=/path/to/alternative/apxs t/TEST
+
+];
+    {
+        my %choices = ();
+        for (grep defined $_,
+             map({ catfile $vars->{$_}, 'apxs' } qw(sbindir bindir)),
+             $test_config->default_apxs,
+             $ENV{APXS},  which('apxs'),
+             $ENV{APXS2}, which('apxs2')) {
+            $choices{$_}++ if -e $_ && -x _;
+        }
+        my $optional = 1;
+        my $wanted = 'apxs';
+        $test_config->{vars}->{$wanted} =
+            _custom_config_prompt_path($wanted, \%choices, $optional);
+    }
+
+    $self->custom_config_save();
+
+    # we probably could reconfigure on the fly ($self->configure), but
+    # the problem is various cached data which won't be refreshed. so
+    # the simplest is just to restart the run from scratch
+    chdir $orig_cwd;
+    warning "rerunning '$original_command' with new config opts";
+    exec $original_command;
+    die "exec $original_command has failed"; # shouldn't be reached
+}
+
+sub _custom_config_prompt_path {
+    my($wanted, $rh_choices, $optional) = @_;
+
+    my $ans;
+    my $default = '';
+    my $optional_str = $optional ? " (optional)" : '';
+    my $prompt =
+        "\nPlease provide a full path to$optional_str '$wanted' executable";
+
+    my @choices = ();
+    if (%$rh_choices) {
+        $prompt .= " or choose from the following options:\n\n";
+        my $c = 0;
+        for (sort keys %$rh_choices) {
+            $c++;
+            $prompt .= "    [$c] $_\n";
+            push @choices, $_;
+        }
+        $prompt .= " \n";
+        $default = 1; # a wild guess
+    }
+    else {
+        $prompt .= ":\n\n";
+    }
+
+    while (1) {
+        $ans = ExtUtils::MakeMaker::prompt($prompt, $default);
+
+        # convert the item number to the path
+        if ($ans =~ /^\s*(\d+)\s*$/) {
+            if ($1 > 0 and $choices[$1-1]) {
+                $ans = $choices[$1-1];
+            }
+            else {
+                warn "The choice '$ans' doesn't exist\n";
+                next;
+            }
+        }
+
+        if ($optional) {
+            return '' unless $ans;
+        }
+
+        unless (File::Spec->file_name_is_absolute($ans)) {
+            my $cwd = Cwd::cwd();
+            warn "The path '$ans' is not an absolute path. " .
+                "Please specify an absolute path\n";
+            next;
+        }
+
+        warn("'$ans' doesn't exist\n"),     next unless -e $ans;
+        warn("'$ans' is not executable\n"), next unless -x $ans;
+
+        return $ans;
+    }
+}
+
  1;

  __END__
@@ -1162,6 +1528,44 @@
    }

  Notice that the extension is I<.c>, and not I<.so>.
+
+=head1 Saving options
+
+When C<Apache::Test> is first installed, it will save the
+values of C<httpd>, C<port>, C<apxs>, C<user>, and C<group>,
+if set, to a configuration file C<Apache::TestConfigData>.
+This information will then be used in setting these options
+for subsequent uses.
+
+The values stored in C<Apache::TestConfigData> can be overriden
+temporarily either by setting the appropriate environment
+variable or by giving the relevant option when the C<TEST>
+script is run. If you want to save these options to
+C<Apache::TestConfigData>, use the C<-save> flag when
+running C<TEST>.
+
+If you are running C<Apache::Test> as a
+user who does not have permission to alter the system
+C<Apache::TestConfigData>, you can place your
+own private configuration file F<TestConfigData.pm>
+under C<$ENV{HOME}/.apache-test/Apache/>,
+which C<Apache::Test> will use, if present. An example
+of such a configuration file is
+
+  # file $ENV{HOME}/.apache-test/Apache/TestConfigData.pm
+  package Apache::TestConfigData;
+  use strict;
+  use warnings;
+  use vars qw($vars);
+
+  $vars = {
+      'group' => 'me',
+      'user' => 'myself',
+      'port' => '8529',
+      'httpd' => '/usr/local/apache/bin/httpd',
+
+  };
+  1;

  =head2 C<new_test_config>

Index: lib/Apache/.cvsignore
===================================================================
RCS file: /home/cvs/modperl-2.0/lib/Apache/.cvsignore,v
retrieving revision 1.2
diff -u -r1.2 .cvsignore
--- lib/Apache/.cvsignore	5 Mar 2001 04:06:54 -0000	1.2
+++ lib/Apache/.cvsignore	7 Jan 2004 03:46:58 -0000
@@ -1,3 +1,4 @@
  BuildConfig.pm
  FunctionTable.pm
  StructureTable.pm
+TestConfigData.pm
Index: lib/ModPerl/BuildMM.pm
===================================================================
RCS file: /home/cvs/modperl-2.0/lib/ModPerl/BuildMM.pm,v
retrieving revision 1.13
diff -u -r1.13 BuildMM.pm
--- lib/ModPerl/BuildMM.pm	1 Dec 2003 04:00:35 -0000	1.13
+++ lib/ModPerl/BuildMM.pm	7 Jan 2004 03:46:58 -0000
@@ -231,6 +231,10 @@
              #so it can be found for 'use Apache2 ()'
              next if $v =~ /Apache2\.pm$/;

+            # another module generated by A-T that needs to go to the
+            # normal @INC
+            next if $v =~ /TestConfigData\.pm$/;
+
              #move everything else to the Apache2/ subdir
              #unless already specified with \$(INST_LIB)
              #or already in Apache2/


__________________________________________________________________
Stas Bekman            JAm_pH ------> Just Another mod_perl Hacker
http://stason.org/     mod_perl Guide ---> http://perl.apache.org
mailto:stas@stason.org http://use.perl.org http://apacheweek.com
http://modperlbook.org http://apache.org   http://ticketmaster.com


Mime
View raw message