Você está na página 1de 39

Introdução aos Formulários

Os FORMULÁRIOS fazem com que as páginas WWW fiquem interactivas. As páginas podem ser
muito bem construídas, podem ter bom aspecto mas, sem formulários, servem apenas para uma
simples leitura.

Ao navegar na Internet, já deve ter visto e preenchido vários exemplos de formulários . Os formulários
são estruturas compostas por vários objectos GUI (Graphical User Interface): campos de texto, campos
senha, botões, checkboxes, menus pull-down, onde se podem solicitam informações como o nome, o
endereço, onde o utilizador pode fazer uma escolha entre várias alternativas, etc. Os mais populares
são os formulários dos sites de pesquisas, onde se digita uma determinada palavra e, como resultado,
são indicados sites relacionados com a mesma.

Quando um utilizador pressiona um botão a indicar que o formulário deve ser enviado, a informação aí
presente é normalmente direccionada para um servidor para processamento através de um
programa CGI. Contudo, se simplesmente se quiser ter os dados enviados para um endereço de e-
mail, terá de se usar a Action mailto.

Neste módulo começamos por analisar a marca (tag) <FORM>, depois vamos explicar as marcas que
desenham os elementos de entrada de dados: <INPUT>, <SELECT> e <TEXTAREA>, e passamos em
seguida à construção dos scripts, que são os programas que tratam esses dados, oferecendo os
serviços desejados (acesso a bases de dados, envio de e-mail, etc.).

Marca FORM
A marca <FORM> delimita um formulário, e contém uma sequência de elementos de entrada e de
formatação do documento:

<FORM ACTION="URL_do_script" METHOD=método>

Marcas de campos de entrada de dados e outro HTML em geral

</FORM>

Os formulários podem conter qualquer formatação - parágrafos, listas, tabelas, imagens - excepto
outros formulários. Um documento pode conter múltiplos elementos <form>, mas os elementos <form>
não podem estar encaixados.

Action

Especifica o URL do script (programa) que irá receber os dados do formulário, decodificá-los e
processar a resposta. Se estiver a referenciar um programa que existe no mesmo servidor que o
formulário, não precisa incluir o URL completo:

 ACTION="/cgi-bin/post-query", para um programa existente no mesmo servidor do formulário.


Neste exemplo, a informação de entrada será enviada para um programa local chamado post-
query presente no diretório /cgi-bin do servidor.
 ACTION="http://www.fe.up.pt/~ceso/cgi-bin/post-query", para um programa noutro servidor.
Onde:
 www.fe.up.pt, é o servidor;
 ~ceso, é o directório base do utilizador ceso, significa o directório ~ceso/public-html;
 post-query, é o nome do executável do programa que processa a resposta.
Method

Define o método de transmissão. Os métodos usados actualmente são o GET e o POST. Ambos os
métodos transferem dados do browser para o servidor, com a seguinte diferença básica:

POST - os dados introduzidos fazem parte do corpo da mensagem enviada para o servidor;
GET - os dados introduzidos fazem parte do URL associado à consulta enviada para o servidor;

Veremos maiores detalhes no item métodos de transmissão.

Enctype

Indica o tipo de codificação dos dados enviados através do formulário. O tipo de codificação default é
application/x-www-form-urlencoded, onde:

 - São formados pares nome=valor para cada campo;


 - Os campos são separados por &;
 - Os espaços nos valores são convertidos para +;
 - Os caracteres não alfanuméricos são convertidos para "%" e o valor hexadecimal.

Caso queira receber o conteúdo do formulário por correio electrónico, deve adicionar
ENCTYPE="TEXT/PLAIN" e ACTION="Mailto: endereço_email".

Exemplo:

<FORM Method=Post Action="mailto:


