16. Funções de rede
Abordaremos agora as funções de rede do PHP, que nos permitem realizar a programação do TCP / IP (Protocolo de Controle de Transferência / Protocolo de Internet).

16.1. Noções básicas de programação na Internet
16.1.1. Noções gerais
Consideremos a comunicação entre duas máquinas remotas A e B:

Quando um aplicativo AppA da máquina A deseja se comunicar com um aplicativo AppB da máquina B na Internet, ele precisa saber algumas informações:
- o endereço IP (Protocolo de Internet) ou o nome da máquina B;
- o número da porta com a qual a aplicação AppB opera. De fato, a máquina B pode hospedar várias aplicações que operam na Internet. Quando recebe informações provenientes da rede, ela precisa saber a qual aplicação essas informações se destinam. Os aplicativos da máquina B têm acesso à rede por meio de interfaces, também chamadas de portas de comunicação. Essa informação está contida no pacote recebido pela máquina B para que ele seja entregue ao aplicativo correto;
- os protocolos de comunicação compreendidos pela máquina B. Em nosso estudo, utilizaremos apenas os protocolos TCP-IP;
- o protocolo de comunicação aceito pelo aplicativo AppB. De fato, as máquinas A e B vão “se comunicar”. O que elas vão transmitir será encapsulado nos protocolos TCP-IP. No entanto, quando, no final da cadeia, o aplicativo AppB receber a informação enviada pelo aplicativo AppA, ele precisará ser capaz de interpretá-la. Isso é semelhante à situação em que duas pessoas, A e B, se comunicam por telefone: o diálogo delas é transmitido pelo telefone. A fala será codificada na forma de sinais pelo telefone A, transmitida pelas linhas telefônicas, chegará ao telefone B para ser decodificada. A pessoa B então ouve as palavras. É aí que entra o conceito de protocolo de diálogo: se A fala francês e B não entende essa língua, A e B não poderão dialogar de forma eficaz;
Portanto, os dois aplicativos que se comunicam devem chegar a um acordo sobre o tipo de diálogo que irão adotar. Por exemplo, o diálogo com um serviço ftp não é o mesmo que com um serviço pop: esses dois serviços não aceitam os mesmos comandos. Eles possuem um protocolo de diálogo diferente;
16.1.2. As características do protocolo TCP
Aqui, abordaremos apenas as comunicações de rede que utilizam o protocolo de transporte TCP, cujas principais características são as seguintes:
- o processo que deseja transmitir estabelece, em primeiro lugar, uma conexão com o processo destinatário das informações que irá transmitir. Essa conexão é estabelecida entre uma porta da máquina transmissora e uma porta da máquina receptora. Entre as duas portas, é criado um caminho virtual que será reservado exclusivamente aos dois processos que estabeleceram a conexão;
- todos os pacotes enviados pelo processo de origem seguem esse caminho virtual e chegam na ordem em que foram enviados;
- a informação transmitida tem um caráter contínuo. O processo emissor envia informações em seu próprio ritmo. Essas informações não são necessariamente enviadas imediatamente: o protocolo TCP aguarda até ter quantidade suficiente para enviá-las. Elas são armazenadas em uma estrutura chamada segmento TCP. Esse segmento, uma vez preenchido, será transmitido para a camada IP, onde será encapsulado em um pacote IP;
- cada segmento enviado pelo protocolo TCP é numerado. O protocolo TCP destinatário verifica se está recebendo os segmentos na sequência correta. Para cada segmento recebido corretamente, ele envia um aviso de recebimento ao remetente;
- quando este último o recebe, ele notifica o processo emissor. Assim, este pode saber que um segmento chegou ao destino;
- se, após um certo tempo, o protocolo TCP que emitiu um segmento não receber uma confirmação de recebimento, ele retransmite o segmento em questão, garantindo assim a qualidade do serviço de encaminhamento da informação;
- o circuito virtual estabelecido entre os dois processos que se comunicam é full-duplex: isso significa que a informação pode transitar nos dois sentidos. Assim, o processo de destino pode enviar confirmações de recebimento mesmo enquanto o processo de origem continua enviando informações. Isso permite, por exemplo, que o protocolo de origem TCP envie vários segmentos sem esperar pela confirmação de recebimento. Se, após um certo tempo, ele perceber que não recebeu a confirmação de recebimento de um determinado segmento nº n, ele retomará a transmissão dos segmentos a partir desse ponto;
16.1.3. A relação cliente-servidor
Frequentemente, a comunicação na Internet é assimétrica: a máquina A inicia uma conexão para solicitar um serviço à máquina B, especificando que deseja estabelecer uma conexão com o serviço SB1 da máquina B. Esta aceita ou recusa. Se aceitar, a máquina A pode enviar suas solicitações ao serviço SB1. Essas solicitações devem estar em conformidade com o protocolo de comunicação compreendido pelo serviço SB1. Estabelece-se, assim, um diálogo de solicitação-resposta entre a máquina A, chamada de máquina cliente, e a máquina B, chamada de máquina servidor. Um dos dois parceiros encerrará a conexão.
16.1.4. Arquitetura de um cliente
A arquitetura de um programa de rede que solicita os serviços de um aplicativo servidor será a seguinte:
ouvrir la connexion avec le service SB1 de la machine B
si réussite alors
tant que ce n'est pas fini
préparer une demande
l'émettre vers la machine B
attendre et récupérer la réponse
la traiter
fin tant que
finsi
fermer la connexion
16.1.5. Arquitetura de um servidor
A arquitetura de um programa que oferece serviços será a seguinte:
ouvrir le service sur la machine locale
tant que le service est ouvert
se mettre à l'écoute des demandes de connexion sur un port dit port d'écoute
lorsqu'il y a une demande, la faire traiter par une autre tâche sur un autre port dit port de service
fin tant que
O programa servidor trata de maneira diferente a solicitação inicial de conexão de um cliente e suas solicitações posteriores para obter um serviço. O programa não presta o serviço propriamente dito. Se o fizesse, durante o tempo em que o serviço estivesse sendo prestado, ele não estaria mais à escuta das solicitações de conexão e, consequentemente, os clientes não seriam atendidos. Portanto, ele procede de outra forma: assim que uma solicitação de conexão é recebida na porta de escuta e, em seguida, aceita, o servidor cria uma tarefa encarregada de prestar o serviço solicitado pelo cliente. Esse serviço é prestado em outra porta da máquina servidora, chamada de porta de serviço. Dessa forma, é possível atender a vários clientes ao mesmo tempo.
Uma tarefa de serviço terá a seguinte estrutura:
tant que le service n'a pas été rendu totalement
attendre une demande sur le port de service
lorsqu'il y en a une, élaborer la réponse
transmettre la réponse via le port de service
fin tant que
libérer le port de service
16.2. Conheça os protocolos de comunicação da Internet
16.2.1. Introdução
Quando um cliente se conecta a um servidor, estabelece-se então um diálogo entre eles. A natureza desse diálogo define o que chamamos de protocolo de comunicação do servidor. Entre os protocolos mais comuns da Internet, encontram-se os seguintes:
- HTTP: HyperText Transfer Protocol — o protocolo de comunicação com um servidor web (servidor HTTP);
- SMTP: Simple Mail Transfer Protocol — o protocolo de comunicação com um servidor de envio de e-mails (servidor SMTP);
- POP: Post Office Protocol — o protocolo de comunicação com um servidor de armazenamento de e-mails (servidor POP). Trata-se de recuperar os e-mails recebidos, e não de enviá-los;
- IMAP: Internet Message Access Protocol — o protocolo de comunicação com um servidor de armazenamento de e-mails (servidor IMAP). Esse protocolo substituiu gradualmente o protocolo mais antigo POP;
- FTP: File Transfer Protocol — o protocolo de comunicação com um servidor de armazenamento de arquivos (servidor FTP);
Todos esses protocolos têm a particularidade de serem protocolos de linhas de texto: o cliente e o servidor trocam linhas de texto. Se tivermos um cliente capaz de:
- estabelecer uma conexão com um servidor TCP;
- exibir no console as linhas de texto que o servidor lhe envia;
- enviar ao servidor as linhas de texto que um usuário digitaria no teclado;
assim, é possível interagir com um servidor TCP que utilize um protocolo de linhas de texto, desde que se conheçam as regras desse protocolo.
16.2.2. Utilitários TCP

Nos códigos associados a este documento, encontram-se dois utilitários de comunicação TCP:
- [RawTcpClient] permite conectar-se à porta P de um servidor S;
- O [RawTcpServer] permite criar um servidor que aguarda clientes na porta P;
O servidor TCP [RawTcpServer] éé chamado com a sintaxe [RawTcpServeur port] para criar um serviço TCP na porta [port] da máquina local (o computador no qual você está trabalhando):
- o servidor pode atender a vários clientes simultaneamente;
- o servidor executa os comandos digitados pelo usuário no teclado. São eles:
- list: lista os clientes atualmente conectados ao servidor. Eles são exibidos no formato [id=x-nom=y]. O campo [id] serve para identificar os clientes;
- send x [texte]: envia texto para o cliente nº x (id=x). Os colchetes [] não são enviados. Eles são necessários no comando e servem para delimitar visualmente o texto enviado ao cliente;
- close x: encerra a conexão com o cliente nº x;
- quit: encerra todas as conexões e interrompe o serviço;
- as linhas enviadas pelo cliente ao servidor são exibidas no console;
- todas as trocas de dados são registradas em um arquivo de texto com o nome [machine-portService.txt], onde
- [machine] é o nome da máquina na qual o código está sendo executado;
- [port] é a porta do serviço que responde às solicitações do cliente;
O cliente TCP [RawTcpClient] é chamado com a sintaxe [RawTcpClient serveur port] para se conectar à porta [port] do servidor [serveur]:
- as linhas digitadas pelo usuário no teclado são enviadas ao servidor;
- as linhas enviadas pelo servidor são exibidas no console;
- todas as trocas de dados são registradas em um arquivo de texto com o nome [serveur-port.txt];
Vejamos um exemplo. Abrimos duas janelas de comando do Windows e, em cada uma delas, acessamos a pasta de utilitários. Em uma das janelas, iniciamos o servidor [RawTcpServer] na porta 100:

- no [1], estamos na pasta de utilitários;
- no [2], iniciamos o servidor TCP na porta 100;
- em [3], o servidor fica aguardando um cliente TCP;
- em [4], o servidor aguarda um comando digitado pelo usuário no teclado;
Na outra janela de comandos, inicia-se o cliente TCP:

- no [5], estamos na pasta de utilitários;
- em [6], iniciamos o cliente TCP: instruímos-o a se conectar à porta 100 da máquina local (aquela com a qual você está trabalhando);
- em [7], o cliente conseguiu se conectar ao servidor. Indicamos as coordenadas do cliente: ele está na máquina [DESKTOP-528I5CU] (a máquina local neste exemplo) e usa a porta [50405] para se comunicar com o servidor:
- em [8], o cliente aguarda um comando digitado pelo usuário no teclado;
Voltemos à janela do servidor. Seu conteúdo mudou:

- em [9], um cliente foi detectado. O servidor atribuiu a ele o número 1. O servidor identificou corretamente o cliente remoto (máquina e porta);
- em [10], o servidor volta a aguardar um novo cliente;
Voltemos à janela do cliente e enviemos um comando ao servidor:

- em [11], o comando enviado ao servidor;
Voltemos à janela do servidor. Seu conteúdo mudou:

- em [12], entre colchetes, a mensagem recebida pelo servidor;
Vamos enviar uma resposta ao cliente:

- para [13], a resposta enviada ao cliente 1. Apenas o texto entre colchetes é enviado, não os próprios colchetes;
Voltemos à janela do cliente:

- em [14], a resposta recebida pelo cliente. O texto recebido é aquele entre colchetes;
Voltemos à janela do servidor para ver outros comandos:

- em [15], solicitamos a lista de clientes;
- em [16], a resposta;
- em [17], encerramos a conexão com o cliente nº 1;
- em [18], a confirmação do servidor;
- em [19], desligamos o servidor;
- em [20], a confirmação do servidor;
Voltemos à janela do cliente:

- em [21], o cliente detectou o fim do serviço;
Dois arquivos de log foram criados, um para o servidor e outro para o cliente:

- em [25], os logs do servidor: o nome do arquivo é o nome do cliente [machine-port];
- em [26], os logs do cliente: o nome do arquivo é o nome do servidor [machine-port];
Os logs do servidor são os seguintes:
Os logs do cliente são os seguintes:
16.3. Obter o nome ou o endereço IP de um computador na Internet

Os computadores na Internet são identificados por um endereço IP (IPv4 ou IPv6) e, na maioria das vezes, por um nome. Mas, no fim das contas, apenas o endereço IP é utilizado. Portanto, às vezes é necessário saber o endereço IP de um computador identificado pelo nome.
O script [ip-01.php] é o seguinte:
<?php
// respeito estrito aos tipos declarados dos parâmetros das funções
declare (strict_types=1);
//
// gestão de erros
error_reporting(E_ALL & E_STRICT);
ini_set("display_errors", "on");
//
// constantes
$HOTES = array("istia.univ-angers.fr", "www.univ-angers.fr", "www.ibm.com", "localhost", "", "xx");
// endereços IP e nomes das máquinas de $HOTES
for ($i = 0; $i < count($HOTES); $i++) {
getIPandName($HOTES[$i]);
}
// fim
print "Terminé\n";
exit;
//------------------------------------------------
function getIPandName(string $nomMachine): void {
//$nomMachine: nome da máquina cujo endereço se deseja obter IP
//
// nomMachine-->endereço IP
$ip = gethostbyname($nomMachine);
print "---------------\n";
if ($ip !== $nomMachine) {
print "ip[$nomMachine]=$ip\n";
// endereço IP --> nomMachine
$name = gethostbyaddr($ip);
if ($name !== $ip) {
print "name[$ip]=$name\n";
} else {
print "Erreur, machine[$ip] non trouvée\n";
}
} else {
print "Erreur, machine[$nomMachine] non trouvée\n";
}
}
Comentários
- linhas 7-8: solicita-se que o PHP sinalize todos os erros (E_ALL e E_STRICT) e que estes sejam exibidos. Esse modo é recomendado apenas no modo de desenvolvimento para aprimorar o código com os avisos do PHP. No modo de produção, na linha 8, deve-se definir como “off”. A partir da versão 5.4 do PHP, o nível E_STRICT está incluído no E_ALL;
- linha 11: a lista de máquinas cujo nome e endereço IP se deseja obter;
As funções de rede de PHP são utilizadas na função getIpandName da linha 21.
- linha 25: a função gethostbyname($nom) permite obter o endereço IP “ip3.ip2.ip1.ip0” da máquina chamada $nom. Se a máquina $nom não existir, a função retorna $nom como resultado;
- linha 30: a função gethostbyaddr($ip) permite obter o nome da máquina com o endereço $ip no formato “ip3.ip2.ip1.ip0”. Se a máquina $ip não existir, a função retorna $ip como resultado;
Resultados:
---------------
ip[istia.univ-angers.fr]=193.49.144.41
name[193.49.144.41]=ametys-fo-2.univ-angers.fr
---------------
ip[www.univ-angers.fr]=193.49.144.41
name[193.49.144.41]=ametys-fo-2.univ-angers.fr
---------------
ip[www.ibm.com]=2.18.220.211
name[2.18.220.211]=a2-18-220-211.deploy.static.akamaitechnologies.com
---------------
ip[localhost]=127.0.0.1
name[127.0.0.1]=DESKTOP-528I5CU
---------------
ip[]=192.168.1.38
name[192.168.1.38]=DESKTOP-528I5CU.home
---------------
Erreur, machine[xx] non trouvée
Terminé
16.4. O protocolo HTTP (Protocolo de Transferência HyperText)
16.4.1. Exemplo 1

Quando um navegador exibe um URL, ele atua como cliente de um servidor web ou, em outras palavras, de um servidor HTTP. É ele quem toma a iniciativa e começa enviando uma série de comandos ao servidor. Para este primeiro exemplo:
- o servidor será o utilitário [RawTcpServer];
- o cliente será um navegador;
Primeiro, iniciamos o servidor na porta 100:

Em seguida, usando um navegador, solicitamos o URL [localhost:100], ou seja, informamos que o servidor HTTP consultado opera na porta 100 da máquina local:

Voltemos à janela do servidor:

- em [3], o cliente que se conectou;
- em [4-7], a sequência de linhas de texto que ele enviou:
- em [4]: essa linha tem o formato [GET URL HTTP/1.1]. Ela solicita o URL / e pede ao servidor para usar o protocolo HTTP 1.1;
- em [5]: essa linha tem o formato [Host: serveur:port]. O uso de maiúsculas ou minúsculas no comando [Host] não faz diferença. Vale lembrar aqui que o cliente consulta um servidor local operando na porta 100;
- o comando [User-Agent] fornece a identidade do cliente;
- o comando [Accept] indica quais tipos de documentos são aceitos pelo cliente;
- o comando [Accept-Language] indica em que idioma os documentos solicitados devem ser fornecidos, caso existam em vários idiomas;
- o comando [Connection] indica o modo de conexão desejado: [keep-alive] indica que a conexão deve ser mantida até que as trocas de dados sejam concluídas;
- em [7]: o cliente encerra seus comandos com uma linha em branco;
Encerramos a conexão ao encerrar o servidor:

16.4.2. Exemplo 2
Agora que conhecemos os comandos enviados por um navegador para solicitar um URL, vamos solicitar esse URL com nosso cliente TCP [RawTcpClient]. O servidor Apache do Laragon será nosso servidor web.
Vamos iniciar o Laragon e, em seguida, o servidor web Apache:


