You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何DirectoryIndex指向Perl CGI脚本时无需手动输出Content-type?

问题分析与解答

操作背景

修改Apache的mods-available/dir.conf文件,设置默认索引页为Perl CGI脚本:

DirectoryIndex /cgi-bin/csv_auth

对应的csv_auth脚本代码如下:

#! /usr/bin/perl                                                                                                                                                                                                                                 

package Logger;                                                                                                                                                                                                                                    
use strict;                                                                                                                                                                                                                                        
use warnings;                                                                                                                                                                                                                                      
use POSIX qw( strftime );

sub debug {
  my $msg = shift;

  mkdir '/tmp/authorize';
  my $fn = '/tmp/authorize/auth.txt';
  open (my $fh, '>>', $fn) or die "Could not open file $fn: $!";
  my $timestamp = strftime("%Y-%m-%d %H:%M:%S", localtime);
  say $fh "$timestamp : $msg";
}

package CGI::Session::Auth::CSV;                                                                                                                                                                                                                   
use strict;                                                                                                                                                                                                                                        
use warnings;                                                                                                                                                                                                                                      
use parent 'CGI::Session::Auth';                                                                                                                                                                                                                   
use Text::CSV;

# Need to override new()                                                                                                                                                                                                                           
sub new {
  my $class = shift;
  my ($params) = shift;
  Logger::debug('CGI::Session::Auth::CSV::new() called');
  $class = ref($class) if ref($class);
  # initialize parent class                                                                                                                                                                                                                        
  my $self = $class->SUPER::new($params);
  bless($self, $class);
  return $self;
}

# Override the _login method to use a CSV file                                                                                                                                                                                                     
sub _login {
  my $self = shift;
  my ($user, $pass) = @_;
  $self->{username} = $user;
  Logger::debug('CGI::Session::Auth::CSV::_login() called');
  my $csv = Text::CSV->new;
  open my $fh, '<', 'login.csv' or die "Can't open login.csv: $!";
  # Loop through the rows and check for a match                                                                                                                                                                                                    
  while (my $row = $csv->getline($fh)) {
    Logger::debug("CGI::Session::Auth::CSV::_login() : user and psswrd : $user $pass");
    Logger::debug("CGI::Session::Auth::CSV::_login() : CSV row 0 and 1 : $row->[0] $row->[1]");
    if ($row->[0] eq $user and $row->[1] eq $pass) {
      # Found a match, return true                                                                                                                                                                                                                
      close $fh;
      Logger::debug("CGI::Session::Auth::CSV::_login() : found match for username: $user");
      #return 1;                                                                                                                                                                                                                                  
    }
  }
  # No match, return false                                                                                                                                                                                                                        
  close $fh;
  #return 0;                                                                                                                                                                                                                                      
}

package main;                                                                                                                                                                                                                                      
use feature qw(say);                                                                                                                                                                                                                               
use strict;                                                                                                                                                                                                                                        
use warnings;                                                                                                                                                                                                                                      
use CGI(-utf8);                                                                                                                                                                                                                                    
use CGI::Carp qw(fatalsToBrowser);                                                                                                                                                                                                                
use CGI::Cookie;                                                                                                                                                                                                                                   
use CGI::Session;                                                                                                                                                                                                                                  
use Data::Dumper qw(Dumper);                                                                                                                                                                                                                       
use utf8;                                                                                                                                                                                                                                          
use File::Copy;

# CGI object for headers, cookies, etc.                                                                                                                                                                                                           
my $cgi = CGI->new();

my $user = $cgi->param('log_username');
my $password = $cgi->param('log_password');
my $cmd = $cgi->param('cmd');

my %cgi_vars = $cgi->Vars;
Logger::debug('cgiscript: rp.cgi : query parameters: ' . Dumper(\%cgi_vars));
Logger::debug('cgiscript: rp.cgi : cookies: ' . (join ',', $cgi->cookie()));
Logger::debug('cgiscript: rp.cgi : cookie: CGISESSID : ' . Dumper($cgi->cookie('CGISESSID')));

# CGI::Session object for session handling                                                                                                                                                                                                         
my $session = CGI::Session->new(undef, $cgi, {Directory=>'/tmp/authorize'});
Logger::debug('cgiscript: rp.cgi : session = ' . $session->dump());
my $session_params = $session->dataref();
Logger::debug('cgiscript: rp.cgi : session data : ' . Dumper($session_params));
my $session_params_text = Dumper($session_params);
if (defined $user) {
  $session->param('username', $user);
  $session->flush();
} else {
  $user = $session->param('username');
}
my $cookie = $cgi->cookie( -name   => $session->name,
                           -value  => $session->id );
print $cgi->header( -cookie=>$cookie );
# CGI::Session::Auth object for authentication                                                                                                                                                                                                     
my $auth = CGI::Session::Auth::CSV->new({CGI => $cgi, Session => $session});
Logger::debug('cgiscript: rp.cgi : auth object 1 : ' . Dumper($auth));
Logger::debug('cgiscript: rp.cgi : calling authenticate');
$auth->authenticate();
Logger::debug('cgiscript: rp.cgi : auth object 2 : ' . Dumper($auth));
if ($cmd eq 'logout') {
  logout($auth, $user);
  exit;
}