nome_user@servidor.pt" Enctype="text/plain" OnSubmit="Window.alert('Enviado!
!')">

Os pares nome=valor são enviados sem codificação, em linhas separadas.

Campo de texto
A marca <INPUT> permite a entrada de texto, quando o atributo type="text". O valor text é assumido
por defeito quando o atributo type não é indicado.

 Sintaxe:
<INPUT TYPE="text" NAME="nome_campo" VALUE="valor_inicial" SIZE=tamanho1
MAXLENGTH=tamanho2>
 Atributos:
TYPE - tipo da entrada;
NAME - variável onde será armazenado o texto;
VALUE - valor inicial atribuido à variável name;
SIZE - tamanho visível do campo. O valor padrão é 20;
MAXLENGTH - número máximo de caracteres do campo.

Nota: Os atributos Size e Maxlength só são válidos nas marcas text e password;

O comando Produz como resultado


<FORM>
<INPUT NAME="Nome">
</FORM>
Um outro exemplo...

O comando Produz como resultado


<FORM ACTION="URL"> Digite a sua
Digite a sua morada? Rua X
<INPUT TYPE="text" NAME="Morada" VALUE="Rua morada?
X" SIZE="25" MAXLENGTH="30">
</FORM>

Uma outra possibilidade de definir esta marca é através de editores de HTML: FrontPage, HomeSite,
HTMLPad, que geram o código HTML correspondente.

No caso do HomeSite

através da barra de ferramentas forms seleccionar

Campo senha
Na marca <INPUT> com o atributo type="password" os caracteres introduzidos são substituidos por
asteriscos, razão pela qual é normalmente utilizado para a introdução de senhas e palavras-chave.
Desta forma impede-se a visualização do texto que está a ser introduzido.

 Sintaxe:
<INPUT TYPE="password" NAME="nome_campo" VALUE="valor_inicial" SIZE=tamanho1
MAXLENGTH=tamanho2>
 Atributos:
TYPE - tipo da entrada;
NAME - nome atribuido ao campo;
VALUE - texto que pretendemos atribuir por defeito ao campo name;
SIZE - tamanho visível do campo. O valor padrão é 20;
MAXLENGTH - número máximo de caracteres do campo.

O comando Produz como resultado


<FORM>
Login:
<INPUT TYPE="text" NAME="utilizador" utilizador
SIZE=10 MAXLENGTH=15>
Login:
Password: **********
Password:
<INPUT TYPE="password" NAME="password1"
VALUE="minhasenha" SIZE=10 MAXLENGTH=15>
</FORM>

Campo de texto multi-linha


Através da marca <TEXTAREA> cria-se uma caixa de texto na qual é possível introduzir várias linhas
de texto. É normalmente utilizada para averiguar os comentários ou opiniões dos utilizadores.

 Sintaxe:
<TEXTAREA NAME="nome_campo" ROWS=x COLS=y> Texto </TEXTAREA>
 Atibutos:
NAME - variável onde será armazenado o texto digitado;
COLS - número de colunas;
ROWS - número de linhas.

O comando Produz como resultado

<TEXTAREA NAME="comentario" ROWS=6 COLS=35 >


Comentário ao site
</TEXTAREA>

No caso do editor HomeSite, seleccione na barra de

ferramentas forms

Campo "hidden"
A marca <INPUT> com o atributo type="hidden" permite definir dados que são passados a um
programa CGI mas não aparecem ao utilizador do formulário. Este campo serve para memorizar
valores durante um diálogo com o utilizador.
Por exemplo, um site que permita encomendar produtos pode apresentar ao utilizador vários
formulários, listando os diferentes produtos que podem ser encomendados, e no último formulário
apresenta uma lista com todos os produtos que o utilizador seleccionou.

 Sintaxe:
<INPUT TYPE="hidden" NAME="nome_variavel" VALUE="valor_inicial">
 Atributos:
TYPE - tipo do campo a ser utilizado;
NAME - variável que contém o valor a ser enviado para o script CGI;
VALUE - valor a ser passado para o script CGI.

O comando Produz como resultado


<FORM> Empresa:
<INPUT TYPE="hidden" NAME="tipo" VALUE="classe1"> Empresa:
<INPUT TYPE="text" NAME="empresa" SIZE=10> </FORM>

No caso do HomeSite, através da barra de

ferramentas forms seleccionar

Botão "checkbox"
Várias marcas <INPUT> com o atributo type="checkbox" são utilizadas quando se pretende seleccionar
mais do que uma opção de um conjunto. Um checkbox possui dois estados "seleccionado" e "não
seleccionado".

 Sintaxe:
<INPUT TYPE="checkbox" NAME="variavel" VALUE="xxx"> descrição
 Atributos:
TYPE - tipo de entrada;
NAME - variável que armazenará o valor das opções seleccionadas;
VALUE - valor que será adicionado à variável name quando esta opção for seleccionada;
descrição - texto que aparece ao lado do checkbox.

O comando Produz como resultado


Seleccione quais os seus hobbies? Seleccione quais os seus hobbies?
<INPUT TYPE="checkbox" NAME="grupo1"
VALUE="valor1"> Cinema Cinema
<INPUT TYPE="checkbox" NAME="grupo1"
VALUE=valor2> Televisão Televisão
<INPUT TYPE="checkbox" NAME="grupo1"
VALUE=valor3> Teatro Teatro
<INPUT TYPE="checkbox" NAME="grupo1"
VALUE=valor4> Natação Natação

No caso do HomeSite, através da barra de

ferramentas forms seleccionar

Botão "radio button"


Várias marcas <INPUT> com o atributo type="radio" permitem-nos seleccionar uma opção entre as
disponíveis. Os radio buttons do mesmo grupo possuem o mesmo valor no atributo name.

 Sintaxe:
<INPUT TYPE="radio" NAME="variavel" VALUE="xxx"> descrição
 Atributos:
TYPE - tipo do campo a ser utilizado;
NAME - variável que armazenará o valor da opção seleccionada;
descrição - texto que aparece ao lado do radio button.

O comando Produz como resultado


Qual a rede que apresenta melhor Qual a rede que apresenta melhor
cobertura? cobertura?
<INPUT TYPE="radio" NAME="grupo1"
VALUE="valor1"> TMN TMN
<INPUT TYPE="radio" NAME=grupo1
VALUE=valor2> TELECEL TELECEL
<INPUT TYPE="radio" NAME=grupo1
VALUE=valor3> OPTIMUS OPTIMUS

No caso do HomeSite, através da barra de

ferramentas forms seleccionar

Botão "reset"
Este comando restaura os valores iniciais das entrada de dados.

 Sintaxe:
<INPUT TYPE="reset" VALUE="xxx">
 Atributos:
TYPE - tipo de botão a ser utilizado;
VALUE - rótulo do botão.

O comando Produz como resultado

Login:
<FORM>
<INPUT TYPE="text" NAME=login> Password:
<Password: <INPUT TYPE="password">
<INPUT TYPE="reset" VALUE="Apaga tudo!>
</FORM> Apaga tudo!

Redefinir
Se o atributo VALUE não for indicado é criado um botão com o seguinte aspecto:

No caso do HomeSite, na barra de ferramentas forms

seleccionar

Botão "submit"
Nos formulários a marca <INPUT> com atributo type="Submit" é utilizada para enviar os dados
introduzidos pelo utilizador para um script CGI.
 Sintaxe:
<INPUT TYPE="submit" VALUE="xxx">
 Atributos:
TYPE - tipo de botão a ser utilizado;
VALUE - rótulo do botão;

O comando Produz o resultado


<FORM>
Envia mensagem
<INPUT TYPE="submit" VALUE="Envia mensagem" >
</FORM>

Enviar
Se o atributo VALUE não for indicado é criado um botão com o seguinte aspecto:

No caso do HomeSite, através da barra de

ferramentas forms seleccionar

Botão "image"
A marca <INPUT> com o atributo type ="image" é utilizada para mapas activos (e outras imagens
clicáveis). Quando este botão é clicado, o formulário é submetido e as coordenadas (x,y) do pixel
seleccionado (posição do rato) são guardadas em variáveis que são enviadas juntamente com os
dados introduzidos no formulário para um script CGI.
Esta marca permite identificar regiões num mapa ou objectos numa imagem. Compare com a nocão
de Usermap.

 Sintaxe:
<INPUT TYPE="image" NAME="Imagem" SRC="imagem.jpg" >
 Atributos:
TYPE - tipo do botão;
NAME - nome da variável;
SRC - nome do ficheiro que contém a imagem.

O comando Produz como resultado

<FORM>
<INPUT TYPE="image" NAME="Imagem"
SRC="imagens/input_image.jpg">
</FORM>

Escolha única
A marca <SELECT> permite seleccionar uma opção de um conjunto de itens. É possível estabelecer
uma escolha-padrão, através do atributo selected.
 Sintaxe:
<SELECT NAME=".." SIZE=y >
<OPTION> Item 1
<OPTION selected> Item 2
.
<OPTION> Item N
</SELECT>
 Atributos:
NAME - variável à qual vai ser atribuído o item seleccionado;
OPTION - vários itens do menu;
SIZE - quantidade de itens visíveis na janela.

O comando Produz como resultado


<SELECT NAME="cores" SIZE=1>
<OPTION> Amarelo
<OPTION> Verde Vermelho
<OPTION SELECTED> Vermelho
<OPTION> Azul
</SELECT>

Escolha múltipla
A marca <SELECT> com o atributo MULTIPLE permite seleccionar uma ou várias opções de um
conjunto de itens. É possível estabelecer uma escolha-padrão, através do atributo selected.

 Sintaxe:
<SELECT NAME=".." SIZE=y MULTIPLE>
<OPTION> Item 1
<OPTION selected> Item 2
.
<OPTION> Item N
</SELECT>
 Atributos:
NAME - variável à qual vai ser atribuído o item seleccionado;
OPTION - vários itens do menu;
SIZE - quantidade de itens visíveis na janela;
MULTIPLE - a utilização deste atributo torna o menu de múltipla escolha.

O comando Produz como resultado


<SELECT NAME="cores" MULTIPLE>
<OPTION> Amarelo
<OPTION> Verde Amarelo
<OPTION> Vermelho
<OPTION> Azul
</SELECT>

NOTA: Para seleccionar mais do que um item utilizar a tecla CRT ou a tecla SHIFT no caso de serem seguidos.
No caso do HomeSite, na barra de ferramentas forms

seleccionar

CGI - Common Gateway Interface


Uma CGI, permite interactividade entre um cliente e um servidor de informações (servidor web ou
servidor HTTP) através protocolo HTTP (Hyper Text Transfer Protocol). Um documento HTML que o
servidor Web entrega é estático, um programa CGI, por outro lado, é executado em tempo real, logo
pode apresentar como resultado informação dinâmica - por exemplo: os resultados mais recentes de
uma pergunta a uma base de dados.

CGI (Common Gateway Interface), também chamado de script CGI, é um programa que é executado
num servidor WWW, em resposta a um pedido de um browser. Geralmente o script é uma interface
entre o servidor e outro programa do sistema. Estes programas podem ser tanto scripts como
programas compilados.

Web Gateways são programas ou scripts que recebem pedidos, e retornam um documento com os
resultados correspondentes. Esse documento pode existir previamente, ou pode ser gerado pelo script
especialmente para responder ao pedido. O servidor pode fornecer informação que não é directamente
legível pelo cliente (ex: uma pergunta em SQL), as gateways funcionam como uma interface entre os
dois.

Suponhamos por exemplo que temos uma base de dados Oracle que contem estatísticas dos
resultados obtidos pelos alunos de mestrado nas disciplinas do 2º semestre e que queremos mostrar
esta informação na web. É claro que não podemos enviar o ficheiro da base de dados directamente ao
cliente (isto é abrir o URL associado com o ficheiro pois os dados não teriam qualquer significado).
Nesta situação a CGI providência uma solução para o problema. Podemos usar uma linguagem como
o OraPerl ou uma extensão DBI ao Perl para formular questões em SQL e assim ler a informação
contida na base de dados. Depois de ter a informação, podemos formatá-la e enviá-la ao cliente. Neste
caso o programa CGI serve como gateway para uma base de dados Oracle.
Em síntese, para que são usados os scripts CGI ?

Estes scripts são programas que tratam pedidos dos clientes (browsers). Estes pedidos são
processados e são gerados documentos que são enviados de volta aos clientes. Os browsers, por sua
vez, interpretam-nos e apresentam-nos no écran. O servidor HTTP pode fornecer dados que os
clientes não conseguem interpretar directamente, uma vez estes scripts CGI servem de gateway entre
um cliente e, por exemplo, uma base de dados.

Exemplos de aplicação de CGI's

Podem usar-se scripts CGI com vários propósitos:


 Processamento de dados submetidos através de formulários;
 Servir de interface com bases de dados, fazendo a conversão da transação de HTML para SQL
e formatar em HTML as respostas obtidas, enviando em seguida os resultados para o cliente;
 Converter dados do sistema para HTML e retornar o resultado para o cliente;
 Processamento de livros de visita e pesquisas de opinião;
 Criação de documentos personalizados;
 Gerir contadores de acesso;
 Processamento de mapas.

Linguagens para programar CGI's

Um script CGI pode ser um ficheiro de comandos, interpretáveis pelo S.O., ou pode ser escrito em
qualquer linguagem que produza um ficheiro executável. Mas as linguagens devem ser compatíveis
com a plataforma sob a qual o servidor está correr, isto é, não importa a linguagem em que o programa
é escrito, desde que tenha permissão e recursos para correr na máquina.
As linguagens mais usadas são:

 C/C++
 PERL
 TCL
 Bourne Shell, C Shell (em ambiente UNIX)
 VB Script (em ambiente Windows)

Apesar de se poder usar qualquer linguagem para programar CGI's, algumas são mais adequadas do
que outras. Antes de escolher uma linguagem devemos considerar as seguintes características:

- Facilidade de manipulação de texto, uma vez que os dados de um formulário são


normalmente descodificados a partir de uma string com certos delimitadores;

- Facilidade de interface com bases de dados;

- Facilidade que a linguagem tem de aceder a variáveis de ambiente (em Unix) uma vez que
estas variáveis constituem o input para o programa CGI.

Métodos de transmissão
O protocolo HTTP (HiperText Transfer Protocol) utiliza vários métodos de manipulação e organização
dos dados. Actualmente, os dois métodos mais utilizados para submeter dados de formulários são o
GET e o POST. Ambos os métodos transferem dados do browser para o servidor, a maior diferença
entre eles é a maneira como a informação é passada para o programa CGI:

GET

 O browser acrescenta ao URL, especificado no atributo ACTION, um "?" e os valores


codificados;
 O programa CGI pode aceder às informações codificadas através da variável de ambiente
QUERY_STRING;
 O browser só faz uma ligação ao servidor;
 Pode ser usado fora de um FORM (& -->& amp;);
 Os dados não são encriptados, logo informações que exigem segurança não devem ser
manipuladas por este método;
 Suporta apenas até 128 caracteres, logo é útil para valores pequenos.

POST

 Os dados introduzidos num formulário fazem parte do corpo da mensagem enviada para o
servidor;
 Enquanto o método GET passa a informação através de variáveis de ambiente , o POST envia
os dados para o programa CGI através do standard input (entrada padrão), como uma string de
comprimento especificado na variável de ambiente CONTENT_LENGTH;
 Faz 2 ligações ao servidor, uma para contactar o servidor e outra para enviar os parâmetros;
Noutras palavras, se o servidor receber um pedido de um formulário usando o POST, ele sabe
que tem que continuar "à espera" do resto da informação;
 Pode encriptar os dados;
 É possível transferir uma grande quantidade de dados.

Este é o método aconselhado.


Vantagens/Desvantagens

A vantagem do GET é que permite aceder ao programa CGI com uma query sem utilizar um formulário,
basicamente estamos a passar parâmetros para um programa.
Exemplo: <A HREF="/cgi-bin/program.pl?user=Larry%20Bird&age=35&pass=testing"> Programa CGI
</A>

A maior desvantagem do Get são a falta de segurança e o facto de ter de haver algum cuidado para
que o browser ou o servidor não trunquem a informação que exceda o número de caracteres permitido.

A maior vantagem do método Post é o tamanho da query poder ser ilimitado. Para obter informação
através do método POST, o programa CGI lê do standard input, por essa razão não é possivel aceder
à CGI sem utilizar um formulário.

Variavéis de Ambiente

Quando um programa CGI é chamado, dispõe de informação sobre o cliente, sobre o servidor e de
dados do formulário que o utilizador forneceu. A maior parte da informação sobre o cliente e o servidor
é colocada em variáveis de ambiente. Os dados do formulário são incorporados numa variável de
ambiente (método GET) ou incluídos no corpo do pedido (método POST), como já se referiu.

Os scripts têm acesso a essas variáveis através da interface CGI. Existem variáveis de ambiente
inicializadas no momento em que o servidor executa o programa CGI solicitado e outras que não estão
ligadas a requisições específicas e estão sempre inicializadas: SERVER_SOFTWARE,
SERVER_NAME, GATEWAY_INTERFACE.

Em seguida são apresentados o nome e a descrição de algumas dessas variáveis:

Variável de Ambiente Descrição


SERVER_SOFTWARE Nome e versão do software do servidor que está a responder a
pedidos do cliente.
SERVER_NAME Endereço IP ou nome da máquina do servidor.
GATEWAY_INTERFACE A versão da CGI que o servidor usa. EX.: CGI/versão
SERVER_PROTOCOL Versão do protocolo HTTP usado pelo servidor. Ex.: HTTP/1.0
SERVER_PORT Porta TCP pela qual o servidor atende as requisições. Ex.: 80
SCRIPT_NAME Caminho e nome do script que está a ser executado (ex:/cgi-
bin/program.pl)
QUERY_STRING No caso de um FORM com o método GET, o conteúdo dos
campos é atribuído a pares do tipo nome_do_campo=valor, e
passado para o script através desta variável de ambiente.
REMOTE_HOST Máquina que solicita a execução do script (máquina do
utilizador).
REMOTE_ADDR O endereço IP de quem está a fazer pedidos.
AUTH_TYPE Usado para autenticação do utilizador, se suportado pelo
servidor.
REMOTE_USER Se o servidor suporta autenticação do utilizador, esta variável
contém o nome do utilizador que está a fazer a requisição.
REMOTE_IDENT Se o servidor suporta identificação padrão RFC 931, esta variável
irá conter nome do utilizador remoto que está a fazer a
requisição. Uso limitado.
CONTENT_TYPE Tipo MIME dos dados enviados. Ex: "text/html"
CONTENT_LENGHT O tamanho dos dados (em bytes ou o número de caracteres)
passados à CGI através do standard input.
REQUEST_METHOD O método utilizado para enviar a requisição. Ex.: GET, POST
HTTP_ACCEPT Lista dos tipos MIME que o cliente pode aceitar.
HTTP_USER_AGENT Nome e versão do browser utilizado pelo utilizador.

Exemplo da utilização das variáveis de ambiente:

Uma das informações mais importantes que o servidor passa ao programa CGI é o nome do cliente (ou
browser). Diferentes browsers web suportam diferentes tags HTML, assim se, por exemplo, o programa
CGI gera uma imagem tem que ser sensível ao facto de alguns browsers suportarem a marca <IMG> e
outros não. Para além disso, alguns browsers suportam imagens JPEG e GIF e outros nem sequer
suportam imagens. Usando a variável de ambiente HTTP-USER-AGENT podemos determinar qual o
browser que está a ser usado.

Funcionamento Interno de uma CGI

A maioria dos servidores esperam que os programas e scripts CGI estejam num directório
chamado cgi-bin e/ou que tenham uma certa extensão (.cgi). Na maioria dos casos, um pedido de um
programa CGI (um utilizador abre um URL associado a uma CGI) é semelhante ao pedido de um outro
qualquer documento. A diferença é que, quando o servidor reconhece que o endereço pedido é de uma
CGI, não retorna o conteúdo do ficheiro e em vez disso tenta executar o programa.

Existem duas formas básicas de passar dados para os scripts CGI's:

 Variáveis de ambiente;
 Entrada padrão (stdin, na nomenclatura Unix), esta forma de passagem de dados para os
scripts serve para submissão de pedidos de formulários, através do método POST.

Os dados chegam numa string constituída por pares nome=valor, separados pelo caracter & . Cada par
está codificado, ou seja, os espaços estão substituídos por "+" e alguns caracteres codificados em
hexadecimal, precedidos por um sinal de "%".
Num formulário, cada item de entrada de dados possui um atributo name, ao qual associamos o nome
do campo. Quando o utilizador introduz dados, valores, num destes campos, esses dados são
associados ao nome do campo. Os pares nome=valor são formados pelo browser quando o utilizador
submete um formulário.

Os scripts CGI têm uma estrutura geral comum:

 Leitura e descodificação de dados (e/ou campos de informação de um pacote HTTP);


 Processamento dos dados (gravar informação em bases de dados, realizar cálculos, recuperar
dados);
 Criação de uma página Web com os resultados produzidos.
CGI's Disponíveis na Internet
Todas as informações que o utilizador fornece numa página com marcas de formulário são agrupadas
e enviadas para um programa - mais conhecido como script CGI - que é escrito especialmente para
processar esses dados de acordo com algumas necessidades ou especificações, tipo actualização ou
consulta de uma base de dados, envio dos dados através de e-mail ou simplesmente, a construção de
uma nova página gerada a partir dos novos dados, são as respostas mais comuns que esses
programas geram.

O utilizador comum não precisa ter a preocupação de criar um script CGI (que não é tarefa fácil, pois o
programa requer algum conhecimento de uma linguagem de programação), pois já existem vários "pré-
fabricados" disponíveis gratuitamente na Internet que processam as informações fornecidas nos
formulários de uma maneira transparente.
Para utilizá-los não precisa saber mais do que alguns novos comandos HTML. Os mais conhecidos na
Internet são o MailMerge, o CGIEmail, o AnyForm, o FormMail, entre outros.

Os dois primeiros precisam estar instalados no servidor do seu fornecedor, mas infelizmente nem todos
os fornecedores oferecem este tipo de facilidade aos seus utilizadores. O AnyForm permite que o
utilizador fique totalmente independente do seu fornecedor, pois tudo está instalado numa máquina em
alguma parte do mundo. Como estamos em rede e todas as máquinas podem comunicar, o local onde
os dados estão a ser tratados não faz tanta diferença.

Exemplo completo
Considere o FORM abaixo:

<FORM METHOD=POST>
Login: <INPUT TYPE="text" NAME="login" SIZE="15">
<INPUT TYPE="submit" VALUE="Enviar">
</FORM>

O acesso aos dados introduzidos num formulário é diferente dependendo do método de


transmissão associado. Vamos analisar essa diferença.

CGI chamada através do método GET


No método GET, os dados são "concatenados" ao URL depois de um '?':

http://alguma.máquina/cgi-bin/regista.pl?login=guest

O servidor ao receber um URL com uma query-string, chama o programa cgi identificado na 1ª parte do
URL (antes do '?') e guarda a parte depois do '?' na variável de ambiente QUERY_STRING. Supondo
que o utilizador digitou "guest" no campo login, quando o botão submit é clicado, o browser envia, ao
servidor, um pedido do tipo:

GET /cgi-bin/regista.pl?login='guest' HTTP/1.0


Accept: www/source
Accept: text/html
Accept: image.gif
User-Agent: Lynx/2.4 libwww/2.14
From: shisshir@bu.edu

O pedido GET identifica o ficheiro a enviar (cgi-bin/regista.pl). Desde que o servidor esteja configurado
para reconhecer todos os ficheiros no directório cgi-bin como sendo um programa cgi, executa o
programa em vez de enviar o documento directamente ao browser, além disso coloca a
string login='guest' na variável de ambiente QUERY_STRING.
HTTP/1.0 identifica o tipo de protocolo usado.

As aplicações CGI podem retornar praticamente quaisquer tipo de documentos virtuais, desde que o
cliente os consiga gerir apropriadamente, por essa razão o pedido do cliente também passa o formato
de dados que ele pode aceitar (www/source, text/html, image.gif), sendo essa informação guardada na
variável de ambiente HTTP_ACCEPT e o programa CGI analisa essa variável para assegurar que
retorna um ficheiro num formato que o browser pode gerir. Este pedido identifica um cliente Lynx e
envia informação do user. Toda esta informação é tornada disponível para o programa CGI, juntamente
com informação adicional do servidor.

Programa CGI capaz de gerir esta informação


#! /usr/local/bin/tclsh
/* Diz ao servidor para correr o interpretador tcl localizado no directório /usr/local/bin/ para
executar o programa cgi/*

puts "Content-type: text/plain", "\n\n"


set query_string $ENV{'QUERY_STRING'};
set par [split $query_string = ]
set field_name [lindex $par 0]
set command [lindex $par 1]
if {$command == "guest"} {
puts "usr/local/bin/guest"
} elsif {$command == "mgi"} {
puts "usr/ucb/mgi"
} else {
puts "usr/local/bin/outro";
}
exit(0);

CGI chamada através do método POST


Para o exemplo explicado no tema anterior, se utilizar o método Post, o pedido enviado ao servidor
seria do tipo:

POST /cgi-bin /regista.pl HTTP/1.0


.
. (informação do cabeçalho)
.
Content-length: 11
login=guest

Programa CGI capaz de gerir esta informação


#! /usr/local/bin/tclsh
set size_of_form_information $env(CONTENT_LENGTH)
set form_info [read stdin $size_of_form_information]
set par [split $form_info = ]
set field_name [lindex $par 0]
set command [lindex $par 1]

puts "Content-type: text/plain", "\n\n"


if {$command == "guest"} {
puts "usr/local/bin/guest"
} elsif {$command == "mgi"} {
puts "usr/ucb/mgi"
} else {
puts "usr/local/bin/outro";
}
exit(0);

O programador de CGI's não consegue controlar por que método o programa vai ser chamado. Assim
os scripts são geralmente escritos para suportar ambos os métodos.

#! /usr/local/bin/tclsh
set request_method $env(REQUEST_METHOD)
if {$request_method =="GET"}{
set form_info $env(QUERY_STRING)
} else {
set size_of_form_information $env(CONTENT_LENGTH)
set form_info [read stdin $size_of_form_information]
}

Resultado apresentado por uma CGI


Quando o programa CGI começa a correr, pode criar e dar como output um novo documento, ou
providenciar um URL para um documento já existente. Em Unix, as cgi's enviam o seu output para o
stdout.

 A 1ª parte é um cabeçalho HTTP completo ou parcial, que no mínimo descreve o formato dos
dados retornados (exemplo html, plain, text, gif, etc) e uma linha em branco, que significa o fim
do cabeçalho.
 A 2ª parte é o corpo, que contem os dados de acordo com o tipo de formato considerado no
cabeçalho. O corpo não é modificado ou interpretado pelo servidor.

Um programa CGI pode escolher entre enviar os dados recém-criados directamente para o cliente ou
enviá-los indirectamente através do servidor.

RESULTADO ENVIADO PARA O CLIENTE

Se o output consiste de um cabeçalho HTTP completo, a informação é enviada directamente ao cliente


sem modificações do servidor.

Cabeçalho da mensagem
HTTP/1.0 200 OK
Date: Thursday, 22-February-96 08:28:00 GMT/
Server:NCSA/1.4.2
MIME-version: 1.0
Content-type: text/html
Content-length: 2000

Notas sobre o cabeçalho


linha 1: Protocolo de comunicação, 200 OK é o Status
linha 2: Data e tempo da resposta
linha 3: Nome e versão do servidor
linha 4: Protocolo MIME
linha 5: Tipo de conteúdo e nº de caracteres (formato MIME do output)

Corpo da mensagem
<HTML>
<HEAD><TITLE> Welcome to </TITLE></HEAD>
<BODY>
<H1>Welcome</H1>
...
</BODY>
</HTML>

RESULTADO ENVIADO PARA O SERVIDOR

Como costuma ser o caso, o output é enviado ao servidor como sendo um conjunto de dados
(cabeçalho HTTP muito parcial) e o servidor é depois responsável por completar a informação do
cabeçalho e de usar o protocolo HTTP para transferir os dados ao cliente.

Cabeçalho da mensagem
Content-type: text/html

Corpo da mensagem
<HTML>
<HEAD><TITLE> Welcome to FEUP HOMEPAGE</TITLE>
</HEAD>
<BODY>
<H1>Welcome!</H1>
...
</BODY>
</HTML>

Explicação do exemplo
A seguir é apresentado o código da cgi, que constrói o resultado que possivelmente viu se clicou no
botão "Enviar" do formulário apresentado na introdução deste módulo. O objectivo deste programa CGI,
é listar os parâmetros que o utilizador introduziu no formulário, está escrito em linguagem c e chama-se
lepar.c.
No que diz respeito a permissões dos ficheiros e directórios, este ficheiro tem permissões rwx--x--x (em
Unix: chmod 711 lepar.c) e foi colocado num directório public-html/cgi-bin, que tem permissões rwxr-xr-
x.

#include <stdio.h> /*Incluir uma biblioteca*/


main(int argc, char * argv[]){
char entrada[255];
int i;
printf( "Content-type: text/html\n\n<ul>" );
i=0;
while( (entrada[i]=getchar()) != EOF ){
/* função getchar lê caracter a caracter do stdin enquando for diferente de fim de ficheiro*/
if (entrada[i] == '&')
{entrada[i]='\0';
printf( "<li>%s", entrada );
i=0;}
else i++;
}

entrada[i]='\0';
printf( "<li>%s</ul>", entrada );
}

O resultado obtido é o seguinte


CGI em Shell Script
Thobias Salazar Trevisan
08/04/2003

1. Introdução
CGI (Common Gateway Interface) é um serviço server-based o qual adiciona
funcionalidade extra a uma página. Esta funcionalidade é fornecida por um 'pequeno'
programa ou 'script' que é executado no servidor onde a página web fica. Estes
programas podem ser feitos em diversas linguagens como Perl, PHP, C, Shell Script,
etc.

Como gosto muito de Shell Script resolvi escrever um tutorial básico sobre como
fazer CGI em Shell. Isto tem várias vantagens, pois você pode utilizar vários
comandos do UNIX para ajudar a construir seu script, por exemplo sed, awk, cut,
grep, cat, echo, bc, etc. além dos recursos do próprio shell.

Ok, como este tutorial não vai ser muito grande, vamos direto ao ponto.

2. Configuração
Como configurar o servidor web Apache para executar CGI ?
CGI é um módulo do Apache, assim ele precisa ser carregado. A maioria das
distribuições já vem com o seu httpd.conf configurado com suporte ao módulo do
CGI (mod_cgi), bastando apenas iniciar o Apache. Para se certificar procure e, se for
o caso, descomente a seguinte linha no seu httpd.conf:
LoadModule cgi_module /usr/lib/apache/1.3/mod_cgi.so

PS: Note que a terceira coluna pode variar dependendo da versão do Apache e da
distribuição que você está usando.

Existem diversas maneiras de configurá-lo:

1. ScriptAlias

esta diretiva define um diretório para o Apache onde serão armazenados os


scripts CGI. Todos os arquivos que estiverem neste diretório serão
interpretados pelo Apache como programas CGI, assim ele tentará executá-
los. Adicione ou descomente a seguinte linha no seu arquivo httpd.conf
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/

O exemplo acima instrui o Apache para que qualquer requisição começando


por /cgi-bin/ deva ser acessada no diretório /usr/lib/cgi-bin/ e deva ser tratada
como um programa CGI, i.e., ele irá executar o arquivo requisitado.

se você acessar por exemplo http://localhost/cgi-bin/meu_script.cgi, o


Apache irá procurar este arquivo em /usr/lib/cgi-bin/meu_script.cgi e tentará
executá-lo.

2. fora do ScriptAlias

você pode especificar um diretório particular e dar permissão para a execução


de CGIs.
<Directory /home/user/public_html/cgi-bin/>
Options +ExecCGI
</Directory>

esta diretiva acima permite a execução de CGIs, mas você ainda precisa avisar
o Apache que tipo de arquivos são estes CGIs. Procure por uma linha igual ou
semelhante a esta no seu httpd.conf e descomente.
AddHandler cgi-script .cgi .sh .pl
OBS: se você colocar um index.cgi em algum diretório e quiser que por default o
Apache execute-o, não se esqueça de adicionar esta extensão no
seu DirectoryIndex.
<IfModule mod_dir.c>
DirectoryIndex index.html index.htm index.shtml index.cgi
</IfModule>

O Apache irá procurar pelo index.cgi seguindo a ordem dos argumentos, ou seja, o
index.cgi será a última opção que ele irá procurar no diretório.

2.0.1. Algumas considerações importantes:

 você não deve colocar seus scripts no document root do Apache, porque
alguém pode pegar seus scripts, analisá-los procurando furos de segurança,
etc. Além do mais, o código do script não é o que você quer mostrar :) Então
mantenha-os em /usr/lib/cgi-bin ou algum outro diretório fora do document
root.
 o script precisa ser um executável, não se esqueça de dar um chmod nele.