Agora, usando um navegador, vamos acessar o URL [http://localhost:80]. Aqui, especificamos apenas o servidor [localhost:80] e não o documento URL. Nesse caso, é o URL / que é solicitado, ou seja, a raiz do servidor web:

- em [1], o URL solicitado. Inicialmente, digitamos [http://localhost:80] e o navegador (Firefox, neste caso) simplesmente a transformou em [localhost], pois o protocolo [http] é implícito quando nenhum protocolo é mencionado e a porta [80] é implícita quando a porta não é especificada;
- em [2], a página raiz / do servidor web consultado;
Agora, vamos visualizar o texto recebido pelo navegador:

- Clique com o botão direito do mouse na página recebida e selecione a opção [2]. Você obterá o seguinte código-fonte:
Agora, vamos solicitar o URL e o [http://localhost:80] com nosso cliente TCP:
![]()
- no [1], nos conectamos à porta 80 do servidor localhost. É lá que opera o servidor web do Laragon;
Agora digitamos os comandos que descobrimos no parágrafo anterior:

- no [1], o comando [GET]. Solicitamos a raiz / do servidor web;
- em [2], o comando [Host];
- esses são os dois únicos comandos indispensáveis. Para os demais comandos, o servidor web utilizará valores padrão;
- em [3], a linha vazia que deve encerrar os comandos do cliente;
- abaixo da linha 3, vem a resposta do servidor web;
- em [4] até a linha vazia [5] vêm os cabeçalhos HTTP da resposta do servidor;
- após a linha [5] vem o documento HTML solicitado em [6];
Digitamos [quit] para encerrar o cliente e carregamos o arquivo de logs [localhost-80.txt]:
- linhas 11-79: o documento HTML recebido. No exemplo anterior, o Firefox havia recebido o mesmo;
Agora temos as bases para programar um cliente TCP que solicitaria um URL.
16.4.3. Exemplo 3

O script [http-01.php] é um cliente HTTP configurado pelo arquivo jSON [config-http-01.json]. O conteúdo deste arquivo é o seguinte:
- linha 2: o nome da máquina que hospeda o servidor web a ser acessado;
- linha 3: a porta na qual esse servidor web opera;
- linha 4: o URL do documento desejado;
- linha 5: a máquina de destino no formato máquina:porta;
- linha 6: a identificação do cliente HTTP: pode-se inserir o que se quiser;
- linha 7: o tipo de documento aceito pelo cliente, neste caso, texto HTML;
- linha 8: o idioma desejado para o documento solicitado;
- linha 9: o caractere de fim de linha para os comandos enviados pelo cliente: ele pode variar dependendo se o servidor está em uma máquina Unix (\n) ou Windows (\r\n);
O script [http-01.php] é o seguinte:
<?php
// respeito estrito aos tipos declarados dos parâmetros das funções
declare (strict_types=1);
//
// gestão de erros
// error_reporting(E_ALL & E_STRICT);
// ini_set("display_errors", "on");
//
// constantes
const CONFIG_FILE_NAME = "config-http-01.json";
//
// recuperamos a configuração
$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);
// obter o texto HTML dos URL do arquivo de configuração
foreach ($config as $site => $protocole) {
// leitura da página inicial do site $ite
$résultat = getURL($site, $protocole);
// exibição do resultado
print "$résultat\n";
}//para
// fim
exit;
//-----------------------------------------------------------------------
function getURL(string $site, array $protocole, $suivi = TRUE): string {
// lê o $siteURL e o armazena no arquivo $site.HTML
// a comunicação cliente/servidor ocorre de acordo com o protocolo $protocole
//
// abertura de uma conexão na porta de $site
$erreurNumber = 0;
$erreur = "";
$connexion = fsockopen($site, $protocole["port"], $erreurNumber, $erreur);
// retorno em caso de erro
if ($connexion === FALSE) {
return "Echec de la connexion au site (" . $site . " ," . $protocole["port"] . " : $erreur";
}
// $connexion representa um fluxo de comunicação bidirecional
// entre o cliente (este programa) e o servidor web acessado
// esse canal é utilizado para a troca de comandos e informações
// o protocolo de comunicação é HTTP
//
// criação do arquivo $site.HTML
$HTML = fopen("output/$site.HTML", "w");
if ($HTML === FALSE) {
// encerramento da conexão cliente/servidor
fclose($connexion);
// retorno de erro
return "Erreur lors de la création du fichier $site.HTML";
}
// o cliente iniciará o diálogo HTTP com o servidor
if ($suivi) {
print "Client : début de la communication avec le serveur [$site] ----------------------------\n";
}
// dependendo dos servidores, as linhas do cliente devem terminar com \n ou \r\n
$endOfLine = $protocole["endOfLine"];
// por uma questão de simplificação, não se testam os casos de erro na comunicação cliente/servidor
// o cliente envia o comando GET para solicitar o $protocolo["GET"]
// sintaxe GET URL HTTP/1.1
$commande = "GET " . $protocole["GET"] . " HTTP/1.1$endOfLine";
// rastreamento?
if ($suivi) {
print "--> $commande";
}
// envia-se o comando ao servidor
fputs($connexion, $commande);
// emissão dos demais cabeçalhos HTTP
foreach ($protocole as $verb => $value) {
if ($verb !== "GET" && $verb != "port"" && $verb !="endOfLine") {
// construindo o comando
$commande = "$verb: $value$endOfLine";
// próximo passo?
if ($suivi) {
print "--> $commande";
}
// envia-se o comando ao servidor
fputs($connexion, $commande);
}
}
// os cabeçalhos (headers) do protocolo HTTP devem terminar com uma linha em branco
fputs($connexion, $endOfLine);
//
// o servidor agora responderá no canal $connexion. Ele enviará todos
// seus dados e, em seguida, fechará o canal. O cliente, portanto, lê tudo o que chega de $connexion
// até o fechamento do canal
//
// primeiro, lê-se os cabeçalhos HTTP enviados pelo servidor
// elas também terminam com uma linha em branco
if ($suivi) {
print "Réponse du serveur [$site] ----------------------------\n";
}
$fini = FALSE;
while (!$fini && $ligne = fgets($connexion, 1000)) {
// há uma linha vazia?
$champs = [];
preg_match("/^(.*?)\s+$/", $ligne, $champs);
if ($champs[1] !== "") {
if ($suivi) {
// exibimos o cabeçalho HTTP
print "<-- " . $champs[1] . "\n";
}
} else {
// essa era a linha vazia — os cabeçalhos HTTP terminaram
$fini = TRUE;
}
}
// está-se lendo o documento HTML, que virá após a linha vazia
while ($ligne = fgets($connexion, 1000)) {
// a linha é gravada no arquivo HTML do site
fputs($HTML, $ligne);
}
// o servidor encerrou a conexão — o cliente, por sua vez, também a encerra
fclose($connexion);
// fechamento do arquivo $HTML
fclose($HTML);
// retorno
return "Fin de la communication avec le site [$site]. Vérifiez le fichier [$site.HTML]";
}
Comentários do código:
- linha 14: o arquivo de configuração é utilizado para criar um dicionário:
- as chaves do dicionário são os servidores web a serem consultados;
- os valores definem o protocolo HTTP a ser seguido;
- linhas 16-21: é feito um loop pela lista de servidores web da configuração;
- linha 26: a função getURL($site,$protocole,$suivi) solicita um documento do site $site e o armazena no arquivo de texto $site.HTML.Par: por padrão, as trocas entre cliente e servidor são registradas no console ($suivi=TRUE);
- linha 33: a função fsockopen($site,$port,$errNumber,$erreur) permite criar uma conexão com um serviço TCP / IP que opera na porta $port da máquina $site. Se a conexão falhar, [$errNumber] é um número de erro e [$erreur] é a mensagem de erro associada. Uma vez estabelecida a conexão cliente/servidor, diversos serviços TCP / IP trocam linhas de texto. Esse é o caso, neste contexto, do protocolo HTTP (Protocolo de Transferência HyperText). O fluxo do servidor que chega ao cliente pode, então, ser tratado como um arquivo de texto lido com o [fgets]. O mesmo se aplica ao fluxo que sai do cliente para o servidor, que pode ser gravado com o [fputs];
- linhas 44-50: criação do arquivo [$site.HTML], no qual será armazenado o documento HTML recebido;
- linha 60: o primeiro comando do cliente deve ser o comando [GET URL HTTP/1.1];
- linha 66: a função fputs permite que o cliente envie dados ao servidor. Aqui, a linha de texto enviada tem o seguinte significado: “Quero (GET) a página [URL] do site ao qual estou conectado. Estou trabalhando com o protocolo HTTP, versão 1.1”;
- linhas 68-79: enviam-se as demais linhas do protocolo HTTP [Host, User-Agent, Accept, Accept-Language]. A ordem delas não importa;
- linha 81: envia-se uma linha vazia ao servidor para indicar que o cliente concluiu o envio de seus cabeçalhos HTTP e que agora aguarda o documento solicitado;
- linhas 92-106: o servidor enviará, em primeiro lugar, uma série de cabeçalhos HTTP que fornecerão diversas informações sobre o documento solicitado. Esses cabeçalhos terminam com uma linha vazia;
- linha 93: lê-se uma linha enviada pelo servidor com a função PHP [fgets];
- linha 96: recupera-se o corpo da linha sem os espaços (espaços em branco, caractere de fim de linha) do fim da linha;
- linha 97: verifica-se se foi recuperada a linha vazia que marca o fim dos cabeçalhos HTTP enviados pelo servidor;
- linhas 98-101: se estivermos no modo [suivi], o cabeçalho HTTP recebido é exibido no console;
- linhas 108-111: as linhas de texto da resposta do servidor podem ser lidas linha por linha com um loop while e gravadas no arquivo de texto [output/$site.HTML]. Quando o servidor web enviar toda a página solicitada, ele encerra sua conexão com o cliente. Do lado do cliente, isso será detectado como um fim de arquivo;
Resultados:
O console exibe os seguintes logs:
Client : début de la communication avec le serveur [localhost] ----------------------------
--> GET / HTTP/1.1
--> Host: localhost:80
--> User-Agent: client PHP
--> Accept: text/HTML
--> Accept-Language: fr
Réponse du serveur [localhost] ----------------------------
<-- HTTP/1.1 200 OK
<-- Date: Thu, 16 May 2019 15:43:18 GMT
<-- Server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
<-- X-Powered-By: PHP/7.2.11
<-- Content-Length: 1781
<-- Content-Type: text/HTML; charset=UTF-8
Fin de la communication avec le site [localhost]. Vérifiez le fichier [localhost.HTML]
No nosso exemplo, o arquivo [output/localhost.HTML] recebido é o seguinte:
<!DOCTYPE HTML>
<HTML>
<head>
<title>Laragon</title>
<link href="https://fonts.googleapis.com/css?family=Karla:400" rel="stylesheet" type="text/css">
<style>
HTML, body {
height: 100%;
}
body {
margin: 0;
padding: 0;
width: 100%;
display: table;
font-weight: 100;
font-family: 'Karla';
}
.container {
text-align: center;
display: table-cell;
vertical-align: middle;
}
.content {
text-align: center;
display: inline-block;
}
.title {
font-size: 96px;
}
.opt {
margin-top: 30px;
}
.opt a {
text-decoration: none;
font-size: 150%;
}
a:hover {
color: red;
}
</style>
</head>
<body>
<div class="container">
<div class="content">
<div class="title" title="Laragon">Laragon</div>
<div class="info"><br />
Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11<br />
PHP version: 7.2.11 <span><a title="phpinfo()" href="/?q=info">info</a></span><br />
Document Root: C:/myprograms/laragon-lite/www<br />
</div>
<div class="opt">
<div><a title="Getting Started" href="https://laragon.org/docs">Getting Started</a></div>
</div>
</div>
</div>
</body>
</HTML>
De fato, obtivemos o mesmo documento que com o navegador Firefox.
16.4.4. Exemplo 4
Neste exemplo, vamos mostrar que o cliente HTTP que escrevemos é insuficiente. Vamos alterar o arquivo de configuração [config-http-01.json] da seguinte maneira:
Aqui, vamos solicitar o URL [http://tahe.developpez.com:443/]. A porta 443 da máquina [tahe.developpez.com] é uma porta utilizada para o protocolo HTTP seguro, chamado HTTPS. Nesse protocolo, a comunicação cliente/servidor começa com uma troca de informações que garantem a segurança da conexão. O cliente deve, então, utilizar o protocolo [HTTPS] e não o protocolo [HTTP], o que nosso cliente não está fazendo.
Com este arquivo de configuração, os resultados do console são os seguintes:
Client : début de la communication avec le serveur [tahe.developpez.com] ----------------------------
--> GET / HTTP/1.1
--> Host: sergetahe.com:443
--> User-Agent: script PHP 7
--> Accept: text/HTML
--> Accept-Language: fr
Réponse du serveur [tahe.developpez.com] ----------------------------
<-- HTTP/1.1 400 Bad Request
<-- Date: Fri, 17 May 2019 13:02:26 GMT
<-- Server: Apache/2.4.25 (Debian)
<-- Content-Length: 454
<-- Connection: close
<-- Content-Type: text/HTML; charset=iso-8859-1
Fin de la communication avec le site [tahe.developpez.com]. Vérifiez le fichier [output/tahe.developpez.com.HTML]
- linha 8: o servidor [tahe.developpez.com] respondeu que a solicitação do cliente estava incorreta;
O conteúdo do arquivo [output/tahe.developpez.com.HTML] é, então, o seguinte:
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
Reason: You're speaking plain HTTP to an SSL-enabled server port.<br />
Instead use the HTTPS scheme to access this URL, please.<br />
</p>
<hr>
<address>Apache/2.4.25 (Debian) Server at 2eurocents.developpez.com Port 443</address>
</body></HTML>
O servidor indica claramente que não utilizamos o protocolo correto.
Vamos agora utilizar o seguinte arquivo de configuração:
Os resultados na console são, então, os seguintes:
Client : début de la communication avec le serveur [sergetahe.com] ----------------------------
--> GET /cours-tutoriels-de-programmation/ HTTP/1.1
--> Host: sergetahe.com:80
--> User-Agent: script PHP 7
--> Accept: text/HTML
--> Accept-Language: fr
Réponse du serveur [sergetahe.com] ----------------------------
<-- HTTP/1.1 200 OK
<-- Date: Fri, 17 May 2019 13:36:06 GMT
<-- Content-Type: text/HTML; charset=UTF-8
<-- Transfer-Encoding: chunked
<-- Server: Apache
<-- X-Powered-By: PHP/7.0
<-- Vary: Accept-Encoding
<-- Set-Cookie: SERVERID68971=2621207|XN64y|XN64y; path=/
<-- Cache-control: private
<-- X-IPLB-Instance: 17106
Fin de la communication avec le site [sergetahe.com]. Vérifiez le fichier [output/sergetahe.com.HTML]
- a linha 11 indica que o servidor envia o documento em partes;
Isso se traduz na presença de números no fluxo enviado ao cliente: cada número indica ao cliente o número de caracteres do próximo trecho enviado pelo servidor. Veja como fica no arquivo [output/sergetahe.com.HTML]:

- em [1] e [2], o tamanho em hexadecimal dos trechos 1 e 2 do documento;
Um cliente HTTP adequado não deveria deixar esses números no documento HTML final.
Aqui está outro exemplo:
Ele se parece com o exemplo anterior, mas o URL solicitado na linha 4 não possui o caractere / para finalizá-lo. Não se trata dos mesmos URL. A execução do cliente HTTP produz, então, os seguintes resultados no console:
Client : début de la communication avec le serveur [sergetahe.com] ----------------------------
--> GET /cours-tutoriels-de-programmation HTTP/1.1
--> Host: sergetahe.com:80
--> User-Agent: script PHP 7
--> Accept: text/HTML
--> Accept-Language: fr
Réponse du serveur [sergetahe.com] ----------------------------
<-- HTTP/1.1 301 Moved Permanently
<-- Date: Fri, 17 May 2019 13:47:00 GMT
<-- Content-Type: text/HTML; charset=iso-8859-1
<-- Content-Length: 262
<-- Server: Apache
<-- Location: http://sergetahe.com:80/cursos-tutoriais-de-programação/
<-- Set-Cookie: SERVERID68971=2621207|XN67V|XN67V; path=/
<-- Cache-control: private
<-- X-IPLB-Instance: 17095
Fin de la communication avec le site [sergetahe.com]. Vérifiez le fichier [output/sergetahe.com.HTML]
- a linha 8 indica que o documento solicitado mudou de URL. O novo URL é apresentado na linha 13. Observe, desta vez, o caractere / que encerra o novo URL;
O arquivo [output/serge.tahe.com.HTML] fica, então, da seguinte forma:
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="http://sergetahe.com/cours-tutoriels-de-programmation/">here</a>.</p>
</body></HTML>
Um cliente HTTP deveria ser capaz de seguir os redirecionamentos. Nesse caso, ele deveria solicitar automaticamente o novo URL [http://sergetahe.com/cours-tutoriels-de-programmation/].
16.4.5. Exemplo 5
Os exemplos anteriores nos mostraram que nosso cliente HTTP era insuficiente. Apresentaremos agora uma ferramenta chamada [curl], que permite recuperar documentos da web, lidando com as dificuldades mencionadas: protocolo HTTPS, documento enviado em partes, redirecionamentos… A ferramenta [curl] foi instalada com o Laragon:

Vamos abrir um terminal do Laragon [1]:

No terminal, digitamos o seguinte comando:

- em [1], o tipo do console;
- em [2], a pasta atual. Essa pasta é especial: é onde o servidor Apache do Laragon busca os documentos solicitados. Portanto, devemos evitar sobrecarregar essa pasta;
- em [3], o comando digitado;
É possível que o comando [curl --help] gere um erro. A causa mais provável é que você não esteja usando o tipo correto de terminal. Nesse caso, abra outro terminal com os comandos [4-6];
O comando [curl --help] exibe todas as opções de configuração do [curl]. São várias dezenas. Usaremos muito poucas delas. Para solicitar um URL, basta digitar o comando [curl URL]. Esse comando exibirá na console o documento solicitado. Se quisermos, além disso, visualizar as trocas de dados entre o cliente e o servidor, digitaremos HTTP. Por fim, para salvar o documento solicitado em um arquivo, digitaremos HTML.
Para evitar sobrecarregar a pasta [www] do Laragon, vamos nos deslocar para outro local do sistema de arquivos:

- em [1], vamos para a pasta [c:\temp]. Se essa pasta não existir, você pode criá-la ou escolher outra;
- em [2], criamos uma pasta chamada [curl];
- em [3], selecione essa pasta;
- em [4], listamos seu conteúdo. Ele está vazio;
Certifique-se de que o servidor Apache do Laragon esteja em execução e, com [curl], solicite o URL e o [http://localhost/] com o comando [curl –verbose –output localhost.HTML http://localhost/]. Obtêm-se os seguintes resultados:
c:\Temp\curl
λ curl --verbose --output localhost.HTML http://localhost/
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying ::1…
* TCP_NODELAY set
* Connected to localhost (::1) port 80 (#0)
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/7.63.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Fri, 17 May 2019 14:32:47 GMT
< Server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
< X-Powered-By: PHP/7.2.11
< Content-Length: 1781
< Content-Type: text/HTML; charset=UTF-8
<
{ [1781 bytes data]
100 1781 100 1781 0 0 14248 0 --:--:-- --:--:-- --:--:-- 14248
* Connection #0 para o host localhost mantido intacto
- linhas 8-12: linhas enviadas por [curl] ao servidor [localhost]. Reconhece-se o protocolo HTTP;
- linhas 13-19: linhas enviadas em resposta pelo servidor;
- linha 13: indica que o documento solicitado foi recebido corretamente;
O arquivo [localhost.HTML] contém o documento solicitado. Você pode verificar isso abrindo o arquivo em um editor de texto.
Agora, vamos solicitar o URL [https://tahe.developpez.com:443/]. Para obter esse URL, o cliente HTTP precisa ser compatível com HTTPS. Esse é o caso do cliente [curl].
Os resultados da console são os seguintes:
c:\Temp\curl
λ curl --verbose --output tahe.developpez.com.HTML https://tahe.developpez.com:443/
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 87.98.130.52…
* TCP_NODELAY set
* Connected to tahe.developpez.com (87.98.130.52) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: C:\myprograms\laragon-lite\bin\laragon\utils\curl-ca-bundle.crt
CApath: none
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [108 bytes data]
* TLSv1.2 (IN), TLS handshake, Certificate (11):
{ [2558 bytes data]
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
{ [333 bytes data]
* TLSv1.2 (IN), TLS handshake, Server finished (14):
{ [4 bytes data]
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
} [70 bytes data]
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.2 (OUT), TLS handshake, Finished (20):
} [16 bytes data]
* TLSv1.2 (IN), TLS handshake, Finished (20):
{ [16 bytes data]
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
* subject: CN=*.developpez.com
* start date: Apr 4 08:25:09 2019 GMT
* expire date: Jul 3 08:25:09 2019 GMT
* subjectAltName: host "tahe.developpez.com" matched cert's "*.developpez.com"
* issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
* SSL certificate verify ok.
} [5 bytes data]
> GET / HTTP/1.1
> Host: tahe.developpez.com
> User-Agent: curl/7.63.0
> Accept: */*
>
{ [5 bytes data]
< HTTP/1.1 200 OK
< Date: Fri, 17 May 2019 14:39:41 GMT
< Server: Apache/2.4.25 (Debian)
< X-Powered-By: PHP/5.3.29
< Vary: Accept-Encoding
< Transfer-Encoding: chunked
< Content-Type: text/HTML
<
{ [6 bytes data]
100 96559 0 96559 0 0 163k 0 --:--:-- --:--:-- --:--:-- 163k
* Connection #0 para o host tahe.developpez.com permaneceu intacto
- linhas 10-40: as trocas entre cliente e servidor para proteger a conexão: esta será criptografada;
- linhas 42-45: os cabeçalhos HTTP enviados pelo cliente [curl] ao servidor;
- linha 48: o documento solicitado foi encontrado;
- linha 53: o documento é enviado em partes;
[curl] lida corretamente tanto com o protocolo seguro HTTPS quanto com o fato de o documento ser enviado em partes. O documento enviado poderá ser encontrado aqui no arquivo [tahe.developpez.com.HTML].
Vamos agora solicitar o URL e o [http://sergetahe.com/cours-tutoriels-de-programmation]. Vimos que, para esse URL, havia um redirecionamento para o URL [http://sergetahe.com/cours-tutoriels-de-programmation/] (com um / no final).
Os resultados na console são, então, os seguintes:
c:\Temp\curl
λ curl --verbose --output sergetahe.com.HTML --location http://sergetahe.com/cursos-tutoriais-de-programação
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 87.98.154.146…
* TCP_NODELAY set
* Connected to sergetahe.com (87.98.154.146) port 80 (#0)
> GET /cours-tutoriels-de-programmation HTTP/1.1
> Host: sergetahe.com
> User-Agent: curl/7.63.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Date: Fri, 17 May 2019 15:13:03 GMT
< Content-Type: text/HTML; charset=iso-8859-1
< Content-Length: 262
< Server: Apache
< Location: http://sergetahe.com/cursos-e-tutoriais-de-programação/
< Set-Cookie: SERVERID68971=2621207|XN7Pg|XN7Pg; path=/
< Cache-control: private
< X-IPLB-Instance: 17095
<
* Ignoring the response-body
{ [262 bytes data]
100 262 100 262 0 0 1401 0 --:--:-- --:--:-- --:--:-- 1401
* Connection #0 para hospedar sergetahe.com mantido intacto
* Issue another request to this URL: 'http://sergetahe.com/cursos-tutoriais-de-programação/'
* Found bundle for host sergetahe.com: 0x1c88548 [can pipeline]
* Could pipeline, but not asked to!
* Re-using existing connection! (#0) com o host sergetahe.com
* Connected to sergetahe.com (87.98.154.146) port 80 (#0)
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
> GET /cours-tutoriels-de-programmation/ HTTP/1.1
> Host: sergetahe.com
> User-Agent: curl/7.63.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Fri, 17 May 2019 15:13:04 GMT
< Content-Type: text/HTML; charset=UTF-8
< Transfer-Encoding: chunked
< Server: Apache
< X-Powered-By: PHP/7.0
< Vary: Accept-Encoding
< Set-Cookie: SERVERID68971=2621207|XN7Pg|XN7Pg; path=/
< Cache-control: private
< X-IPLB-Instance: 17095
<
{ [14205 bytes data]
100 43101 0 43101 0 0 78795 0 --:--:-- --:--:-- --:--:-- 168k
* Connection #0 para o host sergetahe.com, mantido intacto
- linha 2: utiliza-se a opção [--location] para indicar que se deseja seguir os redirecionamentos enviados pelo servidor;
- linha 13: o servidor indica que o documento solicitado mudou para URL;
- linha 18: indica o novo URL do documento solicitado;
- linha 27: o [curl] envia uma nova solicitação, desta vez para o novo URL;
- linha 33: o novo URL é utilizado;
- linha 38: o servidor responde que encontrou o documento solicitado;
- linha 41: ele o envia em partes;
O documento solicitado será encontrado no arquivo [sergetahe.com.HTML].
16.4.6. Exemplo 6
O PHP possui uma extensão chamada [libcurl] que permite utilizar os recursos da ferramenta [curl] em um programa PHP. Primeiramente, é necessário verificar se essa extensão está ativada no arquivo [php.ini] descrito no parágrafo “link”:

Certifique-se de que a linha 889 acima esteja descomentada.
Vamos escrever um script [http-02.php] que utilizará o seguinte arquivo de configuração jSON:
Cada elemento do dicionário [clé, valeur] tem a seguinte estrutura:
- clé: o nome de um servidor web;
- valeur é um dicionário com as seguintes chaves:
- timeout: tempo máximo de espera pela resposta do servidor. Passado esse tempo, o cliente será desconectado;
- url: URL do documento solicitado;
O código do script [http-02.php] é o seguinte:
<?php
// respeito estrito aos tipos declarados dos parâmetros das funções
declare (strict_types=1);
//
// gestão de erros
//error_reporting(E_ALL & E_STRICT);
//ini_set("display_errors", "on");
//
// constantes
const CONFIG_FILE_NAME = "config-http-02.json";
//
// recupera-se a configuração
$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);
// obter o texto HTML dos URL do arquivo de configuração
foreach ($config as $site => $infos) {
// leitura de URL do site $ite
$résultat = getUrl($site, $infos["url"], $infos["timeout"]);
// exibição do resultado
print "$résultat\n";
}//para
// fim
exit;
//-----------------------------------------------------------------------
function getUrl(string $site, string $url, int $timeout, $suivi = TRUE): string {
// lê o arquivo URL $url e o armazena no arquivo output/$site.HTML
//
// acompanhamento
print "Client : début de la communication avec le serveur [$site] ----------------------------\n";
// Inicialização de uma sessão cURL
$curl = curl_init($url);
if ($curl === FALSE) {
// ocorreu um erro
return "Erreur lors de l'initialisation de la session cURL pour le site [$site]";
}
// opções do curl
$options = [
// modo detalhado
CURLOPT_VERBOSE => true,
// nova conexão — sem cache
CURLOPT_FRESH_CONNECT => true,
// tempo limite da solicitação (em segundos)
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => $timeout,
// não verificar a validade dos certificados SSL
CURLOPT_SSL_VERIFYPEER => false,
// seguir redirecionamentos
CURLOPT_FOLLOWLOCATION => true,
// recuperação do documento solicitado na forma de uma sequência de caracteres
CURLOPT_RETURNTRANSFER => true
];
// configuração do curl
curl_setopt_array($curl, $options);
// Execução da solicitação
$page_content = curl_exec($curl);
// Encerramento da sessão cURL
curl_close($curl);
// análise do resultado
if ($page_content !== FALSE) {
// gravação do resultado em $site.HTML
$result = file_put_contents("output/$site.HTML", $page_content);
if ($result === FALSE) {
// retorno de erro
return "Erreur lors de la création du fichier [output/$site.HTML]";
}
// retorno com sucesso
return "Fin de la communication avec le serveur [$site]. Vérifiez le fichier [output/$site.HTML]";
} else {
// ocorreu um erro de comunicação
return "Erreur de communication avec le serveur [$site]";
}
}
Comentários
- linha 14: utiliza-se o arquivo de configuração para criar o dicionário [$config];
- linhas 17-22: é feito um ciclo pela lista de sites encontrados na configuração;
- linha 19: para cada um dos sites, chama-se a função [getUrl], que fará o download doURL $infos[«url»] com um tempo limite $infos[«timeout»];
- linha 34: inicia-se uma sessão [curl]. [curl_init] ainda não estabeleceu conexão com o servidor web. Ela retorna um recurso [$curl] que servirá como parâmetro para todas as funções [curl] a seguir;
- linhas 35-38: se a inicialização da sessão [curl] falhar, a função [curl_init] retorna o valor booleano FALSE;
- linhas 40-54: o dicionário [$options] configurará a conexão [curl] com o servidor;
- linha 57: as opções da conexão são transmitidas ao recurso [$curl];
- linha 59: conexão com o URL solicitada com as opções definidas. Devido à opção [CURLOPT_RETURNTRANSFER => true], a função [curl_exec] retorna como resultado o documento enviado pelo servidor na forma de uma sequência de caracteres. A função [curl_exec] retorna o valor booleano FALSE em caso de falha na conexão;
- linha 64: analisa-se o resultado de [curl_exec];
- linha 66: a página recebida é salva em um arquivo local;
- linhas 69, 72, 75: retorna-se o resultado da função [getUrl];
Ao executar o script [http-02.php], obtêm-se os seguintes resultados no console:
* Rebuilt URL to: http://sergetahe.com/
Client : début de la communication avec le serveur [sergetahe.com] ----------------------------
* Trying 87.98.154.146…
* TCP_NODELAY set
* Connected to sergetahe.com (87.98.154.146) port 80 (#0)
> GET / HTTP/1.1
Host: sergetahe.com
Accept: */*
< HTTP/1.1 302 Found
< Date: Sat, 18 May 2019 08:46:38 GMT
< Content-Type: text/HTML; charset=UTF-8
< Transfer-Encoding: chunked
< Server: Apache
< X-Powered-By: PHP/7.0
< Location: http://sergetahe.com/cursos-tutoriais-de-programação
< Set-Cookie: SERVERID68971=2621236|XN/Gc|XN/Gc; path=/
< X-IPLB-Instance: 17097
<
* Ignoring the response-body
* Connection #0 para hospedar sergetahe.com mantido intacto
* Issue another request to this URL: 'http://sergetahe.com/cursos-tutoriais-de-programação'
* Found bundle for host sergetahe.com: 0x1fee4ebe090 [can pipeline]
* Re-using existing connection! (#0) com o host sergetahe.com
* Connected to sergetahe.com (87.98.154.146) port 80 (#0)
> GET /cours-tutoriels-de-programmation HTTP/1.1
Host: sergetahe.com
Accept: */*
< HTTP/1.1 301 Moved Permanently
< Date: Sat, 18 May 2019 08:46:38 GMT
< Content-Type: text/HTML; charset=iso-8859-1
< Content-Length: 262
< Server: Apache
< Location: http://sergetahe.com/cursos-e-tutoriais-de-programação/
< Set-Cookie: SERVERID68971=2621236|XN/Gc|XN/Gc; path=/
< Cache-control: private
< X-IPLB-Instance: 17097
<
* Ignoring the response-body
* Connection #0 para o host sergetahe.com, mantido intacto
* Issue another request to this URL: 'http://sergetahe.com/cursos-tutoriais-de-programação/'
* Found bundle for host sergetahe.com: 0x1fee4ebe090 [can pipeline]
* Re-using existing connection! (#0) com o host sergetahe.com
* Connected to sergetahe.com (87.98.154.146) port 80 (#0)
> GET /cours-tutoriels-de-programmation/ HTTP/1.1
Host: sergetahe.com
Accept: */*
< HTTP/1.1 200 OK
< Date: Sat, 18 May 2019 08:46:39 GMT
< Content-Type: text/HTML; charset=UTF-8
< Transfer-Encoding: chunked
< Server: Apache
< X-Powered-By: PHP/7.0
< Link: <http://sergetahe.com/cursos-tutoriais-de-programação/wp-json/>; rel="https://api.w.org/"
< Link: <http://sergetahe.com/cursos-e-tutoriais-de-programação/>; rel=shortlink
< Vary: Accept-Encoding
< Set-Cookie: SERVERID68971=2621236|XN/Gc|XN/Gc; path=/
< Cache-control: private
< X-IPLB-Instance: 17097
<
Fin de la communication avec le serveur [sergetahe.com]. Vérifiez le fichier [output/sergetahe.com.HTML]
Client : début de la communication avec le serveur [tahe.developpez.com] ----------------------------
* Connection #0 para manter sergetahe.com intacto
* Rebuilt URL to: https://tahe.developpez.com/
* Trying 87.98.130.52…
* TCP_NODELAY set
* Connected to tahe.developpez.com (87.98.130.52) port 443 (#0)
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: C:\myprograms\laragon-lite\etc\ssl\cacert.pem
CApath: none
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
* subject: CN=*.developpez.com
* start date: Apr 4 08:25:09 2019 GMT
* expire date: Jul 3 08:25:09 2019 GMT
* subjectAltName: host "tahe.developpez.com" matched cert's "*.developpez.com"
* issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
* SSL certificate verify ok.
> GET / HTTP/1.1
Host: tahe.developpez.com
Accept: */*
< HTTP/1.1 200 OK
< Date: Sat, 18 May 2019 08:46:42 GMT
< Server: Apache/2.4.25 (Debian)
< X-Powered-By: PHP/5.3.29
< Vary: Accept-Encoding
< Transfer-Encoding: chunked
< Content-Type: text/HTML
<
Fin de la communication avec le serveur [tahe.developpez.com]. Vérifiez le fichier [output/tahe.developpez.com.HTML]
Client : début de la communication avec le serveur [www.polytech-angers.fr] ----------------------------
* Connection #0 para o host tahe.developpez.com, mantido intacto
* Rebuilt URL to: http://www.polytech-angers.fr/
* Trying 193.49.144.41…
* TCP_NODELAY set
* Connected to www.polytech-angers.fr (193.49.144.41) port 80 (#0)
> GET / HTTP/1.1
Host: www.polytech-angers.fr
Accept: */*
< HTTP/1.1 301 Moved Permanently
< Date: Sat, 18 May 2019 08:46:45 GMT
< Server: Apache/2.4.29 (Ubuntu)
< Location: http://www.polytech-angers.fr/fr/index.HTML
< Cache-Control: max-age=1
< Expires: Sat, 18 May 2019 08:46:46 GMT
< Content-Length: 339
< Content-Type: text/HTML; charset=iso-8859-1
<
* Ignoring the response-body
* Connection #0 para hospedar www.polytech-angers.fr mantido intacto
* Issue another request to this URL: 'http://www.polytech-angers.fr/fr/index.HTML'
* Found bundle for host www.polytech-angers.fr: 0x1fee4ebe390 [can pipeline]
* Re-using existing connection! (#0) com o host www.polytech-angers.fr
* Connected to www.polytech-angers.fr (193.49.144.41) port 80 (#0)
> GET /fr/index.HTML HTTP/1.1
Host: www.polytech-angers.fr
Accept: */*
< HTTP/1.1 200
< Date: Sat, 18 May 2019 08:46:46 GMT
< Server: Apache/2.4.29 (Ubuntu)
< X-Cocoon-Version: 2.1.13-dev
< Accept-Ranges: bytes
< Last-Modified: Sat, 18 May 2019 08:01:36 GMT
< Content-Type: text/HTML; charset=UTF-8
< Content-Length: 47372
< Vary: Accept-Encoding
< Cache-Control: max-age=1
< Expires: Sat, 18 May 2019 08:46:47 GMT
< Content-Language: fr
<
* Connection #0 para o host www.polytech-angers.fr, mantido intacto
Fin de la communication avec le serveur [www.polytech-angers.fr]. Vérifiez le fichier [output/www.polytech-angers.fr.HTML]
Client : début de la communication avec le serveur [localhost] ----------------------------
* Rebuilt URL to: http://localhost/
* Trying ::1…
* TCP_NODELAY set
* Connected to localhost (::1) port 80 (#0)
> GET / HTTP/1.1
Host: localhost
Accept: */*
< HTTP/1.1 200 OK
< Date: Sat, 18 May 2019 08:46:47 GMT
< Server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
< X-Powered-By: PHP/7.2.11
< Content-Length: 1781
< Content-Type: text/HTML; charset=UTF-8
<
* Connection #0 para o host localhost mantido intacto
Fin de la communication avec le serveur [localhost]. Vérifiez le fichier [output/localhost.HTML]
Comentários
- obtêm-se as mesmas trocas de dados que com a ferramenta [curl];
- em verde, os logs do script;
- em azul, os comandos enviados ao servidor;
- em amarelo, os comandos recebidos em resposta pelo cliente;
16.4.7. Conclusão
Neste parágrafo, conhecemos o protocolo HTTP e escrevemos um script [http-02.php] capaz de baixar um URL da web.
16.5. O protocolo SMTP (Simple Mail Transfer Protocol)
16.5.1. Introdução

Neste capítulo:
- O [Serveur B] será um servidor SMTP local que instalaremos;
- [Client A] será um cliente SMTP de diversas formas:
- o cliente [RawTcpClient] para descobrir o protocolo SMTP;
- um script PHP que simula o protocolo SMTP do cliente [RawTcpClient];
- um script PHP que utiliza a biblioteca [SwiftMailServer], permitindo o envio de todos os tipos de e-mails;
16.5.2. Criação de um endereço [gmail]
Para realizar nossos testes SMTP, precisaremos de um endereço de e-mail para o qual enviar mensagens. Para isso, vamos criar um endereço no Gmail:

- em [5], criamos o usuário [php7parlexemple] (escolha outro nome);
- em [6], a senha será [PHP7parlexemple] (escolha outra);
- em [7], confirmamos essas informações;

- preencha os campos [9-10] e, em seguida, confirme (11);
- aceite os termos de uso do Google (12-13) e, em seguida, confirme (14);

- em [15], a caixa de entrada (Inbox) do usuário [PHP7] (16);
- em [17], esse usuário tem uma caixa de entrada vazia;
- em [18-19], faça login na conta do Google do usuário [php7parlexemple@gmail.com]. Vamos configurar a segurança da conta;

- em [21], autorize outros aplicativos, além dos do Google, a acessar a conta [php7parlexemple]. Se não fizermos isso, nosso servidor de e-mails local [hMailServer] não poderá se comunicar com o servidor SMTP do Gmail;

16.5.3. Instalação de um servidor SMTP
Para nossos testes, instalaremos o servidor de e-mail [hMailServer], que é ao mesmo tempo um servidor SMTP, que permite enviar e-mails, um servidor POP3 (Post Office Protocol), que permite ler os e-mails armazenados no servidor, e um servidor IMAP (Internet Message Access Protocol), que também permite ler os e-mails armazenados no servidor, mas vai além disso. Ele permite, notadamente, gerenciar o armazenamento dos e-mails no servidor.
O servidor de e-mail [hMailServer] está disponível no URL [https://www.hmailserver.com/] (maio de 2019).

Durante a instalação, serão solicitadas algumas informações:

- no [1-2], selecione tanto o servidor de e-mails quanto as ferramentas para administrá-lo;
- durante a instalação, será solicitada a senha do administrador: anote-a, pois será necessária;
O [hMailServer] é instalado como um serviço do Windows iniciado automaticamente na inicialização do computador. É preferível escolher a inicialização manual:
- no [3], digite [services] na área de entrada da barra de status;

- em [4-8], coloque o serviço no modo [manuel] (6) e inicie-o (7);
Depois de iniciado, o servidor [hMailServer] deve ser configurado. O servidor foi instalado com um programa de administração [hMailServer Administrator]:

- no [2], na área de entrada da barra de status, digite [hmailserver];
- em [3], inicie o administrador;
- em [4], conecte o administrador ao servidor [hMailServer];
- em [5], digite a senha inserida durante a instalação de [hMailServer];

Vamos criar uma conta de usuário:
- clique com o botão direito do mouse em [Accounts] (7) e, em seguida, (8) para adicionar um novo usuário;
- na aba [General] (9), definimos um usuário [guest] (10) com a senha [guest] (11). Ele terá o endereço de e-mail [guest@localhost] (10);
- em [12], o usuário [guest] está ativado;


- em [15], configura-se o protocolo SMTP do servidor de e-mail;
- em [16], configura-se a distribuição dos e-mails;
- em [17], a configuração da distribuição de e-mails com destino à máquina host (localhost);
- em [18], o nome da máquina local (localhost). O script do parágrafo “link” permite que você obtenha esse nome;
- no [19], configura-se um servidor de retransmissão SMTP: trata-se do servidor que se encarregará da distribuição dos e-mails não destinados à máquina local (localhost);
- no [20], o servidor SMTP do Gmail. Escolhemos o Gmail porque criamos uma conta lá no parágrafo “link”;
- em [21], a porta SMTP do Gmail;
- No [22], o serviço SMTP do Gmail é um serviço seguro: é necessário ter uma conta do Gmail para acessá-lo;
- em [23], o usuário [php7parlexemple] criado no parágrafo “link”;
- em [24], a senha desse usuário: [PHP7parlexemple], criado no parágrafo “link”;
- em [25], indica-se o tipo de protocolo de segurança utilizado pelo Gmail;

- em [27], a porta do serviço SMTP;
- em [28], esse serviço não requer autenticação;
- em [30], insira a mensagem de boas-vindas que o servidor SMTP enviará aos seus clientes;
16.5.4. O protocolo SMTP

Vamos conhecer o protocolo SMTP com o seguinte ambiente:
- o cliente A será o cliente genérico TCP, [RawTcpClient];
- o servidor B será o servidor de e-mails [hMailServer];
- o cliente A solicitará ao servidor B que entregue um e-mail ao usuário [php7parlexemple@gmail.com];
- verificaremos se esse usuário recebeu o e-mail enviado;
Iniciamos o cliente da seguinte maneira:
![]()
- em [1], conectamo-nos à porta 25 da máquina local, onde opera o serviço SMTP de [hMailServer]. O argumento [--quit bye] indica que o usuário encerrará o programa digitando o comando [bye]. Sem esse argumento, o comando para encerrar o programa é [quit]. No entanto, [quit] também é um comando do protocolo SMTP. Portanto, precisamos evitar essa ambiguidade;
- em [2], o cliente está conectado corretamente;
- em [3], o cliente aguarda comandos digitados no teclado;
- em [4], o servidor envia a ele sua mensagem de boas-vindas;

- em [5], o cliente envia o comando [EHLO nom-de-la-machine-client]. O servidor responde com uma sequência de mensagens no formato [250-xx] (6). O código [250] indica que o comando enviado pelo cliente foi bem-sucedido;
- em [7], o cliente indica o remetente da mensagem, neste caso [guest@localhost]. Esse usuário deve existir no servidor de e-mails [hMailServer]. É o que ocorre aqui, pois criamos esse usuário anteriormente;
- em [8], a resposta do servidor;
- em [9], indica-se o destinatário da mensagem, neste caso, o usuário do Gmail [php7parlexemple@gmail.com];
- em [10], a resposta do servidor;
- em [11], o comando [DATA] indica ao servidor que o cliente enviará o conteúdo da mensagem;
- em [12], a resposta do servidor;
- no formato [13-16], o cliente deve enviar uma lista de linhas de texto terminada por uma linha que contenha apenas um único ponto. A mensagem pode conter linhas no formato [Subject :, From :, To :] (13) para definir, respectivamente, o assunto da mensagem, o remetente e o destinatário;
- em [14], os cabeçalhos anteriores devem ser seguidos por uma linha em branco;
- em [15], o texto da mensagem;
- na linha [16], a linha contendo apenas um único ponto, que indica o fim da mensagem;
- em [17], assim que o servidor recebe a linha contendo apenas um único ponto, ele coloca a mensagem na fila;
- em [18], o cliente informa ao servidor que concluiu a operação;
- em [19], a resposta do servidor;
- em [20], verifica-se que o servidor encerrou a conexão que o ligava ao cliente;
Agora, vamos verificar se o usuário [php7parlexemple@gmail.com] recebeu a mensagem corretamente:

- em [2], vemos que o usuário [php7parlexemple@gmail.com] recebeu a mensagem;



- em [7], o remetente do e-mail. Percebe-se que não é [guest@localhost]. Isso se deve ao fato de que foi o servidor de retransmissão definido na configuração de [hmailServer] que entregou a mensagem. Ora, esse servidor de retransmissão é o [smtp.gmail.com], associado às credenciais do usuário do Gmail [php7parlexemple@gmail.com]. Qualquer e-mail proveniente de [hMailServer] parecerá ter sido enviado pelo usuário [php7parlexemple@gmail.com]. Não era isso que queríamos aqui, mas, se não usarmos esse servidor de retransmissão, o serviço SMTP do Gmail rejeita os e-mails enviados por [hMailServer], pois o SMTP do Gmail exige uma autenticação que o [hMailServer] não envia. Certamente há uma maneira de contornar esse problema, mas não a encontrei;
- no [8], vemos que o e-mail foi recebido da máquina [DESKTOP-528I5CU], que hospeda o servidor de e-mails [hMailServer];
- em [9], o remetente da mensagem. Percebe-se que não é [guest@localhost];
- em [10], o remetente original da mensagem. Desta vez, é mesmo [guest@localhost];
- em [11], o assunto;
- em [12], o destinatário;
- em [13], a mensagem;
Por fim, nosso cliente [RawTcpClient] conseguiu enviar a mensagem, mesmo que tenhamos encontrado um problema com o remetente. Temos as bases para criar um cliente SMTP escrito em PHP.
16.5.5. Um cliente SMTP básico escrito em PHP
Vamos reproduzir em PHP o que aprendemos anteriormente sobre o protocolo SMTP.

O script [smtp-01.php] é configurado pelo seguinte arquivo jSON [config-smtp-01.json]:
{
"mail to localhost via localhost": {
"smtp-server": "localhost",
"smtp-port": "25",
"from": "guest@localhost",
"to": "guest@localhost",
"subject": "to localhost via localhost",
"message": "ligne 1\nligne 2\nligne 3"
},
"mail to gmail via localhost": {
"smtp-server": "localhost",
"smtp-port": "25",
"from": "guest@localhost",
"to": "php7parlexemple@gmail.com",
"subject": "to gmail via localhost",
"message": "ligne 1\nligne 2\nligne 3"
},
"mail to gmail via gmail": {
"smtp-server": "smtp.gmail.com",
"smtp-port": "587",
"from": "guest@localhost",
"to": "php7parlexemple@gmail.com",
"subject": "to gmail via gmail",
"message": "ligne 1\nligne 2\nligne 3"
}
}
[config-smtp-01.json] é um array em que cada um dos elementos é um dicionário do tipo [nom=>infos]. O valor [infos] é, por sua vez, um dicionário com as seguintes chaves e valores:
- [smtp-server]: o nome do servidor SMTP a ser utilizado;
- [smtp-port]: o número da porta do serviço SMTP;
- [from]: o remetente da mensagem;
- [to]: o destinatário da mensagem;
- [subject]: o assunto da mensagem;
- [message]: a mensagem a ser enviada;
- O primeiro elemento utiliza o servidor SMTP [localhost] para enviar um e-mail a um usuário de [localhost];
- o segundo elemento utiliza os servidores SMTP e [localhost] para enviar um e-mail a um usuário de [Gmail];
- o terceiro elemento utiliza o servidor SMTP [Gmail] para enviar um e-mail a um usuário do [Gmail];
O código [smtp-01.php] do cliente SMTP é o seguinte:
<?php
// cliente SMTP (Protocolo de Transferência SendMail) que permite enviar uma mensagem
// protocolo de comunicação cliente-servidor SMTP
// -> o cliente se conecta à porta 25 do servidor SMTP
// <- o servidor envia uma mensagem de boas-vindas
// -> o cliente envia o comando EHLO com o nome do seu computador
// <- o servidor responde com OK ou não
// -> o cliente envia o comando MAIL FROM: <remetente>
// <- o servidor responde com OK ou não
// -> o cliente envia o comando RCPT TO: <destinatário>
// <- o servidor responde com OK ou não
// -> o cliente envia o comando DATA
// <- o servidor responde com OK ou não
// -> o cliente envia todas as linhas de sua mensagem e termina com uma linha contendo o
// único caractere.
// <- o servidor responde com OK ou não
// -> o cliente envia o comando QUIT
// <- o servidor responde com OK ou não
// as respostas do servidor têm o formato xxx texto, em que xxx é um número de 3 dígitos. Qualquer
// número xxx >=500 indica um erro.
// A resposta pode conter várias linhas, todas começando com xxx, exceto a última
// no formato xxx(espaço)
// as linhas de texto trocadas devem terminar com os caracteres RC(#13) e LF(#10)
//
// cliente SMTP (Protocolo de Transferência SendMail) que permite enviar uma mensagem
//
// gerenciamento de erros
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// respeito estrito aos tipos declarados dos parâmetros das funções
declare (strict_types=1);
//
// parâmetros para o envio de e-mails
const CONFIG_FILE_NAME = "config-smtp-01.json";
// recuperação da configuração
$mails = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);
// envio de e-mails
foreach ($mails as $name => $infos) {
// rastreamento
print "Envoi du mail [$name]\n";
// envio da correspondência
$résultat = sendmail($name, $infos, TRUE);
// exibição do resultado
print "$résultat\n";
}//para
// fim
exit;
//sendmail
//-----------------------------------------------------------------------
function sendmail(string $name, array $infos, bool $verbose = TRUE): string {
// envia a mensagem [$name,$infos]. Se $verbose=TRUE , monitora as trocas entre cliente e servidor
// recupera-se o nome do cliente
$client = gethostbyaddr(gethostbyname(""));
// abertura de uma conexão com o servidor SMTP
$connexion = fsockopen($infos["smtp-server"], (int) $infos["smtp-port"]);
// retorno em caso de erro
if ($connexion === FALSE) {
return sprintf("Echec de la connexion au site (%s,%s) : %s", $infos["smtp-server"], $infos["smtp-port"]);
}
// $connexion representa um fluxo de comunicação bidirecional
// entre o cliente (este programa) e o servidor SMTP contatado
// esse canal é usado para a troca de comandos e informações
// após a conexão, o servidor envia uma mensagem de boas-vindas que é lida
$erreur = sendCommand($connexion, "", $verbose, TRUE);
if ($erreur !== "") {
// encerramento da conexão
fclose($connexion);
// retorno
return $erreur;
}
// comando EHLO
$erreur = sendCommand($connexion, "EHLO $client", $verbose, TRUE);
if ($erreur !== "") {
// encerramento da conexão
fclose($connexion);
// retorno
return $erreur;
}
// comando MAIL FROM:
$erreur = sendCommand($connexion, sprintf("MAIL FROM: <%s>", $infos["from"]), $verbose, TRUE);
if ($erreur !== "") {
// encerramento da conexão
fclose($connexion);
// retorno
return $erreur;
}
// comando RCPT TO:
$erreur = sendCommand($connexion, sprintf("RCPT TO: <%s>", $infos["to"]), $verbose, TRUE);
if ($erreur !== "") {
// encerramento da conexão
fclose($connexion);
// retorno
return $erreur;
}
// comando DATA
$erreur = sendCommand($connexion, "DATA", $verbose, TRUE);
if ($erreur !== "") {
// encerramento da conexão
fclose($connexion);
// retorno
return $erreur;
}
// preparação da mensagem a ser enviada
// ela deve conter as linhas
// De: remetente
// Para: destinatário
// Assunto:
// linha em branco
// Mensagem
// .
$data = sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s\r\n.\r\n", $infos["from"], $infos["to"], $infos["subject"], $infos["message"]);
$erreur = sendCommand($connexion, $data, $verbose, FALSE);
if ($erreur !== "") {
// encerramento da conexão
fclose($connexion);
// retorno
return $erreur;
}
// comando quit
$erreur = sendCommand($connexion, "QUIT", $verbose, TRUE);
if ($erreur !== "") {
// encerramento da conexão
fclose($connexion);
// retorno
return $erreur;
}
// fim
fclose($connexion);
return "Message envoyé";
}
// --------------------------------------------------------------------------
function sendCommand($connexion, string $commande, bool $verbose, bool $withRCLF): string {
// envia $commande para o canal $connexion
// modo detalhado se $verbose=1
// se $withRCLF=1, adiciona a sequência RCLF à troca
// dados
if ($withRCLF) {
$RCLF = "\r\n";
} else {
$RCLF = "";
}
// envio de comando se $commande não estiver vazio
if ($commande!=="") {
fputs($connexion, "$commande$RCLF");
// possível resposta
if ($verbose) {
affiche($commande, 1);
}
}//se
// leitura da resposta
$réponse = fgets($connexion, 1000);
// eco, se houver
if ($verbose) {
affiche($réponse, 2);
}
// recuperação do código de erro
$codeErreur = (int) substr($réponse, 0, 3);
// última linha da resposta?
while (substr($réponse, 3, 1) === "-") {
// leitura da resposta
$réponse = fgets($connexion, 1000);
// possível eco
if ($verbose) {
affiche($réponse, 2);
}
}//enquanto
// resposta concluída
// erro retornado pelo servidor?
if ($codeErreur >= 500) {
return substr($réponse, 4);
}
// retorno sem erro
return "";
}
// --------------------------------------------------------------------------
function affiche($échange, $sens) {
// exibe $échange na tela
// se $sens=1, exibe -->$echange
// se $sens=2, exibe <-- $échange sem os dois últimos caracteres RCLF
switch ($sens) {
case 1:
print "--> [$échange]\n";
break;
case 2:
$L = strlen($échange);
print "<-- [" . substr($échange, 0, $L - 2) . "]\n";
break;
}//alternância
}
Comentários
- linha 39: utiliza-se o arquivo de configuração;
- linha 42: é feito um loop pelos elementos da matriz [mails]. Cada elemento é um dicionário [name=>infos], em que [name] é um nome que pode ser qualquer um e [infos] é um dicionário contendo as informações necessárias para o envio de um e-mail;
- linha 46: o envio do e-mail é realizado pela função [sendmail], que aceita três parâmetros:
- $name: o nome atribuído a esse envio;
- $infos: o dicionário contendo as informações necessárias para o envio;
- verbose: um valor booleano que indica se as trocas cliente/servidor devem ou não ser registradas no console;
- linha 46: a função [sendmail] retorna uma mensagem de erro vazia caso não tenha ocorrido nenhum erro;
- linha 56: a função [sendmail] envia os diversos comandos que um cliente deve enviar SMTP:
- linhas 77-84: o comando EHLO;
- linhas 85-92: o comando MAIL FROM: ;
- linhas 93-100: o pedido RCPT TO: ;
- linhas 101-108: o comando DATA;
- linhas 117-124: envio da mensagem (De, Para, Assunto, texto);
- linhas 125-132: o comando QUIT;
- linha 140: a função [sendCommand] é responsável por enviar os comandos do cliente ao servidor SMTP. Ela aceita quatro parâmetros:
- [$connexion]: a conexão que liga o cliente ao servidor;
- [$commande]: o comando a ser enviado;
- [$verbose]: se TRUE, então as trocas entre cliente e servidor são registradas no console;
- [$withRCLF]: se TRUE, envia o comando terminado pela sequência \r\n. Isso é necessário para todos os comandos do protocolo SMTP, mas [sendCommand] também serve para enviar a mensagem. Nesse caso, não se adiciona a sequência \r\n;
- linhas 150-157: o comando é enviado ao servidor;
- linhas 158-163: leitura da primeira linha da resposta. Ela pode conter várias linhas. Cada linha tem o formato XXX-YYY, em que XXX é um código numérico, exceto a última linha da resposta, que tem o formato XXX YYY (ausência do caractere -);
- linhas 167-174: leitura de todas as linhas da resposta;
- linha 177: se o código numérico XXX for maior que 500, então o servidor retornou um erro;
Resultados
A execução do script gera os seguintes resultados no console:
Envoi du mail [mail to localhost via localhost]
<-- [220 Bienvenue sur sergetahe@localhost]
--> [EHLO DESKTOP-528I5CU.home]
<-- [250-DESKTOP-528I5CU]
<-- [250-SIZE 20480000]
<-- [250-AUTH LOGIN]
<-- [250 HELP]
--> [MAIL FROM: <guest@localhost>]
<-- [250 OK]
--> [RCPT TO: <guest@localhost>]
<-- [250 OK]
--> [DATA]
<-- [354 OK, send.]
--> [From: guest@localhost
To: guest@localhost
Subject: to localhost via localhost
ligne 1
ligne 2
ligne 3
.
]
<-- [250 Queued (0.016 seconds)]
--> [QUIT]
<-- [221 goodbye]
Message envoyé
Envoi du mail [mail to gmail via localhost]
<-- [220 Bienvenue sur sergetahe@localhost]
--> [EHLO DESKTOP-528I5CU.home]
<-- [250-DESKTOP-528I5CU]
<-- [250-SIZE 20480000]
<-- [250-AUTH LOGIN]
<-- [250 HELP]
--> [MAIL FROM: <guest@localhost>]
<-- [250 OK]
--> [RCPT TO: <php7parlexemple@gmail.com>]
<-- [250 OK]
--> [DATA]
<-- [354 OK, send.]
--> [From: guest@localhost
To: php7parlexemple@gmail.com
Subject: to gmail via localhost
ligne 1
ligne 2
ligne 3
.
]
<-- [250 Queued (0.000 seconds)]
--> [QUIT]
<-- [221 goodbye]
Message envoyé
Envoi du mail [mail to gmail via gmail]
<-- [220 smtp.gmail.com ESMTP d9sm21623375wro.26 - gsmtp]
--> [EHLO DESKTOP-528I5CU.home]
<-- [250-smtp.gmail.com at your service, [90.93.230.110]]
<-- [250-SIZE 35882577]
<-- [250-8BITMIME]
<-- [250-STARTTLS]
<-- [250-ENHANCEDSTATUSCODES]
<-- [250-PIPELINING]
<-- [250-CHUNKING]
<-- [250 SMTPUTF8]
--> [MAIL FROM: <guest@localhost>]
<-- [530 5.7.0 Must issue a STARTTLS command first. d9sm21623375wro.26 - gsmtp]
5.7.0 Must issue a STARTTLS command first. d9sm21623375wro.26 - gsmtp
Done.
- linhas 1-26: o uso do servidor SMTP [hMailServer] para enviar um e-mail para [guest@localhost] ocorreu sem problemas;
- linhas 27-52: o uso dos servidores SMTP e [hMailServer] para enviar um e-mail para [php7parlexemple@gmail.com] ocorre sem problemas;
- linhas 53-65: o uso do servidor SMTP [Gmail] para enviar um e-mail para [php7parlexemple@gmail.com] não está ocorrendo corretamente: na linha 65, o servidor SMTP envia um código de erro 530 com a mensagem de erro. Esta indica que o cliente SMTP deve primeiro se autenticar por meio de uma conexão segura. Nosso cliente não fez isso e, portanto, foi recusado;
16.5.6. Um segundo cliente, SMTP, escreve utilizando a biblioteca [SwiftMailer]
O cliente anterior apresenta pelo menos duas falhas:
- ele não sabe utilizar uma conexão segura caso o servidor a exija;
- não sabe anexar arquivos à mensagem;
Em nosso novo script, utilizaremos a biblioteca [SwiftMailer] [https://swiftmailer.symfony.com/] (maio de 2019). O procedimento de instalação do [SwiftMailer] está descrito no URL [https://swiftmailer.symfony.com/docs/introduction.HTML] (maio de 2019).
Primeiro, inicie o Laragon:

- no [1], abra um terminal;

- em [3], verifique se você está na pasta [<laragon>/www], onde <laragon> é a pasta de instalação do Laragon;
- em [3], digite o comando indicado (maio de 2019). Verifique em URL e [https://swiftmailer.symfony.com/docs/introduction.HTML] o comando exato;
- no [4], é indicado que nenhuma instalação ou atualização foi realizada. Isso ocorre porque a biblioteca já estava instalada neste computador;
- em [5], a pasta de instalação de [swiftmailer] [6];
- no [7], um arquivo do qual precisaremos em nosso script;
Feito isso, verifique se a pasta [<laragon>/www/vendor] [5] está de fato no ramo [Include Path] do NetBeans (veja o parágrafo com o link).
Por fim, a biblioteca [SwiftMailer] requer que a extensão PHP [mbstring] esteja ativa. Para isso, verifique o arquivo [php.ini] (consulte o parágrafo com o link):

O script [smtp-02.php] utilizará o seguinte arquivo de configuração jSON [config-smtp-02.json]:
Encontramos os mesmos campos do arquivo [config-smtp-01.json], com dois campos adicionais:
- [tls]: em TRUE, indica que é necessário utilizar uma conexão segura com o servidor SMTP. Caso [tls] seja igual a TRUE, é necessário adicionar dois campos:
- [user]: o nome do usuário que autentica a conexão;
- [password]: sua senha;
No nosso exemplo, usamos as credenciais do usuário [php7parlexemple@gmail.com] para nos conectarmos ao servidor do Gmail. Use as suas;
- [attachments]: indica os nomes dos arquivos a serem anexados ao e-mail;
O código do script [smtp-02.php] é o seguinte:
<?php
// cliente SMTP (Protocolo de Transferência SendMail) que permite enviar uma mensagem
//
// gerenciamento de erros
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependências
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
//
// parâmetros de envio de e-mail
const CONFIG_FILE_NAME = "config-smtp-02.json";
// recuperação da configuração
$mails = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);
// envio de e-mails
foreach ($mails as $name => $infos) {
// rastreamento
print "Envoi du mail [$name]\n";
// envio de e-mail
$résultat = sendmail($name, $infos);
// exibição do resultado
print "$résultat\n";
}//para
// fim
exit;
//-----------------------------------------------------------------------
function sendmail($name, $infos) {
// envia $infos[message] para o servidor SMTP $infos[smtp-server] na porta $infos[smt-port]
// se $infos[tls] for verdadeiro, será utilizado o suporte TLS
// o e-mail é enviado em nome de $infos[from]
// para o destinatário $infos['to']
// O documento $info[attachment] está anexado à mensagem
// a mensagem tem como assunto $infos[subject]
//
// mensagem no formato HTML
$messageHTML = str_replace("\n", "<br/>", $infos["message"]);
try {
// criação da mensagem
$message = (new \Swift_Message())
// assunto da mensagem
->setSubject($infos["subject"])
// remetente
->setFrom($infos["from"])
// destinatários com um dicionário (setTo/setCc/setBcc)
->setTo($infos["to"])
// texto da mensagem
->setBody($infos["message"])
// variante HTML
->addPart("<b>$messageHTML</b>", 'text/html')
;
// anexos
foreach ($infos["attachments"] as $attachment) {
// caminho do anexo
$fileName = __DIR__ . $attachment;
// verifica-se se o arquivo existe
if (file_exists($fileName)) {
// anexamos o documento à mensagem
$message->attach(\Swift_Attachment::fromPath($fileName));
} else {
// erro
print "L'attachement [$fileName] n'existe pas\n";
}
}
// protocolo TLS?
if ($infos["tls"] === "TRUE") {
// TLS
$transport = (new \Swift_SmtpTransport($infos["smtp-server"], $infos["smtp-port"], 'tls'))
->setUsername($infos["user"])
->setPassword($infos["password"]);
} else {
// sem TLS
$transport = (new \Swift_SmtpTransport($infos["smtp-server"], $infos["smtp-port"]));
}
// o gerenciador de envio
$mailer = new \Swift_Mailer($transport);
// envio da mensagem
$result = $mailer->send($message);
// fim
return "Message [$name] envoyé";
} catch (\Throwable $ex) {
// erro
return "Erreur lors de l'envoi du message [$name] : " . $ex->getMessage();
}
}
Comentários
- linha 10: carregamos o arquivo [autoload.php] localizado na pasta [<lagagon>/www/vendor], onde <laragon> é a pasta de instalação do Laragon. Esse arquivo permitirá carregar os arquivos de definição das classes de [SwiftMailer] logo na primeira vez em que essas classes forem utilizadas. Isso evita que tenhamos que criar tantos arquivos [require] quanto o número de classes e interfaces de SwiftMailer que iremos utilizar;
- linha 32: a nova função [sendmail], que possui dois parâmetros:
- [$name], que serve para diferenciar as mensagens entre si;
- [$infos]: as informações necessárias para enviar a mensagem ao destinatário;
- linha 42: teremos duas versões da mensagem: uma em texto simples e outra em HTML. Aqui, alteramos os caracteres de fim de linha para o código HTML <br/>;
- linhas 45-69: definimos a mensagem usando a classe [\SwiftMessage];
- linha 47: o método [SwiftMessage→setSubject] serve para definir o assunto da mensagem;
- linha 49: o método [SwiftMessage→setFrom] serve para definir o remetente da mensagem;
- linha 51: o método [SwiftMessage→setTo] serve para definir o destinatário da mensagem;
- linha 53: o método [SwiftMessage→setBody] serve para definir o corpo da mensagem;
- linha 55: o método [SwiftMessage→addPart] serve para definir diferentes versões da mensagem, neste caso, a mensagem no formato HTML. Quando a mensagem possui variantes, os leitores de e-mail exibem a variante preferida do usuário;
- linhas 58-69: o método [SwiftMessage→addAttachment] (64) permite anexar um arquivo à mensagem;
- linhas 70-79: uma vez definida a mensagem a ser enviada, é preciso definir como enviá-la. O modo de transporte da mensagem é definido pela classe [\Swift_SmtpTransport]. Há pelo menos duas informações a serem fornecidas: o nom e o port do servidor SMTP. Há também uma terceira informação: o servidor SMTP exige autenticação segura?
- linhas 73-75: a instância [\Swift_SmtpTransport] para uma conexão segura com o servidor SMTP;
- linha 78: a instância [\Swift_SmtpTransport] para uma conexão não segura com o servidor SMTP;
- linha 81: é a classe [\SwiftMailer] que envia as mensagens. É necessário passar a ela o modo de transporte escolhido;
- linha 83: a mensagem [\SwiftMessage] é enviada por meio do transporte [\Swift_SmtpTransport] selecionado. O método [SwiftMailer→send] retorna o valor booleano FALSE caso a mensagem não tenha podido ser enviada;
- linhas 86-89: a biblioteca [SwiftMailer] lança uma exceção assim que algo dá errado;
Observação: note-se que o espaço de nomes das classes da biblioteca [SwiftMailer] é a raiz \. As classes [\SwiftMessage, \Swift_SmtpTransport, \SwiftMailer] foram explicitamente indicadas para relembrar isso;
Resultados
Ao executar o script [smtp-02.php], obtêm-se os seguintes resultados no console:
Se consultarmos a conta do Gmail do usuário [php7parlexemple], temos o seguinte:

- em [1], o assunto;
- em [2], o remetente;
- em [3], o destinatário;
- em [4], a mensagem;
- em [5-10], os anexos;
Se solicitarmos a visualização da mensagem original, obtemos o seguinte documento:
Return-Path: <php7parlexemple@gmail.com>
Received: from [127.0.0.1] (lfbn-1-11924-110.w90-93.abo.wanadoo.fr. [90.93.230.110])
by smtp.gmail.com with ESMTPSA id e14sm7773816wma.41.2019.05.26.03.11.53
for <php7parlexemple@gmail.com>
(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
Sun, 26 May 2019 03:11:54 -0700 (PDT)
Message-ID: <e613c47a421a66e2cf7f8e319616ec49@swift.generated>
Date: Sun, 26 May 2019 10:11:53 +0000
Subject: test-gmail-via-gmail
From: php7parlexemple@gmail.com
To: php7parlexemple@gmail.com
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="_=_swift_1558865513_a3a939017128a4cfb867e968bce5df49_=_"
--_=_swift_1558865513_a3a939017128a4cfb867e968bce5df49_=_
Content-Type: multipart/alternative; boundary="_=_swift_1558865513_43c6d2a54065e4917fb06e3327f8d927_=_"
--_=_swift_1558865513_43c6d2a54065e4917fb06e3327f8d927_=_
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
ligne 1
ligne 2
ligne 3
--_=_swift_1558865513_43c6d2a54065e4917fb06e3327f8d927_=_
Content-Type: text/HTML; charset=utf-8
Content-Transfer-Encoding: quoted-printable
<b>ligne 1<br/>ligne 2<br/>ligne 3</b>
--_=_swift_1558865513_43c6d2a54065e4917fb06e3327f8d927_=_--
--_=_swift_1558865513_a3a939017128a4cfb867e968bce5df49_=_
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document; name="Hello from SwiftMailer.docx"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="Hello from SwiftMailer.docx"
--_=_swift_1558865513_a3a939017128a4cfb867e968bce5df49_=_
Content-Type: application/pdf; name="Hello from SwiftMailer.pdf"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="Hello from SwiftMailer.pdf"
--_=_swift_1558865513_a3a939017128a4cfb867e968bce5df49_=_
Content-Type: application/vnd.oasis.opendocument.text; name="Hello from SwiftMailer.odt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="Hello from SwiftMailer.odt"
--_=_swift_1558865513_a3a939017128a4cfb867e968bce5df49_=_
Content-Type: image/png; name="Cours-Tutoriels-Serge-Tahé-1568x268.png"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="Cours-Tutoriels-Serge-Tahé-1568x268.png"
--_=_swift_1558865513_a3a939017128a4cfb867e968bce5df49_=_
Content-Type: message/rfc822; name=test-localhost.eml
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=test-localhost.eml
Return-Path: guest@localhost
Received: from [127.0.0.1] (localhost [127.0.0.1]) by DESKTOP-528I5CU with ESMTP ; Sat, 25 May 2019 09:48:23 +0200
Message-ID: <620f4628882b011feebe4faa30b45092@swift.generated>
Date: Sat, 25 May 2019 07:48:22 +0000
Subject: test-localhost
From: guest@localhost
To: guest@localhost
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="_=_swift_1558770502_c4b808c99c27ded04595bd11f4bad11b_=_"
--_=_swift_1558770502_c4b808c99c27ded04595bd11f4bad11b_=_
Content-Type: multipart/alternative; boundary="_=_swift_1558770503_3561ca315f33bd15ef6556e98db4a5b8_=_"
--_=_swift_1558770503_3561ca315f33bd15ef6556e98db4a5b8_=_
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
j'ai =C3=A9t=C3=A9 invit=C3=A9 =C3=A0 d=C3=A9je=C3=BBner
--_=_swift_1558770503_3561ca315f33bd15ef6556e98db4a5b8_=_
Content-Type: text/HTML; charset=utf-8
Content-Transfer-Encoding: quoted-printable
<b>j'ai =C3=A9t=C3=A9 invit=C3=A9 =C3=A0 d=C3=A9je=C3=BBner</b>
--_=_swift_1558770503_3561ca315f33bd15ef6556e98db4a5b8_=_--
--_=_swift_1558770502_c4b808c99c27ded04595bd11f4bad11b_=_
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document; name="Hello from SwiftMailer.docx"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="Hello from SwiftMailer.docx"
--_=_swift_1558770502_c4b808c99c27ded04595bd11f4bad11b_=_
Content-Type: application/pdf; name="Hello from SwiftMailer.pdf"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="Hello from SwiftMailer.pdf"
--_=_swift_1558770502_c4b808c99c27ded04595bd11f4bad11b_=_
Content-Type: application/vnd.oasis.opendocument.text; name="Hello from SwiftMailer.odt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="Hello from SwiftMailer.odt"
--_=_swift_1558770502_c4b808c99c27ded04595bd11f4bad11b_=_
Content-Type: image/png; name="Cours-Tutoriels-Serge-Tahé-1568x268.png"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="Cours-Tutoriels-Serge-Tahé-1568x268.png"
--_=_swift_1558770502_c4b808c99c27ded04595bd11f4bad11b_=_--
--_=_swift_1558865513_a3a939017128a4cfb867e968bce5df49_=_--
- linha 9: o assunto;
- linha 10: o remetente;
- linha 11: o destinatário;
- linha 13: a mensagem contém várias partes delimitadas pelas tags [--_=_swift_xx];
- linhas 19-24: a mensagem em texto simples;
- linhas 27-30: a mensagem em HTML;
- linhas 34-36: o arquivo anexado [Hello from SwiftMailer.docx];
- linhas 40-42: o arquivo anexado [Hello from SwiftMailer.pdf];
- linhas 46-48: o arquivo anexado [Hello from SwiftMailer.odt];
- linhas 58-60: o arquivo anexado [Cours-Tutoriels-Serge-Tahé-1568x268.png];
- linhas 58-60: o arquivo anexado [test-localhost.eml];
- linhas 62-114: o arquivo anexado [test-localhost.eml] é, por sua vez, uma mensagem cujo conteúdo é exibido nas linhas 62-114. É possível observar que essa mensagem contém, por sua vez, anexos;
16.6. Os protocolos POP3 (Post Office Protocol) e IMAP (Internet Message Access Protocol)
16.6.1. Introdução
Para ler os e-mails armazenados em um servidor de e-mail, existem dois protocolos:
- o protocolo POP3 (Post Office Protocol), historicamente o primeiro protocolo, mas pouco utilizado atualmente;
- o protocolo IMAP (Internet Message Access Protocol), mais recente que o POP3 e o mais utilizado atualmente;
Para conhecer o protocolo POP3, utilizaremos a seguinte arquitetura:

- [Serveur B] será um servidor POP3 / IMAP local, implementado pelo servidor de e-mail [hMailServer];
- [Client A] será um cliente POP3 / IMAP de diversas formas:
- o cliente [RawTcpClient] para descobrir o protocolo POP3;
- um script PHP que reproduz o protocolo POP3 do cliente [RawTcpClient];
- um script PHP que utiliza a biblioteca IMAP de PHP, a qual permite implementar tanto clientes IMAP quanto POP3;
16.6.2. Descoberta do protocolo POP3
Primeiramente, utilizamos o script [smtp-01.php] para enviar um e-mail ao usuário [guest@localhost]. Se você realizou os testes relacionados ao script, esse usuário normalmente recebeu e-mails, mas não foi possível verificar isso. Para enviar um novo e-mail a ele, utilize, por exemplo, o seguinte arquivo de configuração [config-smtp-01.json]:
Agora, vamos ver com o cliente [RawTcpClient] como é possível acessar a caixa de e-mail do usuário [guest@localhost]:
C:\Data\st-2019\dev\php7\php5-exemples\exemples\inet\utilitaires>RawTcpClient --quit bye localhost 110
Client [DESKTOP-528I5CU:55593] connecté au serveur [localhost-110]
Tapez vos commandes (bye pour arrêter) :
<-- [+OK Bienvenue sur sergetahe@localhost]
USER guest@localhost
<-- [+OK Send your password]
PASS guest
<-- [+OK Mailbox locked and ready]
LIST
<-- [+OK 2 messages (610 octets)]
<-- [1 305]
<-- [2 305]
<-- [.]
RETR 1
<-- [+OK 305 octets]
<-- [Return-Path: guest@localhost]
<-- [Received: from DESKTOP-528I5CU.home (localhost [127.0.0.1])]
<-- [ by DESKTOP-528I5CU with ESMTP]
<-- [ ; Tue, 21 May 2019 12:59:11 +0200]
<-- [Message-ID: <1356373A-33C9-4F31-BA43-2B119E128CE3@DESKTOP-528I5CU>]
<-- [From: guest@localhost]
<-- [To: guest@localhost]
<-- [Subject: to localhost via localhost]
<-- []
<-- [ligne 1]
<-- [ligne 2]
<-- [ligne 3]
<-- [.]
DELE 1
<-- [+OK msg deleted]
LIST
<-- [+OK 1 messages (305 octets)]
<-- [2 305]
<-- [.]
DELE 2
<-- [+OK msg deleted]
LIST
<-- [+OK 0 messages (0 octets)]
<-- [.]
QUIT
<-- [+OK POP3 server saying goodbye…]
Perte de la connexion avec le serveur…
- linha 1: o servidor POP3 geralmente opera na porta 110. É o caso aqui;
- linha 5: o comando [USER] serve para definir o usuário cuja caixa de e-mail se deseja acessar;
- linha 7: o comando [PASS] serve para definir a senha;
- linha 9: o comando [LIST] solicita a lista de mensagens presentes na caixa de correio do usuário;
- linha 14: o comando [RETR] solicita a exibição da mensagem cujo número é fornecido;
- linha 29: o comando [DELE] solicita a exclusão da mensagem cujo número é fornecido;
- linha 40: o comando [QUIT] indica ao servidor que a operação foi concluída;
A resposta do servidor pode assumir várias formas:
- uma única linha começando com [+OK] para indicar que o comando anterior do cliente foi bem-sucedido;
- uma única linha começando com [-ERR] para indicar que o comando anterior do cliente falhou;
- várias linhas em que:
- a primeira linha começa com [+OK];
- a última linha é composta por um único ponto;
16.6.3. Um script básico que implementa o protocolo POP3

Como o protocolo POP3 tem a mesma estrutura que o protocolo SMTP, o script [pop3-01.php] é uma adaptação do script [smtp-01.php]. Ele terá o seguinte arquivo de configuração [config-pop3-01.json]:
- linhas 3-4: o servidor POP3 consultado é o servidor local [hMailServer];
- linhas 5-6: deseja-se ler a caixa de correio do usuário [guest@localhost];
- linha 7: serão lidos no máximo 5 e-mails;
O script [pop3-01.php] é o seguinte:
<?php
// cliente POP3 (Post Office Protocol) que permite ler mensagens de uma caixa de correio
// protocolo de co POP3 cliente-servidor
// -> o cliente se conecta à porta 110 do servidor SMTP
// <- o servidor envia uma mensagem de boas-vindas
// -> o cliente envia o comando USER usuário
// <- o servidor responde com OK ou não
// -> o cliente envia o comando PASS mot_de_passe
// <- o servidor responde com OK ou não
// -> o cliente envia o comando LIST
// <- o servidor responde com OK ou não
// -> o cliente envia o comando RETR, com um número diferente para cada e-mail
// <- o servidor responde com OK ou não. Se for OK, envia o conteúdo do e-mail solicitado
// -> o servidor envia todas as linhas do e-mail e termina com uma linha contendo o
// único caractere.
// -> o cliente envia o comando DELE seguido do número para excluir um e-mail
// <- o servidor responde com OK ou não
// // -> o cliente envia o comando QUIT para encerrar a comunicação com o servidor
// <- o servidor responde com OK ou não
// as respostas do servidor têm o formato +OK texto ou -ERR texto
// A resposta pode conter várias linhas. Nesse caso, a última linha é composta por um único ponto
// as linhas de texto trocadas devem terminar com os caracteres RC(#13) e LF(#10)
//
// cliente POP3 (Protocolo de Transferência SendMail) que permite ler e-mails
//
// gerenciamento de erros
//ini_set (“error_reporting”, E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// respeito estrito aos tipos declarados dos parâmetros das funções
declare (strict_types=1);
//
// parâmetros para o envio de e-mails
const CONFIG_FILE_NAME = "config-pop3-01.json";
// recuperação da configuração
$mailboxes = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);
// leitura das caixas de correio
foreach ($mailboxes as $name => $infos) {
// acompanhamento
print "Lecture de la boîte à lettres [$name]\n";
// leitura da caixa de correio
$résultat = readmail($name, $infos, TRUE);
// exibição do resultado
print "$résultat\n";
}//para
// fim
exit;
//ler e-mail
//-----------------------------------------------------------------------
function readmail(string $name, array $infos, bool $verbose = TRUE): string {
// lê o conteúdo da caixa de correio [$name]
// importa todas as mensagens
// cada mensagem é excluída após ser lida
// Se $verbose=1, monitora as trocas entre cliente e servidor
//
// abertura de uma conexão com o servidor SMTP
$connexion = fsockopen($infos["server"], (int) $infos["port"]);
// retorno em caso de erro
if ($connexion === FALSE) {
return sprintf("Echec de la connexion au site (%s,%s) : %s", $infos["smtp-server"], $infos["smtp-port"]);
}
// $connexion representa um fluxo de comunicação bidirecional
// entre o cliente (este programa) e o servidor POP3 acessado
// esse canal é usado para a troca de comandos e informações
// após a conexão, o servidor envia uma mensagem de boas-vindas que é lida
$erreur = sendCommand($connexion, "", $verbose, TRUE);
if ($erreur !== "") {
// encerramento da conexão
fclose($connexion);
// retorno
return $erreur;
}
// comando USER
$erreur = sendCommand($connexion, "USER {$infos["user"]}", $verbose, TRUE);
if ($erreur !== "") {
// encerramento da conexão
fclose($connexion);
// retorno
return $erreur;
}
// comando PASS
$erreur = sendCommand($connexion, "PASS {$infos["password"]}", $verbose, TRUE);
if ($erreur !== "") {
// encerramento da conexão
fclose($connexion);
// retorno
return $erreur;
}
// comando LIST
$premièreLigne = "";
$erreur = sendCommand($connexion, "LIST", $verbose, TRUE, $premièreLigne);
if ($erreur !== "") {
// encerramento da conexão
fclose($connexion);
// retorno
return $erreur;
}
// análise da primeira linha para determinar o número de mensagens
$champs = [];
preg_match("/^\+OK (\d+)/", $premièreLigne, $champs);
$nbMessages = (int) $champs[1];
// iteração pelas mensagens
$iMessage = 0;
while ($iMessage < $nbMessages && $iMessage < $infos["maxmails"]) {
// comando RETR
$erreur = sendCommand($connexion, "RETR " . ($iMessage + 1), $verbose, TRUE);
if ($erreur !== "") {
// encerramento da conexão
fclose($connexion);
// retorno
return $erreur;
}
// comando DELE
$erreur = sendCommand($connexion, "DELE " . ($iMessage + 1), $verbose, TRUE);
if ($erreur !== "") {
// encerramento da conexão
fclose($connexion);
// retorno
return $erreur;
}
// próxima mensagem
$iMessage++;
}
// comando QUIT
$erreur = sendCommand($connexion, "QUIT", $verbose, TRUE);
if ($erreur !== "") {
// encerramento da conexão
fclose($connexion);
// voltar
return $erreur;
}
// fim
fclose($connexion);
return "Terminé";
}
// --------------------------------------------------------------------------
function sendCommand($connexion, string $commande, bool $verbose, bool $withRCLF, string &$premièreLigne = ""): string {
// envia $commande para o canal $connexion
// modo detalhado se $verbose=1
// se $withRCLF=1, adiciona a sequência RCLF à troca
// coloca a primeira linha da resposta em [$premièreLigne
// ]
// dados
if ($withRCLF) {
$RCLF = "\r\n";
} else {
$RCLF = "";
}
// envia comando se $commande não estiver vazio
if ($commande !== "") {
fputs($connexion, "$commande$RCLF");
// possível resposta
if ($verbose) {
affiche($commande, 1);
}
}//se
// leitura da resposta
$réponse = fgets($connexion, 1000);
// armazenamos a primeira linha
$premièreLigne = $réponse;
// possível eco
if ($verbose) {
affiche($réponse, 2);
}
// recuperação do código de erro
$codeErreur = substr($réponse, 0, 1);
if ($codeErreur === "-") {
// ocorreu um erro
return substr($réponse, 5);
}
// casos específicos dos comandos RETR e LIST, que têm respostas com várias linhas
$commande = substr(strtolower($commande), 0, 4);
if ($commande === "list" || $commande === "retr") {
// última linha da resposta?
$champs = [];
$match = preg_match("/^\.\s+$/", $réponse, $champs);
while (!$match) {
// leitura da resposta
$réponse = fgets($connexion, 1000);
// possível eco
if ($verbose) {
affiche($réponse, 2);
}
// análise da resposta
$champs = [];
$match = preg_match("/^\.\s+$/", $réponse, $champs);
}//enquanto
}
// retorno sem erro
return "";
}
// --------------------------------------------------------------------------
function affiche($échange, $sens) {
// exibe $échange na tela
// se $sens=1, exibe -->$echange
// se $sens=2, exibe <-- $échange sem os dois últimos caracteres RCLF
switch ($sens) {
case 1:
print "--> [$échange]\n";
break;
case 2:
$L = strlen($échange);
print "<-- [" . substr($échange, 0, $L - 2) . "]\n";
break;
}//switch
}
Comentários
Como já mencionamos, [pop3-01.php] é uma adaptação do script [smtp-01.php], que já comentamos anteriormente. Abordaremos apenas as principais diferenças:
- linha 55: a função [readmail] é responsável por ler os e-mails da caixa de correio. As informações para se conectar a essa caixa de correio estão no dicionário [$infos];
- linhas 61-66: abertura de uma conexão com o servidor POP3;
- linhas 71-77: leitura da mensagem de boas-vindas enviada pelo servidor;
- linhas 78-85: envio do comando [USER] para identificar o usuário cujos e-mails se deseja obter;
- linhas 86-93: envio do comando [PASS] para fornecer a senha desse usuário;
- linhas 94-102: envia-se o comando [LIST] para saber quantos e-mails há na caixa de correio desse usuário.
- linha 96: adiciona-se o parâmetro [$premièreLigne] aos parâmetros da função [readmail]. Na primeira linha de sua resposta ao comando LIST, o servidor indica quantas mensagens há na caixa de correio;
- linhas 104-106: recupera-se o número de mensagens na primeira linha da resposta;
- linhas 109-128: é feito um ciclo por cada uma das mensagens. Para cada uma delas, são emitidos dois comandos:
- RETR i: para recuperar a mensagem nº i (linhas 111-117);
- DELE i: para excluí-la depois de lida (linhas 118-125);
- linhas 129-136: envia-se o comando [QUIT] para informar ao servidor que a operação foi concluída;
- linhas 178-194: para os comandos [LIST] e [RETR], a resposta do servidor tem várias linhas, sendo que a última consiste em um único ponto;
Resultados
Ao executar, obtêm-se os seguintes resultados:
Lecture de la boîte à lettres [localhost:110]
<-- [+OK Bienvenue sur sergetahe@localhost]
--> [USER guest@localhost]
<-- [+OK Send your password]
--> [PASS guest]
<-- [+OK Mailbox locked and ready]
--> [LIST]
<-- [+OK 1 messages (305 octets)]
<-- [1 305]
<-- [.]
--> [RETR 1]
<-- [+OK 305 octets]
<-- [Return-Path: guest@localhost]
<-- [Received: from DESKTOP-528I5CU.home (localhost [127.0.0.1])]
<-- [ by DESKTOP-528I5CU with ESMTP]
<-- [ ; Tue, 21 May 2019 14:25:39 +0200]
<-- [Message-ID: <5F912826-F9C4-41B6-BDA7-4A29537781C9@DESKTOP-528I5CU>]
<-- [From: guest@localhost]
<-- [To: guest@localhost]
<-- [Subject: to localhost via localhost]
<-- []
<-- [ligne ]
<-- [ligne ]
<-- [ligne 3]
<-- [.]
--> [DELE 1]
<-- [+OK msg deleted]
--> [QUIT]
<-- [+OK POP3 server saying goodbye…]
Terminé
Done.
Temos aqui um cliente POP3 básico, ao qual faltam algumas capacidades:
- a capacidade de se comunicar com um servidor POP3 seguro;
- a capacidade de ler anexos de uma mensagem;
Vamos implementar a primeira possibilidade com as funções [imap] e PHP.
16.6.4. Cliente POP3 / IMAP implementado com as funções [imap] do PHP
Primeiramente, precisamos verificar se as funções [imap] estão disponíveis na versão do PHP que estamos utilizando. Abrimos o arquivo [php.ini] descrito no parágrafo “link” e procuramos as linhas que mencionam [imap]:

Linha 895, verifique se a extensão [imap] está ativada.
O script [imap-01.php] processará o seguinte arquivo jSON [config-imap-01.json]:
O arquivo [config-imap-01.json] define uma tabela de servidores IMAP / POP3 a serem contatados. Cada elemento é uma estrutura [clé:valeur], em que:
- [clé]: é o servidor a ser contatado. Temos dois aqui:
- [{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX]: indica o servidor [imap.gmail.com] que está escutando na porta 993. O protocolo cliente/servidor é IMAP. O parâmetro /ssl indica que a comunicação cliente/servidor é segura. O parâmetro /novalidate-cert solicita que o cliente não verifique o certificado de segurança que o servidor irá enviar. Por fim, um servidor IMAP gerencia um conjunto de caixas de correio para um mesmo usuário. Ao especificar INBOX no URL do servidor IMAP, indicamos que estamos interessados na caixa de correio denominada INBOX, que normalmente é aquela onde chegam as novas mensagens;
- [{localhost:110/pop3}INBOX]: designa o servidor [localhost], que está escutando na porta 110. O protocolo cliente/servidor aqui é POP3;
- [valeur]: é um dicionário que especifica os seguintes pontos:
- [imap-server]: o nome do servidor IMAP ou POP3;
- [imap-port]: a porta do servidor IMAP ou POP3;
- [user]: o proprietário cuja caixa de correio se deseja ler;
- [password]: a senha dele;
- [output-dir]: a pasta na qual as mensagens devem ser salvas;
- [prefix]: o nome dos arquivos nos quais as mensagens serão salvas terá o formato prefixN, em que N é o número da mensagem;
- [pop3]: um valor booleano definido como TRUE para indicar que o protocolo utilizado é POP3. Nesse caso, após a leitura de uma mensagem, ela será excluída. Esse é o funcionamento habitual dos servidores POP3: uma mensagem lida não é mantida no servidor;
O script [imap-01.php] é o seguinte:
<?php
// cliente IMAP (Internet Message Access Protocol) que permite ler e-mails
//
// respeito estrito aos tipos declarados dos parâmetros das funções
declare (strict_types=1);
// gerenciamento de erros
error_reporting(E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
//
// parâmetros de leitura de e-mails
const CONFIG_FILE_NAME = "config-imap-01.json";
// recuperação da configuração
$mailboxes = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);
// leitura das caixas de correio
foreach ($mailboxes as $name => $infos) {
// acompanhamento
print "------------Lecture de la boîte à lettres [$name]\n";
// leitura da caixa de correio
readmailbox($name, $infos);
}
// fim
exit;
//-----------------------------------------------------------------------
function readmailbox(string $name, array $infos): void {
// Tentativa de conexão
$imapResource = imap_open($name, $infos["user"], $infos["password"]);
// Teste do retorno da função imap_open()
if (!$imapResource) {
// Falha
print "La connexion au serveur [$name] a échoué : " . imap_last_error() . "\n";
} else {
// Conexão estabelecida
print "Connexion établie avec le serveur [$name].\n";
// total de mensagens na caixa de correio
$nbmsg = imap_num_msg($imapResource);
print "Il y a [$nbmsg] messages dans la boîte à lettres [$name]\n";
// mensagens não lidas na caixa de correio atual
if ($nbmsg > 0) {
print "Récupération de la liste des messages non lus de la boîte à lettres [$name]\n";
$msgNumbers = imap_search($imapResource, 'UNSEEN');
if ($msgNumbers === FALSE) {
print "Il n'y a pas de nouveaux messages dans la boîte à lettres [$name]\n";
} else {
foreach ($msgNumbers as $msgNumber) {
// Recuperando informações sobre a mensagem nº $msgNumber
$infosMail = imap_headerinfo($imapResource, $msgNumber);
if ($infosMail === FALSE) {
print "Statut du message n° [$msgNumber] de la boîte à lettres [$name] non récupéré : " . imap_last_error() . "\n";
} else {
print "Statut du message n° [$msgNumber] de la boîte à lettres [$name]\n";
print_r($infosMail);
}
// recuperando o corpo da mensagem nº $msgNumber
getMailBody($imapResource, $msgNumber, $infos);
// se o protocolo for POP3, a mensagem é excluída
$pop3 = $infos["pop3"];
if ($pop3 !== NULL) {
// a mensagem é excluída em duas etapas
imap_delete($imapResource, $msgNumber);
imap_expunge($imapResource);
}
}
}
}
}
// encerramento da conexão
$imapClose = imap_close($imapResource);
if (!$imapClose) {
// Falha
print "La fermeture de la connexion a échoué : " . imap_last_error() . "\n";
} else {
// sucesso
print "Fermeture de la connexion réussie.\n";
}
}
function getMailBody($imapResource, int $msgNumber, array $infos): void {
// recupera-se o corpo da mensagem nº $msgNumber
$corpsMail = imap_body($imapResource, $msgNumber);
print "Enregistrement du message dans le fichier {$infos["output-dir"]}/{$infos["prefix"]}$msgNumber\n";
// cria-se a pasta, se necessário
if (!file_exists($infos["output-dir"])) {
mkdir($infos["output-dir"]);
}
// a mensagem é salva
if (!file_put_contents($infos["output-dir"] . "/" . $infos["prefix"] . $msgNumber, $corpsMail)) {
print "Echec de l'enregistrement\n";
}
}
Comentários
- linhas 19-24: é feito um ciclo por todos os servidores encontrados no arquivo de configuração;
- linha 32: a função [raedmailbox] lê a caixa de correio indicada em [$name];
- linha 32: abertura de uma conexão IMAP;
- o primeiro parâmetro é o URL IMAP da caixa de correio a ser lida;
- o segundo parâmetro é o nome do usuário proprietário dessa caixa de correio;
- o terceiro parâmetro é a senha dele;
A função [imap_open] configura a segurança da conexão se a caixa de correio URL IMAP tiver o parâmetro /ssl;
- linha 41: a função [imap_num_msg] permite obter o número total de mensagens da caixa de correio;
- linha 46: a função [imap_search] permite pesquisar determinadas mensagens. Aqui, buscamos as mensagens que ainda não foram lidas (UNSEEN). O segundo parâmetro é um critério de seleção. Existem cerca de vinte deles. A função [imap_search] retorna uma matriz com os números das mensagens. Esses números podem assumir duas formas: número de sequência ou identificador UID da mensagem. Por padrão, a função [imap_search] retorna uma matriz com os números de sequência. Se for adicionado um terceiro parâmetro [SE_UID], obter-se-ão os identificadores UID das mensagens;
- linha 47: a função [imap_search] retorna o valor booleano FALSE caso não tenha encontrado nenhuma mensagem;
- linha 50: faz-se um loop por todas as mensagens não lidas;
- linha 52: uma mensagem possui cabeçalhos que podem ser obtidos com a função [imap_headerinfo]. Seu segundo parâmetro é normalmente um número de sequência da mensagem. Se quisermos definir um identificador de mensagem UID, é preciso definir o terceiro parâmetro como [FT_UID];
- linha 53: a função [imap_headerinfo] retorna o valor booleano FALSE caso não tenha conseguido executar sua tarefa. Caso contrário, ela retorna um objeto complexo que é exibido com a função [print_r], linha 57;
- linha 60: após os cabeçalhos, solicita-se agora o corpo da mensagem com a função [imap_body]. Essa função retorna NULL caso não tenha conseguido realizar sua tarefa;
- linhas 84-87: o corpo da mensagem é gravado em um arquivo local;
- linhas 63-68: se o protocolo utilizado foi POP3, a mensagem que acabou de ser lida é excluída:
- a função [imap_delete] marca a mensagem como “a ser excluída”, mas não a exclui;
- a função [imap_expunge] exclui fisicamente todas as mensagens que foram marcadas como “a serem excluídas”;
- linha 74: encerra-se a conexão com o servidor IMAP. Para isso, utiliza-se a função [imap_close];
- linha 86: a função [imap_body] permite obter o corpo de uma mensagem identificada pelo seu número;
Vamos executar o script [smtp-02.json] para que o usuário [php7parlexemple] do Gmail e o usuário [guest] do [localhost] recebam novas mensagens. Feito isso, vamos executar o script [imap-01.php] para ler suas caixas de entrada.
Os resultados na console são os seguintes:
------------Lecture de la boîte à lettres [{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX]
Connexion établie avec le serveur [{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX].
Il y a [27] messages dans la boîte à lettres [{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX]
Récupération de la liste des messages non lus de la boîte à lettres [{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX]
Statut du message n° [26] de la boîte à lettres [{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX]
stdClass Object
(
[date] => Wed, 22 May 2019 10:08:24 +0000
[Date] => Wed, 22 May 2019 10:08:24 +0000
[subject] => test-gmail-via-gmail
[Subject] => test-gmail-via-gmail
[message_id] => <d8405cac62d57bd9c531ea79c146c72d@swift.generated>
[toaddress] => php7parlexemple@gmail.com
[to] => Array
(
[0] => stdClass Object
(
[mailbox] => php7parlexemple
[host] => gmail.com
)
)
[fromaddress] => php7parlexemple@gmail.com
[from] => Array
(
[0] => stdClass Object
(
[mailbox] => php7parlexemple
[host] => gmail.com
)
)
[reply_toaddress] => php7parlexemple@gmail.com
[reply_to] => Array
(
[0] => stdClass Object
(
[mailbox] => php7parlexemple
[host] => gmail.com
)
)
[senderaddress] => php7parlexemple@gmail.com
[sender] => Array
(
[0] => stdClass Object
(
[mailbox] => php7parlexemple
[host] => gmail.com
)
)
[Recent] =>
[Unseen] => U
[Flagged] =>
[Answered] =>
[Deleted] =>
[Draft] =>
[Msgno] => 26
[MailDate] => 22-May-2019 10:08:29 +0000
[Size] => 19086
[udate] => 1558519709
)
Enregistrement du message dans le fichier output/gmail-imap/message-26
Statut du message n° [27] de la boîte à lettres [{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX]
stdClass Object
(
…
)
Enregistrement du message dans le fichier output/gmail-imap/message-27
Fermeture de la connexion réussie.
------------Lecture de la boîte à lettres [{localhost:110/pop3}]
Connexion établie avec le serveur [{localhost:110/pop3}].
Il y a [1] messages dans la boîte à lettres [{localhost:110/pop3}]
Récupération de la liste des messages non lus de la boîte à lettres [{localhost:110/pop3}]
Statut du message n° [1] de la boîte à lettres [{localhost:110/pop3}]
stdClass Object
(
…
)
Enregistrement du message dans le fichier output/localhost-pop3/message-1
Fermeture de la connexion réussie.
Done.
Se, logo após esses resultados, executarmos novamente o script [imap-01.php], os resultados serão os seguintes:
------------Lecture de la boîte à lettres [{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX]
Connexion établie avec le serveur [{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX].
Il y a [27] messages dans la boîte à lettres [{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX]
Récupération de la liste des messages non lus de la boîte à lettres [{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX]
Il n'y a pas de nouveaux messages dans la boîte à lettres [{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX]
Fermeture de la connexion réussie.
------------Lecture de la boîte à lettres [{localhost:110/pop3}]
Connexion établie avec le serveur [{localhost:110/pop3}].
Il y a [0] messages dans la boîte à lettres [{localhost:110/pop3}]
Fermeture de la connexion réussie.
- linha 3: ainda há o mesmo número de mensagens na caixa de correio do Gmail, mas não há mais mensagens novas não lidas (linha 5). Isso mostra que a execução anterior alterou o status das mensagens lidas de “não lido” para “lido”;
- linha 9: não há mais mensagens na caixa de entrada do usuário [guest@localhost]. Isso se deve ao fato de que, na execução anterior, as mensagens lidas em [localhost] foram posteriormente excluídas;
As mensagens foram gravadas localmente:

Se analisarmos, por exemplo, o conteúdo da mensagem nº 26 do Gmail, temos o seguinte:
--_=_swift_1558519704_f31b373d6e416dc88eb4db0e45fb3a95_=_
Content-Type: multipart/alternative;
boundary="_=_swift_1558519706_9bffb48891232e50ab645383ca62242d_=_"
--_=_swift_1558519706_9bffb48891232e50ab645383ca62242d_=_
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
ligne 1
ligne 2
ligne 3
--_=_swift_1558519706_9bffb48891232e50ab645383ca62242d_=_
Content-Type: text/HTML; charset=utf-8
Content-Transfer-Encoding: quoted-printable
<b>ligne 1<br/>ligne 2<br/>ligne 3</b>
--_=_swift_1558519706_9bffb48891232e50ab645383ca62242d_=_--
--_=_swift_1558519704_f31b373d6e416dc88eb4db0e45fb3a95_=_
Content-Type: application/pdf; name=Hello.pdf
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=Hello.pdf
JVBERi0xLjUKJcOkw7zDtsOfCjIgMCBvYmoKPDwvTGVuZ3RoIDMgMCBSL0ZpbHRlci9GbGF0ZURl
Y29kZT4+CnN0cmVhbQp4nHWPuQoCQQyG+3mK1MKMyThHFoaAq7uF3cKAhdh5gIXgNr6+swcWshII
……………………………….…
OTQwODU4RDUzRDVENjU0QzJCNTM3Mjc+IF0KL0RvY0NoZWNrc3VtIC9DMjU3MUY1MUNDRjgwQ0Ex
ODU0OUI0RTQ4NDkwMDM3OAo+PgpzdGFydHhyZWYKMTIzMjYKJSVFT0YK
--_=_swift_1558519704_f31b373d6e416dc88eb4db0e45fb3a95_=_--
- linhas 11-13: a mensagem em texto simples;
- linha 19: a mensagem HTML;
- linha 25: o anexo;
Vamos tentar aprimorar esse script para ter, em arquivos separados, os diferentes tipos de mensagens, bem como os anexos.
16.6.5. Cliente POP3 / IMAP aprimorado
No script [imap-01.php], exibimos o corpo da mensagem nº i como um arquivo de texto contendo tanto os diferentes tipos de mensagens quanto o conteúdo codificado dos diversos anexos. É possível obter a estrutura da mensagem para identificar essas diferentes partes. No script [imap-02.php], modificamos a função [getMailBody] da seguinte maneira:
function getMailBody($imapResource, int $msgNumber, array $infos): void {
// recupera-se a estrutura da mensagem
$structure=imap_fetchstructure($imapResource, $msgNumber);
// exibe-se
print_r($structure);
}
- linha 3: solicitamos a estrutura da mensagem;
- linha 5: exibimos a estrutura;
O objetivo é conhecer as informações contidas na estrutura de uma mensagem para ver como podemos obter suas diferentes partes. Em nosso exemplo, a mensagem é enviada pelo script [smtp-02.php] com a seguinte configuração [config-smtp-02.json]:
Trata-se, portanto, de uma mensagem com cinco anexos que é enviada para [guest@localhost] (linhas 11-15). O script [imap-02.php] é executado com a seguinte configuração [config-imap-01.json]:
Portanto, é a caixa de correio de [guest@localhost] que é explorada (linha 5). O script [imap-02.php] exibe então a estrutura da mensagem enviada por [smtp-02.php]. Essa estrutura, exibida no console, é a seguinte:
stdClass Object
(
[type] => 1
[encoding] => 0
[ifsubtype] => 1
[subtype] => MIXED
[ifdescription] => 0
[ifid] => 0
[bytes] => 253599
[ifdisposition] => 0
[ifdparameters] => 0
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => BOUNDARY
[value] => _=_swift_1558872295_5bc8ee2ca8b3723c0b39ca8bbfbebdeb_=_
)
)
[parts] => Array
(
[0] => stdClass Object
(
[type] => 1
[encoding] => 0
[ifsubtype] => 1
[subtype] => ALTERNATIVE
[ifdescription] => 0
[ifid] => 0
[bytes] => 429
[ifdisposition] => 0
[ifdparameters] => 0
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => BOUNDARY
[value] => _=_swift_1558872296_1e51aae79dfca4e7e0af112489fe8734_=_
)
)
[parts] => Array
(
[0] => stdClass Object
(
[type] => 0
[encoding] => 4
[ifsubtype] => 1
[subtype] => PLAIN
[ifdescription] => 0
[ifid] => 0
[lines] => 3
[bytes] => 27
[ifdisposition] => 0
[ifdparameters] => 0
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => CHARSET
[value] => utf-8
)
)
)
[1] => stdClass Object
(
[type] => 0
[encoding] => 4
[ifsubtype] => 1
[subtype] => HTML
[ifdescription] => 0
[ifid] => 0
[lines] => 1
[bytes] => 40
[ifdisposition] => 0
[ifdparameters] => 0
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => CHARSET
[value] => utf-8
)
)
)
)
)
[1] => stdClass Object
(
[type] => 3
[encoding] => 3
[ifsubtype] => 1
[subtype] => VND.OPENXMLFORMATS-OFFICEDOCUMENT.WORDPROCESSINGML.DOCUMENT
[ifdescription] => 0
[ifid] => 0
[bytes] => 16302
[ifdisposition] => 1
[disposition] => ATTACHMENT
[ifdparameters] => 1
[dparameters] => Array
(
[0] => stdClass Object
(
[attribute] => FILENAME
[value] => Hello from SwiftMailer.docx
)
)
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => NAME
[value] => Hello from SwiftMailer.docx
)
)
)
[2] => stdClass Object
(
[type] => 3
[encoding] => 3
[ifsubtype] => 1
[subtype] => PDF
[ifdescription] => 0
[ifid] => 0
[bytes] => 17514
[ifdisposition] => 1
[disposition] => ATTACHMENT
[ifdparameters] => 1
[dparameters] => Array
(
[0] => stdClass Object
(
[attribute] => FILENAME
[value] => Hello from SwiftMailer.pdf
)
)
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => NAME
[value] => Hello from SwiftMailer.pdf
)
)
)
[3] => stdClass Object
(
…
)
[4] => stdClass Object
(
…
)
[5] => stdClass Object
(
[type] => 2
[encoding] => 3
[ifsubtype] => 1
[subtype] => RFC822
[ifdescription] => 0
[ifid] => 0
[lines] => 1881
[bytes] => 146682
[ifdisposition] => 1
[disposition] => ATTACHMENT
[ifdparameters] => 1
[dparameters] => Array
(
[0] => stdClass Object
(
[attribute] => FILENAME
[value] => test-localhost.eml
)
)
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => NAME
[value] => test-localhost.eml
)
)
[parts] => Array
(
…
)
)
)
)
Comentários
- A documentação PHP da função [imap_fetchstructure] explica o significado dos diferentes campos do objeto retornado pela função:

Os valores numéricos do campo [type] têm o seguinte significado:

Os valores numéricos do campo [encoding] têm o seguinte significado:

A mensagem registrada por [imap-01.php] começava com o seguinte texto:
Return-Path: <php7parlexemple@gmail.com>
Received: from [127.0.0.1] (lfbn-1-11924-110.w90-93.abo.wanadoo.fr. [90.93.230.110])
by smtp.gmail.com with ESMTPSA id e14sm7773816wma.41.2019.05.26.03.11.53
for <php7parlexemple@gmail.com>
(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
Sun, 26 May 2019 03:11:54 -0700 (PDT)
Message-ID: <e613c47a421a66e2cf7f8e319616ec49@swift.generated>
Date: Sun, 26 May 2019 10:11:53 +0000
Subject: test-gmail-via-gmail
From: php7parlexemple@gmail.com
To: php7parlexemple@gmail.com
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="_=_swift_1558865513_a3a939017128a4cfb867e968bce5df49_=_"
--_=_swift_1558865513_a3a939017128a4cfb867e968bce5df49_=_
Content-Type: multipart/alternative; boundary="_=_swift_1558865513_43c6d2a54065e4917fb06e3327f8d927_=_"
--_=_swift_1558865513_43c6d2a54065e4917fb06e3327f8d927_=_
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
ligne 1
ligne 2
ligne 3
--_=_swift_1558865513_43c6d2a54065e4917fb06e3327f8d927_=_
Content-Type: text/HTML; charset=utf-8
Content-Transfer-Encoding: quoted-printable
<b>ligne 1<br/>ligne 2<br/>ligne 3</b>
--_=_swift_1558865513_43c6d2a54065e4917fb06e3327f8d927_=_--
--_=_swift_1558865513_a3a939017128a4cfb867e968bce5df49_=_
- as linhas 15) e 33) delimitam a mensagem do tipo [multipart/mixed] (linha m);
- as linhas 18) e 16) delimitam a primeira parte da mensagem: a mensagem em texto simples;
- as linhas 26) e 32) delimitam a segunda parte da mensagem: a mensagem HTML;
Encontramos as diferentes informações da mensagem acima no objeto retornado por [imap_fetchstructure]:
stdClass Object
(
[type] => 1
[encoding] => 0
[ifsubtype] => 1
[subtype] => MIXED
[ifdescription] => 0
[ifid] => 0
[bytes] => 253599
[ifdisposition] => 0
[ifdparameters] => 0
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => BOUNDARY
[value] => _=_swift_1558872295_5bc8ee2ca8b3723c0b39ca8bbfbebdeb_=_
)
)
[parts] => Array
(
[0] => stdClass Object
(
[type] => 1
[encoding] => 0
[ifsubtype] => 1
[subtype] => ALTERNATIVE
[ifdescription] => 0
[ifid] => 0
[bytes] => 429
[ifdisposition] => 0
[ifdparameters] => 0
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => BOUNDARY
[value] => _=_swift_1558872296_1e51aae79dfca4e7e0af112489fe8734_=_
)
)
[parts] => Array
(
[0] => stdClass Object
(
[type] => 0
[encoding] => 4
[ifsubtype] => 1
[subtype] => PLAIN
[ifdescription] => 0
[ifid] => 0
[lines] => 3
[bytes] => 27
[ifdisposition] => 0
[ifdparameters] => 0
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => CHARSET
[value] => utf-8
)
)
)
[1] => stdClass Object
(
[type] => 0
[encoding] => 4
[ifsubtype] => 1
[subtype] => HTML
[ifdescription] => 0
[ifid] => 0
[lines] => 1
[bytes] => 40
[ifdisposition] => 0
[ifdparameters] => 0
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => CHARSET
[value] => utf-8
)
)
)
)
)
- linha 3: a mensagem é do tipo MIME (Multipurpose Internet Mail Extensions) [multipart];
- linha 4: a mensagem está codificada em 7 bits;
- linha 5: [ifsubtype]=1 indica que há um campo [subtype] na estrutura;
- linha 6: o campo [subtype] designa um subtipo MIME, neste caso o tipo [mixed]. No total, o tipo MIME do documento é [multipart/mixed];
- linha 7: [ifdescription]=0 indica que não há campo [description] na estrutura;
- linha 8: [ifid]=0 indica que não há campo [id] na estrutura;
- linha 10: [ifdisposition]=0 indica que não há o campo [disposition] na estrutura;
- linha 11: [ifdparameters]=0 indica que não há o campo [dparameters] na estrutura;
- linha 12: [ifparameters]=1 indica que há um campo [parameters] na estrutura;
- linha 13: o campo [parameters] descreve os parâmetros da mensagem. Aqui, há apenas um;
- linhas 15-19: este objeto descreve a linha seguinte da mensagem de texto:
Essas linhas servem para delimitar a mensagem. Na mensagem recuperada por [imap-01.php], a parte da mensagem que acaba de ser descrita corresponde à linha m). O atributo [boundary] não é o mesmo, pois as capturas de tela correspondem à mesma mensagem, mas enviadas em momentos diferentes;
- linha 23: aqui começa a estrutura das diferentes partes da mensagem;
- linhas 25-45: esta primeira parte é do tipo [multipart/alternative]. Ela corresponde à linha p) do texto da mensagem;
- linha 47: essa primeira parte, por sua vez, possui subpartes;
- linhas 47-70: esta primeira subparte é do tipo [text/plain] (linhas 51, 54), está codificada no tipo [ENCQUOTEDPRINTABLE] (linha 52) e possui um parâmetro [charset=utf-8] (linhas 66-67);
- as linhas 49-72 descrevem as linhas s-x da mensagem de texto;
- linhas 74-99: descrevem a segunda subparte da parte [multipart/alternative];
- linhas 74-99: esta segunda subparte é do tipo [text/HTML] (linhas 76, 79), está codificada no tipo [ENCQUOTEDPRINTABLE] (linha 77) e possui um parâmetro [charset=utf-8] (linhas 89-93);
- as linhas 74-99 descrevem as linhas aa-ad da mensagem de texto;
A parte [multipart/alternative] está agora concluída. Inicia-se a parte [application/vnd.openxmlformats-officedocument.wordprocessingml.document] descrita pelo texto a seguir:
Mais uma vez, essas informações estão contidas no objeto retornado pela função [imap_fetchstructure]:
[1] => stdClass Object
(
[type] => 3
[encoding] => 3
[ifsubtype] => 1
[subtype] => VND.OPENXMLFORMATS-OFFICEDOCUMENT.WORDPROCESSINGML.DOCUMENT
[ifdescription] => 0
[ifid] => 0
[bytes] => 16302
[ifdisposition] => 1
[disposition] => ATTACHMENT
[ifdparameters] => 1
[dparameters] => Array
(
[0] => stdClass Object
(
[attribute] => FILENAME
[value] => Hello from SwiftMailer.docx
)
)
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => NAME
[value] => Hello from SwiftMailer.docx
)
)
)
- linha 1: trata-se da segunda parte da mensagem global. Vale lembrar que a primeira parte era do tipo [multipart/alternative];
- linhas 3-6: esta segunda parte é do tipo [application/vnd.openxmlformats-officedocument.wordprocessingml.document] (linhas 3 e 6) e está codificada em Base 64 (linha 4);
- linha 11: esta segunda parte é um anexo (linha 11) e possui dois parâmetros: [filename=Hello from SwiftMailer.docx] (linhas 15-21) e [name=Hello from SwiftMailer.docx] (linhas 26-32). Observe-se que este último parâmetro não existe na mensagem de texto. Portanto, ele foi adicionado na função [imap_fetchstructure];
As linhas 1 a 36 são reproduzidas para cada um dos cinco anexos da mensagem.
A função [imap_fetch_structure] nos permite, portanto, obter a estrutura de uma mensagem. Essa estrutura define partes que, por sua vez, podem ter subpartes. Para obter o texto de uma parte ou subparte, utiliza-se a função [imap_fetchbody].
Modificamos a função [getMailBody], que nos permite obter o corpo de uma mensagem, da seguinte maneira:
function getMailBody($imapResource, int $msgNumber, array $infos, object $infosMail): void {
// recupera-se a estrutura da mensagem
$structure = imap_fetchstructure($imapResource, $msgNumber);
if ($structure !== FALSE) {
// recuperam-se essas diferentes partes
getParts($imapResource, $msgNumber, $infos, $infosMail, $structure);
}
}
function getParts($imapResource, int $msgNumber, array $infos, object $infosMail, stdclass $part, string $sectionNumber = "0"): void {
// cálculo do número da seção
if (substr($sectionNumber, 0, 2) === "0.") {
$sectionNumber = substr($sectionNumber, 2);
}
print "-----contenu de la partie n° [$sectionNumber]\n";
// tipo de conteúdo
print "Content-Type: ";
switch ($part->type) {
case TYPETEXT:
print "TEXT/{$part->subtype}\n";
break;
case TYPEMULTIPART:
print "MULTIPART/{$part->subtype}\n";
break;
case TYPEAPPLICATION:
print "APPLICATION/{$part->subtype}\n";
break;
case TYPEMESSAGE:
print "MESSAGE/{$part->subtype}\n";
break;
default:
print "UNKNOWN/{$part->subtype}\n";
break;
}
// tipo de codificação
$encodings=["7 bits", "8 bits", "binaire", "base 64", "quoted-printable", "autre"];
print "Transfer-Encoding : ".$encodings[$part->encoding]."\n";
// passa-se às eventuais subpartes
if (isset($part->parts)) {
for ($i = 1; $i <= count($part->parts); $i++) {
// uma nova parte da mensagem
$subpart = $part->parts[$i - 1];
// chamada recursiva — solicita-se o corpo da parte [$subpart]
getParts($imapResource, $msgNumber, $infos, $infosMail, $subpart, "$sectionNumber.$i");
}
}
}
Comentários
- linha 3: recuperamos a estrutura da mensagem;
- linha 6: solicitamos a exibição das diferentes partes que constam na tabela [parts] da estrutura;
- linha 10: a função [getParts] recebe os seguintes parâmetros:
- [$imapResource]: a conexão com o servidor IMAP;
- [$msgNumber]: o número de sequência da mensagem cujas partes se deseja obter;
- [$infos]: informações para determinar onde armazenar as partes encontradas no sistema de arquivos local;
- [$infosMail]: informações gerais sobre o e-mail (remetente, destinatário(s), assunto…;
- [$part]: um objeto que representa uma parte da mensagem;
- [$sectionNumber]: um número de seção (ou parte) da mensagem;
- linhas 17-34: exibe-se o tipo de conteúdo da parte nº [$section] da mensagem. Para isso, utilizam-se os campos [$part→type] e [$part→subtype] da parte [$part];
- linhas 36-37: exibe-se o tipo de codificação da parte [$sectionNumber];
- linhas 40-47: talvez a parte cujas informações acabamos de exibir tenha, por sua vez, subpartes;
- linhas 41-46: se for esse o caso, solicita-se a exibição do tipo de conteúdo das diferentes subpartes da parte que acabamos de exibir. Aqui, fazemos uma chamada recursiva à função [getParts];
Mais uma vez, enviamos um e-mail ao usuário do Gmail [php7parlexemple@gmail.com] com o script [smtp-02.php] e o lemos com o script anterior [imap-02.php]. Isso gera os seguintes resultados no console:
------------Lecture de la boîte à lettres [{localhost:110/pop3}]
Connexion établie avec le serveur [{localhost:110/pop3}].
Il y a [1] messages dans la boîte à lettres [{localhost:110/pop3}]
Récupération de la liste des messages non lus de la boîte à lettres [{localhost:110/pop3}]
-----contenu de la partie n° [0]
Content-Type: MULTIPART/MIXED
Transfer-Encoding : 7 bits
-----contenu de la partie n° [1]
Content-Type: MULTIPART/ALTERNATIVE
Transfer-Encoding : 7 bits
-----contenu de la partie n° [1.1]
Content-Type: TEXT/PLAIN
Transfer-Encoding : quoted-printable
-----contenu de la partie n° [1.2]
Content-Type: TEXT/HTML
Transfer-Encoding : quoted-printable
-----contenu de la partie n° [2]
Content-Type: APPLICATION/VND.OPENXMLFORMATS-OFFICEDOCUMENT.WORDPROCESSINGML.DOCUMENT
Transfer-Encoding : base 64
-----contenu de la partie n° [3]
Content-Type: APPLICATION/PDF
Transfer-Encoding : base 64
-----contenu de la partie n° [4]
Content-Type: APPLICATION/VND.OASIS.OPENDOCUMENT.TEXT
Transfer-Encoding : base 64
-----contenu de la partie n° [5]
Content-Type: UNKNOWN/PNG
Transfer-Encoding : base 64
-----contenu de la partie n° [6]
Content-Type: MESSAGE/RFC822
Transfer-Encoding : base 64
-----contenu de la partie n° [6.1]
Content-Type: TEXT/PLAIN
Transfer-Encoding : 7 bits
Fermeture de la connexion réussie.
Conseguimos recuperar os diferentes tipos de conteúdo da mensagem, bem como seu tipo de codificação. A numeração das partes segue a seguinte regra:
- linhas 6-7: a parte [multipart/mixed], que representa a mensagem completa, recebe o número 0. As diferentes partes desse objeto receberão, então, os números 1, 2…
A mensagem tem, no total, cinco partes:
- linhas 9-10: a parte [multipart/alternative], que recebe o nº 1;
- linhas 17-18: a parte [APPLICATION/VND.OPENXMLFORMATS-OFFICEDOCUMENT.WORDPROCESSINGML.DOCUMENT], que tem o nº 2. Trata-se do anexo de um arquivo do Word;
- linhas 20-21: a parte [APPLICATION/PDF], que tem o nº 3. Trata-se do anexo de um arquivo PDF;
- linhas 23-24: a parte [APPLICATION/VND.OASIS.OPENDOCUMENT.TEXT], que tem o nº 4. Trata-se do anexo de um arquivo OpenOffice;
- linhas 26-27: a parte [UNKNOWN/PNG], que tem o nº 5. Trata-se do anexo de um arquivo de imagem;
- linhas 30-31: a parte [MESSAGE/RFC822], que possui o nº 6. Trata-se do anexo de um e-mail;
Quando uma parte possui subpartes, estas são numeradas como x.1, x.2… onde x é o número da parte que as engloba. Assim:
- linhas 11-12: a 1ª parte da parte [multipart/alternative] tem o nº 1.1. Trata-se de um conteúdo do tipo [text/plain]: a mensagem do e-mail;
- linhas 14-15: a segunda parte da parte [multipart/alternative] tem o nº 1.2. Trata-se de um conteúdo do tipo [text/HTML]: a mensagem do e-mail em HTML;
- linhas 32-33: a primeira parte do anexo [MESSAGE/RFC822] tem o nº 6.1. Trata-se de um conteúdo do tipo [text/plain]. Na verdade, de acordo com o padrão MIME, a numeração das partes de um anexo de e-mail [MESSAGE/RFC822] difere da regra descrita anteriormente. Assim, a primeira parte do anexo [MESSAGE/RFC822] não tem o número 6.1, mas sim outro número;
Agora que sabemos como identificar as diferentes partes e subpartes de um e-mail, resta recuperar seu conteúdo.
O código do script evolui da seguinte forma:
function getParts($imapResource, int $msgNumber, array $infos, object $infosMail, stdclass $part, string $sectionNumber = "0"): void {
// cálculo do número da seção
if (substr($sectionNumber, 0, 2) === "0.") {
$sectionNumber = substr($sectionNumber, 2);
}
print "-----contenu de la partie n° [$sectionNumber]\n";
// tipo de conteúdo
print "Content-Type: ";
switch ($part->type) {
case TYPETEXT:
print "TEXT/{$part->subtype}\n";
break;
case TYPEMULTIPART:
print "MULTIPART/{$part->subtype}\n";
break;
case TYPEAPPLICATION:
print "APPLICATION/{$part->subtype}\n";
break;
case TYPEMESSAGE:
print "MESSAGE/{$part->subtype}\n";
break;
default:
print "UNKNOWN/{$part->subtype}\n";
break;
}
// tipo de codificação
$encodings = ["7 bits", "8 bits", "binaire", "base 64", "quoted-printable", "autre"];
print "Transfer-Encoding : " . $encodings[$part->encoding] . "\n";
// trata-se de uma mensagem?
if ($part->type === TYPEMESSAGE) {
// não vamos processar as subpartes desta mensagem (e-mail anexado)
// exibimos o corpo do e-mail anexado
print imap_fetchbody($imapResource, $msgNumber, $sectionNumber);
} else {
// passamos para as eventuais subpartes
if (isset($part->parts)) {
for ($i = 1; $i <= count($part->parts); $i++) {
// uma nova parte da mensagem
$subpart = $part->parts[$i - 1];
// chamada recursiva — solicita-se o corpo da parte [$subpart]
getParts($imapResource, $msgNumber, $infos, $infosMail, $subpart, "$sectionNumber.$i");
}
} else {
// não há subpartes — exibe-se, então, o corpo da mensagem
print imap_fetchbody($imapResource, $msgNumber, $sectionNumber);
}
}
}
Comentários
- linha 46: a função [imap_fetchbody] recupera o corpo da parte nº [$sectionNumber] da mensagem. A numeração das partes de uma mensagem segue a regra explicada anteriormente;
- linha 1: começa-se com a seção “0”;
- linha 41: as subpartes dessa seção serão então numeradas como “0.1”, “0.2”, quando deveriam ser numeradas como “1”, “2”…
- linhas 3-5: corrige-se essa anomalia;
- linhas 37-43: se a parte atual tiver subpartes, então percorre-se cada uma delas (linhas 38-43). Seu número de seção é [$sectionNumber.$i];
- linhas 44-47: quando não houver mais subpartes, exibe-se o corpo da parte atual com a função [imap_fetchbody]. No nosso exemplo, trata-se das partes [text/plain], [text/HTML] e dos anexos;
A execução deste script produz os seguintes resultados:
------------Lecture de la boîte à lettres [{localhost:110/pop3}]
Connexion établie avec le serveur [{localhost:110/pop3}].
Il y a [1] messages dans la boîte à lettres [{localhost:110/pop3}]
Récupération de la liste des messages non lus de la boîte à lettres [{localhost:110/pop3}]
-----contenu de la partie n° [0]
Content-Type: MULTIPART/MIXED
Transfer-Encoding : 7 bits
-----contenu de la partie n° [1]
Content-Type: MULTIPART/ALTERNATIVE
Transfer-Encoding : 7 bits
-----contenu de la partie n° [1.1]
Content-Type: TEXT/PLAIN
Transfer-Encoding : quoted-printable
ligne 1
ligne 2
ligne 3
-----contenu de la partie n° [1.2]
Content-Type: TEXT/HTML
Transfer-Encoding : quoted-printable
<b>ligne 1<br/>ligne 2<br/>ligne 3</b>
-----contenu de la partie n° [2]
Content-Type: APPLICATION/VND.OPENXMLFORMATS-OFFICEDOCUMENT.WORDPROCESSINGML.DOCUMENT
Transfer-Encoding : base 64
UEsDBBQABgAIAAAAIQDfpNJsWgEAACAFAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIooAAC
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
…
AAAAAAAAAF0mAABkb2NQcm9wcy9jb3JlLnhtbFBLAQItABQABgAIAAAAIQCdxkmwcgEAAMcCAAAQ
AAAAAAAAAAAAAAAAAAgpAABkb2NQcm9wcy9hcHAueG1sUEsFBgAAAAALAAsAwQIAALArAAAAAA==
-----contenu de la partie n° [3]
Content-Type: APPLICATION/PDF
Transfer-Encoding : base 64
JVBERi0xLjUKJcOkw7zDtsOfCjIgMCBvYmoKPDwvTGVuZ3RoIDMgMCBSL0ZpbHRlci9GbGF0ZURl
Y29kZT4+CnN0cmVhbQp4nHWNvQoCMRCE+zzF1sLF2WSTSyAEPD0Lu4OAhdj5AxaC1/j6Rk4s5GSa
…
PDcxQUJGQ0JGQURGODYxM0NBNUJDODNFMDNDNjI1QkQwPgo8NzFBQkZDQkZBREY4NjEzQ0E1QkM4
M0UwM0M2MjVCRDA+IF0KL0RvY0NoZWNrc3VtIC9DMTRCN0Q5N0YwNUU1OTYxQzhDODg0NEI3NkNF
OEIwRQo+PgpzdGFydHhyZWYKMTIzMTQKJSVFT0YK
-----contenu de la partie n° [4]
Content-Type: APPLICATION/VND.OASIS.OPENDOCUMENT.TEXT
Transfer-Encoding : base 64
UEsDBBQAAAgAAAs9uU5exjIMJwAAACcAAAAIAAAAbWltZXR5cGVhcHBsaWNhdGlvbi92bmQub2Fz
aXMub3BlbmRvY3VtZW50LnRleHRQSwMEFAAACAAACz25TgAAAAAAAAAAAAAAABwAAABDb25maWd1
…
AQIUABQACAgIAAs9uU42l0SORAQAABIRAAALAAAAAAAAAAAAAAAAAI8bAABjb250ZW50LnhtbFBL
AQIUABQACAgIAAs9uU4Uf52+LgEAACUEAAAVAAAAAAAAAAAAAAAAAAwgAABNRVRBLUlORi9tYW5p
ZmVzdC54bWxQSwUGAAAAABEAEQBlBAAAfSEAAAAA
-----contenu de la partie n° [5]
Content-Type: UNKNOWN/PNG
Transfer-Encoding : base 64
iVBORw0KGgoAAAANSUhEUgAABiAAAAEMCAYAAABN1n5OAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAg
AElEQVR4nOy9e5TdV3Xn+Zm7aqprlBq1Rq1Wq7XU6opGrXaMMI6jAcfj9ihu4hAehkAghBASICF0
…
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAA2Mb8f9Q5r2ohJn6/AAAAAElFTkSuQmCC
-----contenu de la partie n° [6]
Content-Type: MESSAGE/RFC822
Transfer-Encoding : base 64
UmV0dXJuLVBhdGg6IGd1ZXN0QGxvY2FsaG9zdA0KUmVjZWl2ZWQ6IGZyb20gWzEyNy4wLjAuMV0g
KGxvY2FsaG9zdCBbMTI3LjAuMC4xXSkNCglieSBERVNLVE9QLTUyOEk1Q1Ugd2l0aCBFU01UUA0K
…
cjJvaEpuNi9BQUFBQUVsRlRrU3VRbUNDDQotLV89X3N3aWZ0XzE1NTg3NzA1MDJfYzRiODA4Yzk5
YzI3ZGVkMDQ1OTViZDExZjRiYWQxMWJfPV8tLQ0K
Fermeture de la connexion réussie.
Comentários
- linhas 14-16: o conteúdo da mensagem de texto codificado em [quoted-printable] (linha 13);
- linha 20: o conteúdo da mensagem HTML codificada em [quoted-printable] (linha 19);
- linhas 24-28: o conteúdo do arquivo do Word codificado em [base64] (linha 23);
- linhas 32-37: o conteúdo do arquivo PDF codificado como [base64] (linha 31);
- linhas 41-45: o conteúdo do arquivo OpenOffice codificado como [base64] (linha 40);
- linhas 50-55: o conteúdo do arquivo de imagem codificado como [base64] (linha 49);
- linhas 59-63: o conteúdo do e-mail anexado codificado como [base64] (linha 58);
Agora que:
- sabemos como recuperar os textos das diferentes partes de um e-mail;
- conhecemos a codificação desses textos;
podemos salvar esses textos em arquivos.
O código evolui da seguinte forma:
function getParts($imapResource, int $msgNumber, array $infos, object $infosMail, stdclass $part, string $sectionNumber = "0"): void {
// cálculo do número da seção
if (substr($sectionNumber, 0, 2) === "0.") {
$sectionNumber = substr($sectionNumber, 2);
}
print "-----contenu de la partie n° [$sectionNumber]\n";
// tipo de conteúdo
print "Content-Type: ";
switch ($part->type) {
case TYPETEXT:
print "TEXT/{$part->subtype}\n";
break;
case TYPEMULTIPART:
print "MULTIPART/{$part->subtype}\n";
break;
case TYPEAPPLICATION:
print "APPLICATION/{$part->subtype}\n";
break;
case TYPEMESSAGE:
print "MESSAGE/{$part->subtype}\n";
break;
default:
print "UNKNOWN/{$part->subtype}\n";
break;
}
// tipo de codificação
$encodings = ["7 bits", "8 bits", "binaire", "base 64", "quoted-printable", "autre"];
print "Transfer-Encoding : " . $encodings[$part->encoding] . "\n";
// trata-se de uma mensagem?
if ($part->type === TYPEMESSAGE) {
// não vamos processar as subpartes desta mensagem
savePart($imapResource, $msgNumber, $sectionNumber, $infos, $infosMail);
} else {
// passamos para as eventuais subpartes
if (isset($part->parts)) {
for ($i = 1; $i <= count($part->parts); $i++) {
// uma nova parte da mensagem
$subpart = $part->parts[$i - 1];
// chamada recursiva — solicitamos o corpo da parte [$subpart]
getParts($imapResource, $msgNumber, $infos, $infosMail, $subpart, "$sectionNumber.$i");
}
} else {
// não há subpartes — então, salva-se o corpo da mensagem
savePart($imapResource, $msgNumber, $sectionNumber, $infos, $infosMail);
}
}
}
- linhas 33 e 45: a exibição do texto de uma parte [$imapResource, $msgNumber, $sectionNumber] do e-mail é agora substituída pelo seu salvamento em um arquivo;
A função [savePart] é a seguinte:
// salvamento de uma parte da mensagem
function savePart($imapResource, int $msgNumber, string $sectionNumber, array $infos, object $infosMail): void {
// pasta de salvamento
$outputDir = $infos["output-dir"] . "/message-$msgNumber";
// se a pasta não existir, ela é criada
if (!file_exists($outputDir)) {
mkdir($outputDir);
}
// estrutura da parte a ser salva
$struct = imap_bodystruct($imapResource, $msgNumber, $sectionNumber);
// tipo de documento
$type = $struct->type;
// subtipo de documento
$subtype = "";
if (isset($struct->subtype)) {
$subtype = strtolower($struct->subtype);
}
// analisa-se o tipo da parte
switch ($type) {
case TYPETEXT:
// caso de mensagem de texto: text/xxx
switch ($subtype) {
case plain:
saveText("$outputDir/message.txt", 0, imap_fetchBody($imapResource, $msgNumber, $sectionNumber), $infosMail, $struct);
break;
case HTML:
saveText("$outputDir/message.HTML", 1, imap_fetchBody($imapResource, $msgNumber, $sectionNumber), $infosMail, $struct);
break;
}
break;
default:
// outros casos — considera-se apenas os anexos
if (isset($struct->disposition)) {
$disposition = strtolower($struct->disposition);
if ($disposition === "attachment") {
// trata-se de um anexo — ele é salvo
saveAttachment($imapResource, $msgNumber, $sectionNumber, $outputDir, $struct);
}
} else {
// essa parte não será processada
print "Partie [$sectionNumber] ignorée\n";
}
break;
}
}
- linhas 3-8: criação da pasta de salvamento. Ela leva o número da mensagem cujas partes estão sendo analisadas;
- linha 10: a parte da mensagem a ser salva é definida de forma exclusiva pelos três parâmetros [$imapResource, $msgNumber, $sectionNumber]. Solicita-se a estrutura dessa parte com a função [imap_bodystruct];
- linha 12: recupera-se o tipo principal da parte da mensagem;
- linhas 13-17: recupera-se seu subtipo;
- linhas 20-30: processam-se os dois tipos de conteúdo: [text/plain] (linhas 23-25) e [text/HTML] (linhas 26-28). Os demais tipos [text/xx] são ignorados;
- linha 24: o texto da parte [text/plain] será salvo em um arquivo [message.txt];
- linha 27: o texto da parte [text/HTML] será salvo em um arquivo [message.HTML];
- linhas 31-43: trata-se o caso das partes cujo tipo principal não é [text];
- linha 35: considera-se apenas os anexos da mensagem;
- linha 37: estes são salvos em um arquivo por meio da função [saveAttachment];
Resumindo o código anterior:
- salva as partes [text/plain] e [text/HTML] por meio da função [saveText]. Essas partes representam o conteúdo do e-mail;
- salva os diversos anexos por meio da função [saveAttachment];
A função [saveText] é a seguinte:
// salvamento do texto [$text] da mensagem
function saveText(string $fileName, int $type, string $text, object $infosMail, object $struct) {
// preparação do texto a ser salvo
// $text está codificado — vamos decodificá-lo
switch ($struct->encoding) {
case ENCBASE64:
$text = base64_decode($text);
break;
case ENCQUOTEDPRINTABLE:
$text = quoted_printable_decode($text);
break;
}
// cabeçalhos da mensagem
// de
$from = "From: ";
foreach ($infosMail->from as $expéditeur) {
$from .= $expéditeur->mailbox . "@" . $expéditeur->host . ";";
}
// para
$to = "To: ";
foreach ($infosMail->to as $destinataire) {
$to .= $destinataire->mailbox . "@" . $destinataire->host . ";";
}
// assunto
$subject = "Subject: " . $infosMail->subject;
// criação do texto a ser gravado
switch ($type) {
case 0:
// text/plain
$contents = "$from\n$to\n$subject\n\n$text";
break;
case 1:
// text/HTML
$contents = "$from<br/>\n$to<br/>\n$subject<br/>\n<br/>\n$text";
break;
}
// criação do arquivo
print "sauvegarde d'un message dans [$fileName]\n";
// criação do arquivo
if (! file_put_contents($fileName, $contents)) {
// falha na criação do arquivo
print "Impossible de créer le fichier [$fileName]\n";
}
}
Comentários
- linha 1:
- [$fileName] é o nome do arquivo no qual o texto [$text] será salvo;
- [$type]: tem valor 0 para um arquivo de texto e 1 para um arquivo HTML;
- [$text]: é o texto a ser salvo. Mas é preciso primeiro decodificá-lo, pois está codificado;
- [$infosMail]: contém informações gerais sobre o e-mail. Vamos utilizar os campos [from, to, subject];
- [$struct]: é a estrutura que descreve a parte do e-mail que estamos salvando. Isso nos permitirá saber o tipo de codificação do texto a ser salvo;
- linhas 4-12: decodificamos o texto a ser salvo;
- linhas 13-25: recuperamos as informações [from, to, subject] do e-mail;
- linhas 27-36: dependendo do tipo (0 ou 1) do texto a ser salvo, constrói-se um texto simples (linha 30) ou um texto HTML (linha 34);
- linha 40: o texto completo é gravado no arquivo [$fileName];
Os anexos, por sua vez, são salvos com a seguinte função [saveAttachment]:
// salvamento de um anexo
function saveAttachment($imapResource, int $msgNumber, string $sectionNumber, string $outputDir, object $struct) {
// analisando a estrutura do anexo
// tentando recuperar o nome do arquivo no qual salvar o anexo
// esse nome está nos [dparameters] da estrutura
if (isset($struct->dparameters)) {
// recuperam-se os [dparameters]
$dparameters = $struct->dparameters;
$fileName = "";
// percorre-se a tabela dos [dparameters]
foreach ($dparameters as $dparameter) {
// cada [dparameter] é um objeto com dois atributos [attribute, value]
$attribute = strtolower($dparameter->attribute);
// o atributo [filename] corresponde ao nome do arquivo a ser criado
// nesse caso, o nome do arquivo está em [$dparameter->value]
if ($attribute === "filename") {
$fileName = $dparameter->value;
break;
}
}
// se não for encontrado um nome de arquivo, verifica-se o atributo [parameters] da estrutura
if ($fileName === "" && isset($struct->parameters)) {
//, recuperamos os [parameters]
$parameters = $struct->parameters;
foreach ($parameters as $parameter) {
// cada parâmetro é um dicionário com duas chaves [attribute, value]
$attribute = strtolower($parameter->attribute);
// se o atributo for [name], então [value] é o nome do arquivo
if ($attribute === "name") {
$fileName = $parameter->value;
// o nome do arquivo pode estar codificado
// por exemplo =?utf-8?Q?Cursos-Tutoriais-Serge-Tah=C3=A9-1568x268=2Ep
// recuperamos a codificação com uma expressão regular
$champs = [];
$match = preg_match("/=\?(.+?)\?/", $fileName, $champs);
// se houver correspondência, então decodifica-se o nome do arquivo
if ($match) {
$fileName = iconv_mime_decode($fileName, 0, $champs[1]);
}
break;
}
}
}
}
// se for encontrado um nome de arquivo, salva-se o anexo
if ($fileName !== "") {
// salvar o anexo
$fileName = "$outputDir/$fileName";
print "sauvegarde de l'attachement dans [$fileName]\n";
// criação do arquivo
if ($file = fopen($fileName, "w")) {
// recupera-se o texto codificado do anexo
$text = imap_fetchbody($imapResource, $msgNumber, $sectionNumber);
// o anexo está codificado — ele é decodificado
switch ($struct->encoding) {
// base 64
case ENCBASE64:
$text = base64_decode($text);
break;
// quoted printable
case ENCQUOTEDPRINTABLE:
$text = quoted_printable_decode($text);
break;
default:
// os demais casos são ignorados
break;
}
// gravação do texto no arquivo
fputs($file, $text);
// fechamento do arquivo
fclose($file);
} else {
// falha na criação do arquivo
print "L'attachement n'a pu être sauvegardé dans [$fileName]\n";
}
}
}
Comentários
- linha 2: a função [saveAttachment] aceita os seguintes parâmetros:
- [$imapResource, int $msgNumber, string $sectionNumber] define de forma exclusiva a parte IMAP a ser salva;
- [string $outputDir] é a pasta de salvamento;
- [object $struct] descreve a estrutura da parte da mensagem a ser salva;
- linhas 6-44: procura-se o nome do arquivo associado ao anexo. Esse mesmo nome de arquivo será usado para salvá-lo. O nome do arquivo do anexo encontra-se na tabela [$struct→dparameters] ou na tabela [$struct→parameters], ou mesmo em ambas;
- linhas 30-40: se o nome do arquivo contiver caracteres não codificados em 7 bits, ele foi codificado na tabela [quoted-printable]. Nesse caso, em [$struct→dparameters], o atributo é denominado [fileName*] em vez de [fileName]. Isso significa que ele não atendeu à condição da linha 16. O nome do arquivo é então procurado na tabela [$struct→parameters];
- linha 32: um exemplo de nome de arquivo codificado. Ele tem o seguinte formato: =?codage_original?codage_actuel?nom_encodé. Assim, o nome [=?utf-8?Q?Cours-Tutoriels-Serge-Tah=C3=A9-1568x268=2Ep] significa que o nome do arquivo era UTF-8 e que atualmente é [quoted-printable] (Q);
- linha 38: o nome do arquivo é decodificado com a função [iconv_mime_decode], que aceita aqui três parâmetros:
- a string a ser decodificada;
- deixar como 0 por padrão;
- o conjunto de caracteres a ser usado para representar a string decodificada. Esse parâmetro está presente na string a ser decodificada. Ele é obtido por meio de uma expressão regular nas linhas 34-35;
- linhas 45-75: o anexo é salvo em um arquivo com o nome encontrado;
Para testar o script [imap-02.php], enviamos primeiro um e-mail para [guest@localhost] com a seguinte configuração:
Portanto, há cinco anexos.
Lemos o e-mail enviado com [imap-02.php] e a seguinte configuração:
Os resultados da console são os seguintes:
------------Lecture de la boîte à lettres [{localhost:110/pop3}]
Connexion établie avec le serveur [{localhost:110/pop3}].
Il y a [1] messages dans la boîte à lettres [{localhost:110/pop3}]
Récupération de la liste des messages non lus de la boîte à lettres [{localhost:110/pop3}]
-----contenu de la partie n° [0]
Content-Type: MULTIPART/MIXED
Transfer-Encoding : 7 bits
-----contenu de la partie n° [1]
Content-Type: MULTIPART/ALTERNATIVE
Transfer-Encoding : 7 bits
-----contenu de la partie n° [1.1]
Content-Type: TEXT/PLAIN
Transfer-Encoding : quoted-printable
sauvegarde d'un message dans [output/localhost-pop3/message-1/message.txt]
-----contenu de la partie n° [1.2]
Content-Type: TEXT/HTML
Transfer-Encoding : quoted-printable
sauvegarde d'un message dans [output/localhost-pop3/message-1/message.HTML]
-----contenu de la partie n° [2]
Content-Type: APPLICATION/VND.OPENXMLFORMATS-OFFICEDOCUMENT.WORDPROCESSINGML.DOCUMENT
Transfer-Encoding : base 64
sauvegarde de l'attachement dans [output/localhost-pop3/message-1/Hello from SwiftMailer.docx]
-----contenu de la partie n° [3]
Content-Type: APPLICATION/PDF
Transfer-Encoding : base 64
sauvegarde de l'attachement dans [output/localhost-pop3/message-1/Hello from SwiftMailer.pdf]
-----contenu de la partie n° [4]
Content-Type: APPLICATION/VND.OASIS.OPENDOCUMENT.TEXT
Transfer-Encoding : base 64
sauvegarde de l'attachement dans [output/localhost-pop3/message-1/Hello from SwiftMailer.odt]
-----contenu de la partie n° [5]
Content-Type: UNKNOWN/PNG
Transfer-Encoding : base 64
sauvegarde de l'attachement dans [output/localhost-pop3/message-1/Cours-Tutoriels-Serge-Tahé-1568x268.png]
-----contenu de la partie n° [6]
Content-Type: MESSAGE/RFC822
Transfer-Encoding : base 64
sauvegarde de l'attachement dans [output/localhost-pop3/message-1/test-localhost.eml]
Fermeture de la connexion réussie.
Done.
Encontramos os arquivos salvos na pasta [output/localhost-pop3/message-N]:

16.6.6. Cliente POP3 / IMAP com a biblioteca [php-mime-mail-parser]
No script anterior [imap-02.php], conseguimos salvar:
- os conteúdos [text/plain] e [text/HTML] do e-mail;
- os anexos do e-mail;
Para um anexo do tipo [message/rfc822], também salvamos o conteúdo do anexo. No entanto, esse tipo de anexo é, por si só, um e-mail que, por sua vez, possui os conteúdos [text/plain] e [text/HTML], bem como anexos. Assim, podemos nos deparar com a seguinte situação:
- um [mail 1] cuja estrutura é análoga à de um anexo do tipo [message/rfc822];
- um [mail 2] anexado ao e-mail 1;
- um [mail 3] anexado ao e-mail 2;
- etc…
O script [imap-02.php] salva o conteúdo de [mail 1] (textos e anexos). Ele salva [mail 2] como documento anexado, mas para por aí. Ele não tenta analisar o [mail 2] para extrair os textos e anexos. Seria de se esperar que bastasse aplicar ao [mail 2] o que foi feito para o [mail 1]. Uma chamada recursiva ao método que processava [mail 1] poderia, então, ser suficiente para obter o conteúdo de todos os e-mails aninhados uns nos outros. Infelizmente, as partes de [mail 2] são numeradas com uma lógica diferente daquela usada para [mail 1], o que impede o uso do mesmo algoritmo nos dois casos, a menos que se utilize uma lógica bastante complexa para calcular os números das partes de um e-mail, independentemente da posição dele no conjunto de e-mails aninhados.
O script [imap-02.php] já era complexo. Para evitar torná-lo ainda mais complexo para gerenciar os conteúdos dos e-mails aninhados, vamos utilizar a biblioteca [php-mime-mail-parser] disponível no GitHub (maio de 2019) em URL [https://github.com/php-mime-mail-parser/php-mime-mail-parser] e escrita por Vincent Dauce.
16.6.6.1. Instalação da biblioteca [php-mime-mail-parser]
A página de apresentação da biblioteca indica como instalá-la no Windows:

Existem duas etapas para o OS no Windows:
télécharger une DLL ;
modifier le fichier [php.ini] qui configure PHP ;
LA DLL da biblioteca [mailparse] está disponível no URL [http://pecl.php.net/package/mailparse] (maio de 2019);

- no [2], escolha a versão mais recente e estável da biblioteca;

- em [3], escolha a versão do PHP que você utiliza (neste documento é o PHP 7.2);
- em [4], escolha a versão do seu OS para Windows (aqui é um Windows de 64 bits). Escolhe-se a versão [Thread Safe];
Para saber a versão do PHP baixada com o Laragon, abra um [Terminal] a partir da janela do Laragon e digite o seguinte comando:
C:\myprograms\laragon-lite\www
λ php -v
PHP 7.2.11 (cli) (built: Oct 10 2018 02:04:07) ( ZTS MSVC15 (Visual C++ 2017) x64 )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
A versão do PHP 7.2.11 é indicada na linha 3. A mesma linha informa a versão do Windows usada para a compilação (32 ou 74 bits).
Depois de obter o DLL, é preciso copiá-lo para a pasta [<laragon>/bin/php/<version-php>/ext] [5]:

Feito isso, é preciso ativar essa extensão no arquivo [php.ini], que configura o PHP (veja o parágrafo com o link):

É provável que a linha [7] não exista e que você precise adicioná-la manualmente.
Depois que a extensão estiver ativada, é possível verificar sua validade digitando o seguinte comando em um terminal do Laragon:
C:\myprograms\laragon-lite\www
λ php --ini
Configuration File (php.ini) Path: C:\windows
Loaded Configuration File: C:\myprograms\laragon-lite\bin\php\php-7.2.11-Win32-VC15-x64\php.ini
Scan for additional .ini files in: (none)
Additional .ini files parsed: (none)
O comando [php –-ini] carrega o arquivo de configuração da linha 4. Em seguida, ele carregará os DLL de todas as extensões ativadas no [php.ini]. Se alguma delas estiver incorreta, isso será sinalizado. Assim, a validade do DLL adicionado ao [php_mailparse.dll] será verificada. Ele pode ser declarado incorreto por diversos motivos, sendo os mais frequentes os seguintes:
- você baixou um DLL que não corresponde à versão do PHP utilizada;
- você baixou um arquivo DLL de 32 bits, mas possui um PHP de 64 bits, ou vice-versa;
Depois que a extensão estiver ativada e verificada, é possível prosseguir com a instalação da biblioteca [php-mime-mail-parser]:

O comando [8] deve ser digitado em um terminal do Laragon (consulte o parágrafo com o link):

- no [1], verifique se você está na pasta [<laragon>/www];
- em [2], o comando de instalação da biblioteca [php-mime-mail-parser];
- em [3], nada foi instalado aqui, pois a biblioteca [php-mime-mail-parser] já estava instalada;
A instalação da biblioteca [php-mime-mail-parser] é feita na pasta [<laragon>/www/vendor]:


- em [2-3], os arquivos-fonte da biblioteca [php-mime-mail-parser];
Agora que o ambiente de trabalho foi instalado, podemos passar à criação do script [imap-03.php].
16.6.6.2. O script [imap-03.php]
O script [imap-03.php] utiliza o mesmo arquivo de configuração [config-imap-01.json] que os scripts anteriores:
O script [imap-03.php] é o seguinte:
<?php
// cliente IMAP (Internet Message Access Protocol) que permite ler e-mails
// escrito com a biblioteca [php-mime-mail-parser]
// disponível noURL [https://github.com/php-mime-mail-parser/php-mime-mail-parser] (maio de 2019)
//
// estrito cumprimento dos tipos declarados dos parâmetros das funções
declare (strict_types=1);
// gestão de erros
error_reporting(E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// dependências
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
// parâmetros de leitura de e-mails
const CONFIG_FILE_NAME = "config-imap-01.json";
// recuperação da configuração
if (!file_exists(CONFIG_FILE_NAME)) {
print "Le fichier de configuration " . CONFIG_FILE_NAME . " n'existe pas";
exit;
}
$mailboxes = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);
// leitura das caixas de correio
foreach ($mailboxes as $name => $infos) {
// acompanhamento
print "------------Lecture de la boîte à lettres [$name]\n";
// leitura da caixa de correio
readmailbox($name, $infos);
}
// fim
exit;
Comentários
- linhas 18-23: o conteúdo do arquivo de configuração é inserido no dicionário [$mailboxes];
- linhas 26-31: cada caixa de correio é lida pela função [readmailbox] (linha 30). Essa função, na verdade, lê as mensagens não lidas da caixa de correio. Uma caixa de correio corresponde ao endereço de e-mail de um determinado usuário;
A função [readmailbox] é a seguinte:
function readmailbox(string $name, array $infos): void {
// estabelecendo conexão
$imapResource = imap_open($name, $infos["user"], $infos["password"]);
if (!$imapResource) {
// falha
print "La connexion au serveur [$name] a échoué : " . imap_last_error() . "\n";
exit;
}
// Conexão estabelecida
print "Connexion établie avec le serveur [$name].\n";
// total de mensagens na caixa de correio
$nbmsg = imap_num_msg($imapResource);
print "Il y a [$nbmsg] messages dans la boîte à lettres [$name]\n";
// mensagens não lidas na caixa de correio atual
if ($nbmsg > 0) {
print "Récupération de la liste des messages non lus de la boîte à lettres [$name]\n";
$msgNumbers = imap_search($imapResource, 'UNSEEN');
if ($msgNumbers === FALSE) {
print "Il n'y a pas de nouveaux messages dans la boîte à lettres [$name]\n";
} else {
// navegando pela lista de mensagens não lidas
foreach ($msgNumbers as $msgNumber) {
print "---message n° [$msgNumber]\n";
// recuperando o corpo da mensagem nº $msgNumber
getMailBody($imapResource, $msgNumber, $infos);
// se o protocolo for POP3, a mensagem é excluída após ser recuperada
$pop3 = $infos["pop3"];
if ($pop3 !== NULL) {
// marca-se a mensagem como “a ser excluída”
imap_delete($imapResource, $msgNumber);
}
}
// fim da leitura das mensagens não lidas
if ($pop3 !== NULL) {
// as mensagens marcadas como “a serem excluídas” são excluídas
imap_expunge($imapResource);
}
}
}
// encerramento da conexão
$imapClose = imap_close($imapResource);
if (!$imapClose) {
// falha
print "La fermeture de la connexion a échoué : " . imap_last_error() . "\n";
} else {
// sucesso
print "Fermeture de la connexion réussie.\n";
}
}
Comentários
O código da função [readmailbox] é o mesmo dos scripts anteriores.
A função [getMailBody] (linha 25), que analisa o corpo de uma mensagem (conteúdo + anexos), é a seguinte:
// análise do corpo da mensagem
function getMailBody($imapResource, int $msgNumber, array $infos): void {
// recupera-se o texto completo da mensagem
$text = imap_fetchbody($imapResource, $msgNumber, "");
if ($text === FALSE) {
print "Le corps du message [$msgNumber] n'a pu être récupéré";
return;
}
// criação de um analisador que irá analisar o texto da mensagem
$parser = (new PhpMimeMailParser\Parser())->setText($text);
// recuperação das diferentes partes da mensagem
$outputDir = $infos["output-dir"] . "/message-$msgNumber";
getParts($parser, $msgNumber, $outputDir);
}
Comentários
- linha 2: a função [getMailBody] aceita três parâmetros:
- [$imapResource]: o recurso IMAP ao qual estamos conectados;
- [$msgNumber]: o número da mensagem (na caixa de correio) a ser processada;
- [$infos]: informações diversas sobre a caixa de correio processada;
- linha 4: recupera-se a mensagem n.º [$msgNumber] na íntegra;
- linhas 5-8: caso em que o conteúdo da mensagem não pôde ser recuperado;
- linha 10: começa-se a utilizar a biblioteca [php-mime-mail-parser]. O objeto [$parser] será encarregado de analisar o texto da mensagem;
- linha 12: [$outputDir] será a pasta na qual serão salvos os conteúdos de texto e os anexos da mensagem nº [$msgNumber];
- linha 13: solicita-se à função [getParts] que identifique as diferentes partes (conteúdo de texto e anexos) da mensagem nº [$msgNumber] e as salve na pasta [$outputDir];
A função [getParts] é a seguinte:
// recuperação das diferentes partes de uma mensagem
function getParts(PhpMimeMailParser\Parser $parser, int $msgNumber, string $outputDir): void {
// cria-se a pasta de armazenamento da mensagem, se necessário
if (!file_exists($outputDir)) {
if (!mkdir($outputDir)) {
print "Le dossier [$outputDir] n'a pu être créé\n";
return;
}
}
// recuperam-se os cabeçalhos da mensagem
$arrayHeaders = $parser->getHeaders();
// salva-se as mensagens de texto
$parts = $parser->getInlineParts("text");
for ($i = 1; $i <= count($parts); $i++) {
print "-- Sauvegarde d'un message de type [text/plain]\n";
saveMessage($parts[$i - 1], 0, $arrayHeaders, "$outputDir/message_$i.txt");
}
// salva-se as mensagens em HTML
$parts = $parser->getInlineParts("html");
for ($i = 1; $i <= count($parts); $i++) {
print "-- Sauvegarde d'un message de type [text/html]\n";
saveMessage($parts[$i - 1], 1, $arrayHeaders, "$outputDir/message_$i.html");
}
// recupera-se os anexos da mensagem
$attachments = $parser->getAttachments();
// número do anexo
$iAttachment = 0;
// navega-se pela lista de anexos
foreach ($attachments as $attachment) {
// tipo de anexo
$fileType = $attachment->getContentType();
print "-- Sauvegarde d'un attachement de type [$fileType] dans le fichier [$outputDir/{$attachment->getFilename()}]\n";
// salva-se o anexo
try {
$attachment->save($outputDir, PhpMimeMailParser\Parser::ATTACHMENT_DUPLICATE_SUFFIX);
} catch (Exception $e) {
print "L'attachement n'a pu être sauvegardé : " . $e->getMessage() . "\n";
}
// caso específico do tipo message/rfc822
if ($fileType === "message/rfc822") {
// o anexo é, ele próprio, uma mensagem — vamos analisá-lo também
// mudamos o diretório de salvamento
$iAttachment++;
$outputDir = $outputDir . "/rfc822-$iAttachment";
// altera-se o conteúdo a ser analisado
$parser->setText($attachment->getContent());
// analisamos a mensagem de forma recursiva
getParts($parser, $msgNumber, $outputDir);
}
}
}
Comentários
- linha 2: a função [getParts] aceita três parâmetros:
- um analisador [$parser] ao qual foi transmitido o texto completo da mensagem a ser analisada;
- [$msgNumber] é o número da mensagem que está sendo analisada;
- [$outputDir] é a pasta na qual o conteúdo e os anexos da mensagem devem ser salvos;
- linhas 4-9: criação da pasta [$outputDir];
- linha 11: recuperam-se os cabeçalhos da mensagem em análise (de, para, assunto…);
- linha 13: recuperam-se as partes do e-mail com o tipo [text/plain]. Recupera-se uma tabela;
- linhas 14-17: salvam-se todos os elementos da tabela recuperada, atribuindo a cada um um nome de arquivo diferente;
- linha 19: recuperam-se as partes do e-mail com o tipo [text/html]. Recupera-se uma matriz;
- linhas 20-23: salvamos todos os elementos da matriz recuperada, atribuindo a cada um um nome de arquivo diferente;
- linha 25: recupera-se a lista de anexos da mensagem analisada;
- linha 29: percorre-se essa lista;
- linha 24: recupera-se o tipo do anexo (atributo Content-Type);
- linhas 34-38: o anexo é salvo na pasta [$outputDir]. O segundo parâmetro, [PhpMimeMailParser\Parser::ATTACHMENT_DUPLICATE_SUFFIX], é uma estratégia de nomenclatura para os anexos. Se [$attachment→getFilename()] for igual a X e o arquivo X já existir, então a biblioteca [php-mime-mail-parser] tenta os nomes [X_1], [X_2], etc., até encontrar um nome de arquivo que ainda não exista;
- linha 40: verifica-se se o anexo é um e-mail;
- linhas 41-48: se for o caso, esse e-mail é analisado por sua vez para extrair conteúdos e anexos;
- linha 44: se [$outputDir] for igual a X e, entre os anexos da mensagem analisada, houver dois e-mails, então o primeiro será salvo na pasta [$outputDir/rfc822-1] e o segundo na pasta [$outputDir/rfc822-2];
- linha 46: o conteúdo do e-mail anexado passa a ser o novo texto a ser analisado;
- linha 48: chama-se a função [getParts] de forma recursiva para analisar o novo texto;
A função [saveMessage] salva o conteúdo de texto da mensagem a ser analisada:
// salvamento de uma mensagem de texto
function saveMessage(string $text, int $type, array $arrayHeaders, string $filename): void {
// conteúdo a ser salvo
$contents = "";
// adição dos cabeçalhos
switch ($type) {
case 0:
// text/plain
foreach ($arrayHeaders as $key => $value) {
$contents .= "$key: $value\n";
}
$contents .= "\n";
break;
case 1:
// text/HTML
foreach ($arrayHeaders as $key => $value) {
$contents .= "$key: $value<br/>\n";
}
$contents .= "<br/>\n";
}
// adição do texto da mensagem
$contents .= $text;
// salvar tudo
if (!file_put_contents($filename, $contents)) {
// falha
print "Le message n'a pu être sauvegardé dans le fichier [$filename]\n";
} else {
// sucesso
print "Le message a été sauvegardé dans le fichier [$filename]\n";
}
}
Comentários
- a função [saveMessage] aceita os seguintes parâmetros:
- [$text]: o texto a ser salvo;
- [$type]: o tipo de texto (0: text/plain, 1: text/HTML);
- [$arrayHeaders]: os cabeçalhos da mensagem analisada;
- [$filename]: o nome do arquivo no qual [$text] deve ser salvo;
- linha 4: [$contents] representará todo o texto a ser salvo;
- linhas 6-20: primeiro serão salvos todos os cabeçalhos da mensagem (from, to, subject…);
- linhas 16-19: no caso de um texto HTML, cada linha termina com a tag <br/> para que cada cabeçalho apareça sozinho em sua própria linha no navegador;
- linha 22: adiciona-se aos cabeçalhos o texto da mensagem a ser salvo;
- linhas 24-30: o conjunto é salvo no arquivo [$filename];
O uso da biblioteca [php-mime-mail-parser] facilita consideravelmente a criação do script de leitura de e-mails.
O script [smtp-02.php] é usado para enviar um e-mail ao usuário [guest@localhost] com a seguinte configuração:
- linhas 11-15: há cinco anexos;
- linha 15: [test-localhost-2.eml] é um e-mail estruturado da seguinte forma:
- [test-localhost-2.eml] contém 4 anexos (os mesmos das linhas 11 a 14) e um e-mail anexado;
- o e-mail anexado a [test-localhost-2.eml] contém 4 anexos (os mesmos das linhas 11 a 14);
O script [imap-03.php] é usado para ler a caixa de correio do usuário [guest@localhost] com a seguinte configuração:
Após a execução, a estrutura de pastas do [output/localhost-pop3] ficou da seguinte forma:

- em [1], os 5 anexos do e-mail recebido por [guest@localhost];
- em [2], os 5 anexos do e-mail [test-localhost-2.eml] de [1];
- em [3], os 4 anexos do e-mail [test-localhost.eml] enviado por [2];
As mensagens exibidas no console são as seguintes:
------------Lecture de la boîte à lettres [{localhost:110/pop3}]
Connexion établie avec le serveur [{localhost:110/pop3}].
Il y a [1] messages dans la boîte à lettres [{localhost:110/pop3}]
Récupération de la liste des messages non lus de la boîte à lettres [{localhost:110/pop3}]
---message n° [1]
-- Sauvegarde d'un message de type [text/plain]
Le message a été sauvegardé dans le fichier [output/localhost-pop3/message-1/message_1.txt]
-- Sauvegarde d'un message de type [text/html]
Le message a été sauvegardé dans le fichier [output/localhost-pop3/message-1/message_1.html]
-- Sauvegarde d'un attachement de type [application/vnd.openxmlformats-officedocument.wordprocessingml.document] dans le fichier [output/localhost-pop3/message-1/Hello from SwiftMailer.docx]
-- Sauvegarde d'un attachement de type [application/pdf] dans le fichier [output/localhost-pop3/message-1/Hello from SwiftMailer.pdf]
-- Sauvegarde d'un attachement de type [application/vnd.oasis.opendocument.text] dans le fichier [output/localhost-pop3/message-1/Hello from SwiftMailer.odt]
-- Sauvegarde d'un attachement de type [image/png] dans le fichier [output/localhost-pop3/message-1/Cours-Tutoriels-Serge-Tahé-1568x268.png]
-- Sauvegarde d'un attachement de type [message/rfc822] dans le fichier [output/localhost-pop3/message-1/test-localhost-2.eml]
-- Sauvegarde d'un message de type [text/plain]
Le message a été sauvegardé dans le fichier [output/localhost-pop3/message-1/rfc822-1/message_1.txt]
-- Sauvegarde d'un message de type [text/html]
Le message a été sauvegardé dans le fichier [output/localhost-pop3/message-1/rfc822-1/message_1.html]
-- Sauvegarde d'un attachement de type [application/vnd.openxmlformats-officedocument.wordprocessingml.document] dans le fichier [output/localhost-pop3/message-1/rfc822-1/Hello from SwiftMailer.docx]
-- Sauvegarde d'un attachement de type [application/pdf] dans le fichier [output/localhost-pop3/message-1/rfc822-1/Hello from SwiftMailer.pdf]
-- Sauvegarde d'un attachement de type [application/vnd.oasis.opendocument.text] dans le fichier [output/localhost-pop3/message-1/rfc822-1/Hello from SwiftMailer.odt]
-- Sauvegarde d'un attachement de type [image/png] dans le fichier [output/localhost-pop3/message-1/rfc822-1/Cours-Tutoriels-Serge-Tahé-1568x268.png]
-- Sauvegarde d'un attachement de type [message/rfc822] dans le fichier [output/localhost-pop3/message-1/rfc822-1/test-localhost.eml]
-- Sauvegarde d'un message de type [text/plain]
Le message a été sauvegardé dans le fichier [output/localhost-pop3/message-1/rfc822-1/rfc822-1/message_1.txt]
-- Sauvegarde d'un message de type [text/html]
Le message a été sauvegardé dans le fichier [output/localhost-pop3/message-1/rfc822-1/rfc822-1/message_1.html]
-- Sauvegarde d'un attachement de type [application/vnd.openxmlformats-officedocument.wordprocessingml.document] dans le fichier [output/localhost-pop3/message-1/rfc822-1/rfc822-1/Hello from SwiftMailer.docx]
-- Sauvegarde d'un attachement de type [application/pdf] dans le fichier [output/localhost-pop3/message-1/rfc822-1/rfc822-1/Hello from SwiftMailer.pdf]
-- Sauvegarde d'un attachement de type [application/vnd.oasis.opendocument.text] dans le fichier [output/localhost-pop3/message-1/rfc822-1/rfc822-1/Hello from SwiftMailer.odt]
-- Sauvegarde d'un attachement de type [image/png] dans le fichier [output/localhost-pop3/message-1/rfc822-1/rfc822-1/Cours-Tutoriels-Serge-Tahé-1568x268.png]
Fermeture de la connexion réussie.
Se visualizarmos o [message_1.HTML] do [3] em um navegador, obtemos o seguinte:
