Tecnologias Web 2010/11
Comon Gateway Interface (CGI)
Departamento de Ciência de Computadores
Faculdade de Ciências da Universidade do Porto
CGI — O que é?
• Common Gateway Interface
• Interface standard para a execução de
programas via web
(server-side scripts)
– RFC 3875
– Define aspectos como
• Directório em que o
script
é executado
• Variáveis de ambiente
• Tratamento dos descritores de ficheiro standard
• Etc.
• Não necessariamente em Perl…
CGI — Como funciona?
• O servidor web recebe um pedido HTTP com URL que
identifica como recurso dinâmico gerado por CGI
– E.g., através da extensão ".cgi" ou por estar no directório cgi-bin
• O servidor lança um novo processo para executar o
programa CGI
– Se for um executável nativo é corrido normalmente
– Se for um script é corrido o interpretador da linguagem respectiva
– Se for bytecode Java é corrida a JVM
• Os parâmetros são passados através de variáveis de
ambiente
– Query string
– Cabeçalhos HTTP
CGI — Como funciona?
• O corpo do pedido HTTP é passado através da
entrada-padrão
– Apenas se existir (e.g., no método POST)…
• O programa CGI gera a resposta na saída-padrão
– Cabeçalhos (parciais)
– Corpo da resposta
• O servidor recebe a saída-padrão do CGI e envia-a para o
cliente (navegador)
Exemplo simples
#!/usr/bin/perl -wT
print << "END_OF_HTML";
Content-type: text/html
<html>
<head><title>Hello, World!</title></head>
<body>
<h1>About this server</h1>
<ul>
<li>Server name: $ENV{SERVER_NAME}</li>
<li>Running on port: $ENV{SERVER_PORT}</li>
<li>Server software: $ENV{SERVER_SOFTWARE}</li>
<li>Server protocol: $ENV{SERVER_PROTOCOL}</li>
<li>CGI revision: $ENV{GATEWAY_INTERFACE}</li>
</ul>
</body>
</html>
Descritores standard
• STDIN
– Usado para obter o corpo do pedido (se existir)
– Não existe marcador de fim de ficheiro
tentativas de ler mais do que foi recebido
bloqueiam o script
• Não tentar ler quando o método for GET
• Quando o método for POST, deve obter-se o valor de
Content-length: e ler apenas esse número de bytes
• STDOUT
– Resposta a devolver
– Alguns cabeçalhos, uma linha em branco e o corpo
da resposta
Descritores standard
• STDERR
– CGI não impõe tratamento do STDERR
– A escrita para STDERR normalmente induz um erro
500 Internal Server Error
– Alguns servidores (e.g., Apache) guardam saída nos
logs
• Útil para debugging
Servidor Web CGI
v.a. e stdin stdout stderr Pedido Resposta
Variáveis de ambiente standard
Variável
Descrição
AUTH_TYPE
Método de autenticação (vazio se o pedido não requeria
autenticação)
CONTENT_LENGTH
Comprimento em bytes do corpo da mensagem
CONTENT_TYPE
Tipo do corpo (e.g., ―application/x-www-form-urlencoded‖)
DOCUMENT_ROOT
Directório-base a partir de onde se servem os documentos
GATEWAY_INTERFACE
Versão da interface CGI usada pelo servidor
PATH_INFO
Informação adicional de caminho passada ao script CGI
(e.g., sub-recurso)
PATH_TRANSLATED
Pathname do recurso no sistema de ficheiros
QUERY_STRING
Pergunta no URL pedido (tudo a seguir ao "?").
REMOTE_ADDR
Endereço IP do cliente que fez o pedido (navegador ou
proxy HTTP)
REMOTE_HOST
Nome do cliente que fez o pedido (navegador ou proxy)
Variáveis de ambiente standard
Variável
Descrição
REMOTE_IDENT
Utilizador que fez o pedido (indicado pelo identd)
REMOTE_USER
Login do utilizador (se autenticado pelo servidor web)
REQUEST_METHOD
Método HTTP usado para fazer o pedido (GET ou POST)
SCRIPT_NAME
Caminho no URL (e.g., /cgi-bin/program.cgi) do script
SERVER_NAME
Nome ou endereço IP do servidor web
SERVER_PORT
Porta na qual o servidor web está à escuta
SERVER_PROTOCOL
Nome e versão do protocolo do pedido (e.g., "HTTP/1.1")
SERVER_SOFTWARE
Nome e versão do servidor web
Cabeçalhos do pedido HTTP
• Todos os cabeçalhos do pedido HTTP que não estão em
variáveis standard podem ser acedidos através de
HTTP_*
– HTTP_ACCEPT, HTTP_ACCEPT_CHARSET, HTTP_ACCEPT_ENCODING,
HTTP_ACCEPT_LANGUAGE, HTTP_COOKIE, HTTP_FROM, HTTP_HOST,
HTTP_REFERER, HTTP_USER_AGENT, …
– Também cabeçalhos desconhecidos
• Variável HTTPS indica se a conexão é segura (―on‖ ou
―ON‖ se for, ―‖ ou ―OFF‖ se não for)
• Geralmente é possível configurar o servidor web para
passar variáveis adicionais que possam ser úteis
Saída
• O programa tem que gerar pelo menos um
cabeçalho — um dos seguintes é obrigatório
– Content-type: especificando o conteúdo do corpo
gerado
– Location: especificando um URL para
redireccionamento
– Status: com um código de estado que não requeira
dados adicionais (e.g., 204 No Response)
• A mensagem em texto pode diferir da standard, mas um
Saída
• Geração de um documento
– É necessário especificar o tipo de documento
gerado
– As duas mudanças de linha correspondem à linha
vazia que separa os cabeçalhos do corpo
– O servidor web converte cada mudança de linha em
CR-LF, confome especificado pelo HTTP
Saída
• Redireccionamento
– É necessário um cabeçalho Location: para
especificar o alvo do redireccionamento
– Se o URL for absoluto ou se for relativo com um
caminho relativo, é devolvida a resposta ao cliente
que faz outro pedido para o novo URL
– So o URL for relativo com caminho absoluto ocorre
um redireccionamento interno
• O servidor web vai buscar o recurso indicado e retorna-o
como se fosse a resposta do CGI mais rápido
Saída
• Especificação do código de resposta
– Feita através do pseudo-cabeçalho Status:
– Script indica código e descrição
– O servidor web intercepta este pseudo-cabeçalho e
gera a linha de estado em conformidade
– Este pseudo-cabeçalho é opcional:
• Se for gerado um ―Content-type:‖, o servidor web gera
automaticamente uma resposta 200 OK
• Se for gerado um ―Location:‖, o servidor gera
automaticamente uma resposta 302 Found
Cabeçalhos completos
• Normalmente não é necessário o script gerar
todos os cabeçalhos
– O servidor web interpreta os cabeçalhos emitidos
e completa-os conforme necessário
• Contudo, é possível ser o script a gerar a
totalidade dos cabeçalhos
– Modo NPH (Non-Parsed Headers)
– Seleccionado iniciando o nome do script por ―nph-‖
(e.g., nph-mycgi em vez de mycgi)
– Neste modo, o script tem que gerar também a linha
de estado da resposta HTTP
Exemplo simples com NPH
#!/usr/bin/perl -wT
print << "END_OF_HTML";
$ENV{SERVER_PROTOCOL} 200 OK
Content-type: text/html
<html>
<head><title>Hello, World!</title></head>
<body>
<h1>About this server</h1>
<ul>
<li>Server name: $ENV{SERVER_NAME}</li>
<li>Running on port: $ENV{SERVER_PORT}</li>
<li>Server software: $ENV{SERVER_SOFTWARE}</li>
<li>Server protocol: $ENV{SERVER_PROTOCOL}</li>
<li>CGI revision: $ENV{GATEWAY_INTERFACE}</li>
</ul>
</body>
</html>
Exemplo 1
#!/usr/bin/perl -wT
use strict;
my $image_type = $ENV{HTTP_ACCEPT} =~ m|image/png| ? "png" : "jpeg";
my $basename = $ENV{PATH_INFO} =~ /^(\w+)/;
my $image_path = "$ENV{DOCUMENT_ROOT}/images/$basename.$image_type";
unless ( $basename and -B $image_path and open IMAGE, $image_path ) {
print "Location: /errors/not_found.html\n\n";
exit;
}
my $buffer;
print "Content-type: image/$image_type\n\n";
binmode;
while ( read( IMAGE, $buffer, 16_384 ) ) {
print $buffer;
}
Exemplo 2
$remote_user = $ENV{REMOTE_USER};
if ( $remote_user eq "mary" ) {
print "Welcome Mary, how is your company doing these days?\n";
} elsif ( $remote_user eq "bob" ) {
print "Hey Bob, how are you doing? I heard you were sick.\n";
}
Descodificação da entrada de formulários
• Frequentemente, a entrada para um CGI é
obtida a partir de formulários
• Estes formulários podem estar em páginas
estáticas ou ser gerados pelo mesmo ou por
outro CGI
• Se o método for GET, os pares <chave>=<valor>
são colocados na
query string
• Se o método for POST, são colocados no
corpo do pedido
Descodificação da entrada de formulários
1. Ler a
query string
de $ENV{QUERY_STRING}
2. Se $ENV{REQUEST_METHOD} é POST, determinar o tamanho do
pedido usando $ENV{CONTENT_LENGTH} e ler essa quantidade de
bytes de STDIN. Acrescentar estes dados aos lidos da
query
string
(se presente); a junção faz-se com ―&‖
3. Separar os resultados pelo carácter " &" character, que separ os
diferentes pares <chave>=<valor>
4. Separar cada par <chave>=<valor> pelo carácter ―=‖
5. Descodificar os caracteres URL-encoded no nome (chave) e no
valor
6. Associar cada nome com o(s) respectivo(s) valore(s), recordando
que cada opção pode ter múltiplos valores
Descodificação da entrada de formulários
sub parse_form_data {
my %form_data;
my $name_value;
my @name_value_pairs = split /&/, $ENV{QUERY_STRING};
if ($ENV{REQUEST_METHOD} eq 'POST') {
my $query = "";
read(STDIN, $query, $ENV{CONTENT_LENGTH}) == $ENV{CONTENT_LENGTH}
or return undef;
push @name_value_pairs, split /&/, $query;
}
foreach $name_value (@name_value_pairs) {
my($name, $value) = split /=/, $name_value;
$name =~ tr/+/ /;
$name =~ s/%([\da-f][\da-f])/chr(hex($1))/egi;
$value = [] unless defined $value;
$value =~ tr/+/ /;
$value =~ s/%([\da-f][\da-f])/chr(hex($1))/egi;
push @{$form_data{$name}}, $value;
}
return %form_data;
}
O módulo CGI.pm
• Simplifica muito as tarefas necessárias num CGI
– É uma das razões para o Perl ser tão popular para CGIs
• Tratamento da entrada
– Informação de ambiente (cabeçalhos, etc.)
– Parsing da entrada de formulários
– Gestão de uploads
• Geração da saída
– Geração de cabeçalhos
– Geração de código HTML
• Controlo de erros
– CGI::Carp permite apanhar
die
e outras condições de erro
que poderiam terminar abruptamente o script
CGI.pm — exemplo simples
#!/usr/bin/perl -Tw use strict; use CGI; my $q = new CGI; my $name = $q->server_name(); print $q->header("text/html"), $q->start_html("Welcome"),$q->p("Hi there! Server at $name speaking."), $q->end_html;
Content-Type: text/html; charset=ISO-8859-1 <!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US"> <head>
<title>Welcome</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> </head>
<body>
<p>Hi there! Server at localhost speaking.</p> </body>
Métodos e variáveis correspondentes
Método CGI.pm Variável de Ambiente CGI
auth_type
AUTH_TYPE
N/D
CONTENT_LENGTH
content_type
CONTENT_TYPE
N/D
DOCUMENT_ROOT
N/D
GATEWAY_INTERFACE
path_info
PATH_INFO
path_translated PATH_TRANSLATED
query_string
QUERY_STRING
remote_addr
REMOTE_ADDR
remote_host
REMOTE_HOST
remote_ident
REMOTE_IDENT
remote_user
REMOTE_USER
Métodos e variáveis correspondentes
Método CGI.pm
Variável de Ambiente CGI
request_method
REQUEST_METHOD
script_name
SCRIPT_NAME
self_url
Indisponível
server_name
SERVER_NAME
server_port
SERVER_PORT
server_protocol
SERVER_PROTOCOL
url
Indisponível
Accept *
HTTP_ACCEPT
http("Accept-charset") HTTP_ACCEPT_CHARSET
http("Accept-encoding") HTTP_ACCEPT_ENCODING
* Quando um método do CGI.pm tem o mesmo nome de uma função interna
ou palavra-chave do Perl, a primeira letra do método é maiúscula.
Métodos e variáveis correspondentes
Método CGI.pm
Variável de Ambiente CGI
http("Accept-language") HTTP_ACCEPT_LANGUAGE
http("From")
HTTP_FROM
raw_cookie
HTTP_COOKIE
virtual_host
HTTP_HOST
referer
HTTP_REFERER
user_agent
HTTP_USER_AGENT
https
HTTPS
https("Cipher")
HTTPS_CIPHER
https("Keysize")
HTTPS_KEYSIZE
https("SecretKeySize") HTTPS_SECRETKEYSIZE
Notas sobre alguns métodos
• O funcionamento ―standard‖ para os métodos de acesso a variáveis de
ambiente é ser invocados sem parâmetros e devolver o conteúdo da
variável correspondente; no entanto há excepções a esta regra.
• Accept
– Invocado sem argumentos retorna a lista de tipos aceites
– Invocado com um argumento (e.g., ―text/plain‖), devolve o factor de
preferência associado
• http
– Invocado sem argumentos devolve a lista de variáveis de ambiente HTTP_*
disponíveis
– Invocado com o nome de uma variável HTTP_* ou do cabeçalho
correspondente devolve o valor desse cabeçalho
• https
– Invocado sem argumentos devolve o conteúdo da variável HTTPS, inicializada
pelo servidor se a conexão é segura
– Invocado com um parâmetro, funciona de forma semelhante ao http, mas
para HTTPS_*
Notas sobre alguns métodos
• query_string
– Se o método for GET devolve a
query string
, incluindo quaisquer alterações
que entretanto lhe tenham sido feitas, ao contrário de
$ENV{QUERY_STRING}
– Se o método for POST, retorna os parâmetros POST enviados no corpo da
mensagem
• Neste caso, não inclui a query string (que normalmente não existe com o POST)
• self_url
– Devolve um URL que pode ser usado para invocar o CGI com o método GET
com os mesmos parâmetros com que foi invocado
• Ainda que tenha sido invocado com o método POST
• url
– Semelhante ao anterior, mas sem parâmetros (informação de caminho ou
query string
)
• virtual_host
– Retorna o conteúdo do cabeçalho ―Host:‖, se este existir (i.e., HTTP/1.1), ou
SERVER_NAME no caso contrário
Acesso aos parâmetros do formulário
• O acesso aos parâmetros é feito usando o método
param
– Independentemente de o formulário ter sido submetido por
GET ou POST
• Invocado sem argumentos, param retorna a lista de
todos os parâmetros
• Invocado com o nome de um parâmetro, param devolve
– A lista de valores para esse parâmetro, se invocado em
contexto de lista
– O primeiro (ou único) valor, se invocado em contexto escalar
– undef se não existir esse parâmetro no formulário
Alteração dos parâmetros do formulário
• É possível alterar o valor de um parâmetro invocando
param com dois ou mais argumentos
• Também é possível apagar um parâmetro específico ou
todos os parâmetros
• Pode ser útil para definir valores-padrão para os
parâmetros de um formulário
$q->param(name => "John Doe");
$q->param(hobbies => "Biking", "Windsurfing", "Music");
$q->delete("age");
$q->delete_all;
Exportação de parâmetros para
um espaço de nomes
• Método param não permite a interpolação em
strings de forma simples
• Em alternativa é possível exportar todos os
parâmetros para variáveis de um dado espaço
de nomes
• Único senão: maior consumo de memória
$q->import_names("Q");
Submissão de ficheiros
• É possível efectuar a submissão de ficheiros usando formulários
com enctype=“multipart/form-data”
• O valor fornecido é o nome do ficheiro tal como aparecia no
servidor
– Os delimitadores de directórios são diferentes nos diferentes
sistemas operativos
– Os caracteres válidos num nome também
– Alguns browsers fazem tradução do nome
• O conteúdo é guardado num ficheiro temporário
– Pode obter-se um handle para este temporário usando o método
upload com o nome do ficheiro como argumento
– É possível obter o nome do ficheiro temporário usando o método
tmpFileName (método não-documentado, deve evitar-se)
• Pode pedir-se no formulário o nome com o qual o ficheiro deve
ser guardado no servidor
Prevenir ataques DoS
• Na configuração standard, o módulo CGI
permite upload de ficheiros e não limita
o tamanho de um POST
– Seria possível lançar um DoS que enchesse
o disco ou a memória do servidor
– Para evitar este problema, é conveniente
usar o seguinte código antes de criar o
objecto CGI:
$CGI::DISABLE_UPLOADS = 1;
Submissão de ficheiros
#!/usr/bin/perl -wT use strict;
use CGI;
use Fcntl qw( :DEFAULT :flock );
use constant UPLOAD_DIR => "/usr/local/apache/data/uploads"; use constant BUFFER_SIZE => 16_384;
use constant MAX_FILE_SIZE => 1_048_576; # Limit each upload to 1 MB
use constant MAX_DIR_SIZE => 100 * 1_048_576; # Limit total uploads to 100 MB
use constant MAX_OPEN_TRIES => 100;
$CGI::DISABLE_UPLOADS = 0;
$CGI::POST_MAX = MAX_FILE_SIZE; my $q = new CGI;
$q->cgi_error and error( $q, "Error transferring file: " . $q->cgi_error );
my $file = $q->param( "file" ) || error( $q, "No file received." ); my $filename = $q->param( "filename" ) || error( $q, "No filename entered." ); my $fh = $q->upload( $file );
my $buffer = "";
if ( dir_size( UPLOAD_DIR ) + $ENV{CONTENT_LENGTH} > MAX_DIR_SIZE ) { error( $q, "Upload directory is full." );
Submissão de ficheiros (cont.)
# Conversão do nome do ficheiro de destino
$filename =~ s/[^\w.-]/_/g;
if ( $filename =~ /^(\w[\w.-]*)/ ) { $filename = $1;
} else {
error( $q, "Invalid file name; files must start with a letter or number." ); }
# Abrir ficheiro de destino, garantindo que o nome é único
until ( sysopen OUTPUT, UPLOAD_DIR . $filename, O_WRONLY | O_CREAT | O_EXCL ) { $filename =~ s/(\d*)(\.\w+)?$/($1||0) + 1 . $2/e;
$1 + 0 >= MAX_OPEN_TRIES and error( $q, "Unable to save your file." ); }
# Necessário em sistemas não-Unix; não faz nada em sistemas Unix
binmode OUTPUT; binmode $fh;
# Copiar conteúdo do ficheiro temporário para o de destino por blocos
while ( read( $fh, $buffer, BUFFER_SIZE ) ) { print OUTPUT $buffer;
}
Submissão de ficheiros (cont.)
sub dir_size {
my $dir = shift; my $dir_size = 0;
# Somar o tamanho de todos os ficheiros no directório
opendir DIR, $dir or die "Unable to open $dir: $!"; while ( readdir DIR ) {
$dir_size += -s "$dir/$_"; } return $dir_size; } sub error { my( $q, $reason ) = @_;
print $q->header( "text/html" ), $q->start_html( "Error" ), $q->h1( "Error" ),
$q->p( "Your upload was not procesed because the following error ", "occured: " ),
$q->p( $q->i( $reason ) ), $q->end_html;
Submissão de ficheiros com
hook
#!/usr/bin/perl use CGI;
# Questões de segurança / prevenção de DoS omitidas
my $q = CGI->new(\&hook, undef, 0); # $hook, $data, $use_tempfile
sub hook { # Invocado antes de CGI->new() retornar => objecto $q não existe
my ($filename, $buffer, $bytes_read, $data) = @_; our $fh;
unless (defined($fh)) { open $fh, '>', $filename; binmode $fh; } print $fh $buffer;
}
print $q->header("text/html");
if (defined($fh)) { # Invocado pela submissão dum ficheiro
print $q->start_html("Upload finished!"), $q->h1("Well Done!"), $q->end_html; } else { # Invocado sem submissão => gerar formulário
print $q->start_html("Testing upload using hooks"), $q->start_multipart_form,
$q->filefield(-name => 'uploaded_file'), $q->submit, $q->end_form, $q->end_html;
Geração de saída com o CGI.pm
• O módulo CGI.pm simplifica
grandemente a geração de respostas
– Cabeçalhos HTTP
– Código HTML
Geração de cabeçalhos HTTP
print $q->header( "text/plain" );
print $q->header( -type => "text/plain" );
print $q->header( -type => "text/html", -expires => "+30m" );
print $q->header( -type => "text/html", -target => "main_frame" );
• Tipo de media
• Código de estado
• Expiração do documento gerado p/ caching
– Instante absoluto, relativo ou now
• Especificação do alvo (frame, janela)
Geração de cabeçalhos HTTP
• Redireccionamento
• Outros cabeçalhos
– Basta passar o par nome/valor ao método header;
os sublinhados são automaticamente convertidos
para hífenes
print $q->redirect( "http://localhost/survey/thanks.html" );
Geração de (X)HTML
• O método start_html gera a parte inicial do
documento, até à etiqueta <body> (inclusive)
• Algumas opções possíveis:
– Especificação de meta-informação com -meta
– Inclusão de script com –script
• Código do script numa string ou
• Referência a hash com chaves possíveis –language, -src,
ou –code
– Alternativa se o browser não suportar JavaScript
com -noscript
Geração de (X)HTML
• Algumas opções possíveis (cont.):
– Especificação de folha de estilos CSS com –style
• Código CSS numa string ou
• Referência a hash com chaves possíveis –code ou –src
– Título do documento com –title
– Especificação de URL-base como o URL do script
com –base e valor true
• Útil com sub-recursos
– Especificação de URL-base passado como
argumento seguinte com -xbase
Geração de (X)HTML
• Elementos HTML standard
– Linha horizontal
– Mudança de linha
– Parágrafo
• Sem espaço adicional
print $q->hr;
print $q->br;
print $q->p( "This is a paragraph." );
print $q->p( "The server name is:", $q->em( $q->server_name ) );
{
local $" = "";
print $q->p( "Server=", $q->server_name );
}
Geração de (X)HTML
• Elementos HTML standard (cont.)
– Âncora
(link)
– Listas
• Propriedade distributiva
print $q->a( { -href => "/downloads" }, "Download Area" );
print $q->ol( $q->li( [ "First", "Second", "Third" ] ) );
<ol>
<li>First</li>
<li>Second</li>
<li>Third</li>
</ol>
Geração de (X)HTML
• Elementos HTML standard (cont.)
– Tabelas
print $q->table( { -border => 1,
-width => "100%" }, $q->Tr( [ $q->th( { -bgcolor => "#cccccc" }, [ "Name", "Age" ] ), $q->td( [ "Mary", 29 ] ), $q->td( [ "Bill", 27 ] ), $q->td( [ "Sue", 26 ] ) ] ) );
<table border="1" width="100%"> <tr> <th bgcolor="#cccccc">Name</th> <th bgcolor="#cccccc">Age</th> </tr> <tr> <td>Mary</td> <td>29</td> </tr> <tr> <td>Bill</td> <td>27</td> </tr> <tr> <td>Sue</td> <td>26</td> </tr> </table>
Geração de formulários
Método CGI.pm
Etiqueta (X)HTML
start_form, start_multipart_form <form>
end_form
</form>
textfield
<input type="text">
password_field
<input type="password">
filefield
<input type="file">
button
<input type="button">
• A geração de formulários também é facilitada pelo
módulo CGI.pm
• A tabela mostra os métodos disponíveis, bem como as
etiquetas (X)HTML por eles geradas
Geração de formulários (cont.)
Método CGI.pm
Etiqueta (X)HTML
image_button
<input type="image">
submit
<input type="submit">
reset
<input type="reset">
checkbox, checkbox_group <input type="checkbox">
radio_group
<input type="radio">
popup_menu
<select size="1">
scrolling_list
<select size="n"> (n > 1)
textarea
<textarea>
Geração de formulários (cont.)
• O método HTTP normalmente usado para
submissão por start_form é o POST
– Possível especificar GET com –method => 'GET'
• Nos métodos que geram elementos do
formulário é possível especificar o
valor-padrão com a opção –default
– Se o CGI tiver sido invocado pela submissão de um
formulário com esse elemento, o valor submetido é
usado em vez do padrão
Formulários — Exemplo
use CGI; my $q = CGI->new(); print $q->header('text/html'), $q->start_html(-title => 'Register'), $q->h1('Registration form'), $q->start_form, $q->start_table, $q->Tr([$q->td([ 'Name:', $q->textfield(-name => 'name') ]), $q->td([ 'Email:', $q->textfield(-name => 'email') ]),
$q->td([ 'Password:', $q->password_field(-name => 'pass') ]),
$q->td([ 'Confirm password:', $q->password_field(-name => 'cpass') ]), $q->td([ 'Receive newsletter:',
scalar ($q->radio_group(-name => 'rcvnl',
-values => [ 'yes', 'no' ], -default => 'no')) ]), ]), $q->end_table, $q->submit(-value => 'Register'), $q->end_form, $q->end_html;
Formulários — Exemplo 2
print $q->start_form;
print $q->table( { -border => 0, -width => 550 }, $q->Tr(
$q->td($q->textfield(-name => 'fname', -default => 'John', -size => 25), $q->br, "First Name"),
$q->td($q->textfield(-name => 'mi', -size => 2, -default => 'A'), $q->br, "M.I."),
$q->td($q->textfield(-name => 'lname', -default => 'Doe', -size => 25), $q->br, "Last Name")
), $q->Tr(
$q->td({ colspan => 3 }, $q->textfield(-name => 'address', -size => 75), $q->br, "Street Address")
), $q->Tr(
$q->td($q->textfield(-name => 'city', -size => 25), $q->br, "City"),
$q->td($q->textfield(-name => 'state', -size => 2), $q->br, "State"),
$q->td($q->textfield(-name => 'zip', -size => 10), $q->br, "Zip Code")
)
Formulários — Exemplo 2
print $q->table( { -border => 0, -width => 550 }, $q->Tr(
$q->td($q->em("What Operating Systems Do You Use?"), $q->br, $q->checkbox_group(
-name => 'Operating Systems',
-values => ['Linux', 'MacOS', 'Windows', 'Other'], -linebreak => 'yes',
-defaults => ['Linux', 'Windows'] )
), $q->td($q->em("What Platform is used most?"), $q->br, $q->radio_group(
-name => 'platform',
-values => ['PC', 'Mac', 'Sun', 'Other'], -linebreak => 'yes', -default => 'PC' ) ) ) ), $q->hr;
Formulários — Exemplo 2
print $q->p(
$q->em("How are you connected to the Internet?"), $q->br, $q->popup_menu(
-name => 'Connection',
-values => ['ADSL', 'Cable', 'T-1/E-1', 'Dial-up', 'Satellite'], -default => 'Dial-up'
) );
print $q->p(
$q->em("What Peripherals are connected to your computer?"), $q->br, $q->scrolling_list(
-name => 'configuration',
-values => ['CDROM', 'Sound Card', 'Video Camera', '3D Graphics'], -size => 4,
-multiple => 'true' )
);
print $q->p($q->em("What do you like about the World Wide Web?"), $q->br, $q->textarea(-name => 'Comments', -rows => 8, -columns => 60)); print $q->p($q->checkbox('Add me to your mailing list'));
Formulários — Exemplo 2
print $q->p( $q->reset,
$q->submit('Action', 'Send Free Catalog'), $q->submit('Action', 'No Free Catalog') );
print $q->endform, $q->hr;
# Imprime valores recebidos da submissao anterior
if ($q->param) {
my (@values, $key);
print $q->h2("Here are the current settings:"); foreach $key ($q->param) {
print $q->strong("$key : "); @values = $q->param($key);
print join(", ",@values), $q->br; }
print $q->hr; } else {
print $q->strong("No query submitted yet."); }
Tratamento de erros
• Um CGI não deve morrer, pois originaria um erro 500
Internal Server Error
– Um simples warn gera esse erro
• No entanto, existe muito código que invoca die, warn
ou funções relacionadas
• Pode-se colocar código ―perigoso‖ dentro de blocos
eval, mas não é muito prático
• Módulo CGI::Carp simplifica o tratamento de erros
– Adiciona informação (timestamp e nome do CGI) às
mensagens de erro
Tratamento de erros — CGI::Carp
• Parâmetro fatalsToBrowser para apanhar os erros e warnings
• Ajuda de valor incalculável na fase de desenvolvimento
• CGI::Carp::set_message para personalizar a página de erro
use CGI;
$CGI::HEADERS_ONCE = 1;
use CGI::Carp 'fatalsToBrowser'
;
BEGIN {
sub carp_error {
my $error_message = shift;
my $q = new CGI;
print $q->start_html( "Error" ),
$q->h1( "Error" ),
$q->p( "Sorry, the following error has occurred: " );
$q->p( $q->i( $error_message ) ),
$q->end_html;
}
CGI::Carp::set_message( \&carp_error );
}
Modularização do código
• Para simplificar a manutenção do código e a uniformidade do site é
conveniente criar código modular
• Criação de módulo
– Definir package com o nome do módulo — sequência de identificadores
separados por ::
– Dar ao ficheiro o nome do último desses identificadores e a extensão .pm
– Guardar esse ficheiro num subdirectório constituído pelos restantes
identificadores separados por / em vez de ::
– Este subdirectório deve estar num directório em @INC
– O ficheiro deve terminar com ―1;‖ para ser incluído com sucesso
– É comum definir a versão em $VERSION
• Exemplo: o módulo MyMods::CGI::Login deve pertencer a um
package com o mesmo nome e ser guardado no ficheiro Login.pm
dentro, e.g., do directório
Modularização do código: Exemplo
#!/usr/bin/perl -wT
package MyMods::CGI::Login; use CGI;
use base Exporter;
our @EXPORT = qw( login_form ); # Exporta a subrotina login_form()
our $VERSION = "0.1"; # Versão do módulo
sub login_form {
my $q = shift; # Objecto CGI passado como primeiro argumento
print $q->header(-type => 'text/html'), $q->start_html('Login'),
$q->start_form(-action => $scriptname), # Processado pelo próprio script
$q->p('Username:', $q->textfield(-name => 'user')),
$q->p('Password:', $q->password_field(-name => 'pass', -default => '', -override => 1)), $q->p($q->submit(-value => 'Login')), $q->end_form(), $q->end_html(); }
Manutenção de estado
• HTTP é
stateless
(mesmo com conexões permanentes)
• Por vezes é necessário manter estado entre diversos
ciclos pedido/resposta. Técnicas possíveis:
Técnica
Aplicação
Fiabilidade e
desempenho
Requisitos do
cliente
Query strings
Extra path info
Grupo de páginas ou site
inteiro; perde-se se o
utilizador deixar o site e
voltar mais tarde
Difícil interceptar de
forma fiável todos os
links; é pesado passar
conteúdos estáticos
através de CGIs
Nenhuns
Hidden fields Sequência de submissões
de formulários
Fácil implementação; não
afecta o desempenho
Nenhuns
Cookies
utilizador saia do site e
Sempre, mesmo que o
regresse mais tarde
Fácil implementação; não
afecta o desempenho
Suporte para
cookies
imple-mentado e activo
Query strings & extra path information
• Necessário configurar servidor web para invocar CGI quando se
acede a determinadas zonas do sistema de ficheiros
• Quando é feito um pedido de
http://example.com/store/index.html
o CGI (
query track
) é invocado como
http://example.com/cgi/track.cgi/store/index.html
• O script atribui ao utilizador um identificador único e altera todos
os
links
para incluir esse identificador
– E.g.,
http://example.com/store/.CC7e2BMb_H6UdK9KfPtR1g/faq.html
<Directory /usr/local/apache/htdocs/store>
AddType text/html .html
AddType Tracker .html
Action Tracker /cgi/track.cgi
</Directory>
Query strings & extra path information
• Este método implica a intercepção de todos os
links
nos
documentos estáticos servidos para introduzir o ID
• Pode utilizar-se o módulo HTML::Parser para o efeito
– Parsing é tarefa pesada penalização em termos de desempenho
• Em alternativa podem pré-processar-se os documentos para
agilizar o processo (e.g., colocar em todos os
links
#SESSID# no
sítio onde deve introduzir-se o ID)
sub parse {
my( $filename, $id ) = @_;
local *FH;
open FH, $filename or die "Cannot open file: $!";
while (<FH>) {
s/#SESSID#/$id/g;
print;
}
}
Cookies
• Geração do cabeçalho ―Set-cookie:‖
• Recuperação de uma
cookie
recebida
• Problema: se houver várias
cookies
com o mesmo nome
apenas retorna a primeira
– Possível desde que tenham
path
ou
domain
distintos
– Podem recuperar-se as restantes através de
$ENV{HTTP_COOKIE}
my $cookie = $q->cookie( -name => 'cart_id',
-value => 12345,
-domain => '.oreilly.com',
-expires => '+1y',
-path => '/cgi',
-secure => 1 );
print $q->header( -type => 'text/html', -cookie => $cookie );
Cookies
• É possível detectar automaticamente se o
cliente tem suporte para
cookies
activo
• Se o CGI não receber a
cookie
pretendida
– Gera cabeçalho Set-cookie:
– Redirecciona para outro CGI que testa se a
instalação da
cookie
foi bem sucedida
• Este segundo CGI de teste pode redireccionar
outra vez para o CGI de entrada
Autenticação e manutenção de sessões
Cenário típico de acesso a áreas restritas:
• Utilizador autentica-se usando as suas credenciais
• É criada uma sessão associada ao utilizador
– Permite controlar autorização permissões associadas ao
utilizador
• Sessão mantém-se activa até que até que
– O utilizador faça logout do site (sessão é apagada)
– Passe um certo período de tempo inactivo (sessão expira)
• Utilizador não precisa de reintroduzir credenciais
enquanto a sessão estiver activa
Autenticação e manutenção de sessões
Alguns aspectos básicos de segurança:
• Autenticação deve sempre ser feita sobre https
– Ou então usando a autenticação Digest do HTTP
• Em aplicações de elevado risco toda a comunicação
deve ser feita sobre https
– Evita que o identificador de sessão seja capturado na rede
• Após autenticação, o servidor deve sempre criar um
novo identificador de sessão
– Evita a fixação de sessões
– Evita reutilização de sessões antigas
Autenticação e manutenção de sessões
Alguns aspectos básicos de segurança:
• Sessão deve ter tempo de vida limitado
– Expirar após um período de inactividade
– Expiração garantida pelo servidor — nunca confiar no cliente
– Mecanismo de logout simples de utilizar para apagar sessão
• Identificador de sessão deve ter componente
aleatória
– Evita que se consiga adivinhar o identificador mesmo que se
conheça o algoritmo de geração
• Se possível, apenas um identificador por utilizador
Autenticação e manutenção de sessões
Alguns aspectos básicos de segurança:
• A aplicação NUNCA deve usar informação sensível ou
pessoal (e.g., username) como estado mantido pelo
cliente
– Seria muito fácil ao cliente forjar essa informação
– Servidor deve passar ao cliente apenas um identificador de
sessão opaco (e.g., gerado criptograficamente) e manter
localmente a informação associada a esse identificador
• A aplicação deve usar uniformemente um único
mecanismo de autenticação e manutenção de sessões
– De preferência um bem testado — não reinventar a roda, que
às vezes sai quadrada…
Autenticação e manutenção de sessões
Alguns aspectos básicos de segurança:
• NUNCA armazenar na base de dados
passwords
em
clear text
– Enorme problema caso alguém consiga explorar alguma falha
de segurança e aceder à base de dados
• Muitos utilizadores reutilizam
passwords
em diferentes sistemas
– Quando é necessário armazenar passwords, deve-se fazê-lo
sob a forma de um
hash
da concatenação da
password
com um
sal
Autenticação e manutenção de sessões:
exemplo
Problema
• Implementação de uma
área restrita
• Um utilizador não
auten-ticado é redireccionado
para uma página de login
• Após a autenticação
ini-cial, o utilizador deve
poder aceder à área
restrita sem ter que
reintroduzir as suas
credenciais
Implementação
• Nome de utilizador e
hash da senha
armaze-nados em BD
backend
• Sessão criada quando o
utilizador se autentica
• Sessões mantidas
tam-bém na BD
backend
• Identificadores de
ses-são transportados em
cookies
CGI::Session
• Módulo genérico para manutenção de sessões
• Sessões armazenadas em disco ou BD relacional
• Não inclui autenticação
– Pode ser usado quando a autenticação não é necessária
– Pode ser usado com autenticação HTTP ou outra
– Programador tem que se preocupar com isso
• Permite múltiplas sessões por utilizador ( ou )
• Necessita de limpeza periódica de sessões antigas
– Script CRON:
• Parâmetros da sessão guardados em bruto
– Mesmo em BD relacional não são guardados em colunas separadas
– Não é problemático se a sessão guardar pouca informação (e.g.,
apenas chaves para acesso à base de dados)
CGI::Session (com sessões na BD)
• Fragmento do script principal
# Objecto CGI já criado em $q e database handle em $dbh
# Tenta carregar sessão estabelecida
my $s = CGI::Session->load('driver:mysql', $q,
{ TableName => 'my_sessions',
Handle => $dbh})
or die CGI::Session->errstr();
if ($s->is_empty) {
# Sessão inexistente, inválida ou expirada
login_page;
# Gera página de entrada para o utilizador se autenticar
} else {
# Utilizador com sessão válida => acesso autorizado
my $user = $s->param('user');
# Parâmetro guardado na sessão
print $q->header('text/html'),
# Conteúdo altamente secreto :-)
$q->start_html,
$q->h1("Hello, $user!"),
$q->end_html;
CGI::Session (com sessões na BD)
• Fragmento do script de login
• Fragmento do script de logout
# Objecto CGI já criado em $q e database handle em $dbh
# Após verificação das crecedenciais, utilizador em $user
my $s = CGI::Session->new('driver:mysql', $q,
{ TableName => 'my_sessions',
Handle => $dbh})
or die CGI::Session->errstr();
$s->param('user', $user);
# Guarda parâmetro da sessão
print $s->header();
# Invoca $q->header() acrescentando-lhe uma cookie
com o identificador da sessão
# Sessão activa em $s
$s->delete;
• Por vezes é útil enviar email a partir dum CGI
– Há milhentas maneiras de o fazer em Perl…
• Exemplo com Mime::Lite:TT
use MIME::Lite::TT;
my %params = ( name => 'Rui', day => 'next monday', time => '14:00' );
my $template = <<'
TEMPLATE
';
Hello, [% name %].
Our meeting will be [% day %] at [% time %].
TEMPLATE
my $msg = MIME::Lite::TT->new(
From => '[email protected]',
To => '[email protected]',
Subject => 'Meeting schedule',
Template => \$template,
TmplParams => \%params,
);
Problemas com o CGI
• Lançamento de novo processo (perl) por cada
invocação do CGI
• Carregamento e recompilação do CGI por cada
invocação
• Sem persistência
– Alguma persistência pode ser obtida através dos métodos
anteriormente descritos
– Informação de sessão tem que ser carregada por cada
invocação
– Ligações a bases de dados têm que ser restabelecidas por
cada invocação
• Possibilidades de interacção com o servidor web
reduzidas
Aceleração
• Estratégias básicas de aceleração
– Arrancar
pool
de processos perl que depois
são reutilizados
– Correr o código a vermelho apenas uma vez
e reutilizar os resultados
#!/usr/bin/perl –wT
use CGI;
use DBI;
$q = CGI->new;
$dbh = DBI->connect(...);
query_db();
print_response();
FastCGI
• Extensão ao CGI que proporciona maior velocidade,
escalabilidade e persistência
• Protocolo de comunicação entre servidor web e
aplicação FastCGI
• Bibliotecas que implementam a extensão e o protocolo
Servidor Web CGI
v.a. e stdin stdout stderr Pedido Resposta
Servidor Web FastCGI
Socket full duplex Protocolo FastCGI Pedido Resposta