Ah, certifique que o script tem permissão de execução para o usuário que o
Apache está rodando.

3. Diversão
3.1. Iniciando
Assumimos que você já tem o seu servidor web configurado para executar CGI.
Agora é hora da diversão :-)

O básico que você precisa saber é que toda a saída padrão (stdout) do seu script vai
ser enviada para o browser.

O exemplo mais simples é você imprimir algo na tela. Vamos ao nosso exemplo:
================ simples.cgi ====================
#!/bin/bash

echo "content-type: text/plain"


echo
echo -e "
vamos ver se isto funciona mesmo :-)

hmm, parece legal

igual ah um shell normal \n<b>tag html</b>


"
=================================================

Feito isto basta acessar o nosso arquivo. http://localhost/cgi-bin/simples.cgi Ah, não


se esqueça de colocar permissão de execução no arquivo

Ok, então percebemos que toda saída do nosso script é enviada para o browser, toda
a saída mesmo. Por exemplo, podemos utilizar a saída de um comando:
================ saida_cmd.cgi ==================
#!/bin/bash

echo "content-type: text/plain"


echo
echo "uname -a"
echo
uname -a
=================================================

Quando utilizamos CGI o servidor coloca diversas informações sobre o cliente e o


servidor em variáveis de ambiente. Dentre estas informações pode-se destacar:

DOCUMENT_ROOT diretório root dos documentos html

HTTP_ACCEPT quais os content-type suportados pelo browser do cliente

HTTP_HOST nome do host do servidor

HTTP_USER_AGENT o browser do cliente

REMOTE_ADDR IP do cliente

REQUEST_URI página requisitada

SERVER_ADDR IP do servidor

SERVER_NAME o server name (configurado no Apache)

SERVER_PORT porta que o servidor está escutando

SERVER_SOFTWARE sistema operacional e servidor www rodando no server

Para a lista completa destas variáveis


acesse http://hoohoo.ncsa.uiuc.edu/cgi/env.html.

O exemplo a seguir mostra todas as variáveis.


================ export.cgi =====================
#!/bin/bash

echo "content-type: text/plain"


echo
echo "
Informacoes que o servidor coloca em variaveis de ambiente
Para ver utilizamos o comando set

"
set
=================================================

Okay, no protocolo http temos que enviar um cabeçalho obrigatório. O primeiro


echo sem string dentro de um script vai avisar o browser para interpretar o que veio
antes como cabeçalho. Nos exemplos anteriores, a seguinte linha content-type:
text/plain informa ao browser para interpretar o que receber como texto puro. Se o
cabeçalho não possuir nenhuma linha, ou seja, colocarmos somente um echo, será
utilizado o default que normalmente é text/plain. Este default é configurado no
arquivo httpd.conf do Apache na opção:
DefaultType text/plain