# check if visitor has already logged in                                                                                                                                                                                                           
if ($auth->loggedIn) {
  Logger::debug('cgiscript: rp.cgi : user is logged in');
  print <<HTML                                                                                                                                                                                                                                    
!DOCTYPE html>                                                                                                                                                                                                                                    
<html>                                                                                                                                                                                                                                            
<body>                                                                                                                                                                                                                                            
<h3>Hello you are lógged in $user</h3>                                                                                                                                                                                                             
<p><a href='rp.cgi?cmd=logout'>Log out</a></p>                                                                                                                                                                                                     
</body>                                                                                                                                                                                                                                           
</html>                                                                                                                                                                                                                                           
HTML                                                                                                                                                                                                                                              
} else {
  Logger::debug('cgiscript: rp.cgi : user is not logged in');
  show_login_page();
}
$session->flush();

sub show_login_page {
  my $self = shift;
  print <<HTML;                                                                                                                                                                                                                                   
<!DOCTYPE html>                                                                                                                                                                                                                                   
<html>                                                                                                                                                                                                                                            
<head><title>Not logged in</title></head>                                                                                                                                                                                                          
<body>                                                                                                                                                                                                                                            
<h1>You are not logged in</h1>                                                                                                                                                                                                                    
<p>Please log in to see the secret page:</p>                                                                                                                                                                                                      
<form action='/cgi-bin/rp.cgi' method='POST'>
<p><input type='text' size='30'' name='log_username'</p>                                                                                                                                                                                          
<p><input type='password' size='30' name='log_password'</p>                                                                                                                                                                                       
<p><input type='submit'></p>                                                                                                                                                                                                                      
</form>                                                                                                                                                                                                                                           
</body>                                                                                                                                                                                                                                           
</html>                                                                                                                                                                                                                                           
HTML                                                                                                                                                                                                                                              
}

sub logout {
  my ($auth, $user) = @_;
  auth->logout();
  print <<HTML;                                                                                                                                                                                                                                   
<!DOCTYPE html>                                                                                                                                                                                                                                   
<html>                                                                                                                                                                                                                                            
<head><title>Logged out</title></head>                                                                                                                                                                                                             
<body>                                                                                                                                                                                                                                            
<h1>You have logged out user $user.</h1>                                                                                                                                                                                                          
<p>Please log in to see the secret page:</p>                                                                                                                                                                                                      
<form action='maing.cgi' method='POST'>                                                                                                                                                                                                          
<p><input type='text' size='30' name='log_username'></p>                                                                                                                                                                                          
<p><input type='email' size='30' name='log_password'></p>                                                                                                                                                                                       
<p><input type='submit'></p>                                                                                                                                                                                                                      
</form>                                                                                                                                                                                                                                           
</body>                                                                                                                                                                                                                                           
</html>                                                                                                                                                                                                                                           
HTML                                                                                                                                                                                                                                              
}

现象描述

执行脚本后,浏览器显示内容包含Content-type行:

Content-type: text/html; charset=utf-8
Hello you are logged in marcos

对应的响应头信息:

HTTP/1.1 200 OK
Date: Thu, 03 Aug 2023 05:02:39 GMT
Server: Apache/2.4.56 (Debian)
Set-Cookie: CGISESSID=fc23b50f1b15cbf978e5f797fddc9986; path=/
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 240
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=ISO-8859-1

请求头信息:

GET / HTTP/1.1
Host: recordspreservation.org
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cookie: CGISESSID=fc23b50f1b15cbf978e5f797fddc9986
Upgrade-Insecure-Requests: 1
Sec-GPC: 1

问题解答

1. 为什么浏览器会显示Content-type行?

这是因为你手动打印了Content-type:字符串,且没有遵循CGI响应格式规范:

  • CGI要求响应头和正文之间必须有一个空行。如果没有这个空行,Apache会把所有输出内容当作正文处理,所以浏览器会把Content-type:字符串显示出来。
  • 你的脚本中已经通过print $cgi->header( -cookie=>$cookie );正确输出了响应头(包括Content-Type)和空行,这部分是正常的。但如果脚本中存在其他地方手动打印Content-type:字符串,就会被当作正文内容显示。

2. Perl CGI脚本是否需要手动输出Content-Type行?

必须输出Content-Type头,但不能手动打印字符串,要通过CGI模块的方法生成:

  • CGI规范明确要求,所有CGI脚本必须输出响应头,Content-Type是必填项(除非是HTTP 204等无正文的状态码)。
  • 使用Perl的CGI模块时,$cgi->header()方法会自动生成符合规范的Content-Type头(你指定了-utf8参数,应该输出text/html; charset=utf-8),同时自动添加头和正文之间的空行,这是正确的做法。
  • 绝对不要手动打印Content-type:字符串,否则会导致它出现在浏览器的正文内容里。

脚本额外问题修正

你的脚本还有几个小错误,建议修正:

  • logout函数中auth->logout();缺少变量符号,应改为$auth->logout();,否则会引发语法错误。
  • 登录成功的HTML中lógged拼写错误,应改为logged。
  • show_login_page函数里的输入框size='30''多了一个单引号,应改为size='30'。
  • logout函数里的表单action是maing.cgi,但你的脚本入口是csv_auth或rp.cgi,路径不匹配,建议修正为正确的脚本路径。

内容的提问来源于stack exchange,提问作者Marcos Camargo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 17:10:56