Então para garantir procure sempre especificar o cabeçalho!! Para enviarmos tags
html e o browser interpretá-las, temos que utilizar um cabeçalho diferente. Este
cabeçalho é content-type: text/html Então vamos enviar tags html para deixar nossa
saída mais bonita:
================ sobre.cgi ======================
#!/bin/bash

echo "content-type: text/html"


echo
echo
echo "
<html> <head> <title> CGI script </title> </head>
<body>
<h1>Algumas informações sobre a máquina que o CGI está rodando:</h1>
"

echo "<h4>uptime</h4>"
echo "<pre>$(uptime)</pre>"

echo "<h4>uname</h4>"
echo "<pre>$(uname -a)</pre>"

echo "<h4>/proc/cpuinfo</h4>"
echo "<pre>$(cat /proc/cpuinfo)</pre>"

echo "
</body>
</html>
"
=================================================

Um exemplo mais interessante seria como fazer um contador de acesso. A cada


execução do script o contador será incrementado, não importando se é uma
solicitação de reload de um mesmo endereço IP!! Isto é simples. Veja o exemplo:
================ contador.cgi ===================
#!/bin/bash

echo "content-type: text/html"


echo
echo
echo "<html> <head> <title> CGI script </title> </head>"
echo "<body>"

ARQ="/tmp/page.hits"

n="$(cat $ARQ 2> /dev/null)" || n=0


echo $((n=n+1)) > "$ARQ"

echo "
<h1>Esta página já foi visualizada: $n vezes</h1>
<br>

</body>
</html>"
=================================================

Com o que sabemos até agora, dá (ops!) para fazer vários script legais, fazer
monitoramento do sistema, de sua rede..., tudo via web.

Agora que já aprendemos o básico, queremos interagir com o usuário. Neste ponto
nós temos um detalhe, pois o nosso script não pode usar a entrada padrão (stdin) para
receber dados e não podemos fazer um read em um CGI, pois como nós leriamos o
que o usuário digitasse no teclado? :)

Para realizar esta interação existem duas maneiras.

1. através da URL, utilizando o método GET, como por exemplo:


http://localhost/cgi-bin/script.cgi?user=nobody&profissao=vaga

(veremos mais sobre o método GET mais adiante)

2. utilizando um formulário HTML. No FORM podemos utilizar dois métodos, o


GET e o POST. No método GET, que é o default, os campos de input do FORM
são concatenados a URL. Já no método POST, os inputs são passados
internamente do servidor para o script pela entrada padrão.

3.2. Método GET

Usando o método GET o nosso script deve pegar os inputs do usuário via uma
variável de ambiente, no caso a $QUERY_STRING. Tudo o que vier após o
caractere '?' na URL será colocado naquela variável. Os campos de input do FORM
são separados pelo caractere '&' e possuem a seguinte construção: name=value.
Vamos a um exemplo de um CGI que recebe como entrada um host que será
executado no ping.
================ ping_get.cgi ===================
#!/bin/bash

echo "content-type: text/html"


echo
echo
echo "
<html> <head> <title> CGI script </title> </head>
<body>
"

echo "<h2>Exemplo de uso do GET</h2>"


if [ "$QUERY_STRING" ];then
echo "QUERY_STRING $QUERY_STRING"
host="$(echo $QUERY_STRING | sed 's/\(.*=\)\(.*\)\(\&.*\)/\2/')"
echo "<br>"
echo "Disparando o comando ping para o host <b>$host</b>"
echo "<pre>"
ping -c5 $host
echo "</pre>"
echo "Fim."
else
echo "
<form method=\"GET\" action=\"ping_get.cgi\">
<b>Entre com o nome ou IP do host para o ping:</b>
<input size=40 name=host value=\"\">
<input type=hidden size=40 name=teste value=\"nada\">
</form>"
fi

echo "</body>"
echo "</html>"
=================================================

3.3. Método POST

No método POST as opções do FORM não são passadas pela URL, elas são
passadas internamente do servidor para o CGI. Deste modo, com este método o
nosso script deve ler as opções pela entrada padrão. Vamos a um exemplo em que o
CGI envia um mail para alguém através da página. Neste caso, um pouco mais
complexo, temos dois arquivos. O primeiro um .html puro, onde construimos o
FORM, e colocamos como opção 'action' o nosso script. A opção action passada no
FORM, indica qual script será chamado para tratar os dados passados pelo FORM.
================ contato.html ===================
<html> <head> <title> CGI script </title> </head>

<body>
<form method="post" action="/cgi-bin/contato.cgi">
Nome:<br>
<input type="text" name="name" maxlength="50" size="30">
<p>
E-mail:<br>
<input type="text" name="address" maxlength="50" size="30">
<p>
Selecione o assunto:
<select name="subject">
<option value="none">-------------------------
<option value="venda">Informações sobre produto
<option value="suporte">Suporte técnico
<option value="web">Problema no site
</select>
<p>
Sua mensagem:<br>
<textarea name="message" wrap="physical" rows="6" cols="50">
</textarea>
<p>
<input type="submit" value="Enviar Mensagem">
<input type="reset" value="Limpar">
</form>

</body>
</html>
=================================================

Agora o nosso script lê da entrada padrão e faz o tratamento necessário. Note que
para enviar o mail, podemos utilizar qualquer programa de mail, por exemplo o mail,
sendmail...
================ contato.cgi ====================
#!/bin/bash

meu_mail="user@localhost.com.br"

echo "content-type: text/html"


echo
echo
echo "<html> <head> <title> CGI script </title> </head>"
echo "<body>"
VAR=$(sed -n '1p')
echo "$VAR"
nome=$(echo $VAR | sed 's/\(name=\)\(.*\)\(\&address=.*\)/\2/;s/+/ /g')
mail=$(echo $VAR | sed
's/\(.*&address=\)\(.*\)\(\&subject=.*\)/\2/;s/%40/@/')
subj=$(echo $VAR | sed 's/\(.*&subject=\)\(.*\)\(\&message=.*\)/\2/')
text=$(echo $VAR | sed 's/.*\&message=//')

echo "<br>
<br><b>Nome:</b> $nome
<br><b>mail:</b> $mail
<br><b>Subject:</b> $subj
<br><b>Message:</b> $text
<br>"

mail -s "Mail from CGI" "$meu_mail" < $(echo -e "


Nome: $nome
mail: $mail
Subject: $subj
Message: $text")

echo "</body>"
echo "</html>"
=================================================

Quando o seu servidor web envia os dados do FORM para o seu CGI, ele faz um
encode dos dados recebidos. Caracteres alfanumérico são enviados normalmente,
espaços são convertidos para o sinal de mais (+), outros caracteres como tab, aspas
são convertidos para %HH, onde HH são dois dígitos hexadecimais representando o
código ASCII do caractere. Este processo é chamado de URL encoding.
Tabela para os caracteres mais comuns:

Caractere URL Enconded

\t (tab) %09

\n (return) %0A

/ %2F

~ %7E

: %3A

; %3B

@ %40

& %26

Aqui vão dois links para fazer e desfazer esta conversão:

 http://www.shelldorado.com/scripts/cmds/urlencode
 http://www.shelldorado.com/scripts/cmds/urldecode

3.4. Upload
Agora que já sabemos utilizar os métodos GET e POST vamos a um exemplo um
pouco diferente. Vamos supor que precisamos fazer um CGI que permita o usuário
fazer um upload de um arquivo para o servidor. Aqui utilizamos um FORM um
pouco diferente. Falamos para o FORM utilizar um tipo de codificação diferente, no
caso enctype="multipart/form-data". Criamos um HTML normalmente e como
opção do action colocamos o nosso script.
================ upload.html ====================
<html>
<body>
<form enctype="multipart/form-data" action="/cgi-bin/upload.cgi"
method="post">
Enviar arquivo: <input name="userfile" size="30" type="file">
<BR><BR>
<input type="submit" value="Envia" name="Envia">
</form>
</body>
</html>
=================================================

O nosso script é quase igual a um POST normal. A principal diferença é que o input
para o script não vem em uma única linha, e sim em várias. Quem faz isto é
o enctype="multipart/form-data". Vem inclusive o conteúdo do arquivo via POST!!
Então pegamos tudo da entrada padrão. Note que junto com o input vem outras
cositas mas =8) Vamos a um exemplo:
================ upload.cgi =====================
#!/bin/bash

echo "content-type: text/html"


echo
echo
echo "<html> <head> <title> CGI script </title> </head>"
echo "<body><pre>"
# descomente se quiser ver as variaveis de ambiente
#export

# ele separa as varias partes do FORM usando um limite (boundary) que eh


# diferente a cada execucao. Este limite vem na variavel de ambiente
CONTENT_TYPE
# algo mais ou menos assim
# CONTENT_TYPE="multipart/form-data; boundary=--------------
1086400738455992438608787998"
# Aqui pegamos este limite
boundary=$(export | sed '/CONTENT_TYPE/!d;s/^.*dary=//;s/.$//')

#echo
#echo "boundary = $boundary"

# pegamos toda a entrada do POST e colocamos em VAR


VAR=$(sed -n '1,$p')
# imprimimos o que vem no input
echo "$VAR"
echo -e '\n\n'
echo "================= FIM ============================="
echo -e '\n\n'
# pegamos o nome do arquivo que foi feito o upload
FILENAME=$(echo "$VAR" | sed -n
'2!d;s/\(.*filename=\"\)\(.*\)\".*$/\2/;p')

# pegamos somente o conteudo do arquivo do upload


FILE=$(echo "$VAR" | sed -n "1,/$boundary/p" | sed '1,4d;$d')

echo "Nome do arquivo : $FILENAME"


echo
# imprimimos no browser o conteudo do arquivo
echo "$FILE"
# redirecionamos o conteudo do arquivo para um arquivo local no server
# upload feito ;)
echo "$FILE" | sed '$d' > "/tmp/$FILENAME"

echo "</pre></body></html>"
=================================================
3.5. CheckBox
Para relaxar um exemplo mais simples, vamos fazer um CheckBox. Primeiro criamos
uma página HTML com o FORM para checkbox normalmente. Vamos utilizar o
método POST no exemplo.
================ checkbox.html ==================
<html><head><title>distro</title></head>
<body>

<form action="/cgi-bin/checkbox.cgi" method="POST">

<h3>Quais destas distro voce gosta ?</h3>


<input type="checkbox" name="debian" value=1> Debian<br>
<input type="checkbox" name="redhat" value=1> RedHat<br>
<input type="checkbox" name="conectiva" value=1> Conectiva<br>
<input type="checkbox" name="mandrake" value=1> Mandrake<br>
<input type="submit" value="Enviar">
</form>

</body>
</html>
=================================================

Pegamos os inputs do FORM na entrada padrão. Eles são separados por &. Todas as
opções que o usuário selecionar virão no POST. Ah, não se esqueça, name=value.
Um exemplo de input que recereberemos:
debian=1&conectiva=1
Assim, sabemos que o usuário escolheu estas duas opções. Basta fazer o script.
================ checkbox.cgi ===================
#!/bin/bash

echo "content-type: text/plain"


echo
VAR=$(sed -n 1p)
echo "$VAR"
echo
[ "$VAR" ] || { echo "voce nao gosta de nada";exit; }
echo "Voce gosta de :"
echo
IFS="&"
for i in `echo "$VAR"`;do
echo " $(echo $i | cut -d= -f1)"
done
=================================================

3.6. Radio Buttons


Outro exemplo é o Radio Buttons. Também utilizaremos o método POST aqui.
Criamos um html normalmente.
================ radiobuttons.html ==============
<html><head><title>distro</title></head>
<body>
<form action="/cgi-bin/radiobuttons.cgi" method="POST">

<h3>Qual sua distro predileta ?</h3>


<input type="radio" name="distro" value=Debian> Debian<br>
<input type="radio" name="distro" value=RedHat> RedHat<br>
<input type="radio" name="distro" value=Conectiva> Conectiva<br>
<input type="radio" name="distro" value=Mandrake> Mandrake<br>
<input type="radio" name="distro" value=none> Nenhuma destas<br>
<input type="submit" value="Enviar">
</form>

</body>
</html>
=================================================

O que será enviado pelo POST é o input escolhido. Como é Radio Buttons, somente
uma opção é aceita, assim temos o input: name=value, onde name é a variável distro
e value é a opção escolhida pelo usuário. Um exemplo é: distro=Debian
================ radiobuttons.cgi ===============
#!/bin/bash

echo "content-type: text/html"


echo
VAR=$(sed -n 1p)
echo "$VAR <br>"
echo "<br>"
[ "$VAR" ] || { echo "voce nao gosta de nada";exit; }

echo "Sua distro predileta eh: <b>$(echo $VAR | cut -d= -f2)</b>"
=================================================

3.7. Contador de Acesso Genérico

Um dos primeiros exemplos que vimos foi como fazer um contador de


acesso contador.cgi. Aquela implementação tem um problema de concorrência. Se a
página tiver 2 acessos 'simultâneos' ela pode deixar de contabilizar 1 acesso. Vamos
imaginar que temos dois acessos a página 'ao mesmo tempo'. O fluxo de execução do
CGI do primeiro acesso executa as seguintes linhas:
ARQ="/tmp/page.hits"
n="$(cat $ARQ 2> /dev/null)" || n=0

O kernel interrompe a execução do CGI neste momento e começa a executar o CGI


do segundo acesso a página. O segundo CGI é todo executado, assim, ele leu o valor
que tinha no arquivo /tmp/page.hits, somou 1 e sobrescreveu o arquivo com o novo
valor. Agora o kernel volta a executar o primeiro CGI de onde parou, seguindo nosso
algoritmo, o CGI já tem o valor antigo do arquivo na variável n, assim ele vai para a
próxima instrução:
echo $((n=n+1)) > "$ARQ"
Sobrescreveu o antigo valor. Note: como ele foi interrompido antes da segunda
execução do CGI, ele estava com o valor antigo em n. Nosso contador perdeu 1
acesso. :/

Para arrumar este problema, vamos aproveitar e incluir um novo tópico aqui.

3.7.1. SSI - Server Side Includes

SSI são diretivas que colocamos em uma página HTML pura para que o servidor
avalie quando a página for acessada. Assim podemos adiconar conteúdo dinâmico a
página sem precisarmos escrever toda ela em CGI. Para isto basta configurar o seu
Apache corretamente. Quem fornece esta opção é o módulo includes (mod_include),
simplesmente descomentamos a linha que carrega este módulo:
LoadModule includes_module /usr/lib/apache/1.3/mod_include.so

Exitem duas maneiras de configurá-lo:

1. através da extensão do arquivo, normalmente .shtml


2. através da opção XBitHack. Esta opção testa se o arquivo HTML (.html)
requisitado tem o bit de execução setado, se tiver ele executará o que tiver
usando as suas diretivas. Acrescente a seguinte linha em seu httpd.conf.
3. XBitHack on
PS: em ambos os casos o arquivo HTML precisa ser executável.

Este tópico está muito bem documentado nos seguintes endereços:

 http://httpd.apache.org/docs/howto/ssi.html
 http://httpd.apache.org/docs/mod/mod_include.html

3.7.2. Contador

Para resolver o problema de concorrência vamos utilizar um named pipe. Criamos o


seguinte script que será o daemon que receberá todos os pedidos para incrementar o
contador. Note que ele vai ser usado por qualquer página no nosso site que precise
de um contador.
================ daemon_contador.sh= ===========
#!/bin/bash

PIPE="/tmp/pipe_contador" # arquivo named pipe


# dir onde serao colocados os arquivos contadores de cada pagina
DIR="/var/www/contador"

[ -p "$PIPE" ] || mkfifo "$PIPE"

while :;do
for URL in $(cat < $PIPE);do
FILE="$DIR/$(echo $URL | sed 's,.*/,,')"
# quando rodar como daemon comente a proxima linha
echo "arquivo = $FILE"

n="$(cat $FILE 2> /dev/null)" || n=0


echo $((n=n+1)) > "$FILE"
done
done
=================================================

Como só este script altera os arquivos, não existe problema de concorrência.

Este script será um daemon, isto é, rodará em background. Quando uma página
sofrer um acesso, ela escreverá a sua URL no arquivo de pipe. Para testar, execute
este comando:
echo "teste_pagina.html" > /tmp/pipe_contador

Em cada página que quisermos adicionar o contador acrescentamos a seguinte linha:


<!--#exec cmd="echo $REQUEST_URI > /tmp/pipe_contador"-->

Note que a variável $REQUEST_URI contém o nome do arquivo que o browser


requisitou.

PS: assim que tiver tempo vou tentar explicar melhor este tópico.

3.8. Segurança
3.8.1. Introdução e Configuração

Este é um tópico importante quando falamos sobre CGI, principalmente os que têm
algum tipo de interação com o usuário. Mas para aumentar um pouco a segurança de
nossos CGIs podemos utilizar a opção AccessFileName do Apache. Ela nos permite
especificar quais usuário terão acesso a um determinado diretório. Por exemplo,
podemos especificar quais usuários terão acesso aos scripts
em http://localhost/cgi-bin/controle/

Primeiro vamos configurar o Apache. Procure e, se for o caso, descomente a seguine


linha em seu httpd.conf
AccessFileName .htaccess

Esta opção define para o Apache o nome do arquivo que terá as informações sobre o
controle de acesso de cada diretório. Procure e, se for o caso, descomente as
seguintes linhas para não deixar nenhum usuário baixar nossos arquivos de controle
de acesso e de usuários e senhas.
<Files ~ "^\.ht">
Order allow,deny
Deny from all
</Files>

Este próximo passo é necessário porque normalmente a opção AllowOverride default


é None. Assim, para cada diretório que você deseja ter este controle, adicione a
seguinte linha em seu httpd.conf:
<Directory /diretorio/que/tera/htaccess/>
AllowOverride AuthConfig
</Directory>

Agora temos o nosso Apache configurado, vamos configurar o nosso .htaccess. Este
arquivo tem a seguinte estrutura:
AuthName "Acesso Restrito"
AuthType Basic
AuthUserFile /PATH/TO/.htpasswd

require valid-user

Onde:

AuthName mensagem que irá aparecer quando pedir o username e passwd

AuthType normalmente é Basic

AuthUserFile o PATH para o arquivo que contém a lista de user e senha válido

require valid-user especifica que somente usuários válidos terão acesso

Vamos a um exemplo. Vamos supor que queremos proteger o acesso ao diretório


/usr/lib/cgi-bin/controle. Configuramos o httpd.conf como descrito acima. Depois
criamos o seguinte arquivo neste diretório.
AuthName "Acesso Restrito"
AuthType Basic
AuthUserFile /usr/lib/cgi-bin/controle/.htpasswd

require valid-user

Feito isto, criamos o nosso arquivo com os usuários válidos. Importante: os


usuários que vamos criar não precisam existir na máquina, i.e., não tem nenhuma
relação com o arquivo /etc/passwd. Para criar o arquivo utilizamos o comando:
htpasswd -m -c ./.htpasswd user

Após criarmos e adicionarmos o primeiro usuário, basta tirar a opção -c do comando


para adicionar novos usuários no mesmo arquivo. Exemplo:htpasswd -m ./.htpasswd
outro_user
OBS: a cada execução do comando aparecerá um prompt pedindo para definir uma
senha para o usuário. A opção -m serve para utilizar o algoritmo MD5 modificado
pelo Apache. Mais detalhes: man htpasswd

3.8.2. Tá, e daí? Onde está o CGI em Shell?

Calma, isto que vimos é Apache puro. Mas agora vem o pulo do gato :) Vamos
continuar nosso exemplo. Crie o seguinte arquivo e coloque em /usr/lib/cgi-
bin/controle
================ set.cgi =====================
#!/bin/bash

echo "content-type: text/plain"


echo
set
=================================================

Depois acesse http://localhost/cgi-bin/controle/set.cgi. Se tudo ocorreu bem,


aparecerá uma tela pedindo usuário e senha. Entre com um usuário e senha que você
cadastrou em ./.htpasswd. Será mostrado todas as variáveis de ambiente. Dê uma
olhada na variável REMOTE_USER. É o nome do usuário que fez o login. Agora
podemos ter CGIs onde só determinados usuários podem acessar, e dentre estes
usuários só alguns terão acesso a certas opções do script, etc.

Um exemplo :) Vamos imaginar que só determinados usuários tem acesso ao CGI de


controle sobre a máquina. Então configuramos o Apache, criamos o.htaccess e
cadastramos os usuários válidos em .htpasswd, isto no diretório /usr/lib/cgi-
bin/controle

Criamos o nosso script de controle:


================ controle.cgi ================
#!/bin/bash

echo "content-type: text/html"


echo
echo "<html><head><title>Controle</title></head>"
echo "<body>"

echo "Voce esta logado como usuario: <b>$REMOTE_USER</b><br>"

echo "<form action=\"/cgi-bin/controle/controle_post.cgi\"


method=\"POST\">"

echo "<h3>Qual destas operações voce deseja executar ?</h3>"

[ "$REMOTE_USER" = "gerente" ] && {


echo "<input type=radio name=op value=halt> Desligar máquina<br>"
echo "<input type=radio name=op value=reboot> Reinicializar<br>"; }

echo "
<input type=radio name=op value=w> Ver quem esta logado<br>
<input type=radio name=op value=df> Ver uso do disco<br>
<input type=submit value=Enviar>
</form>

</body>
</html>"

=================================================

Ok, especificamos que somente o usuário gerente terá acesso as opções


de halt e reboot. Criamos o script que tratará este input.
================ controle_post.cgi ==============
#!/bin/bash

echo "content-type: text/html"


echo
echo "<html><head><title>Controle</title></head>"
echo "<body>"

echo "Voce esta logado como usuario: <b>$REMOTE_USER</b><br>"


op=$(sed '/./s/^op=//')

case "$op"
in

"halt" )
echo "desligando a maquina ..."
;;
"reboot" )
echo "reinicializando a maquina ..."
;;
"w" )
echo "Usuários logado:"
echo "<pre>$(who)</pre>"
;;
"df" )
echo "Disco"
echo "<pre>$(df -Th)</pre>"
;;
* )
echo "opcao invalida</body></html>"
exit
;;
esac
echo "</body></html>"
=================================================

Bom, tudo tranqüilo. Script sem problemas. NÃO

Pois, se algum usuário olhar o source HTML da página http://localhost/cgi-


bin/controle/controle.cgi, ele verá os inputs do FORM. Assim, ele sabe que o que
é enviado pelo POST no nosso exemplo é op=xx. Ele não enxergará as
opções halt e reboot, mas ele perceberá que são comandos e que o CGI é para
executar algum comando sobre a máquina. Então fizemos o seguinte comando.
echo "op=halt" | lynx -dump -post-data -auth=user:senha \
http://localhost/cgi-bin/controle/controle_post.cgi
No user:senha, coloque um user e senha válido, mas use um user diferente de gerente

Note que estamos indo direto a segunda página controle_post.cgi. O lynx pra quem
não sabe é um browser modo texto. No exemplo, ele está enviando os dados que
recebeu da entrada padrão via o método POST para aquela URL.

Como no script controle_post.cgi não existe nenhum controle de usuário, o nosso


usuário != de gerente conseguiu desligar a nossa máquina. :/

Então vamos arrumar:


================ controle_post.cgi ==============
#!/bin/bash

echo "content-type: text/html"


echo
echo "<html><head><title>Controle</title></head>"
echo "<body>"

echo "Voce esta logado como usuario: <b>$REMOTE_USER</b><br>"


op=$(sed '/./s/^op=//')

case "$op"
in

"halt" )
[ "$REMOTE_USER" != "gerente" ] && { echo "opcao
invalida"
set >> "/tmp/CGI_halt_$REMOTE_ADDR"; echo
"</body></html>"; exit; }
echo "desligando a maquina ..."
;;
"reboot" )
[ "$REMOTE_USER" != "gerente" ] && { echo "opcao
invalida"
set >> "/tmp/CGI_reboot_$REMOTE_ADDR"; echo
"</body></html>"; exit; }
echo "reinicializando a maquina ..."
;;
"w" )
echo "Usuários logado:"
echo "<pre>$(who)</pre>"
;;
"df" )
echo "Disco"
echo "<pre>$(df -Th)</pre>"
;;
* )
echo "opcao invalida</body></html>"
exit
;;
esac
echo "</body></html>"
=================================================
Moral da história: quando utilizar interação com o usuário tem que testar tudo!!!
teste, teste, teste. Verifique as opcões, variáveis, etc.

4. LAN +_+
Exemplo prático. Vamos monitorar os host de nossa LAN. Saber quais estão ativos,
quais não respondem e informações sobre um determinado host. Este fonte é apenas
uma estrutura básica, mas server para se ter uma idéia do quão poderoso pode ficar
um CGI em Shell. Crie os seguintes arquivos em /usr/lib/cgi-bin/lan
================ lan.cgi ========================
#!/bin/bash

echo "content-type: text/html"


echo
echo "<html>"
echo "<head>"
echo "<title>Monitoramento da LAN</title>"
# Descomente se quiser auto refresh
#echo "<meta http-equiq=\"refresh\" content=\"10;url=/cgi-
bin/lan/lan.cgi\">"
echo "</head>"

echo "
<body bgcolor=white>
<div align=right>Usuário: <b>$REMOTE_USER</b></div>
<center><h2>Máquinas da LAN</h2>
<form method=\"post\" action=\"lan_info.cgi\">
<table widthborder=0 cellpadding=2>"

# arquivo contendo o nome dos host a monitorar


FILE_host="host"
maxcol=4
numcol=1
for host in $(cat "$FILE_host" | sort -g -tl -k2);do
[ $numcol = 1 ] && echo "<tr>"
# depedendo da versao do ping existe a opcao -w, que especifica
quantos
# segundo o ping deve esperar por resposta. coloque -w1 para agilizar
o
# tempo de resposta
ping -c1 "$host" > /dev/null 2>&1

if [ $? -eq 0 ];then
echo "<td align=\"center\">&nbsp;&nbsp;&nbsp;<img
src=\"/icons/penguin_on.jpg\" alt=\"$host OK\" border=0></a></td>"
echo "<td><input type=radio name=host
value=\"$host\"><br>$host</td>"
elif [ $? -eq 1 ] ;then
echo "<td align=\"center\">&nbsp;&nbsp;&nbsp;<img
src=\"/icons/penguin_off.jpg\" alt=\"Sem reposta da $host\"
border=0></a></td>"
echo "<td><br>$host</td>"
elif [ $? -eq 2 ] ;then
echo "<td align=\"center\">&nbsp;&nbsp;&nbsp;<img
src=\"/icons/penguin_off.jpg\" alt=\"$host nao existe\" border=0></a></td>"
echo "<td><br>$host</td>"
fi

[ $numcol = 4 ] && { echo "</tr>"; numcol=1; } ||


numcol=$(($numcol+1))
done

echo "
</table><br>

<input type=submit name=\"botao\" value=\"info\"> &nbsp;&nbsp;&nbsp;


<input type=submit name=\"botao\" value=\"processos\"> &nbsp;&nbsp;&nbsp;

</form>
</center>
</body></html>"
=================================================

Abaixo o script que receberá os pedidos da página principal. Para buscar


informações nos outros hosts estou utilizando um rsh. Você também pode utilizar
ssh, é só trocar.

PS: não se esqueça que para executar o rsh, ele precisa estar configurado e não pedir
senha. Para testar rsh host ls. O usuário default do Apache não tem shell, assim você
precisa rodá-lo com um outro usuário.
================ lan_info.cgi ===================
#!/bin/bash

echo "content-type: text/html"


echo
echo "<html>
<head>
<title>Monitoramento da LAN</title></head>

<body bgcolor=white>
<div align=right>Usuário: <b>$REMOTE_USER</b></div>"

VAR=$(sed -n '1p')
host=$(echo "$VAR" | sed 's/^host=\(.*\)\&.*$/\1/')
botao=$(echo "$VAR" | cut -d= -f3)

[ "$host" -a "$botao" ] || { echo "Opcao invalida</body></html>";exit; }

if [ "$botao" = "info" ];then


ip=$(ping -c1 "$host" 2> /dev/null | sed -n '/^PING/{s/^.*(\([0-
9\.]\+\)):.*$/\1/;p;}')
echo "<center><h2>Informacoes da maquina: <i>$host</i></h2></center>"
echo "<strong>Nome:</strong> $host<br>"
echo "<strong>IP:</strong> $ip<br>"
echo "<br>"

saida=$(rsh "$host" cat /proc/version)


[ "$?" -eq "0" ] && echo "<strong>Sistema
Operacional</strong><pre>$saida</pre>"

saida=$(rsh "$host" uptime)


[ "$?" -eq "0" ] && echo "<strong>uptime</strong><pre>$saida</pre>"
saida=$(rsh "$host" cat /proc/cpuinfo)
[ "$?" -eq "0" ] && echo "<strong>Informacoes da
CPU</strong><pre>$saida</pre>"

saida=$(rsh "$host" free -ok)


[ "$?" -eq "0" ] && echo "<strong>Informacoes de
Memoria</strong><pre>$saida</pre>"
mem=$(rsh $host cat /proc/meminfo)
percent=`echo "$mem" | sed -n '2p' | awk '{printf("%d",$3*100/$2)}'`
used=$(echo "$percent*2" | bc); free=$(echo "200-$percent*2" | bc)
echo "
<table border=0 cellspacing=0 cellpadding=2>
<tr>
<td><font size=\"3\">0%</font></td>
<td align=center width=$used bgcolor=red><font size=\"3\"
color=white>$percent%</font></td>
<td width=$free bgcolor=green><font size=\"3\">&nbsp;</font></td>
<td><font size=\"3\">100%</font></td>
</tr>
</table>"
echo "<br><br><strong>Detalhes:</strong>"
echo "<pre>$(echo "$mem" | sed '1,3d')"
echo "<br><br></pre>"

echo "<strong>Informacoes de Disco</strong><br>"


echo "Discos SCSI <br>"
echo "<pre>$(rsh "$host" cat /proc/scsi/scsi 2> /dev/null)</pre>"

echo "Discos IDE:<br> "

for i in a b c d; do
TEMP=$(rsh "$host" "test -L \"/proc/ide/hd$i\" && echo sim")
[ "$TEMP" = "sim" ] && \
echo -n "hd$i : $(rsh "$host" cat
"/proc/ide/hd$i/{media,model}" | sed 'N;s/\n/ /')<br>"
done

echo "<br>Particoes dos Discos"


echo "<pre>$(rsh "$host" cat /proc/partitions)</pre>"
echo "<pre>$(rsh "$host" df -Th)</pre>"
echo "<strong><i>swap</i></strong><pre>$(rsh "$host" cat
/proc/swaps)</pre>"

elif [ "$botao" = "processos" ];then


saida=$(rsh "$host" ps aux)
[ "$?" -eq "0" ] && echo "<strong> Informacoes sobre os processos da
\
maquina: <i>$host</i></strong><pre>$saida</pre>"
fi
echo "</body></html>"
=================================================

Você precisa baixar estas duas imagens utilizadas para mostrar se os hosts estão
respondendo ou não.
5. Resumão
POST & GET

No GET você pega as opções através da variável de ambiente $QUERY_STRING.


No POST você pega através da entrada padrão, ex:
VAR=$(sed -n '1p')
Após isto é só pegar as opções nestas variáveis e fazer o script necessário.

Bom, com este texto espero ter dado uma visão geral de como funciona CGI em
Shell Script. Assim que tiver tempo vou procurar incrementar o texto adicionando
novos exemplo, explicando mais os conceitos...

Se você quiser contribuir, me envie um mail =8)

 Silvano B. Dias
 Aurélio Marinho Jargas
 Vinícius Della Líbera
 Julio Cezar Neves