Skip to content

16. Netwerkfuncties

We gaan nu in op de netwerkfuncties van PHP, waarmee we kunnen programmeren in TCP / IP (Transfer Control Protocol / Internet Protocol).

Image

16.1. De basisprincipes van internetprogrammering

16.1.1. Algemeen

Laten we eens kijken naar de communicatie tussen twee op afstand gelegen computers A en B:

Image

Wanneer een toepassing AppA op computer A wil communiceren met een toepassing AppB op computer B via het internet, moet deze verschillende gegevens kennen:

  • het IP-adres (Internet Protocol) of de naam van computer B;
  • het poortnummer waarmee de applicatie AppB werkt. Computer B kan namelijk talrijke applicaties ondersteunen die via het internet werken. Wanneer deze computer informatie uit het netwerk ontvangt, moet hij weten voor welke applicatie deze informatie bestemd is. De applicaties op machine B hebben toegang tot het netwerk via poorten, ook wel communicatiepoorten genoemd. Deze informatie is opgenomen in het pakket dat door machine B wordt ontvangen, zodat het aan de juiste applicatie kan worden afgeleverd;
  • de communicatieprotocollen die door machine B worden begrepen. In onze studie zullen we uitsluitend de protocollen TCP-IP gebruiken;
  • het dialoogprotocol dat door de applicatie AppB wordt geaccepteerd. Machine A en machine B gaan namelijk met elkaar ‘praten’. Wat ze gaan zeggen, wordt ingekapseld in de protocollen TCP-IP. Wanneer echter aan het einde van de keten de applicatie AppB de door de applicatie AppA verzonden informatie ontvangt, moet deze in staat zijn om deze te interpreteren. Dit is vergelijkbaar met de situatie waarin twee personen, A en B, via de telefoon communiceren: hun dialoog wordt via de telefoon overgebracht. De spraak wordt door telefoon A in de vorm van signalen gecodeerd, via telefoonlijnen overgebracht en komt bij telefoon B aan om daar te worden gedecodeerd. Persoon B hoort dan spraak. Hier komt het begrip dialoogprotocol om de hoek kijken: als A Frans spreekt en B deze taal niet begrijpt, kunnen A en B geen zinvolle dialoog voeren;

Daarom moeten de twee communicerende applicaties het eens zijn over het type dialoog dat ze gaan hanteren. De dialoog met een dienst ftp is bijvoorbeeld niet dezelfde als die met een dienst pop: deze twee diensten accepteren niet dezelfde opdrachten. Ze hebben een verschillend dialoogprotocol;

16.1.2. De kenmerken van het protocol TCP

We zullen hier alleen netwerkcommunicatie behandelen die gebruikmaakt van het transportprotocol TCP, waarvan hier de belangrijkste kenmerken volgen:

  • het proces dat gegevens wil verzenden, brengt eerst een verbinding tot stand met het proces dat de te verzenden informatie moet ontvangen. Deze verbinding wordt tot stand gebracht tussen een poort van de verzendende machine en een poort van de ontvangende machine. Tussen de twee poorten ontstaat zo een virtueel pad dat uitsluitend is gereserveerd voor de twee processen die de verbinding tot stand hebben gebracht;
  • alle pakketten die door het bronproces worden verzonden, volgen dit virtuele pad en komen aan in de volgorde waarin ze zijn verzonden;
  • de verzonden informatie heeft een continu karakter. Het verzendende proces verstuurt informatie in zijn eigen tempo. Deze informatie wordt niet noodzakelijkerwijs onmiddellijk verzonden: het protocol TCP wacht tot er voldoende informatie is verzameld om deze te verzenden. De informatie wordt opgeslagen in een structuur die het TCP-segment wordt genoemd. Zodra dit segment gevuld is, wordt het doorgestuurd naar de laag IP, waar het wordt ingekapseld in een pakket IP;
  • elk segment dat via het protocol TCP wordt verzonden, is genummerd. Het ontvangende protocol TCP controleert of het de segmenten in de juiste volgorde ontvangt. Voor elk correct ontvangen segment stuurt het een ontvangstbevestiging naar de afzender;
  • wanneer de afzender deze ontvangt, meldt hij dit aan het verzendende proces. Dit proces weet dan dat een segment goed is aangekomen;
  • als het protocol TCP, dat een segment heeft verzonden, na een bepaalde tijd geen ontvangstbevestiging ontvangt, verzendt het het betreffende segment opnieuw, waardoor de kwaliteit van de dienst voor het doorsturen van informatie wordt gewaarborgd;
  • de virtuele verbinding die tot stand is gebracht tussen de twee processen die met elkaar communiceren, is full-duplex: dit betekent dat de informatie in beide richtingen kan worden verzonden. Zo kan het bestemmingsproces ontvangstbevestigingen versturen terwijl het bronproces doorgaat met het verzenden van informatie. Hierdoor kan bijvoorbeeld het bronprotocol TCP meerdere segmenten verzenden zonder op een ontvangstbevestiging te wachten. Als het na verloop van tijd vaststelt dat het geen ontvangstbevestiging heeft ontvangen voor een bepaald segment met nummer n, zal het de verzending van de segmenten vanaf dat punt hervatten;

16.1.3. De client-serverrelatie

Vaak verloopt de communicatie via internet asymmetrisch: machine A initieert een verbinding om een dienst aan te vragen bij machine B; daarbij geeft hij aan dat hij een verbinding wil openen met de dienst SB1 van machine B. Deze laatste accepteert of weigert het verzoek. Als machine B de verbinding accepteert, kan machine A verzoeken sturen naar de dienst SB1. Deze verzoeken moeten voldoen aan het communicatieprotocol dat door de dienst SB1 wordt ondersteund. Zo ontstaat een vraag-antwoorddialoog tussen machine A, die we de clientmachine noemen, en machine B, die we de servermachine noemen. Een van beide partners zal de verbinding verbreken.

16.1.4. Architectuur van een client

De architectuur van een netwerkprogramma dat gebruikmaakt van de diensten van een servertoepassing ziet er als volgt uit:

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. Architectuur van een server

De architectuur van een programma dat diensten aanbiedt, ziet er als volgt uit:

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

Het serverprogramma behandelt het eerste verbindingsverzoek van een klant anders dan zijn latere verzoeken om een dienst te verkrijgen. Het programma levert de dienst zelf niet. Als het dat wel zou doen, zou het gedurende de looptijd van de dienst niet meer luisteren naar verbindingsverzoeken en zouden klanten dan niet bediend worden. Het gaat daarom anders te werk: zodra een verbindingsverzoek wordt ontvangen op de luisterpoort en vervolgens wordt geaccepteerd, maakt de server een taak aan die verantwoordelijk is voor het leveren van de door de client gevraagde dienst. Deze dienst wordt geleverd op een andere poort van de server, de zogenaamde servicepoort. Zo kunnen meerdere clients tegelijkertijd worden bediend.

Een servicetaken heeft de volgende structuur:

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. Ontdek de communicatieprotocollen van het internet

16.2.1. Inleiding

Wanneer een client verbinding heeft gemaakt met een server, ontstaat er een dialoog tussen beide. De aard van deze dialoog vormt het zogenaamde communicatieprotocol van de server. Tot de meest gangbare protocollen op het internet behoren de volgende:

  • HTTP: HyperText Transfer Protocol – het protocol voor de communicatie met een webserver (HTTP-server);
  • SMTP: Simple Mail Transfer Protocol – het protocol voor communicatie met een e-mailverzendingsserver (server SMTP);
  • POP: Post Office Protocol – het protocol voor communicatie met een e-mailopslagserver (server POP). Dit dient om ontvangen e-mails op te halen en niet om ze te verzenden;
  • IMAP: Internet Message Access Protocol – het protocol voor communicatie met een e-mailopslagserver (server IMAP). Dit protocol heeft het oudere protocol POP geleidelijk vervangen;
  • FTP: File Transfer Protocol – het protocol voor communicatie met een server voor bestandsopslag (server FTP);

Al deze protocollen hebben als bijzonderheid dat het tekstregelprotocollen zijn: de client en de server wisselen tekstregels uit. Als men een client heeft die in staat is om:

  • een verbinding tot stand te brengen met een TCP-server;
  • de tekstregels die de server naar de client stuurt, op de console weer te geven;
  • de tekstregels die een gebruiker via het toetsenbord invoert naar de server sturen;

dan is het mogelijk om te communiceren met een server TCP die een tekstregelprotocol gebruikt, mits men de regels van dit protocol kent.

16.2.2. Hulpprogramma's TCP

Image

In de bij dit document behorende codes bevinden zich twee communicatiehulpprogramma's TCP:

  • [RawTcpClient] maakt het mogelijk verbinding te maken met poort P van een server S;
  • met [RawTcpServer] kan een server worden aangemaakt die op poort P op clients wacht;

De server TCP [RawTcpServer]wordt aangeroepen met de syntaxis [RawTcpServeur port] om een dienst TCP aan te maken op poort [port] van de lokale machine (de computer waarop u werkt):

  • de server kan meerdere clients tegelijkertijd bedienen;
  • de server voert de commando’s uit die de gebruiker via het toetsenbord invoert. Dit zijn de volgende:
    • list: geeft een overzicht van de clients die momenteel met de server zijn verbonden. Deze worden weergegeven in de vorm [id=x-nom=y]. Het veld [id] dient om de clients te identificeren;
    • send x [texte]: verstuurt tekst naar client nr. x (id=x). De vierkante haakjes [] worden niet meegestuurd. Ze zijn nodig in het commando. Ze dienen om de naar de client verzonden tekst visueel af te bakenen;
    • close x: verbreekt de verbinding met klant nr. x;
    • quit: sluit alle verbindingen en stopt de dienst;
  • de regels die door de klant naar de server worden verzonden, worden op de console weergegeven;
  • alle communicatie wordt gelogd in een tekstbestand met de naam [machine-portService.txt], waarbij
    • [machine] de naam is van de machine waarop de code wordt uitgevoerd;
    • [port] de servicepoort is die de verzoeken van de client beantwoordt;

De client TCP [RawTcpClient] wordt aangeroepen met de syntaxis [RawTcpClient serveur port] om verbinding te maken met poort [port] van de server [serveur]:

  • de regels die de gebruiker via het toetsenbord invoert, worden naar de server verzonden;
  • de door de server verzonden regels worden weergegeven op de console;
  • de volledige communicatie wordt vastgelegd in een tekstbestand met de naam [serveur-port.txt];

Laten we eens een voorbeeld bekijken. We openen twee Windows-opdrachtvensters en gaan in elk daarvan naar de map met hulpprogramma’s. In een van de vensters starten we de server [RawTcpServer] op poort 100:

Image

  • in [1] bevinden we ons in de map met hulpprogramma’s;
  • in [2] starten we de server TCP op poort 100;
  • in [3] wacht de server op een client TCP;
  • in [4] wacht de server op een commando dat de gebruiker via het toetsenbord invoert;

In het andere opdrachtvenster starten we de client TCP:

Image

  • in [5] bevinden we ons in de map met hulpprogramma’s;
  • in [6] starten we de client TCP: we geven hem de opdracht om verbinding te maken met poort 100 van de lokale machine (die waarmee u werkt);
  • in [7] is de client erin geslaagd verbinding te maken met de server. We geven de gegevens van de client weer: deze bevindt zich op de machine [DESKTOP-528I5CU] (in dit voorbeeld de lokale machine) en gebruikt poort [50405] om met de server te communiceren:
  • in [8] wacht de client op een commando dat de gebruiker via het toetsenbord invoert;

Laten we teruggaan naar het servervenster. De inhoud ervan is veranderd:

Image

  • naar [9], er is een client gedetecteerd. De server heeft hem nummer 1 toegewezen. De server heeft de externe client (computer en poort) correct geïdentificeerd;
  • in [10] wacht de server weer op een nieuwe client;

Laten we teruggaan naar het venster van de client en een commando naar de server sturen:

Image

  • in [11] is de opdracht naar de server verzonden;

Laten we teruggaan naar het servervenster. De inhoud is veranderd:

Image

  • in [12], tussen haakjes, het bericht dat de server heeft ontvangen;

Laten we een antwoord naar de client sturen:

Image

  • naar [13], het antwoord dat naar de klant is verzonden. Alleen de tekst tussen de haakjes wordt verzonden, niet de haakjes zelf;

Laten we teruggaan naar het venster van de klant:

Image

  • in [14], het antwoord dat de klant heeft ontvangen. De ontvangen tekst is de tekst tussen de vierkante haakjes;

Laten we teruggaan naar het servervenster om andere commando’s te bekijken:

Image

  • naar [15], we vragen de lijst met clients op;
  • in [16], het antwoord;
  • met [17] verbreken we de verbinding met klant nr. 1;
  • in [18], de bevestiging van de server;
  • in [19] stoppen we de server;
  • in [20], de bevestiging van de server;

Laten we teruggaan naar het clientvenster:

Image

  • in [21] heeft de client het einde van de dienst gedetecteerd;

Er zijn twee logbestanden aangemaakt, één voor de server en één voor de client:

Image

  • in [25], de logbestanden van de server: de bestandsnaam is de naam van de client [machine-port];
  • en [26], de logbestanden van de client: de bestandsnaam is de naam van de server [machine-port];

De serverlogs zijn als volgt:

<-- [hello from client]
--> [hello from server]

De logbestanden van de client zijn als volgt:

--> [hello from client]
<-- [hello from server]

16.3. De naam of het adres IP van een computer op het internet opvragen

Image

Computers op het internet worden geïdentificeerd aan de hand van een adres (IP, IPv4 of IPv6) en meestal ook aan de hand van een naam. Maar uiteindelijk wordt alleen het adres gebruikt. Het is dus soms nodig om het adres IP te kennen van een computer die bij zijn naam wordt aangeduid.

Het script [ip-01.php] luidt als volgt:


<?php

// strikte naleving van de gedeclareerde typen van functieparameters
declare (strict_types=1);
//
// foutafhandeling
error_reporting(E_ALL & E_STRICT);
ini_set("display_errors", "on");
//
// constanten
$HOTES = array("istia.univ-angers.fr", "www.univ-angers.fr", "www.ibm.com", "localhost", "", "xx");
// adressen IP en namen van de machines van $HOTES
for ($i = 0; $i < count($HOTES); $i++) {
  getIPandName($HOTES[$i]);
}
// einde
print "Terminé\n";
exit;

//------------------------------------------------
function getIPandName(string $nomMachine): void {
  //$nomMachine: naam van de machine waarvan het adres IP wordt gevraagd
  //
  // nomMachine-->adres IP
  $ip = gethostbyname($nomMachine);
  print "---------------\n";
  if ($ip !== $nomMachine) {
    print "ip[$nomMachine]=$ip\n";
    // adres 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";
  }
}

Opmerkingen

  • regels 7-8: er wordt gevraagd dat PHP alle fouten (E_ALL & E_STRICT) signaleert en dat deze worden weergegeven. Deze modus wordt alleen aanbevolen in de ontwikkelingsmodus om de code te verbeteren met behulp van de waarschuwingen van PHP. In de productiemodus, regel 8, zou men ‘off’ instellen. Vanaf PHP 5.4 is het niveau E_STRICT opgenomen in E_ALL;
  • regel 11: de lijst met machines waarvan de naam en het adres IP nodig zijn;

De netwerkfuncties van PHP worden gebruikt in de functie getIpandName op regel 21.

  • regel 25: met de functie gethostbyname($nom) kan het adres IP "ip3.ip2.ip1.ip0" worden verkregen van de machine met de naam $nom. Als de machine $nom niet bestaat, geeft de functie $nom als resultaat;
  • regel 30: de functie gethostbyaddr($ip) wordt gebruikt om de naam van de machine met adres $ip te verkrijgen in de vorm "ip3.ip2.ip1.ip0". Als de machine $ip niet bestaat, geeft de functie $ip als resultaat;

Resultaten:


---------------
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. Het protocol HTTP (HyperText Transfer Protocol)

16.4.1. Voorbeeld 1

Image

Wanneer een browser een URL weergeeft, fungeert deze als client van een webserver of, anders gezegd, van een HTTP-server. De browser neemt het initiatief en begint met het verzenden van een aantal opdrachten naar de server. Voor dit eerste voorbeeld:

  • is de server het hulpprogramma [RawTcpServer];
  • de client is een browser;

We starten eerst de server op poort 100:

Image

Vervolgens vragen we met een browser de URL [localhost:100] op, d.w.z. we geven aan dat de opgevraagde server HTTP op poort 100 van de lokale machine draait:

Image

Laten we teruggaan naar het servervenster:

Image

  • in [3], de client die verbinding heeft gemaakt;
  • in [4-7], de reeks tekstregels die hij heeft verzonden:
    • in [4]: deze regel heeft het formaat [GET URL HTTP/1.1]. Hierin wordt gevraagd om URL / en wordt de server gevraagd om het protocol HTTP 1.1 te gebruiken;
    • in [5]: deze regel heeft het formaat [Host: serveur:port]. Het gebruik van hoofdletters of kleine letters in het commando [Host] maakt niet uit. We herinneren er hier aan dat de client een lokale server op poort 100 benadert;
    • het commando [User-Agent] geeft de identiteit van de client weer;
    • het commando [Accept] geeft aan welke documenttypes door de klant worden geaccepteerd;
    • het commando [Accept-Language] geeft aan in welke taal de opgevraagde documenten gewenst zijn, indien ze in meerdere talen beschikbaar zijn;
    • het commando [Connection] geeft de gewenste verbindingsmodus aan: [keep-alive] geeft aan dat de verbinding in stand moet worden gehouden totdat de uitwisseling is voltooid;
    • in [7]: de klant sluit zijn opdrachten af met een lege regel;

We beëindigen de verbinding door de server af te sluiten:

Image

16.4.2. Voorbeeld 2

Nu we weten welke commando's een browser verstuurt om een URL op te vragen, gaan we deze URL opvragen met onze client TCP [RawTcpClient]. De Apache-server van Laragon zal onze webserver zijn.

Laten we Laragon starten en vervolgens de Apache-webserver:

Image

Image

Laten we nu met een browser de pagina’s URL en [http://localhost:80] opvragen. Hier specificeren we alleen de server [localhost:80] en geen specifiek document. In dit geval wordt de URL / opgevraagd, d.w.z. de root van de webserver:

Image

  • in [1], de opgevraagde URL. We hadden aanvankelijk [http://localhost:80] ingetikt en de browser (hier Firefox) heeft dit eenvoudigweg omgezet in [localhost], omdat het protocol [http] impliciet is wanneer er geen protocol wordt vermeld en de poort [80] impliciet is wanneer de poort niet wordt gespecificeerd;
  • in [2], de hoofdpagina / van de opgevraagde webserver;

Laten we nu eens kijken naar de tekst die door de browser is ontvangen:

Image

  • Klik met de rechtermuisknop op de ontvangen pagina en kies de optie [2]. Je krijgt dan de volgende broncode:
<!DOCTYPE HTML>
<HTML>
    <head>
        <title>Laragon</title>

        <link href="<a href="view-source:https://fonts.googleapis.com/css?family=Karla:400">https://fonts.googleapis.com/css?family=Karla:400</a>" 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="<a href="view-source:http://localhost/?q=info">/?q=info</a>">info</a></span><br />
                      Document Root: C:/myprograms/laragon-lite/www<br />

                </div>
                <div class="opt">
                  <div><a title="Getting Started" href="<a href="view-source:https://laragon.org/docs">https://laragon.org/docs</a>">Getting Started</a></div>
                </div>
            </div>

        </div>
    </body>
</HTML>

Laten we nu URL en [http://localhost:80] opvragen met onze client TCP:

Image

  • met [1] maken we verbinding met poort 80 van de server localhost. Daar draait de webserver van Laragon;

We voeren nu de commando’s in die we in de vorige paragraaf hebben ontdekt:

Image

  • op [1] voeren we het commando [GET] uit. We vragen de rootmap / van de webserver op;
  • in [2], het commando [Host];
  • dit zijn de enige twee onmisbare commando's. Voor de overige commando's gebruikt de webserver standaardwaarden;
  • in [3], de lege regel waarmee de opdrachten van de client moeten worden afgesloten;
  • onder regel 3 volgt het antwoord van de webserver;
  • van [4] tot aan de lege regel [5] staan de headers HTTP van het antwoord van de server;
  • na de regel [5] volgt het opgevraagde document HTML [6];

We typen [quit] om de client af te sluiten en laden het logbestand [localhost-80.txt]:

--> [GET / HTTP/1.1]
--> [Host: localhost:80]
--> []
<-- [HTTP/1.1 200 OK]
<-- [Date: Thu, 16 May 2019 14:24:39 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]
<-- []
<-- [<!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>]
  • regels 11-79: het ontvangen document HTML. In het vorige voorbeeld had Firefox hetzelfde ontvangen;

We beschikken nu over de basis om een client TCP te programmeren die een URL zou opvragen.

16.4.3. Voorbeeld 3

Image

Het script [http-01.php] is een HTTP-client die is geconfigureerd door het bestand jSON [config-http-01.json]. De inhoud daarvan is als volgt:

{
    "localhost": {
        "port": 80,
        "GET": "/",
        "Host": "localhost:80",
        "User-Agent": "client PHP",
        "Accept": "text/HTML",
        "Accept-Language": "fr",
        "endOfLine":"\r\n"
    }
}
  • regel 2: de naam van de machine waarop de te bereiken webserver draait;
  • regel 3: de poort waarop deze webserver draait;
  • regel 4: de URL van het gewenste document;
  • regel 5: de doelmachine in de vorm machine:poort;
  • regel 6: de identificatie van de client HTTP: hier kan men invullen wat men wil;
  • regel 7: het type document dat door de client wordt geaccepteerd, in dit geval tekst HTML;
  • regel 8: de gewenste taal voor het opgevraagde document;
  • regel 9: het einde-van-regel-teken voor de door de client verzonden opdrachten: dit kan namelijk verschillen naargelang de server op een Unix-machine (\n) of een Windows-machine (\r\n) draait;

Het script [http-01.php] is als volgt:


<?php

// strikte naleving van de gedeclareerde typen van functieparameters
declare (strict_types=1);
//
// foutafhandeling
// error_reporting(E_ALL & E_STRICT);
// ini_set("display_errors", "on");
//
// constanten
const CONFIG_FILE_NAME = "config-http-01.json";
//
// de configuratie wordt opgehaald
$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);
// de tekst HTML uit het configuratiebestand ophalen
foreach ($config as $site => $protocole) {
  // de indexpagina van de website $ite lezen
  $résultat = getURL($site, $protocole);
  // weergave van het resultaat
  print "$résultat\n";
}//voor
// einde
exit;

//-----------------------------------------------------------------------
function getURL(string $site, array $protocole, $suivi = TRUE): string {
  // leest het bestand URL $site["GET"] en slaat het op in het bestand $site.HTML
  // de communicatie tussen client en server verloopt volgens het protocol $protocole
  //
  // verbinding tot stand gebracht op de poort van $site
  $erreurNumber = 0;
  $erreur = "";
  $connexion = fsockopen($site, $protocole["port"], $erreurNumber, $erreur);
  // terugkeren bij fout
  if ($connexion === FALSE) {
    return "Echec de la connexion au site (" . $site . " ," . $protocole["port"] . " : $erreur";
  }
  // $connexion vertegenwoordigt een bidirectionele communicatiestroom
  // tussen de client (dit programma) en de benaderde webserver
  // dit kanaal wordt gebruikt voor de uitwisseling van opdrachten en informatie
  // het communicatieprotocol is HTTP
  //
  // aanmaken van het bestand $site.HTML
  $HTML = fopen("output/$site.HTML", "w");
  if ($HTML === FALSE) {
    // verbinding tussen client en server wordt verbroken
    fclose($connexion);
    // foutmelding
    return "Erreur lors de la création du fichier $site.HTML";
  }
  // de client start de dialoog HTTP met de server
  if ($suivi) {
    print "Client : début de la communication avec le serveur [$site] ----------------------------\n";
  }
  // afhankelijk van de server moeten de regels van de client eindigen op \n of \r\n
  $endOfLine = $protocole["endOfLine"];
  // omwille van de eenvoud worden foutgevallen in de communicatie tussen client en server niet getest
  // De client verstuurt het commando GET om het $protocole["GET"] aan te vragen
  // syntaxis GET URL HTTP/1.1
  $commande = "GET " . $protocole["GET"] . " HTTP/1.1$endOfLine";
  // tracking?
  if ($suivi) {
    print "--> $commande";
  }
  // het commando wordt naar de server verzonden
  fputs($connexion, $commande);
  // de overige headers worden verzonden HTTP
  foreach ($protocole as $verb => $value) {
    if ($verb !== "GET" && $verb != "port"" && $verb !="endOfLine") {
      // het commando wordt samengesteld
      $commande = "$verb: $value$endOfLine";
      // vervolg?
      if ($suivi) {
        print "--> $commande";
      }
      // het commando wordt naar de server verzonden
      fputs($connexion, $commande);
    }
  }
  // de headers van het protocol HTTP moeten eindigen met een lege regel
  fputs($connexion, $endOfLine);
  //
  // de server zal nu reageren op het kanaal $connexion. Hij zal alle
  // zijn gegevens verzenden en vervolgens het kanaal sluiten. De client leest dus alles wat binnenkomt via $connexion
  // totdat het kanaal wordt gesloten
  //
  // worden eerst de door de server verzonden headers HTTP gelezen
  // ook deze eindigen met een lege regel
  if ($suivi) {
    print "Réponse du serveur [$site] ----------------------------\n";
  }
  $fini = FALSE;
  while (!$fini && $ligne = fgets($connexion, 1000)) {
    // is er een lege regel?
    $champs = [];
    preg_match("/^(.*?)\s+$/", $ligne, $champs);
    if ($champs[1] !== "") {
      if ($suivi) {
        // we geven de header HTTP weer
        print "<-- " . $champs[1] . "\n";
      }
    } else {
      // dat was de lege regel – de headers HTTP zijn voltooid
      $fini = TRUE;
    }
  }
  // het document HTML wordt ingelezen, dat op de lege regel volgt
  while ($ligne = fgets($connexion, 1000)) {
    // de regel wordt opgeslagen in het bestand HTML op de site
    fputs($HTML, $ligne);
  }
  // de server heeft de verbinding verbroken –  de client verbreekt deze op zijn beurt
  fclose($connexion);
  // het bestand $HTML wordt gesloten
  fclose($HTML);
  // terug
  return "Fin de la communication avec le site [$site]. Vérifiez le fichier [$site.HTML]";
}

Opmerkingen bij de code:

  • regel 14: het configuratiebestand wordt gebruikt om een woordenboek aan te maken:
    • de sleutels van het woordenboek zijn de te raadplegen webservers;
    • de waarden bepalen welk protocol (HTTP) moet worden gevolgd;
  • regels 16-21: er wordt een lus door de lijst met webservers uit de configuratie gelopen;
  • regel 26: de functie getURL($site,$protocole,$suivi) vraagt een document op van de website $site en slaat dit op in het tekstbestand $site.HTML.Par: standaard worden de client/server-uitwisselingen gelogd op de console ($suivi=TRUE);
  • regel 33: met de functie fsockopen($site,$port,$errNumber,$erreur) maakt het mogelijk een verbinding tot stand te brengen met een dienst TCP / IP die actief is op poort $port van de machine $site. Als de verbinding mislukt, is [$errNumber] een foutcode en [$erreur] de bijbehorende foutmelding. Zodra de verbinding tussen client en server tot stand is gebracht, wisselen talrijke TCP / IP-diensten tekstregels uit. Dit is hier het geval bij het protocol HTTP (HyperText Transfer Protocol). De datastroom van de server naar de client kan vervolgens worden verwerkt als een tekstbestand dat wordt gelezen met [fgets]. Hetzelfde geldt voor de datastroom van de client naar de server, die kan worden geschreven met [fputs];
  • regels 44-50: aanmaken van het bestand [$site.HTML] waarin het ontvangen document HTML wordt opgeslagen;
  • regel 60: het eerste commando van de client moet het commando [GET URL HTTP/1.1] zijn;
  • regel 66: met de functie fputs kan de klant gegevens naar de server verzenden. De verzonden tekstregel heeft hier de volgende betekenis: "Ik wil (GET) de pagina [URL] van de website waarmee ik verbonden ben. Ik werk met het protocol HTTP versie 1.1";
  • regels 68-79: de overige regels van het protocol HTTP [Host, User-Agent, Accept, Accept-Language] worden verzonden. De volgorde ervan doet er niet toe;
  • regel 81: er wordt een lege regel naar de server verzonden om aan te geven dat de client klaar is met het verzenden van zijn HTTP-headers en nu wacht op het opgevraagde document;
  • regels 92-106: de server verstuurt eerst een reeks headers HTTP die diverse informatie over het opgevraagde document geven. Deze headers eindigen met een lege regel;
  • regel 93: er wordt een door de server verzonden regel gelezen met de functie PHP [fgets];
  • regel 96: de hoofdtekst van de regel wordt opgehaald zonder de spaties (witruimte, regeleinde) aan het einde van de regel;
  • regel 97: er wordt gecontroleerd of de lege regel is opgehaald die het einde aangeeft van de door de server verzonden HTTP-headers;
  • regels 98-101: als we in de modus [suivi] zitten, wordt de ontvangen header HTTP op de console weergegeven;
  • regels 108-111: de tekstregels van het antwoord van de server kunnen regel voor regel worden gelezen met een lus while en worden opgeslagen in het tekstbestand [output/$site.HTML]. Wanneer de webserver de volledige opgevraagde pagina heeft verzonden, verbreekt hij de verbinding met de client. Aan de clientzijde wordt dit gedetecteerd als een einde van het bestand;

Resultaten:

De console geeft de volgende logberichten weer:


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]

In ons voorbeeld is het ontvangen bestand [output/localhost.HTML] als volgt:


<!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>

We hebben inderdaad hetzelfde document ontvangen als met de Firefox-browser.

16.4.4. Voorbeeld 4

In dit voorbeeld laten we zien dat de client HTTP die we hebben geschreven, niet volstaat. Pas het configuratiebestand [config-http-01.json] als volgt aan:

{
    "tahe.developpez.com": {
        "port": 443,
        "GET": "/",
        "Host": "sergetahe.com:443",
        "User-Agent": "script PHP 7",
        "Accept": "text/HTML",
        "Accept-Language": "fr",
        "endOfLine":"\n"
    }
}

Hier gaan we de URL [http://tahe.developpez.com:443/] opvragen. Poort 443 van de machine [tahe.developpez.com] is een poort die wordt gebruikt voor het beveiligde http-protocol, ook wel https genoemd. Bij dit protocol begint de communicatie tussen client en server met een uitwisseling van informatie die de verbinding beveiligt. De client moet dan het protocol [HTTPS] gebruiken en niet het protocol [HTTP], wat onze client niet doet.

Met dit configuratiebestand zijn de resultaten in de console als volgt:


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]
  • regel 8: de server [tahe.developpez.com] heeft geantwoord dat het verzoek van de klant onjuist was;

De inhoud van het bestand [output/tahe.developpez.com.HTML] is dan als volgt:


<!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>

De server geeft duidelijk aan dat we niet het juiste protocol hebben gebruikt.

Laten we nu het volgende configuratiebestand gebruiken:

{
    "sergetahe.com": {
        "port": 80,
        "GET": "/cours-tutoriels-de-programmation/",
        "Host": "sergetahe.com:80",
        "User-Agent": "script PHP 7",
        "Accept": "text/HTML",
        "Accept-Language": "fr",
        "endOfLine": "\n"
    }
}

De console-uitvoer ziet er dan als volgt uit:


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]
  • regel 11 geeft aan dat de server het document in stukjes verstuurt;

Dit komt tot uiting in de aanwezigheid van getallen in de stroom die naar de client wordt verzonden: elk getal geeft de client het aantal tekens aan van het volgende deel dat door de server wordt verzonden. Dit is het resultaat in het bestand [output/sergetahe.com.HTML]:

Image

  • in [1] en [2], de hexadecimale grootte van de delen 1 en 2 van het document;

Een correcte HTTP-client zou deze getallen niet in het uiteindelijke HTML-document mogen laten staan.

Hier is nog een voorbeeld:

{
    "sergetahe.com": {
        "port": 80,
        "GET": "/cours-tutoriels-de-programmation",
        "Host": "sergetahe.com:80",
        "User-Agent": "script PHP 7",
        "Accept": "text/HTML",
        "Accept-Language": "fr",
        "endOfLine": "\n"
    }
}

Dit lijkt op het vorige voorbeeld, maar de in regel 4 opgevraagde URL ontbreekt het /-teken aan het einde. Dit zijn niet dezelfde URL. Het uitvoeren van de client HTTP levert dan de volgende console-uitvoer op:


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/cursussen-programmeerhandleidingen/
<-- 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]
  • regel 8 geeft aan dat het opgevraagde document is gewijzigd in URL. De nieuwe URL wordt weergegeven op regel 13. Let deze keer op het teken / waarmee de nieuwe URL eindigt;

Het bestand [output/serge.tahe.com.HTML] ziet er dan als volgt uit:


<!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>

Een klant met het nummer HTTP zou de omleidingen moeten kunnen volgen. Hier zou hij automatisch de nieuwe URL [http://sergetahe.com/cours-tutoriels-de-programmation/] opnieuw moeten aanvragen.

16.4.5. Voorbeeld 5

Uit de voorgaande voorbeelden is gebleken dat onze client HTTP ontoereikend was. We gaan nu een tool presenteren met de naam [curl] waarmee webdocumenten kunnen worden opgehaald en de genoemde problemen worden opgelost: het https-protocol, documenten die in delen worden verzonden, omleidingen… De tool [curl] is geïnstalleerd met Laragon:

Image

Laten we een Laragon-terminal openen: [1]:

Image

In de terminal typen we de volgende opdracht:

Image

  • in [1], het type console;
  • in [2], de huidige map. Deze map is bijzonder: hier haalt de Apache-server van Laragon de documenten op die erom worden gevraagd. We moeten dus voorkomen dat deze map vol raakt;
  • in [3], de ingevoerde opdracht;

Het is mogelijk dat het commando [curl --help] een foutmelding geeft. De meest waarschijnlijke oorzaak is dat u niet het juiste type terminal hebt. Open in dat geval een andere terminal met de commando’s [4-6];

Het commando [curl --help] geeft alle configuratieopties van [curl] weer. Er zijn er tientallen. We zullen er maar heel weinig gebruiken. Om een URL op te vragen, volstaat het om de opdracht [curl URL] in te voeren. Deze opdracht geeft het gevraagde document weer op de console. Als we bovendien de uitwisselingen HTTP tussen de client en de server willen zien, typen we [curl --verbose URL]. Tot slot, om het opgevraagde document HTML in een bestand op te slaan, typen we [curl --verbose --output fichier URL].

Om te voorkomen dat de map [www] van Laragon vol raakt, gaan we naar een andere locatie in het bestandssysteem:

Image

  • in [1] gaan we naar de map [c:\temp]. Als deze map niet bestaat, kunt u deze aanmaken of een andere map kiezen;
  • in [2] maken we een map aan met de naam [curl];
  • in [3] selecteer je deze map;
  • in [4] geven we de inhoud weer. Deze is leeg;

Zorg ervoor dat de Apache-server van Laragon is gestart en vraag met [curl] de mappen URL en [http://localhost/] op met het commando [curl –verbose –output localhost.HTML http://localhost/]. Dit levert de volgende resultaten op:


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 naar host localhost, ongewijzigd gelaten                                                   
  • regels 8-12: regels die door [curl] naar de server [localhost] zijn verzonden. We herkennen het protocol HTTP;
  • regels 13-19: regels die door de server als antwoord zijn verzonden;
  • regel 13: geeft aan dat het gevraagde document inderdaad is ontvangen;

Het bestand [localhost.HTML] bevat het gevraagde document. U kunt dit controleren door het bestand in een teksteditor te openen.

Laten we nu URL [https://tahe.developpez.com:443/] opvragen. Om dit URL te verkrijgen, moet de client HTTP de taal HTTPS beheersen. Dit is het geval bij de client [curl].

De console-uitvoer is als volgt:


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 naar host tahe.developpez.com, ongewijzigd
  • regels 10-40: de communicatie tussen client en server om de verbinding te beveiligen: deze wordt versleuteld;
  • regels 42-45: de HTTP-headers die door de client [curl] naar de server worden verzonden;
  • regel 48: het opgevraagde document is gevonden;
  • regel 53: het document wordt in delen verzonden;

[curl] verwerkt zowel het beveiligde protocol HTTPS als het feit dat het document in delen wordt verzonden op de juiste manier. Het verzonden document is hier te vinden in het bestand [tahe.developpez.com.HTML].

Laten we nu de URL [http://sergetahe.com/cours-tutoriels-de-programmation] opvragen. We hadden gezien dat er voor deze URL een omleiding was naar de URL [http://sergetahe.com/cours-tutoriels-de-programmation/] (met een / aan het einde).

De console-uitvoer is dan als volgt:


c:\Temp\curl
λ curl --verbose --output sergetahe.com.HTML --location http://sergetahe.com/cursussen-tutorials-programmeren
  % 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/cursussen-en-tutorials-over-programmeren/
< 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 om sergetahe.com intact te laten
* Issue another request to this URL: 'http://sergetahe.com/cursussen-en-tutorials-over-programmeren/'
* Found bundle for host sergetahe.com: 0x1c88548 [can pipeline]
* Could pipeline, but not asked to!
* Re-using existing connection! (#0) met 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 naar host sergetahe.com, ongewijzigd gelaten
  • regel 2: we gebruiken de optie [--location] om aan te geven dat we de door de server verzonden omleidingen willen volgen;
  • regel 13: de server geeft aan dat het opgevraagde document is gewijzigd in URL;
  • regel 18: de server geeft de nieuwe URL van het opgevraagde document aan;
  • regel 27: [curl] verstuurt een nieuw verzoek, ditmaal naar de nieuwe URL;
  • regel 33: de nieuwe URL wordt gebruikt;
  • regel 38: de server antwoordt dat hij het gevraagde document heeft gevonden;
  • regel 41: hij verstuurt het in delen;

Het gevraagde document is te vinden in het bestand [sergetahe.com.HTML].

16.4.6. Voorbeeld 6

PHP heeft een extensie met de naam [libcurl] waarmee de mogelijkheden van de tool [curl] in een programma PHP kunnen worden benut. U moet er eerst voor zorgen dat deze uitbreiding is ingeschakeld in het bestand [php.ini], zoals beschreven in de paragraaf ‘link’:

Image

Zorg ervoor dat regel 889 hierboven niet is uitgecommentarieerd.

We gaan een script [http-02.php] schrijven dat gebruikmaakt van het volgende configuratiebestand jSON:

{
    "sergetahe.com": {
        "timeout": 5,
        "url": "http://sergetahe.com"
    },
    "tahe.developpez.com": {
        "timeout": 5,
        "url": "https://tahe.developpez.com"
    },  
    "www.polytech-angers.fr": {
        "timeout": 5,
        "url": "http://www.polytech-angers.fr"
    },  
    "localhost": {
        "timeout": 5,
        "url": "http://localhost"
    }
}

Elk element in het woordenboek [clé, valeur] heeft de volgende structuur:

  • clé: de naam van een webserver;
  • valeur is een woordenboek met de volgende sleutels:
    • timeout: maximale wachttijd voor het antwoord van de server. Na het verstrijken van deze tijd wordt de verbinding van de client verbroken;
    • url: URL van het opgevraagde document;

De scriptcode voor [http-02.php] is als volgt:


<?php

// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);
//
// foutafhandeling
//error_reporting(E_ALL & E_STRICT);
//ini_set("display_errors", "on");
//
// constanten
const CONFIG_FILE_NAME = "config-http-02.json";
//
// de configuratie ophalen
$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);

// de tekst HTML uit het configuratiebestand ophalen
foreach ($config as $site => $infos) {
  // URL van de site $ite lezen
  $résultat = getUrl($site, $infos["url"], $infos["timeout"]);
  // weergave resultaat
  print "$résultat\n";
}//voor
// einde
exit;

//-----------------------------------------------------------------------
function getUrl(string $site, string $url, int $timeout, $suivi = TRUE): string {
  // leest het bestand URL $url en slaat het op in het bestand output/$site.HTML
  //
  // vervolg
  print "Client : début de la communication avec le serveur [$site] ----------------------------\n";

  // Initialisatie van een sessie cURL
  $curl = curl_init($url);
  if ($curl === FALSE) {
    // er is een fout opgetreden
    return "Erreur lors de l'initialisation de la session cURL pour le site [$site]";
  }
  // curl-opties
  $options = [
    // uitgebreide modus
    CURLOPT_VERBOSE => true,
    // nieuwe verbinding – geen cache
    CURLOPT_FRESH_CONNECT => true,
    // time-out van het verzoek (in seconden)
    CURLOPT_TIMEOUT => $timeout,
    CURLOPT_CONNECTTIMEOUT => $timeout,
    // certificaten niet op geldigheid controleren SSL
    CURLOPT_SSL_VERIFYPEER => false,
    // omleidingen volgen
    CURLOPT_FOLLOWLOCATION => true,
    // het opgevraagde document ophalen in de vorm van een tekenreeks
    CURLOPT_RETURNTRANSFER => true
  ];

  // configuratie van curl
  curl_setopt_array($curl, $options);
  // Uitvoering van het verzoek
  $page_content = curl_exec($curl);
  // Sessie afsluiten cURL
  curl_close($curl);

  // verwerking van het resultaat
  if ($page_content !== FALSE) {
    // het resultaat opslaan in $site.HTML
    $result = file_put_contents("output/$site.HTML", $page_content);
    if ($result === FALSE) {
      // foutmelding
      return "Erreur lors de la création du fichier [output/$site.HTML]";
    }
    // succesvol terug
    return "Fin de la communication avec le serveur [$site]. Vérifiez le fichier [output/$site.HTML]";
  } else {
    // er is een communicatiefout opgetreden
    return "Erreur de communication avec le serveur [$site]";
  }
}

Opmerkingen

  • regel 14: het configuratiebestand wordt gebruikt om het woordenboek [$config] aan te maken;
  • regels 17-22: er wordt een lus doorlopen over de lijst met sites die in de configuratie zijn gevonden;
  • regel 19: voor elke site wordt de functie [getUrl] aangeroepen, die deURL $infos[«url»] met een time-out $infos[«timeout»];
  • regel 34: er wordt een sessie gestart met [curl]. [curl_init] maakt nog geen verbinding met de webserver. Deze functie retourneert een resource [$curl] die als parameter zal dienen voor alle volgende functies [curl];
  • regels 35-38: als het initialiseren van de sessie [curl] mislukt, retourneert de functie [curl_init] de booleaanse waarde FALSE;
  • regels 40-54: het woordenboek [$options] configureert de verbinding [curl] met de server;
  • regel 57: de verbindingsopties worden doorgegeven aan de bron [$curl];
  • regel 59: verbinding met URL aangevraagd met de gedefinieerde opties. Vanwege de optie [CURLOPT_RETURNTRANSFER => true] retourneert de functie [curl_exec] het door de server verzonden document als een tekenreeks. De functie [curl_exec] retourneert de booleaanse waarde FALSE als de verbinding mislukt;
  • regel 64: het resultaat van [curl_exec] wordt geanalyseerd;
  • regel 66: de ontvangen pagina wordt opgeslagen in een lokaal bestand;
  • regels 69, 72, 75: het resultaat van de functie [getUrl] wordt weergegeven;

Wanneer het script [http-02.php] wordt uitgevoerd, krijgt men de volgende console-uitvoer:


* 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/cursussen-programmeerhandleidingen
< Set-Cookie: SERVERID68971=2621236|XN/Gc|XN/Gc; path=/
< X-IPLB-Instance: 17097
<
* Ignoring the response-body
* Connection #0 om sergetahe.com intact te laten
* Issue another request to this URL: 'http://sergetahe.com/cursussen-en-tutorials-over-programmeren'
* Found bundle for host sergetahe.com: 0x1fee4ebe090 [can pipeline]
* Re-using existing connection! (#0) met 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/cursussen-en-tutorials-over-programmeren/
< Set-Cookie: SERVERID68971=2621236|XN/Gc|XN/Gc; path=/
< Cache-control: private
< X-IPLB-Instance: 17097
<
* Ignoring the response-body
* Connection #0 naar host sergetahe.com, ongewijzigd gelaten
* Issue another request to this URL: 'http://sergetahe.com/cursussen-en-tutorials-over-programmeren/'
* Found bundle for host sergetahe.com: 0x1fee4ebe090 [can pipeline]
* Re-using existing connection! (#0) met 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/cursussen-en-tutorials-over-programmeren/wp-json/>; rel="https://api.w.org/"
< Link: <http://sergetahe.com/cursussen-en-tutorials-over-programmeren/>; 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 om sergetahe.com intact te laten
* 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 naar host tahe.developpez.com blijft intact
* 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 om www.polytech-angers.fr intact te laten
* 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) met 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 naar host www.polytech-angers.fr, ongewijzigd gelaten
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 naar host localhost, ongewijzigd gelaten

Fin de la communication avec le serveur [localhost]. Vérifiez le fichier [output/localhost.HTML]

Opmerkingen

  • we krijgen dezelfde uitwisselingen als met de tool [curl];
  • in het groen: de logbestanden van het script;
  • in het blauw: de commando's die naar de server worden verzonden;
  • in geel: de commando's die de client als antwoord ontvangt;

16.4.7. Conclusie

In deze paragraaf hebben we het protocol HTTP ontdekt en een script [http-02.php] geschreven waarmee een URL van het web kan worden gedownload.

16.5. Het SMTP-protocol (Simple Mail Transfer Protocol)

16.5.1. Inleiding

Image

In dit hoofdstuk:

  • [Serveur B] is een lokale SMTP-server die we zullen installeren;
  • [Client A] is een SMTP-client in verschillende vormen:
    • de client [RawTcpClient] om het protocol SMTP te verkennen;
    • een script PHP dat het protocol SMTP van de client [RawTcpClient] nabootst;
    • een script PHP dat gebruikmaakt van de bibliotheek [SwiftMailServer] waarmee allerlei soorten e-mails kunnen worden verzonden;

16.5.2. Een e-mailadres aanmaken: [gmail]

Om onze tests SMTP uit te voeren, hebben we een e-mailadres nodig om naar te schrijven. Hiervoor gaan we een adres aanmaken op Gmail:

Image

  • in [5] maken we de gebruiker [php7parlexemple] aan (kies iets anders);
  • bij [6] is het wachtwoord [PHP7parlexemple] (kies iets anders);
  • bij [7] bevestigen we deze gegevens;

Image

  • vul de velden in [9-10] in en bevestig (11);
  • ga akkoord met de gebruiksvoorwaarden van Google (12-13) en bevestig vervolgens (14);

Image

  • in [15], de inbox van de gebruiker [PHP7] (16);
  • in [17] heeft deze gebruiker een lege inbox;
  • in [18-19]: log in op het Google-account van de gebruiker [php7parlexemple@gmail.com]. We gaan de beveiliging van het account configureren;

Image

  • in [21]: geef andere applicaties dan die van Google toestemming om het account [php7parlexemple] te gebruiken. Als we dit niet doen, kan onze lokale e-mailserver [hMailServer] geen verbinding maken met de Gmail-server SMTP;

Image

16.5.3. Installatie van een SMTP-server

Voor onze tests zullen we de mailserver [hMailServer] installeren, die zowel een SMTP-server is waarmee e-mails kunnen worden verzonden, een POP3-server (Post Office Protocol) waarmee e-mails kunnen worden gelezen die op de server zijn opgeslagen, en een IMAP-server (Internet Message Access Protocol) waarmee eveneens e-mails kunnen worden gelezen die op de server zijn opgeslagen, maar die nog meer mogelijkheden biedt. Deze maakt het met name mogelijk om de opslag van e-mails op de server te beheren.

De e-mailserver [hMailServer] is beschikbaar op de URL [https://www.hmailserver.com/] (mei 2019).

Image

Tijdens de installatie wordt u om bepaalde gegevens gevraagd:

Image

  • selecteer in [1-2] zowel de mailserver als de tools om deze te beheren;
  • tijdens de installatie wordt u om het beheerderswachtwoord gevraagd: noteer dit, want u zult het nodig hebben;

[hMailServer] wordt geïnstalleerd als een Windows-service die automatisch wordt gestart bij het opstarten van de computer. Het is beter om handmatig opstarten te kiezen:

  • in [3] typt u [services] in het invoerveld van de statusbalk;

Image

  • bij [4-8] zet u de service in de modus [manuel] (6) en start u deze op (7);

Zodra de server [hMailServer] is opgestart, moet deze worden geconfigureerd. De server is geïnstalleerd met een beheerprogramma [hMailServer Administrator]:

Image

  • in [2], typ in het invoerveld van de statusbalk [hmailserver];
  • in [3] de beheerder starten;
  • in [4]: de beheerder verbinden met de server [hMailServer];
  • in [5]: voer het wachtwoord in dat u bij de installatie van [hMailServer] hebt opgegeven;

Image

We gaan een gebruikersaccount aanmaken:

  • klik met de rechtermuisknop op [Accounts] (7) en vervolgens op (8) om een nieuwe gebruiker toe te voegen;
  • in het tabblad [General] (9) stellen we een gebruiker [guest] (10) in met het wachtwoord [guest] (11). Deze krijgt het e-mailadres [guest@localhost] (10);
  • in [12] is de gebruiker [guest] geactiveerd;

Image

Image

  • in [15] wordt het protocol SMTP van de e-mailserver geconfigureerd;
  • in [16] wordt de distributie van e-mails geconfigureerd;
  • in [17] de configuratie van de e-maildistributie naar de hostmachine (localhost);
  • in [18] de naam van de lokale machine (localhost). Met het script uit de paragraaf ‘link’ kunt u deze naam verkrijgen;
  • in [19] configureert u een SMTP-relayserver: dit is de server die zorgt voor de distributie van e-mails die niet bestemd zijn voor de lokale machine (localhost);
  • in [20], de Gmail-server SMTP. We kiezen voor Gmail omdat we daar in de paragraaf 'link' een account hebben aangemaakt;
  • in [21], de poort SMTP van Gmail;
  • In [22] is de Gmail-dienst SMTP een beveiligde dienst: je hebt een Gmail-account nodig om er toegang toe te krijgen;
  • in [23], de gebruiker [php7parlexemple] die in de paragraaf ‘link’ is aangemaakt;
  • in [24] staat het wachtwoord van deze gebruiker: [PHP7parlexemple], aangemaakt in de paragraaf met de link;
  • in [25] wordt het type beveiligingsprotocol aangegeven dat door Gmail wordt gebruikt;

Image

  • in [27] de poort van de dienst SMTP;
  • in [28]: voor deze dienst is geen authenticatie vereist;
  • in [30] voert u het welkomstbericht in dat de server SMTP naar zijn klanten zal sturen;

16.5.4. Het protocol SMTP

Image

We gaan het protocol SMTP verkennen met de volgende omgeving:

  • klant A is de generieke klant TCP, [RawTcpClient];
  • server B is de e-mailserver [hMailServer];
  • client A zal server B vragen om een e-mail te bezorgen aan gebruiker [php7parlexemple@gmail.com];
  • we zullen controleren of deze gebruiker de verzonden e-mail daadwerkelijk heeft ontvangen;

We starten de client als volgt:

Image

  • in [1] maken we verbinding met poort 25 van de lokale machine, waar de dienst SMTP van [hMailServer] draait. Het argument [--quit bye] geeft aan dat de gebruiker het programma zal afsluiten door het commando [bye] in te voeren. Zonder dit argument is het commando om het programma te beëindigen [quit]. Maar [quit] is ook een commando van het protocol SMTP. We moeten deze dubbelzinnigheid dus vermijden;
  • bij [2] is de client wel verbonden;
  • in [3] wacht de client op commando’s die via het toetsenbord worden ingevoerd;
  • in [4] stuurt de server hem zijn welkomstbericht;

Image

  • in [5] verstuurt de client de opdracht [EHLO nom-de-la-machine-client]. De server antwoordt met een reeks berichten in de vorm [250-xx] (6). De code [250] geeft aan dat het door de client verzonden commando is geslaagd;
  • in [7] geeft de client de afzender van het bericht aan, in dit geval [guest@localhost]. Deze gebruiker moet bestaan op de mailserver [hMailServer]. Dat is hier het geval, omdat we deze gebruiker eerder hebben aangemaakt;
  • in [8] staat het antwoord van de server;
  • in [9] wordt de ontvanger van het bericht aangegeven, in dit geval de Gmail-gebruiker [php7parlexemple@gmail.com];
  • in [10], het antwoord van de server;
  • in [11] geeft het commando [DATA] aan de server door dat de client de inhoud van het bericht gaat verzenden;
  • in [12], het antwoord van de server;
  • in [13-16] moet de klant een lijst met tekstregels verzenden die eindigt met een regel die slechts één punt bevat. Het bericht kan [Subject :, From :, To :]-regels (13) bevatten om respectievelijk het onderwerp van het bericht, de afzender en de ontvanger te definiëren;
  • in [14] moeten de voorgaande kopteksten worden gevolgd door een lege regel;
  • in [15] de tekst van het bericht;
  • in [16]: de regel die slechts één punt bevat en het einde van het bericht aangeeft;
  • in [17]: zodra de server de regel met slechts één punt heeft ontvangen, plaatst hij het bericht in de wachtrij;
  • in [18] geeft de client aan de server door dat hij klaar is;
  • in [19], het antwoord van de server;
  • in [20] zien we dat de server de verbinding met de client heeft verbroken;

Laten we nu controleren of de gebruiker [php7parlexemple@gmail.com] het bericht inderdaad heeft ontvangen:

Image

  • in [2] zien we dat de gebruiker [php7parlexemple@gmail.com] het bericht inderdaad heeft ontvangen;

Image

Image

Image

  • in [7], de afzender van de e-mail. We zien dat dit niet [guest@localhost] is. Dit komt doordat het de relaisserver is die is gedefinieerd in de configuratie van [hmailServer] die het bericht heeft afgeleverd. Deze relaisserver is echter [smtp.gmail.com], die gekoppeld is aan de inloggegevens van de Gmail-gebruiker [php7parlexemple@gmail.com]. Elke e-mail afkomstig van [hMailServer] lijkt afkomstig te zijn van de gebruiker [php7parlexemple@gmail.com]. Dit was hier niet de bedoeling, maar als deze relaisserver niet wordt gebruikt, weigert de Gmail-service SMTP de e-mails die door [hMailServer] worden verzonden, omdat de Gmail-service SMTP om authenticatie vraagt die [hMailServer] niet verstrekt. Er is ongetwijfeld een manier om dit probleem te omzeilen, maar ik heb die niet gevonden;
  • in [8] is te zien dat de e-mail is ontvangen van de machine [DESKTOP-528I5CU], waarop de e-mailserver [hMailServer] draait;
  • in [9], de afzender van het bericht. We zien dat dit niet [guest@localhost] is;
  • in [10], de oorspronkelijke afzender van het bericht. Dit keer is het wel degelijk [guest@localhost];
  • in [11], het onderwerp;
  • in [12], de ontvanger;
  • in [13], het bericht;

Uiteindelijk is onze client [RawTcpClient] erin geslaagd het bericht te verzenden, ook al was er een probleem met de afzender. We hebben nu de basis om een client SMTP te maken, geschreven in PHP.

16.5.5. Een eenvoudige SMTP-client geschreven in PHP

We gaan in PHP toepassen wat we eerder hebben geleerd over het protocol SMTP.

Image

Het script [smtp-01.php] wordt geconfigureerd door het volgende bestand 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] is een array waarvan elk element een woordenboek van het type [nom=>infos] is. De waarde [infos] is zelf een woordenboek met de volgende sleutels en waarden:

  • [smtp-server]: de naam van de te gebruiken server SMTP;
  • [smtp-port]: het poortnummer van de dienst SMTP;
  • [from]: de afzender van het bericht;
  • [to]: de ontvanger van het bericht;
  • [subject]: het onderwerp van het bericht;
  • [message]: het te verzenden bericht;
  • Het eerste element gebruikt de server SMTP [localhost] om een e-mail te versturen naar een gebruiker van [localhost];
  • het tweede element gebruikt de server SMTP [localhost] om een e-mail te versturen naar een gebruiker van [Gmail];
  • het derde element gebruikt de server SMTP [Gmail] om een e-mail te versturen naar een gebruiker van [Gmail];

De code [smtp-01.php] van de client SMTP is als volgt:


<?php

// client SMTP (SendMail-overdrachtsprotocol) waarmee een bericht kan worden verzonden
// SMTP client-server-communicatieprotocol
// -> client maakt verbinding met poort 25 van de SMTP-server
// <- de server stuurt hem een welkomstbericht
// -> de client stuurt het commando EHLO met de naam van zijn computer
// <- de server antwoordt met OK of niet
// -> de client stuurt het commando MAIL FROM: <afzender>
// <- server antwoordt met OK of niet
// -> client verstuurt het commando RCPT TO: <ontvanger>
// <- server antwoordt met OK of niet
// -> de client verstuurt het commando DATA
// <- server antwoordt met OK of niet
// -> de client verzendt alle regels van zijn bericht en sluit af met een regel die het
// enige teken bevat.
// <- server antwoordt met OK of niet
// -> de client verstuurt het commando QUIT
// <- de server antwoordt met OK of niet
// de antwoorden van de server hebben de vorm xxx tekst, waarbij xxx een getal van 3 cijfers is. Alles
// getal xxx >=500 duidt op een fout.
// Het antwoord kan uit meerdere regels bestaan die allemaal beginnen met xxx, behalve de laatste
// in de vorm xxx(spatie)
// de uitgewisselde tekstregels moeten eindigen met de tekens RC(#13) en LF(#10)
//
//  SMTP-client (SendMail Transfer Protocol) waarmee een bericht kan worden verzonden
//
// foutbeheer
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);
//
// de parameters voor het verzenden van e-mail
const CONFIG_FILE_NAME = "config-smtp-01.json";

// de configuratie wordt opgehaald
$mails = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);

// e-mails verzenden
foreach ($mails as $name => $infos) {
  // tracking
  print "Envoi du mail [$name]\n";
  // verzending van de post
  $résultat = sendmail($name, $infos, TRUE);
  // weergave van het resultaat
  print "$résultat\n";
}//voor
// einde
exit;

//sendmail
//-----------------------------------------------------------------------

function sendmail(string $name, array $infos, bool $verbose = TRUE): string {
  // bericht verzenden [$name,$infos]. Als $verbose=TRUE    , volg dan de communicatie tussen client en server
  // de naam van de client wordt opgehaald
  $client = gethostbyaddr(gethostbyname(""));
  // er wordt een verbinding met de server geopend SMTP
  $connexion = fsockopen($infos["smtp-server"], (int) $infos["smtp-port"]);
  // terugkeren bij fout
  if ($connexion === FALSE) {
    return sprintf("Echec de la connexion au site (%s,%s) : %s", $infos["smtp-server"], $infos["smtp-port"]);
  }
  // $connexion vertegenwoordigt een bidirectionele communicatiestroom
  // tussen de client (dit programma) en de benaderde SMTP-server
  // dit kanaal wordt gebruikt voor de uitwisseling van opdrachten en informatie
  // na het tot stand brengen van de verbinding stuurt de server een welkomstbericht dat wordt gelezen
  $erreur = sendCommand($connexion, "", $verbose, TRUE);
  if ($erreur !== "") {
    // de verbinding wordt verbroken
    fclose($connexion);
    // terug
    return $erreur;
  }
  // commando EHLO
  $erreur = sendCommand($connexion, "EHLO $client", $verbose, TRUE);
  if ($erreur !== "") {
    // verbinding verbreken
    fclose($connexion);
    // terug
    return $erreur;
  }
  // opdracht MAIL FROM:
  $erreur = sendCommand($connexion, sprintf("MAIL FROM: <%s>", $infos["from"]), $verbose, TRUE);
  if ($erreur !== "") {
    // verbinding verbreken
    fclose($connexion);
    // terug
    return $erreur;
  }
  // commando RCPT TO:
  $erreur = sendCommand($connexion, sprintf("RCPT TO: <%s>", $infos["to"]), $verbose, TRUE);
  if ($erreur !== "") {
    // verbinding verbreken
    fclose($connexion);
    // terug
    return $erreur;
  }
  // commando DATA  
  $erreur = sendCommand($connexion, "DATA", $verbose, TRUE);
  if ($erreur !== "") {
    // verbinding verbreken
    fclose($connexion);
    // terug
    return $erreur;
  }
  // bericht voorbereiden om te verzenden
  // het moet de volgende regels bevatten
  // From: afzender
  // To: ontvanger
  // Onderwerp:
  // lege regel
  // Bericht
  // .
  $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 !== "") {
    // verbinding beëindigd
    fclose($connexion);
    // terug
    return $erreur;
  }
  // commando quit
  $erreur = sendCommand($connexion, "QUIT", $verbose, TRUE);
  if ($erreur !== "") {
    // verbinding verbreken
    fclose($connexion);
    // terug
    return $erreur;
  }
  // einde
  fclose($connexion);
  return "Message envoyé";
}

// --------------------------------------------------------------------------

function sendCommand($connexion, string $commande, bool $verbose, bool $withRCLF): string {
  // verzendt $commande naar het kanaal $connexion
  // logboekmodus als $verbose=1
  // als $withRCLF=1, voeg dan de reeks RCLF toe aan de uitwisseling
  // gegevens
  if ($withRCLF) {
    $RCLF = "\r\n";
  } else {
    $RCLF = "";
  }
  // commando verzenden als $commande niet leeg is
  if ($commande!=="") {
    fputs($connexion, "$commande$RCLF");
    // eventuele echo
    if ($verbose) {
      affiche($commande, 1);
    }
  }//if
  // antwoord lezen
  $réponse = fgets($connexion, 1000);
  // eventuele echo
  if ($verbose) {
    affiche($réponse, 2);
  }
  // ophalen foutcode
  $codeErreur = (int) substr($réponse, 0, 3);
  // laatste regel van het antwoord?
  while (substr($réponse, 3, 1) === "-") {
    // antwoord lezen
    $réponse = fgets($connexion, 1000);
    // eventuele echo
    if ($verbose) {
      affiche($réponse, 2);
    }
  }//while
  // antwoord voltooid
  // fout teruggestuurd door de server?
  if ($codeErreur >= 500) {
    return substr($réponse, 4);
  }
// terugkeer zonder fout
  return "";
}

// --------------------------------------------------------------------------

function affiche($échange, $sens) {
  // geeft $échange weer op het scherm
  // als $sens=1, toon -->$echange
  // als $sens=2, wordt <-- $échange weergegeven zonder de laatste 2 tekens RCLF
  switch ($sens) {
    case 1:
      print "--> [$échange]\n";
      break;
    case 2:
      $L = strlen($échange);
      print "<-- [" . substr($échange, 0, $L - 2) . "]\n";
      break;
  }//schakelaar
}

Opmerkingen

  • regel 39: het configuratiebestand wordt verwerkt;
  • regel 42: er wordt een lus doorlopen over de elementen van de array [mails]. Elk element is een woordenboek [name=>infos], waarbij [name] een willekeurige naam is en [infos] een woordenboek dat de informatie bevat die nodig is voor het verzenden van een e-mail;
  • regel 46: het verzenden van de e-mail wordt verzorgd door de functie [sendmail], die drie parameters accepteert:
    • $name: de naam die aan deze verzending is gegeven;
    • $infos: het woordenboek met de informatie die nodig is voor het verzenden;
    • verbose: een booleaanse waarde die aangeeft of de communicatie tussen client en server al dan niet op de console moet worden gelogd;
  • regel 46: de functie [sendmail] retourneert een foutmelding die leeg is als er geen fout is opgetreden;
  • regel 56: de functie [sendmail] verzendt de verschillende commando’s die een SMTP-client moet verzenden:
    • regels 77-84: het commando EHLO;
    • regels 85-92: het commando MAIL FROM: ;
    • regels 93-100: de opdracht RCPT TO: ;
    • regels 101-108: de opdracht DATA;
    • regels 117-124: verzending van het bericht (Van, Aan, Onderwerp, tekst);
    • regels 125-132: het commando QUIT;
  • regel 140: de functie [sendCommand] is verantwoordelijk voor het verzenden van de opdrachten van de client naar de server SMTP. Deze functie accepteert vier parameters:
    • [$connexion]: de verbinding tussen de client en de server;
    • [$commande]: het te verzenden commando;
    • [$verbose]: als TRUE, dan wordt de communicatie tussen client en server op de console gelogd;
    • [$withRCLF]: als TRUE, stuur dan het commando afgesloten met de reeks \r\n. Dit is nodig voor alle commando’s van het protocol SMTP, maar [sendCommand] wordt ook gebruikt om het bericht te verzenden. Hier wordt de reeks \r\n niet toegevoegd;
  • regels 150-157: het commando wordt naar de server verzonden;
  • regels 158-163: de eerste regel van het antwoord wordt gelezen. Dit antwoord kan uit meerdere regels bestaan. Elke regel heeft de vorm XXX-YYY, waarbij XXX een numerieke code is, behalve de laatste regel van het antwoord, die de vorm XXX YYY heeft (zonder het teken -);
  • regels 167-174: alle regels van het antwoord worden gelezen;
  • regel 177: als de numerieke code XXX groter is dan 500, dan heeft de server een foutmelding teruggestuurd;

Resultaten

De uitvoering van het script levert de volgende console-resultaten op:


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.
  • regels 1-26: het gebruik van de server SMTP [hMailServer] om een e-mail te verzenden naar [guest@localhost] verloopt goed;
  • regels 27-52: het gebruik van de server SMTP [hMailServer] om een e-mail te verzenden naar [php7parlexemple@gmail.com] verloopt goed;
  • regels 53-65: het gebruik van de server SMTP [Gmail] om een e-mail te versturen naar [php7parlexemple@gmail.com] verloopt niet soepel: in regel 65 stuurt de server SMTP een foutcode 530 met de foutmelding. Hierin staat dat de client SMTP zich eerst via een beveiligde verbinding moet authenticeren. Onze client heeft dit niet gedaan en wordt daarom geweigerd;

16.5.6. Een tweede client SMTP schrijft met de bibliotheek [SwiftMailer]

De vorige client vertoont ten minste twee tekortkomingen:

  • hij kan geen gebruik maken van een beveiligde verbinding als de server daarom vraagt;
  • hij weet niet hoe hij bijlagen aan het bericht moet toevoegen;

In ons nieuwe script gaan we de bibliotheek [SwiftMailer] [https://swiftmailer.symfony.com/] (mei 2019) gebruiken. De installatieprocedure voor [SwiftMailer] wordt beschreven in URL [https://swiftmailer.symfony.com/docs/introduction.HTML] (mei 2019).

Start eerst Laragon:

Image

  • in [1], open een terminal;

Image

  • in [3], controleer of je je in de map [<laragon>/www] bevindt, waarbij <laragon> de installatiemap van Laragon is;
  • in [3], voer de aangegeven opdracht in (mei 2019). Controleer in URL en [https://swiftmailer.symfony.com/docs/introduction.HTML] of de opdracht correct is;
  • in [4] wordt aangegeven dat er geen installatie of update heeft plaatsgevonden. Dit komt doordat de bibliotheek al op deze computer was geïnstalleerd;
  • in [5], de installatiemap van [swiftmailer] [6];
  • in [7], een bestand dat we nodig hebben in ons script;

Controleer vervolgens of de map [<laragon>/www/vendor] [5] zich daadwerkelijk in de tak [Include Path] van NetBeans bevindt (zie paragraaf ‘link’).

Ten slotte vereist de bibliotheek [SwiftMailer] dat de extensie PHP [mbstring] actief is. Hiervoor controleren we het bestand [php.ini] (zie paragraaf ‘link’):

Image

Het script [smtp-02.php] gebruikt het volgende configuratiebestand jSON [config-smtp-02.json]:

{
    "mail to localhost via localhost": {
        "smtp-server": "localhost",
        "smtp-port": "25",
        "from": "guest@localhost",
        "to": "guest@localhost",
        "subject": "test-localhost",
        "message": "ligne 1\nligne 2\nligne 3",
        "tls": "FALSE",
        "attachments": ["/attachments/Hello from SwiftMailer.docx",
            "/attachments/Hello from SwiftMailer.pdf",
            "/attachments/Hello from SwiftMailer.odt",
            "/attachments/Cours-Tutoriels-Serge-Tahé-1568x268.png",
            "/attachments/test-localhost.eml"
        ]
    },
    "mail to gmail via gmail": {
        "smtp-server": "smtp.gmail.com",
        "smtp-port": "587",
        "from": "php7parlexemple@gmail.com",
        "to": "php7parlexemple@gmail.com",
        "subject": "test-gmail-via-gmail",
        "message": "ligne 1\nligne 2\nligne 3",
        "tls": "TRUE",
        "user": "php7parlexemple@gmail.com",
        "password": "PHP7parlexemple",
        "attachments": ["/attachments/Hello from SwiftMailer.docx",
            "/attachments/Hello from SwiftMailer.pdf",
            "/attachments/Hello from SwiftMailer.odt",
            "/attachments/Cours-Tutoriels-Serge-Tahé-1568x268.png",
            "/attachments/test-localhost.eml"
        ]
    },
    "mail to gmail via localhost": {
        "smtp-server": "localhost",
        "smtp-port": "25",
        "from": "guest@localhost",
        "to": "php7parlexemple@gmail.com",
        "subject": "test-gmail-via-localhost",
        "message": "ligne 1\nligne 2\nligne 3",
        "tls": "FALSE",
        "attachments": ["/attachments/Hello from SwiftMailer.docx",
            "/attachments/Hello from SwiftMailer.pdf",
            "/attachments/Hello from SwiftMailer.odt",
            "/attachments/Cours-Tutoriels-Serge-Tahé-1568x268.png",
            "/attachments/test-localhost.eml"
        ]
    }
}

We zien hier dezelfde rubrieken terug als in het bestand [config-smtp-01.json], met twee extra rubrieken:

  • [tls]: geeft aan dat er een beveiligde verbinding met de server SMTP moet worden gebruikt. Als [tls] gelijk is aan TRUE, moeten twee velden worden toegevoegd:
    • [user]: de gebruikersnaam waarmee de verbinding wordt geauthenticeerd;
    • [password]: zijn wachtwoord;

In ons voorbeeld hebben we de inloggegevens van gebruiker [php7parlexemple@gmail.com] gebruikt om verbinding te maken met de Gmail-server. Gebruik uw eigen gegevens;

  • [attachments]: geeft de namen op van de bestanden die aan de e-mail moeten worden toegevoegd;

De code van het script [smtp-02.php] is als volgt:


<?php

// client SMTP (SendMail Transfer Protocol) waarmee een bericht kan worden verzonden
//
// foutbeheer
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
//
// de instellingen voor het verzenden van e-mail
const CONFIG_FILE_NAME = "config-smtp-02.json";

// de configuratie ophalen
$mails = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);

// e-mails verzenden
foreach ($mails as $name => $infos) {
  // tracking
  print "Envoi du mail [$name]\n";
  // e-mail verzenden
  $résultat = sendmail($name, $infos);
  // weergave van het resultaat
  print "$résultat\n";
}//for
// einde
exit;

//-----------------------------------------------------------------------

function sendmail($name, $infos) {

  // verzendt $infos[message] naar de SMTP-server $infos[smtp-server] op poort $infos[smt-port]
  // als $infos[tls] waar is, wordt het medium TLS gebruikt
  // de e-mail wordt verzonden namens $infos[from]
  // voor de ontvanger $infos['to']
  // Het document $info[attachment] is als bijlage bij het bericht gevoegd
  // het bericht heeft als onderwerp $infos[subject]
  //
  // bericht in het formaat HTML
  $messageHTML = str_replace("\n", "<br/>", $infos["message"]);
  try {
    // aanmaken van het bericht
    $message = (new \Swift_Message())
      // onderwerp van het bericht
      ->setSubject($infos["subject"])
      // afzender
      ->setFrom($infos["from"])
      // ontvangers met een woordenboek (setTo/setCc/setBcc)
      ->setTo($infos["to"])
      // berichttekst
      ->setBody($infos["message"])
      // HTML-variant
      ->addPart("<b>$messageHTML</b>", 'text/html')
    ;
    // bijlagen
    foreach ($infos["attachments"] as $attachment) {
      // pad naar de bijlage
      $fileName = __DIR__ . $attachment;
      // we controleren of het bestand bestaat
      if (file_exists($fileName)) {
        // het document wordt aan het bericht toegevoegd
        $message->attach(\Swift_Attachment::fromPath($fileName));
      } else {
        // fout
        print "L'attachement [$fileName] n'existe pas\n";
      }
    }
    // protocol TLS ?
    if ($infos["tls"] === "TRUE") {
      // TLS
      $transport = (new \Swift_SmtpTransport($infos["smtp-server"], $infos["smtp-port"], 'tls'))
        ->setUsername($infos["user"])
        ->setPassword($infos["password"]);
    } else {
      // geen TLS
      $transport = (new \Swift_SmtpTransport($infos["smtp-server"], $infos["smtp-port"]));
    }
    // de verzendmanager
    $mailer = new \Swift_Mailer($transport);
    // verzending van het bericht
    $result = $mailer->send($message);
    // einde
    return "Message [$name] envoyé";
  } catch (\Throwable $ex) {
    // fout
    return "Erreur lors de l'envoi du message [$name] : " . $ex->getMessage();
  }
}

Opmerkingen

  • regel 10: we laden het bestand [autoload.php] uit de map [<lagagon>/www/vendor], waarbij <laragon> de installatiemap van Laragon is. Met dit bestand kunnen de definitiebestanden van de klassen uit [SwiftMailer] worden geladen zodra deze klassen voor het eerst worden gebruikt. Hierdoor hoeven we niet voor elke klasse en interface uit SwiftMailer een apart [require]-bestand aan te maken;
  • regel 32: de nieuwe functie [sendmail] met twee parameters:
    • [$name], die dient om de berichten van elkaar te onderscheiden;
    • [$infos]: de informatie die nodig is om het bericht naar de ontvanger te verzenden;
  • regel 42: we hebben twee versies van het bericht: één in platte tekst en één in HTML. Hier veranderen we de regeleinden in de code HTML <br/>;
  • regels 45-69: we definiëren het bericht met behulp van de klasse [\SwiftMessage];
  • regel 47: de methode [SwiftMessage→setSubject] wordt gebruikt om het onderwerp van het bericht vast te leggen;
  • regel 49: de methode [SwiftMessage→setFrom] wordt gebruikt om de afzender van het bericht vast te leggen;
  • regel 51: de methode [SwiftMessage→setTo] wordt gebruikt om de ontvanger van het bericht vast te leggen;
  • regel 53: de methode [SwiftMessage→setBody] wordt gebruikt om de tekst van het bericht vast te leggen;
  • regel 55: de methode [SwiftMessage→addPart] wordt gebruikt om verschillende versies van het bericht vast te leggen, in dit geval het bericht in het formaat HTML. Wanneer het bericht varianten heeft, geven e-mailprogramma’s de door de gebruiker geprefereerde variant weer;
  • regels 58-69: met de methode [SwiftMessage→addAttachment] (64) kan een bestand aan het bericht worden toegevoegd;
  • regels 70-79: zodra het te verzenden bericht is gedefinieerd, moet worden bepaald hoe het moet worden verzonden. De verzendwijze van het bericht wordt bepaald door de klasse [\Swift_SmtpTransport]. Er moeten ten minste twee gegevens worden opgegeven: de nom en de port van de server SMTP. Er is ook nog een derde: vereist de server SMTP beveiligde authenticatie?
  • regels 73-75: de instantie [\Swift_SmtpTransport] voor een beveiligde verbinding met de server SMTP;
  • regel 78: de instantie [\Swift_SmtpTransport] voor een onbeveiligde verbinding met de server SMTP;
  • regel 81: de klasse [\SwiftMailer] verzendt de berichten. Hieraan moet de gekozen transportmodus worden doorgegeven;
  • regel 83: het bericht [\SwiftMessage] wordt verzonden via de gekozen transportmethode [\Swift_SmtpTransport]. De methode [SwiftMailer→send] retourneert de booleaanse waarde FALSE als het bericht niet kon worden verzonden;
  • regels 86-89: de bibliotheek [SwiftMailer] genereert een uitzondering zodra er iets misgaat;

Opmerking: merk op dat de naamruimte van de klassen in de bibliotheek [SwiftMailer] de root \ is. We hebben de klassen [\SwiftMessage, \Swift_SmtpTransport, \SwiftMailer] expliciet vermeld om hieraan te herinneren;

Resultaten

Wanneer het script [smtp-02.php] wordt uitgevoerd, krijgt men de volgende console-uitvoer:

1
2
3
4
5
6
Envoi du mail [mail to localhost via localhost]
Message [mail to localhost via localhost] envoyé
Envoi du mail [mail to gmail via gmail]
Message [mail to gmail via gmail] envoyé
Envoi du mail [mail to gmail via localhost]
Message [mail to gmail via localhost] envoyé

Als we het Gmail-account van de gebruiker [php7parlexemple] bekijken, zien we het volgende:

Image

  • in [1], het onderwerp;
  • in [2], de afzender;
  • in [3], de ontvanger;
  • in [4], het bericht;
  • in [5-10], de bijlagen;

Als men vraagt om het originele bericht te zien, krijgt men het volgende document:


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_=_--

  • regel 9: het onderwerp;
  • regel 10: de afzender;
  • regel 11: de ontvanger;
  • regel 13: het bericht bestaat uit verschillende delen die worden afgebakend door de tags [--_=_swift_xx];
  • regels 19-24: het bericht in platte tekst;
  • regels 27-30: het bericht in HTML;
  • regels 34-36: het bijgevoegde bestand [Hello from SwiftMailer.docx];
  • regels 40-42: het bijgevoegde bestand [Hello from SwiftMailer.pdf];
  • regels 46-48: het bijgevoegde bestand [Hello from SwiftMailer.odt];
  • regels 58-60: het bijgevoegde bestand [Cours-Tutoriels-Serge-Tahé-1568x268.png];
  • regels 58-60: het bijgevoegde bestand [test-localhost.eml];
  • regels 62-114: het bijgevoegde bestand [test-localhost.eml] is zelf een bericht waarvan de inhoud wordt weergegeven in de regels 62-114. We zien dat dit bericht zelf ook bijlagen bevat;

16.6. De protocollen POP3 (Post Office Protocol) en IMAP (Internet Message Access Protocol)

16.6.1. Inleiding

Om e-mails te lezen die op een e-mailserver zijn opgeslagen, bestaan er twee protocollen:

  • het protocol POP3 (Post Office Protocol), historisch gezien het eerste protocol, maar tegenwoordig weinig gebruikt;
  • het protocol IMAP (Internet Message Access Protocol), een recenter protocol dan POP3 en momenteel het meest gebruikte;

Om het protocol POP3 te verkennen, gebruiken we de volgende architectuur:

Image

  • [Serveur B] is een lokale POP3 / IMAP-server, geïmplementeerd door de mailserver [hMailServer];
  • [Client A] is een POP3 / IMAP-client in verschillende vormen:
    • de client [RawTcpClient] om het protocol POP3 te ontdekken;
    • een script PHP dat het protocol POP3 van de client [RawTcpClient] nabootst;
    • een script PHP dat gebruikmaakt van de bibliotheek IMAP van PHP, waarmee zowel IMAP- als POP3-clients kunnen worden geïmplementeerd;

16.6.2. Kennismaking met het POP3-protocol

Allereerst gebruiken we het script [smtp-01.php] om een e-mail te versturen naar de gebruiker [guest@localhost]. Als u de bijbehorende tests voor het script hebt uitgevoerd, heeft deze gebruiker normaal gesproken e-mails ontvangen, maar we hebben dit niet kunnen verifiëren. Om hem een nieuwe e-mail te sturen, gebruikt u bijvoorbeeld het volgende configuratiebestand [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"
    }
}

Laten we nu met de client [RawTcpClient] bekijken hoe we de mailbox van de gebruiker [guest@localhost] kunnen lezen:


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…
  • regel 1: de server POP3 werkt doorgaans op poort 110. Dat is hier het geval;
  • regel 5: het commando [USER] wordt gebruikt om de gebruiker te specificeren wiens mailbox men wil lezen;
  • regel 7: het commando [PASS] dient om het wachtwoord van die gebruiker in te stellen;
  • regel 9: het commando [LIST] vraagt de lijst met berichten op die zich in de mailbox van de gebruiker bevinden;
  • regel 14: het commando [RETR] vraagt om het bericht waarvan het nummer wordt opgegeven;
  • regel 29: het commando [DELE] vraagt om het bericht met het opgegeven nummer te verwijderen;
  • regel 40: het commando [QUIT] geeft aan de server door dat men klaar is;

Het antwoord van de server kan verschillende vormen aannemen:

  • een enkele regel die begint met [+OK] om aan te geven dat het vorige commando van de client is geslaagd;
  • één regel die begint met [-ERR] om aan te geven dat het vorige commando van de client is mislukt;
  • meerdere regels waarin:
    • de eerste regel begint met [+OK];
    • de laatste regel bestaat uit één enkele punt;

16.6.3. Een eenvoudig script dat het protocol POP3 implementeert

Image

Aangezien het protocol POP3 dezelfde structuur heeft als het protocol SMTP, is het script [pop3-01.php] een aanpassing van het script [smtp-01.php]. Het zal het volgende configuratiebestand [config-pop3-01.json] hebben:

1
2
3
4
5
6
7
8
9
{
    "localhost:110": {
        "server": "localhost",
        "port": "110",
        "user": "guest@localhost",
        "password": "guest",
        "maxmails":5
    }
}
  • regels 3-4: de opgevraagde server POP3 is de lokale server [hMailServer];
  • regels 5-6: we willen de mailbox van de gebruiker [guest@localhost] lezen;
  • regel 7: er worden maximaal 5 e-mails gelezen;

Het script [pop3-01.php] is als volgt:


<?php

// POP3-client (Post Office Protocol) waarmee berichten uit een mailbox kunnen worden gelezen
// communicatieprotocol    POP3 client-server
// -> client maakt verbinding via poort 110 van de SMTP-server
// <- de server stuurt hem een welkomstbericht
// -> de client stuurt het commando USER gebruiker
// <- de server antwoordt met OK of niet
// -> de client verstuurt het commando PASS mot_de_passe
// <- server antwoordt met OK of niet
// -> de client verstuurt het commando LIST
// <- server antwoordt met OK of niet
// -> de client verstuurt het commando RETR, met een uniek nummer voor elke e-mail
// <- server antwoordt met OK of niet. Indien OK, verzendt de inhoud van de gevraagde e-mail
// -> de server verstuurt alle regels van de e-mail en sluit af met een regel die het
// enige teken bevat.
// -> de client verstuurt het commando DELE nr. om een e-mail te verwijderen
// <- de server antwoordt met OK of niet
// // -> de client verstuurt het commando QUIT om de dialoog met de server te beëindigen
// <- server antwoordt met OK of niet
// de antwoorden van de server hebben de vorm +OK tekst of -ERR tekst
// Het antwoord kan uit meerdere regels bestaan. In dat geval bestaat de laatste regel uit één enkele punt
// De uitgewisselde tekstregels moeten eindigen met de tekens RC(#13) en LF(#10)
//
// POP3-client (SendMail Transfer Protocol) waarmee e-mails kunnen worden gelezen
//
// foutbeheer
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);
//
// de parameters voor het verzenden van e-mail
const CONFIG_FILE_NAME = "config-pop3-01.json";

// de configuratie wordt opgehaald
$mailboxes = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);

// mailboxen lezen
foreach ($mailboxes as $name => $infos) {
  // opvolging
  print "Lecture de la boîte à lettres [$name]\n";
  // de mailbox uitlezen
  $résultat = readmail($name, $infos, TRUE);
  // weergave van het resultaat
  print "$résultat\n";
}//for
// einde
exit;

//readmail
//-----------------------------------------------------------------------

function readmail(string $name, array $infos, bool $verbose = TRUE): string {
  // leest de inhoud van de mailbox [$name]
  // importeert alle berichten
  // elk bericht wordt verwijderd nadat het is gelezen
  // Als $verbose=1, houdt het de communicatie tussen client en server bij
  //
  // een verbinding met de server SMTP tot stand brengen
  $connexion = fsockopen($infos["server"], (int) $infos["port"]);
  // terugkeren bij fout
  if ($connexion === FALSE) {
    return sprintf("Echec de la connexion au site (%s,%s) : %s", $infos["smtp-server"], $infos["smtp-port"]);
  }
  // $connexion vertegenwoordigt een bidirectionele communicatiestroom
  // tussen de client (dit programma) en de benaderde POP3-server
  // dit kanaal wordt gebruikt voor de uitwisseling van opdrachten en informatie
  // na het tot stand brengen van de verbinding stuurt de server een welkomstbericht dat wordt gelezen
  $erreur = sendCommand($connexion, "", $verbose, TRUE);
  if ($erreur !== "") {
    // de verbinding wordt verbroken
    fclose($connexion);
    // terug
    return $erreur;
  }
  // opdracht USER
  $erreur = sendCommand($connexion, "USER {$infos["user"]}", $verbose, TRUE);
  if ($erreur !== "") {
    // verbinding verbreken
    fclose($connexion);
    // terug
    return $erreur;
  }
  // opdracht PASS
  $erreur = sendCommand($connexion, "PASS {$infos["password"]}", $verbose, TRUE);
  if ($erreur !== "") {
    // verbinding beëindigd
    fclose($connexion);
    // terug
    return $erreur;
  }
  // commando LIST
  $premièreLigne = "";
  $erreur = sendCommand($connexion, "LIST", $verbose, TRUE, $premièreLigne);
  if ($erreur !== "") {
    // verbinding beëindigd
    fclose($connexion);
    // terug
    return $erreur;
  }
  // analyse van de eerste regel om het aantal berichten te bepalen
  $champs = [];
  preg_match("/^\+OK (\d+)/", $premièreLigne, $champs);
  $nbMessages = (int) $champs[1];
  // we doorlopen de berichten
  $iMessage = 0;
  while ($iMessage < $nbMessages && $iMessage < $infos["maxmails"]) {
    // commando RETR  
    $erreur = sendCommand($connexion, "RETR " . ($iMessage + 1), $verbose, TRUE);
    if ($erreur !== "") {
      // verbinding verbreken
      fclose($connexion);
      // terug
      return $erreur;
    }
    // commando DELE
    $erreur = sendCommand($connexion, "DELE " . ($iMessage + 1), $verbose, TRUE);
    if ($erreur !== "") {
      // verbinding beëindigd
      fclose($connexion);
      // terug
      return $erreur;
    }
    // volgend bericht
    $iMessage++;
  }
  // commando QUIT
  $erreur = sendCommand($connexion, "QUIT", $verbose, TRUE);
  if ($erreur !== "") {
    // verbinding verbreken
    fclose($connexion);
    // terug
    return $erreur;
  }
  // einde
  fclose($connexion);
  return "Terminé";
}

// --------------------------------------------------------------------------

function sendCommand($connexion, string $commande, bool $verbose, bool $withRCLF, string &$premièreLigne = ""): string {
  // verzendt $commande naar het kanaal $connexion
  // logboekmodus indien $verbose=1
  // als $withRCLF=1, voeg dan de reeks RCLF toe aan de uitwisseling
  // zet de eerste regel van het antwoord in [$premièreLigne
  // ]
  // gegevens
  if ($withRCLF) {
    $RCLF = "\r\n";
  } else {
    $RCLF = "";
  }
  // verzend commando als $commande niet leeg is
  if ($commande !== "") {
    fputs($connexion, "$commande$RCLF");
    // eventuele echo
    if ($verbose) {
      affiche($commande, 1);
    }
  }//if
  // antwoord lezen
  $réponse = fgets($connexion, 1000);
  // de eerste regel wordt opgeslagen
  $premièreLigne = $réponse;
  // eventuele echo
  if ($verbose) {
    affiche($réponse, 2);
  }
  // foutcode ophalen
  $codeErreur = substr($réponse, 0, 1);
  if ($codeErreur === "-") {
    // er is een fout opgetreden
    return substr($réponse, 5);
  }
  // bijzondere gevallen van de commando's RETR en LIST, die antwoorden van meerdere regels hebben
  $commande = substr(strtolower($commande), 0, 4);
  if ($commande === "list" || $commande === "retr") {
    // laatste regel van het antwoord?
    $champs = [];
    $match = preg_match("/^\.\s+$/", $réponse, $champs);
    while (!$match) {
      // antwoord lezen
      $réponse = fgets($connexion, 1000);
      // eventuele echo
      if ($verbose) {
        affiche($réponse, 2);
      }
      // analyse van het antwoord
      $champs = [];
      $match = preg_match("/^\.\s+$/", $réponse, $champs);
    }//while
  }
  // terugkeer zonder fout
  return "";
}

// --------------------------------------------------------------------------

function affiche($échange, $sens) {
  // geeft $échange weer op het scherm
  // als $sens=1, geef dan -->$echange weer
  // als $sens=2, wordt <-- $échange weergegeven zonder de laatste 2 tekens RCLF
  switch ($sens) {
    case 1:
      print "--> [$échange]\n";
      break;
    case 2:
      $L = strlen($échange);
      print "<-- [" . substr($échange, 0, $L - 2) . "]\n";
      break;
  }//schakelaar
}

Opmerkingen

Zoals gezegd is [pop3-01.php] een aanpassing van het script [smtp-01.php] dat we al hebben besproken. We zullen alleen de belangrijkste verschillen toelichten:

  • regel 55: de functie [readmail] is verantwoordelijk voor het ophalen van de e-mails uit de mailbox. De inloggegevens voor deze mailbox staan in het woordenboek [$infos];
  • regels 61-66: het tot stand brengen van een verbinding met de server POP3;
  • regels 71-77: het welkomstbericht van de server wordt gelezen;
  • regels 78-85: het commando [USER] wordt verzonden om de gebruiker te identificeren wiens e-mails we willen ophalen;
  • regels 86-93: het commando [PASS] wordt verzonden om het wachtwoord van deze gebruiker op te geven;
  • regels 94-102: het commando [LIST] wordt verzonden om te achterhalen hoeveel e-mails er in de mailbox van deze gebruiker staan.
  • regel 96: de parameter [$premièreLigne] wordt toegevoegd aan de parameters van de functie [readmail]. In de eerste regel van het antwoord op het commando LIST geeft de server aan hoeveel berichten er in de mailbox staan;
  • regels 104-106: het aantal berichten wordt opgehaald uit de eerste regel van het antwoord;
  • regels 109-128: er wordt een lus doorlopen voor elk bericht. Voor elk bericht worden twee commando's verzonden:
    • RETR i: om bericht nr. i op te halen (regels 111-117);
    • DELE i: om het bericht te verwijderen zodra het is gelezen (regels 118-125);
  • regels 129-136: we sturen het commando [QUIT] om de server te laten weten dat we klaar zijn;
  • regels 178-194: voor de commando’s [LIST] en [RETR] bestaat het antwoord van de server uit meerdere regels, waarvan de laatste uit één enkele punt bestaat;

Resultaten

Bij uitvoering krijgt men de volgende resultaten:


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.

Dit is een eenvoudige POP3-client waaraan bepaalde mogelijkheden ontbreken:

  1. de mogelijkheid om te communiceren met een beveiligde POP3-server;
  2. de mogelijkheid om bijlagen bij een bericht te lezen;

We gaan de eerste mogelijkheid implementeren met de functies [imap] en PHP.

16.6.4. POP3 / IMAP-client geïmplementeerd met de functies [imap] van PHP

We moeten eerst controleren of de functies [imap] beschikbaar zijn in de versie van PHP die we gebruiken. We openen het bestand [php.ini] dat in de paragraaf ‘link’ wordt beschreven en zoeken naar de regels die verwijzen naar [imap]:

Image

Regel 895: controleer of de extensie [imap] is ingeschakeld.

Het script [imap-01.php] zal het volgende bestand jSON [config-imap-01.json] verwerken:

{

    "{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX": {
        "imap-server": "imap.gmail.com",
        "imap-port": "993",
        "user": "php7parlexemple@gmail.com",
        "password": "PHP7parlexemple",
        "output-dir": "output/gmail-imap",
        "prefix": "message-"
    },
    "{localhost:110/pop3}": {
        "imap-server": "localhost",
        "imap-port": "110",
        "user": "guest@localhost",
        "password": "guest",
        "pop3": "TRUE",
        "output-dir": "output/localhost-pop3",
        "prefix": "message-"
    }
}

Het bestand [config-imap-01.json] definieert een array van IMAP / POP3-servers waarmee contact moet worden opgenomen. Elk element is een structuur [clé:valeur], waarbij:

  • [clé]: de server is waarmee verbinding moet worden gemaakt. We hebben er hier twee:
    • [{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX]: verwijst naar de server [imap.gmail.com] die luistert op poort 993. Het client/server-protocol is IMAP. De parameter /ssl geeft aan dat de communicatie tussen client en server beveiligd is. De parameter /novalidate-cert vraagt de client om het beveiligingscertificaat dat de server zal versturen niet te controleren. Ten slotte beheert een server met de naam IMAP een reeks mailboxen voor één en dezelfde gebruiker. Door INBOX op te geven in de URL van de server IMAP, geven we aan dat we geïnteresseerd zijn in de mailbox met de naam INBOX, die normaal gesproken de mailbox is waar nieuwe berichten binnenkomen;
    • [{localhost:110/pop3}INBOX]: verwijst naar de server [localhost] die luistert op poort 110. Het client/server-protocol is hier POP3;
  • [valeur]: is een woordenboek waarin de volgende punten worden gespecificeerd:
    • [imap-server]: de naam van de server IMAP of POP3;
    • [imap-port]: de poort van de server IMAP of POP3;
    • [user]: de eigenaar van wie u de mailbox wilt lezen;
    • [password]: zijn wachtwoord;
    • [output-dir]: de map waarin de berichten moeten worden opgeslagen;
    • [prefix]: de bestandsnamen waarin de berichten worden opgeslagen, hebben de vorm prefixN, waarbij N een berichtnummer is;
    • [pop3]: een booleaanse waarde in TRUE om aan te geven dat het gebruikte protocol POP3 is. In dit geval wordt een bericht verwijderd nadat het is gelezen. Dit is de gebruikelijke werkwijze van POP3-servers: een gelezen bericht wordt niet op de server bewaard;

Het script [imap-01.php] is als volgt:


<?php

// client IMAP (Internet Message Access Protocol) waarmee e-mails kunnen worden gelezen
//
// strikte naleving van de gedeclareerde typen van functieparameters
declare (strict_types=1);
// foutbeheer
error_reporting(E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
//
// parameters voor het lezen van e-mail
const CONFIG_FILE_NAME = "config-imap-01.json";

// de configuratie ophalen
$mailboxes = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);

// mailboxen lezen
foreach ($mailboxes as $name => $infos) {
  // opvolging
  print "------------Lecture de la boîte à lettres [$name]\n";
  // de mailbox uitlezen
  readmailbox($name, $infos);
}
// einde
exit;

//-----------------------------------------------------------------------

function readmailbox(string $name, array $infos): void {
  // Verbindingspoging
  $imapResource = imap_open($name, $infos["user"], $infos["password"]);
  // Test van de retourwaarde van de functie imap_open()
  if (!$imapResource) {
    // Mislukt
    print "La connexion au serveur [$name] a échoué : " . imap_last_error() . "\n";
  } else {
    // Verbinding tot stand gebracht
    print "Connexion établie avec le serveur [$name].\n";
    // Totaal aantal berichten in de mailbox
    $nbmsg = imap_num_msg($imapResource);
    print "Il y a [$nbmsg] messages dans la boîte à lettres [$name]\n";
    // Ongelezen berichten in de huidige mailbox
    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) {
          // informatie over bericht nr. $msgNumber wordt opgehaald
          $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);
          }
          // de tekst van bericht nr. $msgNumber wordt opgehaald
          getMailBody($imapResource, $msgNumber, $infos);

          // als het protocol POP3 is, wordt het bericht verwijderd
          $pop3 = $infos["pop3"];
          if ($pop3 !== NULL) {
            // het bericht wordt in twee stappen verwijderd
            imap_delete($imapResource, $msgNumber);
            imap_expunge($imapResource);
          }
        }
      }
    }
  }
  // de verbinding wordt verbroken
  $imapClose = imap_close($imapResource);
  if (!$imapClose) {
    // Mislukt
    print "La fermeture de la connexion a échoué : " . imap_last_error() . "\n";
  } else {
    // geslaagd
    print "Fermeture de la connexion réussie.\n";
  }
}

function getMailBody($imapResource, int $msgNumber, array $infos): void {
  // de tekst van bericht nr. $msgNumber wordt opgehaald
  $corpsMail = imap_body($imapResource, $msgNumber);

  print "Enregistrement du message dans le fichier {$infos["output-dir"]}/{$infos["prefix"]}$msgNumber\n";
  // de map wordt aangemaakt indien nodig
  if (!file_exists($infos["output-dir"])) {
    mkdir($infos["output-dir"]);
  }
  // het bericht wordt opgeslagen
  if (!file_put_contents($infos["output-dir"] . "/" . $infos["prefix"] . $msgNumber, $corpsMail)) {
    print "Echec de l'enregistrement\n";
  }
}

Opmerkingen

  • regels 19-24: er wordt een lus doorlopen over alle servers die in het configuratiebestand zijn gevonden;
  • regel 32: de functie [raedmailbox] leest de mailbox die is opgegeven in [$name];
  • regel 32: er wordt een verbinding geopend met IMAP;
    • de eerste parameter is de URL IMAP van de te lezen mailbox;
    • de tweede parameter is de gebruikersnaam van de eigenaar van deze mailbox;
    • de derde parameter is zijn wachtwoord;

De functie [imap_open] zorgt voor de beveiliging van de verbinding als de URL IMAP van de mailbox de parameter /ssl heeft;

  • regel 41: met de functie [imap_num_msg] kan het totale aantal berichten in de mailbox worden opgehaald;
  • regel 46: met de functie [imap_search] kunnen bepaalde berichten worden opgezocht. Hier zoeken we naar berichten die nog niet zijn gelezen (UNSEEN). De tweede parameter is een selectiecriterium. Er zijn er ruim twintig. De functie [imap_search] retourneert een array met berichtnummers. Deze kunnen twee vormen aannemen: volgnummer of UID-bericht-ID. Standaard retourneert de functie [imap_search] een array met volgnummers. Als we een derde parameter [SE_UID] toevoegen, krijgen we de identificatiecodes UID van de berichten;
  • regel 47: de functie [imap_search] retourneert de booleaanse waarde FALSE als er geen berichten zijn gevonden;
  • regel 50: er wordt een lus doorlopen voor alle ongelezen berichten;
  • regel 52: een bericht heeft headers die we kunnen ophalen met de functie [imap_headerinfo]. De tweede parameter daarvan is normaal gesproken een berichtvolgnummer. Als men een bericht-ID UID wil instellen, moet de derde parameter worden ingesteld op [FT_UID];
  • regel 53: de functie [imap_headerinfo] retourneert de booleaanse waarde FALSE als ze haar taak niet heeft kunnen uitvoeren. Anders retourneert ze een complex object dat wordt weergegeven met de functie [print_r], regel 57;
  • regel 60: na de kopteksten wordt nu de hoofdtekst van het bericht opgevraagd met de functie [imap_body]. Deze functie retourneert NULL als ze haar taak niet heeft kunnen uitvoeren;
  • regels 84-87: de hoofdtekst van het bericht wordt opgeslagen in een lokaal bestand;
  • regels 63-68: als het gebruikte protocol POP3 was, wordt het zojuist gelezen bericht verwijderd:
    • de functie [imap_delete] markeert het bericht als „te verwijderen”, maar verwijdert het niet;
    • de functie [imap_expunge] verwijdert fysiek alle berichten die als ‘te verwijderen’ zijn gemarkeerd;
  • regel 74: de verbinding met de server IMAP wordt verbroken. Hiervoor wordt de functie [imap_close] gebruikt;
  • regel 86: met de functie [imap_body] kan de tekst van een bericht worden opgehaald aan de hand van het nummer;

Laten we het script [smtp-02.json] uitvoeren, zodat de gebruiker [php7parlexemple] van Gmail en de gebruiker [guest] van [localhost] nieuwe berichten krijgen. Laten we vervolgens het script [imap-01.php] uitvoeren om hun mailboxen te lezen.

De console-uitvoer is als volgt:


------------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.

Als we direct na deze resultaten het script [imap-01.php] opnieuw uitvoeren, zijn de resultaten als volgt:


------------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.
  • regel 3: er staat nog steeds hetzelfde aantal berichten in de Gmail-postbus, maar er zijn geen nieuwe ongelezen berichten meer (regel 5). Dit toont aan dat de vorige uitvoering de gelezen berichten van de status ‘ongelezen’ naar de status ‘gelezen’ heeft veranderd;
  • regel 9: er zijn geen berichten meer in de mailbox van de gebruiker [guest@localhost]. Dit komt doordat in de vorige uitvoering de gelezen berichten op [localhost] vervolgens werden verwijderd;

De berichten zijn lokaal opgeslagen:

Image

Als we bijvoorbeeld de inhoud van bericht nr. 26 in Gmail bekijken, zien we het volgende:



--_=_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_=_--

  • regels 11-13: het bericht in platte tekst;
  • regel 19: het bericht HTML;
  • regel 25: de bijlage;

Laten we dit script eens verbeteren, zodat we de verschillende soorten berichten en de bijlagen in afzonderlijke bestanden krijgen.

16.6.5. Verbeterde client POP3 / IMAP

In het script [imap-01.php] wordt de hoofdtekst van bericht nr. i weergegeven als een tekstbestand dat zowel de verschillende soorten berichten als de gecodeerde inhoud van de verschillende bijlagen bevat. Het is mogelijk om de structuur van het bericht te achterhalen om deze verschillende onderdelen te identificeren. In het script [imap-02.php] passen we de functie [getMailBody] als volgt aan:


function getMailBody($imapResource, int $msgNumber, array $infos): void {
  // de structuur van het bericht wordt opgehaald
  $structure=imap_fetchstructure($imapResource, $msgNumber);
  // we geven deze weer
  print_r($structure);
}
  • regel 3: we vragen de structuur van het bericht op;
  • regel 5: we geven deze weer;

Het doel is om inzicht te krijgen in de informatie die in de structuur van een bericht is opgenomen, zodat we kunnen zien hoe we de verschillende onderdelen ervan kunnen verkrijgen. In ons voorbeeld wordt het bericht verzonden door het script [smtp-02.php] met de volgende configuratie [config-smtp-02.json]:

{
    "mail to localhost via localhost": {
        "smtp-server": "localhost",
        "smtp-port": "25",
        "from": "guest@localhost",
        "to": "guest@localhost",
        "subject": "test-localhost",
        "message": "ligne 1\nligne 2\nligne 3",
        "tls": "FALSE",
        "attachments": [
            "/attachments/Hello from SwiftMailer.docx",
            "/attachments/Hello from SwiftMailer.pdf",
            "/attachments/Hello from SwiftMailer.odt",
            "/attachments/Cours-Tutoriels-Serge-Tahé-1568x268.png",
            "/attachments/test-localhost.eml"
        ]
    }
}

Het gaat dus om een bericht met vijf bijlagen dat naar [guest@localhost] wordt verzonden (regels 11-15). Het script [imap-02.php] wordt uitgevoerd met de volgende configuratie: [config-imap-01.json]:

{
    "{localhost:110/pop3}": {
        "imap-server": "localhost",
        "imap-port": "110",
        "user": "guest@localhost",
        "password": "guest",
        "pop3": "TRUE",
        "output-dir": "output/localhost-pop3"
    }
}

Het is dus de mailbox van [guest@localhost] die wordt misbruikt (regel 5). Het script [imap-02.php] geeft vervolgens de structuur weer van het bericht dat door [smtp-02.php] is verzonden. Deze structuur, die op de console wordt weergegeven, is als volgt:


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
                        (

                        )

                )

        )

)

Opmerkingen

  • De documentatie PHP van de functie [imap_fetchstructure] geeft de betekenis weer van de verschillende velden van het object dat door de functie wordt geretourneerd:

Image

De numerieke waarden van het veld [type] hebben de volgende betekenis:

Image

De numerieke waarden van het veld [encoding] hebben de volgende betekenis:

Image

Het bericht dat door [imap-01.php] werd opgeslagen, begon met de volgende tekst:


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_=_
  • regels 15) en 33) bakenen het bericht van het type [multipart/mixed] af (regel m);
  • regels 18) en 16) markeren het eerste deel van het bericht: het bericht in platte tekst;
  • regels 26) en 32) bakenen het tweede deel van het bericht af: het bericht HTML;

We vinden de verschillende gegevens van het bovenstaande bericht terug in het object dat door [imap_fetchstructure] wordt geretourneerd:


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
                                                )

                                        )

                                )

                        )

                )

  • regel 3: het bericht is van het type MIME (Multipurpose Internet Mail Extensions) [multipart];
  • regel 4: het bericht is 7-bits gecodeerd;
  • regel 5: [ifsubtype]=1 geeft aan dat er een veld [subtype] in de structuur aanwezig is;
  • regel 6: het veld [subtype] verwijst naar een subtype MIME, in dit geval het type [mixed]. In totaal is het type MIME van het document [multipart/mixed];
  • regel 7: [ifdescription]=0 geeft aan dat er geen veld [description] in de structuur voorkomt;
  • regel 8: [ifid]=0 geeft aan dat er geen veld [id] in de structuur voorkomt;
  • regel 10: [ifdisposition]=0 geeft aan dat er geen veld [disposition] in de structuur voorkomt;
  • regel 11: [ifdparameters]=0 geeft aan dat er geen veld [dparameters] in de structuur voorkomt;
  • regel 12: [ifparameters]=1 geeft aan dat er een veld [parameters] in de structuur aanwezig is;
  • regel 13: het veld [parameters] beschrijft de parameters van het bericht. Hier is er slechts één;
  • regels 15-19: dit object beschrijft de volgende regel van het tekstbericht:
boundary="_=_swift_1558872295_5bc8ee2ca8b3723c0b39ca8bbfbebdeb_=_"

Deze regels dienen om het bericht af te bakenen. In het bericht dat door [imap-01.php] is opgehaald, komt het zojuist beschreven deel van het bericht overeen met regel m). Het attribuut [boundary] is niet hetzelfde, omdat de schermafbeeldingen betrekking hebben op hetzelfde bericht, maar op verschillende tijdstippen zijn verzonden;

  • regel 23: hier begint de structuur van de verschillende delen van het bericht;
  • regels 25-45: dit eerste deel is van het type [multipart/alternative]. Het komt overeen met regel p) van de tekst van het bericht;
  • regel 47: dit eerste deel heeft zelf weer subdelen;
  • regels 47-70: dit eerste subdeel is van het type [text/plain] (regels 51, 54), is gecodeerd in het type [ENCQUOTEDPRINTABLE] (regel 52) en heeft een parameter [charset=utf-8] (regels 66-67);
  • de regels 49-72 beschrijven de regels s-x van het tekstbericht;
  • regels 74-99: beschrijven het tweede subdeel van het deel [multipart/alternative];
  • regels 74-99: dit tweede subdeel is van het type [text/HTML] (regels 76, 79), is gecodeerd als het type [ENCQUOTEDPRINTABLE] (regel 77) en heeft een parameter [charset=utf-8] (regels 89-93);
  • de regels 74-99 beschrijven de regels aa-ad van het tekstbericht;

Het deel [multipart/alternative] is nu voltooid. Het deel [application/vnd.openxmlformats-officedocument.wordprocessingml.document] begint, beschreven door de volgende tekst:

1
2
3
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"

Ook hier is deze informatie terug te vinden in het object dat door de functie [imap_fetchstructure] wordt geretourneerd:


[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
                                )

                        )

                )

            
  • regel 1: dit is het tweede deel van het totale bericht. Ter herinnering: het eerste deel was van het type [multipart/alternative];
  • regels 3-6: dit tweede deel is van het type [application/vnd.openxmlformats-officedocument.wordprocessingml.document] (regels 3 en 6) en is gecodeerd in Base 64 (regel 4);
  • regel 11: dit tweede deel is een bijlage (regel 11) en heeft twee parameters: [filename=Hello from SwiftMailer.docx] (regels 15-21) en [name=Hello from SwiftMailer.docx] (regels 26-32). Opgemerkt moet worden dat deze laatste parameter niet in het tekstbericht voorkomt. Hij is dus toegevoegd in de functie [imap_fetchstructure];

De regels 1-36 worden voor elk van de vijf bijlagen van het bericht herhaald.

Met de functie [imap_fetch_structure] kunnen we dus de structuur van een bericht verkrijgen. Deze structuur definieert onderdelen die op hun beurt weer subonderdelen kunnen hebben. Om de tekst van een onderdeel of subonderdeel te verkrijgen, gebruiken we de functie [imap_fetchbody].

We passen de functie [getMailBody], waarmee we de hoofdtekst van een bericht kunnen ophalen, als volgt aan:


function getMailBody($imapResource, int $msgNumber, array $infos, object $infosMail): void {
  // de structuur van het bericht wordt opgehaald
  $structure = imap_fetchstructure($imapResource, $msgNumber);
  if ($structure !== FALSE) {
    // de verschillende onderdelen worden opgehaald
    getParts($imapResource, $msgNumber, $infos, $infosMail, $structure);
  }
}

function getParts($imapResource, int $msgNumber, array $infos, object $infosMail, stdclass $part, string $sectionNumber = "0"): void {
  // berekening van het sectienummer
  if (substr($sectionNumber, 0, 2) === "0.") {
    $sectionNumber = substr($sectionNumber, 2);
  }
  print "-----contenu de la partie n° [$sectionNumber]\n";
  // inhoudstype
  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;
  }
  // coderingstype
  $encodings=["7 bits", "8 bits", "binaire", "base 64", "quoted-printable", "autre"];
  print "Transfer-Encoding : ".$encodings[$part->encoding]."\n";
   
  // we gaan verder met eventuele subonderdelen
  if (isset($part->parts)) {
    for ($i = 1; $i <= count($part->parts); $i++) {
      // een nieuw deel van het bericht
      $subpart = $part->parts[$i - 1];
      // recursieve aanroep – de hoofdtekst van het deel wordt opgevraagd [$subpart]
      getParts($imapResource, $msgNumber, $infos, $infosMail, $subpart, "$sectionNumber.$i");
    }
  }
}

Opmerkingen

  • regel 3: we halen de structuur van het bericht op;
  • regel 6: we vragen om de verschillende onderdelen die in de tabel [parts] van de structuur staan;
  • regel 10: de functie [getParts] ontvangt de volgende parameters:
    • [$imapResource]: de verbinding met de server IMAP;
    • [$msgNumber]: het volgnummer van het bericht waarvan de onderdelen worden opgevraagd;
    • [$infos]: informatie over waar de gevonden delen in het lokale bestandssysteem moeten worden opgeslagen;
    • [$infosMail]: algemene informatie over de e-mail (afzender, ontvanger(s), onderwerp…;
    • [$part]: een object dat een deel van het bericht vertegenwoordigt;
    • [$sectionNumber]: een sectie- (of deel-)nummer van het bericht;
  • regels 17-34: het type inhoud van deel nr. [$section] van het bericht wordt weergegeven. Hiervoor wordt gebruikgemaakt van de velden [$part→type] en [$part→subtype] van het deel [$part];
  • regels 36-37: het coderingstype van het deel [$sectionNumber] wordt weergegeven;
  • regels 40-47: misschien heeft het deel waarvan we zojuist de informatie hebben weergegeven zelf weer subdelen;
  • regels 41-46: als dat het geval is, vragen we om het inhoudstype van de verschillende subonderdelen van het onderdeel dat zojuist is weergegeven. Hier maken we een recursieve aanroep naar de functie [getParts];

Opnieuw sturen we een e-mail naar de Gmail-gebruiker [php7parlexemple@gmail.com] met het script [smtp-02.php] en lezen we deze met het vorige script [imap-02.php]. Dit levert de volgende console-uitvoer op:


------------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.

We slagen er inderdaad in om de verschillende soorten inhoud van het bericht en hun coderingstype op te halen. De nummering van de delen volgt de volgende regel:

  • regels 6-7: het deel [multipart/mixed], dat het volledige bericht vertegenwoordigt, draagt nummer 0. De verschillende delen van dit object krijgen vervolgens de nummers 1, 2…

Het bericht bestaat in totaal uit vijf delen:

  • regels 9-10: het deel [multipart/alternative] met nummer 1;
  • regels 17-18: het deel [APPLICATION/VND.OPENXMLFORMATS-OFFICEDOCUMENT.WORDPROCESSINGML.DOCUMENT] met nummer 2. Dit is de bijlage van een Word-bestand;
  • regels 20-21: het gedeelte [APPLICATION/PDF] met nummer 3. Dit is de bijlage van een bestand PDF;
  • regels 23-24: het gedeelte [APPLICATION/VND.OASIS.OPENDOCUMENT.TEXT] met nummer 4. Dit is de bijlage van een bestand OpenOffice;
  • regels 26-27: het gedeelte [UNKNOWN/PNG] met nummer 5. Dit is de bijlage van een afbeeldingsbestand;
  • regels 30-31: het onderdeel [MESSAGE/RFC822] met nummer 6. Dit is de bijlage van een e-mail;

Wanneer een deel subdelen bevat, worden deze genummerd als x.1, x.2… waarbij x het nummer is van het overkoepelende deel. Dus:

  • regels 11-12: het eerste deel van het deel [multipart/alternative] heeft nummer 1.1. Dit is een inhoud van het type [text/plain]: de tekst van de e-mail;
  • regels 14-15: het tweede deel van het onderdeel [multipart/alternative] heeft nummer 1.2. Dit is een inhoud van het type [text/HTML]: de e-mailtekst in HTML;
  • regels 32-33: het eerste deel van de bijlage [MESSAGE/RFC822] heeft nummer 6.1. Dit is een inhoud van het type [text/plain]. Volgens de standaard MIME wijkt de nummering van de delen van een e-mailbijlage [MESSAGE/RFC822] echter af van de hierboven beschreven regel. Zo draagt het eerste deel van de bijlage [MESSAGE/RFC822] dus niet het nummer 6.1, maar een ander nummer;

Nu we weten hoe we de verschillende delen en subdelen van een e-mail kunnen herkennen, moeten we nog de inhoud ervan ophalen.

De code van het script verandert als volgt:


function getParts($imapResource, int $msgNumber, array $infos, object $infosMail, stdclass $part, string $sectionNumber = "0"): void {
  // berekening van het sectienummer
  if (substr($sectionNumber, 0, 2) === "0.") {
    $sectionNumber = substr($sectionNumber, 2);
  }
  print "-----contenu de la partie n° [$sectionNumber]\n";
  // inhoudstype
  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;
  }
  // coderingstype
  $encodings = ["7 bits", "8 bits", "binaire", "base 64", "quoted-printable", "autre"];
  print "Transfer-Encoding : " . $encodings[$part->encoding] . "\n";

  // is dit een bericht?
  if ($part->type === TYPEMESSAGE) {
    // de subonderdelen van dit bericht (bijgevoegde e-mail) worden niet verwerkt
    // de hoofdtekst van de bijgevoegde e-mail wordt weergegeven
    print imap_fetchbody($imapResource, $msgNumber, $sectionNumber);
  } else {
    // we gaan verder met eventuele subonderdelen
    if (isset($part->parts)) {
      for ($i = 1; $i <= count($part->parts); $i++) {
        // een nieuw deel van het bericht
        $subpart = $part->parts[$i - 1];
        // recursieve aanroep – de hoofdtekst van het onderdeel [$subpart] wordt opgevraagd
        getParts($imapResource, $msgNumber, $infos, $infosMail, $subpart, "$sectionNumber.$i");
      }
    } else {
      // er zijn geen subdelen – vervolgens wordt de hoofdtekst van het bericht weergegeven
      print imap_fetchbody($imapResource, $msgNumber, $sectionNumber);
    }
  }
}

Opmerkingen

  • regel 46: de functie [imap_fetchbody] haalt de hoofdtekst op van deel nr. [$sectionNumber] van het bericht. De nummering van de delen van een bericht volgt de eerder uitgelegde regel;
  • regel 1: we beginnen met sectie „0“;
  • regel 41: de subdelen van deze sectie worden vervolgens genummerd als “0.1”, “0.2”, terwijl ze eigenlijk “1”, “2”… zouden moeten zijn;
  • regels 3-5: deze afwijking wordt gecorrigeerd;
  • regels 37-43: als het huidige deel subdelen heeft, doorloopt men elk daarvan (regels 38-43). Hun sectienummer is [$sectionNumber.$i];
  • regels 44-47: wanneer er geen subdelen meer zijn, wordt de hoofdtekst van het huidige deel weergegeven met de functie [imap_fetchbody]. In ons voorbeeld gaat het om de delen [text/plain], [text/HTML] en de bijlagen;

Het uitvoeren van dit script levert de volgende resultaten op:


------------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.

Opmerkingen

  • regels 14-16: de inhoud van het tekstbericht gecodeerd in [quoted-printable] (regel 13);
  • regel 20: de inhoud van het bericht HTML, gecodeerd in [quoted-printable] (regel 19);
  • regels 24-28: de inhoud van het Word-bestand gecodeerd in [base64] (regel 23);
  • regels 32-37: de inhoud van het bestand PDF, gecodeerd als [base64] (regel 31);
  • regels 41-45: de inhoud van het bestand OpenOffice, gecodeerd als [base64] (regel 40);
  • regels 50-55: de inhoud van het afbeeldingsbestand gecodeerd als [base64] (regel 49);
  • regels 59-63: de inhoud van de bijgevoegde e-mail, gecodeerd als [base64] (regel 58);

Nu we:

  • we weten hoe we de teksten van de verschillende delen van een e-mail kunnen terugvinden;
  • we de codering van deze teksten kennen;

kunnen we deze teksten opslaan in bestanden.

De code verandert als volgt:


function getParts($imapResource, int $msgNumber, array $infos, object $infosMail, stdclass $part, string $sectionNumber = "0"): void {
  // berekening van het sectienummer
  if (substr($sectionNumber, 0, 2) === "0.") {
    $sectionNumber = substr($sectionNumber, 2);
  }
  print "-----contenu de la partie n° [$sectionNumber]\n";
  // inhoudstype
  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;
  }
  // coderingstype
  $encodings = ["7 bits", "8 bits", "binaire", "base 64", "quoted-printable", "autre"];
  print "Transfer-Encoding : " . $encodings[$part->encoding] . "\n";

  // is dit een bericht?
  if ($part->type === TYPEMESSAGE) {
    // de subdelen van dit bericht worden niet verwerkt
    savePart($imapResource, $msgNumber, $sectionNumber, $infos, $infosMail);
  } else {
    // we gaan verder met eventuele subdelen
    if (isset($part->parts)) {
      for ($i = 1; $i <= count($part->parts); $i++) {
        // een nieuw deel van het bericht
        $subpart = $part->parts[$i - 1];
        // recursieve aanroep – we vragen de hoofdtekst van het onderdeel [$subpart]
        getParts($imapResource, $msgNumber, $infos, $infosMail, $subpart, "$sectionNumber.$i");
      }
    } else {
      // er zijn geen subdelen – de hoofdtekst van het bericht wordt dan opgeslagen
      savePart($imapResource, $msgNumber, $sectionNumber, $infos, $infosMail);
    }
  }
}
  • regels 33 en 45: de weergave van de tekst van een deel [$imapResource, $msgNumber, $sectionNumber] van de e-mail wordt nu vervangen door het opslaan ervan in een bestand;

De functie [savePart] is als volgt:


// een deel van het bericht opslaan
function savePart($imapResource, int $msgNumber, string $sectionNumber, array $infos, object $infosMail): void {
  // opslagmap
  $outputDir = $infos["output-dir"] . "/message-$msgNumber";
  // als de map niet bestaat, wordt deze aangemaakt
  if (!file_exists($outputDir)) {
    mkdir($outputDir);
  }
  // structuur van het op te slaan deel
  $struct = imap_bodystruct($imapResource, $msgNumber, $sectionNumber);
  // documenttype
  $type = $struct->type;
  // documentondertype
  $subtype = "";
  if (isset($struct->subtype)) {
    $subtype = strtolower($struct->subtype);
  }
  // het type van het onderdeel wordt geanalyseerd
  switch ($type) {
    case TYPETEXT:
      // in het geval van een tekstbericht: 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:
      // andere gevallen – we kijken alleen naar de bijlagen
      if (isset($struct->disposition)) {
        $disposition = strtolower($struct->disposition);
        if ($disposition === "attachment") {
          // het betreft een bijlage – deze wordt opgeslagen
          saveAttachment($imapResource, $msgNumber, $sectionNumber, $outputDir, $struct);
        }
      } else {
        // dit deel wordt niet verwerkt
        print "Partie [$sectionNumber] ignorée\n";
      }
      break;
  }
}
  • regels 3-8: aanmaken van de back-upmap. Deze draagt het nummer van het bericht waarvan de onderdelen worden geanalyseerd;
  • regel 10: het op te slaan berichtgedeelte wordt eenduidig gedefinieerd door de drie parameters [$imapResource, $msgNumber, $sectionNumber]. De structuur van dit gedeelte wordt opgevraagd met de functie [imap_bodystruct];
  • regel 12: het hoofdtype van het berichtgedeelte wordt opgehaald;
  • regels 13-17: het subtype wordt opgehaald;
  • regels 20-30: de twee inhoudstypen worden verwerkt: [text/plain] (regels 23-25) en [text/HTML] (regels 26-28). De overige typen [text/xx] worden genegeerd;
  • regel 24: de tekst van het gedeelte [text/plain] wordt opgeslagen in een bestand [message.txt];
  • regel 27: de tekst van het gedeelte [text/HTML] wordt opgeslagen in een bestand met de naam [message.HTML];
  • regels 31-43: we behandelen de gevallen waarbij het hoofdtype niet [text] is;
  • regel 35: er wordt alleen gekeken naar de bijlagen van het bericht;
  • regel 37: deze worden met behulp van de functie [saveAttachment] in een bestand opgeslagen;

Samenvattend:

  • slaat de onderdelen [text/plain] en [text/HTML] op met behulp van de functie [saveText]. Deze onderdelen vertegenwoordigen de inhoud van de e-mail;
  • slaat de verschillende bijlagen op met behulp van de functie [saveAttachment];

De functie [saveText] is als volgt:


// de tekst [$text] van het bericht opslaan
function saveText(string $fileName, int $type, string $text, object $infosMail, object $struct) {
  // voorbereiding van de op te slaan tekst
  // $text is gecodeerd - we decoderen het
  switch ($struct->encoding) {
    case ENCBASE64:
      $text = base64_decode($text);
      break;
    case ENCQUOTEDPRINTABLE:
      $text = quoted_printable_decode($text);
      break;
  }
  // kopteksten van het bericht
  // van
  $from = "From: ";
  foreach ($infosMail->from as $expéditeur) {
    $from .= $expéditeur->mailbox . "@" . $expéditeur->host . ";";
  }
  // naar
  $to = "To: ";
  foreach ($infosMail->to as $destinataire) {
    $to .= $destinataire->mailbox . "@" . $destinataire->host . ";";
  }
  // onderwerp
  $subject = "Subject: " . $infosMail->subject;
  // aanmaak van de op te slaan tekst
  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;
  }
  // aanmaken van het bestand
  print "sauvegarde d'un message dans [$fileName]\n";
  // bestand aanmaken
  if (! file_put_contents($fileName, $contents)) {
    // het aanmaken van het bestand is mislukt
    print "Impossible de créer le fichier [$fileName]\n";
  }
}

Opmerkingen

  • regel 1:
    • [$fileName] is de naam van het bestand waarin de tekst [$text] wordt opgeslagen;
    • [$type]: is 0 voor een tekstbestand, 1 voor een HTML-bestand;
    • [$text]: is de tekst die moet worden opgeslagen. Deze moet echter eerst worden gedecodeerd, omdat hij gecodeerd is;
    • [$infosMail]: bevat algemene informatie over de e-mail. We gaan de velden [from, to, subject] gebruiken;
    • [$struct]: is de structuur die het deel van de e-mail beschrijft dat we aan het opslaan zijn. Hierdoor kunnen we achterhalen welk type codering de op te slaan tekst heeft;
  • regels 4-12: we decoderen de tekst die we willen opslaan;
  • regels 13-25: we halen de informatie [from, to, subject] uit de e-mail op;
  • regels 27-36: afhankelijk van het type (0 of 1) van de op te slaan tekst, wordt een platte tekst (regel 30) of een HTML-tekst (regel 34) samengesteld;
  • regel 40: de volledige tekst wordt opgeslagen in het bestand [$fileName];

De bijlagen worden opgeslagen met de volgende functie [saveAttachment]:


// bijlage opgeslagen
function saveAttachment($imapResource, int $msgNumber, string $sectionNumber, string $outputDir, object $struct) {
  // de structuur van de bijlage wordt geanalyseerd
  // er wordt gezocht naar de bestandsnaam waarin de bijlage moet worden opgeslagen
  // deze naam wordt gevonden in de [dparameters] van de structuur
  if (isset($struct->dparameters)) {
    // we halen de [dparameters] op
    $dparameters = $struct->dparameters;
    $fileName = "";
    // we doorlopen de tabel met de [dparameters]
    foreach ($dparameters as $dparameter) {
      // elk [dparameter] is een object met twee attributen [attribute, value]
      $attribute = strtolower($dparameter->attribute);
      // het attribuut [filename] komt overeen met de naam van het aan te maken bestand
      // in dit geval staat de bestandsnaam in [$dparameter->value]
      if ($attribute === "filename") {
        $fileName = $dparameter->value;
        break;
      }
    }
    // als er geen bestandsnaam is gevonden, wordt gekeken naar het attribuut [parameters] van de structuur
    if ($fileName === "" && isset($struct->parameters)) {
      // worden de [parameters] opgehaald
      $parameters = $struct->parameters;
      foreach ($parameters as $parameter) {
        // elke parameter is een woordenboek met twee sleutels [attribute, value]
        $attribute = strtolower($parameter->attribute);
        // als het attribuut [name] is, dan is [value] de bestandsnaam
        if ($attribute === "name") {
          $fileName = $parameter->value;
          // de bestandsnaam kan gecodeerd zijn
          // bijvoorbeeld =?utf-8?Q?Cursussen-Tutorials-Serge-Tah=C3=A9-1568x268=2Ep
          // we halen de codering op met een reguliere expressie
          $champs = [];
          $match = preg_match("/=\?(.+?)\?/", $fileName, $champs);
          // als er een overeenkomst is, decoderen we de bestandsnaam
          if ($match) {
            $fileName = iconv_mime_decode($fileName, 0, $champs[1]);
          }
          break;
        }
      }
    }
  }
  // als er een bestandsnaam is gevonden, slaan we de bijlage op
  if ($fileName !== "") {
    // de bijlage opslaan
    $fileName = "$outputDir/$fileName";
    print "sauvegarde de l'attachement dans [$fileName]\n";
    // bestand aanmaken
    if ($file = fopen($fileName, "w")) {
      // de gecodeerde tekst van de bijlage wordt opgehaald
      $text = imap_fetchbody($imapResource, $msgNumber, $sectionNumber);
      // de bijlage is gecodeerd – deze wordt gedecodeerd
      switch ($struct->encoding) {
        // Base64
        case ENCBASE64:
          $text = base64_decode($text);
          break;
        // quoted printable
        case ENCQUOTEDPRINTABLE:
          $text = quoted_printable_decode($text);
          break;
        default:
          // de overige gevallen worden genegeerd
          break;
      }
      // tekst naar het bestand schrijven
      fputs($file, $text);
      // bestand sluiten
      fclose($file);
    } else {
      // het aanmaken van het bestand is mislukt
      print "L'attachement n'a pu être sauvegardé dans [$fileName]\n";
    }
  }
}

Opmerkingen

  • regel 2: de functie [saveAttachment] accepteert de volgende parameters:
    • [$imapResource, int $msgNumber, string $sectionNumber] definieert op unieke wijze het deel IMAP dat moet worden opgeslagen;
    • [string $outputDir] is de map waarin de back-up wordt opgeslagen;
    • [object $struct] beschrijft de structuur van het op te slaan berichtgedeelte;
  • regels 6-44: er wordt gezocht naar de bestandsnaam die bij de bijlage hoort. Deze bestandsnaam wordt gebruikt om de bijlage op te slaan. De bestandsnaam van de bijlage staat in de tabel [$struct→dparameters] of de tabel [$struct→parameters], of zelfs in beide;
  • regels 30-40: als de bestandsnaam tekens bevat die niet op 7 bits zijn gecodeerd, dan is deze gecodeerd in [quoted-printable]. In dat geval heet het attribuut in [$struct→dparameters] [fileName*] in plaats van [fileName]. Dit betekent dat het niet voldeed aan de voorwaarde van regel 16. De bestandsnaam wordt vervolgens opgezocht in de tabel [$struct→parameters];
  • regel 32: een voorbeeld van een gecodeerde bestandsnaam. Deze heeft de volgende vorm: =?codage_original?codage_actuel?nom_encodé. De naam [=?utf-8?Q?Cours-Tutoriels-Serge-Tah=C3=A9-1568x268=2Ep] betekent dus dat de bestandsnaam eerst UTF-8 was en nu [quoted-printable] (Q) is;
  • regel 38: de bestandsnaam wordt gedecodeerd met de functie [iconv_mime_decode], die hier drie parameters accepteert:
    • de te decoderen tekenreeks;
    • standaard op 0 laten staan;
    • de tekenset die moet worden gebruikt om de gedecodeerde tekenreeks weer te geven. Deze parameter is aanwezig in de te decoderen tekenreeks. Deze wordt verkregen met een reguliere expressie op de regels 34-35;
  • regels 45-75: de bijlage wordt opgeslagen in een bestand met de gevonden naam;

Om het script [imap-02.php] te testen, sturen we eerst een e-mail naar [guest@localhost] met de volgende configuratie:

{
    "mail to localhost via localhost": {
        "smtp-server": "localhost",
        "smtp-port": "25",
        "from": "guest@localhost",
        "to": "guest@localhost",
        "subject": "test-localhost",
        "message": "ligne 1\nligne 2\nligne 3",
        "tls": "FALSE",
        "attachments": [
            "/attachments/Hello from SwiftMailer.docx",
            "/attachments/Hello from SwiftMailer.pdf",
            "/attachments/Hello from SwiftMailer.odt",
            "/attachments/Cours-Tutoriels-Serge-Tahé-1568x268.png",
            "/attachments/test-localhost.eml"
        ]
    }
}

Er zijn dus vijf bijlagen.

We lezen de verzonden e-mail met [imap-02.php] en de volgende configuratie:

{
    "{localhost:110/pop3}": {
        "imap-server": "localhost",
        "imap-port": "110",
        "user": "guest@localhost",
        "password": "guest",
        "pop3": "TRUE",
        "output-dir": "output/localhost-pop3"
    }
}

De console-uitvoer is als volgt:


------------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.

De opgeslagen bestanden zijn te vinden in de map [output/localhost-pop3/message-N]:

Image

16.6.6. Client POP3 / IMAP met de bibliotheek [php-mime-mail-parser]

In het vorige script [imap-02.php] hebben we het volgende kunnen opslaan:

  • de inhoud [text/plain] en [text/HTML] van de e-mail;
  • de bijlagen van de e-mail;

Voor een bijlage van het type [message/rfc822] hebben we ook de inhoud van de bijlage opgeslagen. Dit type bijlage is echter zelf een e-mail die op zijn beurt de inhoud [text/plain] en [text/HTML] bevat, evenals bijlagen. We kunnen dan in de volgende situatie terechtkomen:

  • een [mail 1] waarvan de structuur vergelijkbaar is met die van een bijlage van het type [message/rfc822];
  • een [mail 2] als bijlage bij e-mail 1;
  • een [mail 3] als bijlage bij e-mail 2;
  • enzovoort…

Het script [imap-02.php] slaat de inhoud van [mail 1] op (teksten en bijlagen). Het slaat [mail 2] op als bijgevoegd document, maar houdt daar op. Het probeert niet [mail 2] te analyseren om de teksten en bijlagen eruit te halen. Je zou denken dat het volstaat om op [mail 2] toe te passen wat er voor [mail 1] is gedaan. Een recursieve aanroep van de methode die [mail 1] verwerkte, zou dan voldoende kunnen zijn om de inhoud van alle in elkaar geneste e-mails te verkrijgen. Helaas zijn de delen van [mail 2] genummerd volgens een andere logica dan die voor [mail 1], waardoor het onmogelijk is om in beide gevallen hetzelfde algoritme te gebruiken, tenzij er een vrij complexe logica wordt toegepast om de nummers van de delen van een e-mail te berekenen, ongeacht de positie daarvan in de reeks geneste e-mails.

Het script [imap-02.php] was al complex. Om te voorkomen dat het nog complexer wordt om de inhoud van de geneste e-mails te verwerken, gaan we de bibliotheek [php-mime-mail-parser] gebruiken die beschikbaar is op GitHub (mei 2019) onder de namen URL en [https://github.com/php-mime-mail-parser/php-mime-mail-parser] en geschreven is door Vincent Dauce.

16.6.6.1. Installatie van de bibliotheek [php-mime-mail-parser]

Op de overzichtspagina van de bibliotheek staat beschreven hoe je deze onder Windows kunt installeren:

Image

Er zijn twee stappen voor de OS onder Windows:


télécharger une DLL ;
modifier le fichier [php.ini] qui configure PHP ;

LA DLL uit de bibliotheek [mailparse] is beschikbaar in de URL [http://pecl.php.net/package/mailparse] (mei 2019);

Image

  • in [2], kies de meest recente en stabiele versie van de bibliotheek;

Image

  • in [3] de versie van PHP kiezen die u gebruikt (in dit document is dat PHP 7.2);
  • bij [4]: kies de versie van uw Windows (hier is dat een 64-bits Windows). We nemen de versie [Thread Safe];

Om de versie te achterhalen van het met Laragon gedownloade bestand PHP, open je een [Terminal] vanuit het Laragon-venster en typ je de volgende opdracht:


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                      

De versie van PHP 7.2.11 staat vermeld op regel 3. Op dezelfde regel staat ook de versie van Windows die voor de compilatie is gebruikt (32 of 74 bits).

Zodra u het bestand DLL hebt verkregen, moet u het kopiëren naar de map [<laragon>/bin/php/<version-php>/ext] [5]:

Image

Zodra dit is gebeurd, moet je deze extensie activeren in het bestand [php.ini], dat PHP configureert (zie paragraaf ‘link’):

Image

Waarschijnlijk bestaat de regel [7] nog niet en moet u deze zelf toevoegen.

Zodra de extensie is geactiveerd, kunt u de geldigheid ervan controleren door de volgende opdracht in een Laragon-terminal in te voeren:


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)                                                              

Het commando [php –-ini] laadt het configuratiebestand van regel 4. Vervolgens laadt het de DLL-bestanden van alle geactiveerde extensies in [php.ini]. Als een van deze extensies onjuist is, wordt dit gemeld. Zo wordt de geldigheid van het toegevoegde DLL aan [php_mailparse.dll] gecontroleerd. Het kan om verschillende redenen als onjuist worden aangemerkt, waarvan de meest voorkomende de volgende zijn:

  • u hebt een DLL gedownload die niet overeenkomt met de gebruikte versie van PHP;
  • u hebt een 32-bits DLL gedownload terwijl u een 64-bits PHP hebt, of omgekeerd;

Zodra de extensie is geactiveerd en gecontroleerd, kunt u doorgaan met de installatie van de bibliotheek [php-mime-mail-parser]:

Image

Het commando [8] moet in een Laragon-terminal worden ingevoerd (zie paragraaf ‘link’):

Image

  • in [1], controleer of u zich in de map [<laragon>/www] bevindt;
  • in [2], de installatieopdracht voor de bibliotheek [php-mime-mail-parser];
  • in [3] is hier niets geïnstalleerd omdat de bibliotheek [php-mime-mail-parser] al was geïnstalleerd;

De bibliotheek [php-mime-mail-parser] wordt geïnstalleerd in de map [<laragon>/www/vendor]:

Image

Image

  • in [2-3], de broncode van de bibliotheek [php-mime-mail-parser];

Nu de werkomgeving is geïnstalleerd, kunnen we verdergaan met het schrijven van het script [imap-03.php].

16.6.6.2. Het script [imap-03.php]

Het script [imap-03.php] gebruikt hetzelfde configuratiebestand [config-imap-01.json] als de voorgaande scripts:

{
    "{localhost:110/pop3}": {
        "imap-server": "localhost",
        "imap-port": "110",
        "user": "guest@localhost",
        "password": "guest",
        "pop3": "TRUE",
        "output-dir": "output/localhost-pop3"
    }
}

Het script [imap-03.php] is als volgt:


<?php

// IMAP-client (Internet Message Access Protocol) waarmee e-mails kunnen worden gelezen
// geschreven met de bibliotheek [php-mime-mail-parser]
// beschikbaar opURL [https://github.com/php-mime-mail-parser/php-mime-mail-parser] (mei 2019)
//
// strikte naleving van de gedeclareerde typen van functieparameters
declare (strict_types=1);
// foutafhandeling
error_reporting(E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// afhankelijkheden
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
// instellingen voor het lezen van e-mail
const CONFIG_FILE_NAME = "config-imap-01.json";

// de configuratie wordt opgehaald
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);

// mailboxen lezen
foreach ($mailboxes as $name => $infos) {
  // opvolging
  print "------------Lecture de la boîte à lettres [$name]\n";
  // de mailbox lezen
  readmailbox($name, $infos);
}
// einde
exit;

Opmerkingen

  • regels 18-23: de inhoud van het configuratiebestand wordt in het woordenboek [$mailboxes] geplaatst;
  • regels 26-31: elke mailbox wordt gelezen door de functie [readmailbox] (regel 30). Deze functie leest in feite de ongelezen berichten uit de mailbox. Een mailbox komt overeen met het e-mailadres van een bepaalde gebruiker;

De functie [readmailbox] is als volgt:


function readmailbox(string $name, array $infos): void {
  // verbinding maken
  $imapResource = imap_open($name, $infos["user"], $infos["password"]);
  if (!$imapResource) {
    // mislukt
    print "La connexion au serveur [$name] a échoué : " . imap_last_error() . "\n";
    exit;
  }
  // Verbinding tot stand gebracht
  print "Connexion établie avec le serveur [$name].\n";
  // totaal aantal berichten in de mailbox
  $nbmsg = imap_num_msg($imapResource);
  print "Il y a [$nbmsg] messages dans la boîte à lettres [$name]\n";
  // Ongelezen berichten in de huidige mailbox
  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 {
      // de lijst met ongelezen berichten wordt doorlopen
      foreach ($msgNumbers as $msgNumber) {
        print "---message n° [$msgNumber]\n";
        // de tekst van bericht nr. $msgNumber wordt opgehaald
        getMailBody($imapResource, $msgNumber, $infos);
        // als het protocol POP3 is, wordt het bericht verwijderd nadat het is opgehaald
        $pop3 = $infos["pop3"];
        if ($pop3 !== NULL) {
          // het bericht wordt gemarkeerd als "te verwijderen"
          imap_delete($imapResource, $msgNumber);
        }
      }
      // einde van het lezen van ongelezen berichten
      if ($pop3 !== NULL) {
        // de berichten die zijn gemarkeerd als "te verwijderen" worden verwijderd
        imap_expunge($imapResource);
      }
    }
  }
  // de verbinding wordt verbroken
  $imapClose = imap_close($imapResource);
  if (!$imapClose) {
    // mislukt
    print "La fermeture de la connexion a échoué : " . imap_last_error() . "\n";
  } else {
    // geslaagd
    print "Fermeture de la connexion réussie.\n";
  }
}

Opmerkingen

De code van de functie [readmailbox] is dezelfde als in de voorgaande scripts.

De functie [getMailBody] (regel 25), die de hoofdtekst van een bericht (inhoud + bijlagen) analyseert, is als volgt:


// analyse van de tekst van het bericht
function getMailBody($imapResource, int $msgNumber, array $infos): void {
  // de volledige tekst van het bericht wordt opgehaald
  $text = imap_fetchbody($imapResource, $msgNumber, "");
  if ($text === FALSE) {
    print "Le corps du message [$msgNumber] n'a pu être récupéré";
    return;
  }
  // er wordt een parser aangemaakt die de tekst van het bericht gaat analyseren
  $parser = (new PhpMimeMailParser\Parser())->setText($text);
  // de verschillende onderdelen van het bericht worden opgehaald
  $outputDir = $infos["output-dir"] . "/message-$msgNumber";
  getParts($parser, $msgNumber, $outputDir);
}

Opmerkingen

  • regel 2: de functie [getMailBody] accepteert drie parameters:
    • [$imapResource]: de bron IMAP waarmee verbinding is gemaakt;
    • [$msgNumber]: het berichtnummer (in de mailbox) dat moet worden verwerkt;
    • [$infos]: diverse gegevens over de verwerkte mailbox;
  • regel 4: het volledige bericht met nummer [$msgNumber] wordt opgehaald;
  • regels 5-8: het geval waarin de inhoud van het bericht niet kon worden opgehaald;
  • regel 10: we beginnen met het gebruik van de bibliotheek [php-mime-mail-parser]. Het object [$parser] krijgt de taak om de tekst van het bericht te analyseren;
  • regel 12: [$outputDir] wordt de map waarin de tekstinhoud en de bijlagen van bericht nr. [$msgNumber] worden opgeslagen;
  • regel 13: de functie [getParts] wordt gevraagd om de verschillende onderdelen (tekstinhoud en bijlagen) van bericht nr. [$msgNumber] te vinden en deze op te slaan in de map [$outputDir];

De functie [getParts] is als volgt:


// de verschillende onderdelen van een bericht ophalen
function getParts(PhpMimeMailParser\Parser $parser, int $msgNumber, string $outputDir): void {
  // indien nodig wordt de map voor het opslaan van het bericht aangemaakt
  if (!file_exists($outputDir)) {
    if (!mkdir($outputDir)) {
      print "Le dossier [$outputDir] n'a pu être créé\n";
      return;
    }
  }
  // we halen de headers van het bericht op
  $arrayHeaders = $parser->getHeaders();
  // de tekstberichten worden opgeslagen
  $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");
  }
  // de HTML-berichten worden opgeslagen
  $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");
  }
  // de bijlagen van het bericht worden opgehaald
  $attachments = $parser->getAttachments();
  // nummer van de bijlage
  $iAttachment = 0;
  // de lijst met bijlagen doorlopen
  foreach ($attachments as $attachment) {
    // type bijlage
    $fileType = $attachment->getContentType();
    print "-- Sauvegarde d'un attachement de type [$fileType] dans le fichier [$outputDir/{$attachment->getFilename()}]\n";
    // de bijlage wordt opgeslagen
    try {
      $attachment->save($outputDir, PhpMimeMailParser\Parser::ATTACHMENT_DUPLICATE_SUFFIX);
    } catch (Exception $e) {
      print "L'attachement n'a pu être sauvegardé : " . $e->getMessage() . "\n";
    }
    // speciaal geval van het type message/rfc822
    if ($fileType === "message/rfc822") {
      // de bijlage is zelf een bericht – we gaan deze ook parseren
      // we wisselen van opslagmap
      $iAttachment++;
      $outputDir = $outputDir . "/rfc822-$iAttachment";
      // de te parseren inhoud wordt gewijzigd
      $parser->setText($attachment->getContent());
      // het bericht wordt recursief geparseerd
      getParts($parser, $msgNumber, $outputDir);
    }
  }
}

Opmerkingen

  • regel 2: de functie [getParts] accepteert drie parameters:
    • een parser [$parser] waaraan de volledige tekst van het te analyseren bericht is doorgegeven;
    • [$msgNumber] is het nummer van het bericht dat momenteel wordt geanalyseerd;
    • [$outputDir] is de map waarin de inhoud en bijlagen van het bericht moeten worden opgeslagen;
  • regels 4-9: aanmaken van de map [$outputDir];
  • regel 11: de kopteksten van het bericht dat momenteel wordt geanalyseerd (van, aan, onderwerp…) worden opgehaald;
  • regel 13: de delen van de e-mail met het type [text/plain] worden opgehaald. Er wordt een array opgehaald;
  • regels 14-17: alle elementen van de opgehaalde array worden opgeslagen, waarbij aan elk element een andere bestandsnaam wordt gegeven;
  • regel 19: we halen de delen van de e-mail op met het type [text/html]. We krijgen een array;
  • regels 20-23: alle elementen van de opgehaalde array worden opgeslagen, waarbij aan elk element een andere bestandsnaam wordt gegeven;
  • regel 25: de lijst met bijlagen van het geanalyseerde bericht wordt opgehaald;
  • regel 29: deze lijst wordt doorlopen;
  • regel 24: het type van de bijlage wordt opgehaald (attribuut Content-Type);
  • regels 34-38: de bijlage wordt opgeslagen in de map [$outputDir]. De tweede parameter [PhpMimeMailParser\Parser::ATTACHMENT_DUPLICATE_SUFFIX] is een naamgevingsstrategie voor de bijlagen. Als [$attachment→getFilename()] de waarde X heeft en het bestand X al bestaat, dan probeert de bibliotheek [php-mime-mail-parser] de namen [X_1], [X_2], enz. totdat er een bestandsnaam wordt gevonden die nog niet bestaat;
  • regel 40: er wordt gekeken of het bijgevoegde bestand een e-mail is;
  • regels 41-48: als dat het geval is, wordt deze e-mail op zijn beurt geanalyseerd om de inhoud en bijlagen eruit te halen;
  • regel 44: als [$outputDir] gelijk is aan X en er onder de bijlagen van het geanalyseerde bericht twee e-mails zitten, dan wordt de eerste opgeslagen in de map [$outputDir/rfc822-1] en de tweede in de map [$outputDir/rfc822-2];
  • regel 46: de inhoud van de bijgevoegde e-mail wordt de nieuwe tekst die moet worden geparseerd;
  • regel 48: de functie [getParts] wordt recursief aangeroepen om de nieuwe tekst te analyseren;

De functie [saveMessage] slaat de tekstinhoud van het te analyseren bericht op:


// een tekstbericht opslaan
function saveMessage(string $text, int $type, array $arrayHeaders, string $filename): void {
  // te opslaan inhoud
  $contents = "";
  // de headers toevoegen
  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";
  }
  // toevoeging van de berichttekst
  $contents .= $text;
  // alles opslaan
  if (!file_put_contents($filename, $contents)) {
    // mislukt
    print "Le message n'a pu être sauvegardé dans le fichier [$filename]\n";
  } else {
    // geslaagd
    print "Le message a été sauvegardé dans le fichier [$filename]\n";
  }
}

Opmerkingen

  • de functie [saveMessage] accepteert de volgende parameters:
    • [$text]: de op te slaan tekst;
    • [$type]: het type tekst (0: text/plain, 1: text/HTML);
    • [$arrayHeaders]: de headers van het geanalyseerde bericht;
    • [$filename]: de naam van het bestand waarin [$text] moet worden opgeslagen;
  • regel 4: [$contents] vertegenwoordigt de volledige tekst die moet worden opgeslagen;
  • regels 6-20: eerst worden alle kopteksten van het bericht (from, to, subject…) opgeslagen;
  • regels 16-19: in het geval van een tekst HTML wordt elke regel afgesloten met de tag <br/>, zodat elke koptekst in een browser op een aparte regel verschijnt;
  • regel 22: de tekst van het bericht dat moet worden opgeslagen, wordt aan de kopteksten toegevoegd;
  • regels 24-30: het geheel wordt opgeslagen in het bestand [$filename];

Het gebruik van de bibliotheek [php-mime-mail-parser] maakt het schrijven van het script voor het lezen van e-mails aanzienlijk eenvoudiger.

Het script [smtp-02.php] wordt gebruikt om een e-mail te versturen naar de gebruiker [guest@localhost] met de volgende configuratie:

{
    "mail to localhost via localhost": {
        "smtp-server": "localhost",
        "smtp-port": "25",
        "from": "guest@localhost",
        "to": "guest@localhost",
        "subject": "test-localhost",
        "message": "ligne 1\nligne 2\nligne 3",
        "tls": "FALSE",
        "attachments": [
            "/attachments/Hello from SwiftMailer.docx",
            "/attachments/Hello from SwiftMailer.pdf",
            "/attachments/Hello from SwiftMailer.odt",
            "/attachments/Cours-Tutoriels-Serge-Tahé-1568x268.png",
            "/attachments/test-localhost-2.eml"
        ]
    }
}
  • regels 11-15: er zijn vijf bijlagen;
  • regel 15: [test-localhost-2.eml] is een e-mail die als volgt is opgebouwd:
    • [test-localhost-2.eml] bevat 4 bijlagen (dezelfde als in de regels 11-14) en een bijgevoegde e-mail;
    • de e-mail die aan [test-localhost-2.eml] is toegevoegd, bevat 4 bijlagen (dezelfde als in de regels 11-14);

Het script [imap-03.php] wordt gebruikt om de mailbox van de gebruiker [guest@localhost] te lezen met de volgende configuratie:

{
    "{localhost:110/pop3}": {
        "imap-server": "localhost",
        "imap-port": "110",
        "user": "guest@localhost",
        "password": "guest",
        "pop3": "TRUE",
        "output-dir": "output/localhost-pop3"
    }
}

Na uitvoering is de mappenstructuur van [output/localhost-pop3] als volgt geworden:

Image

  • in [1], de 5 bijlagen van de e-mail die is ontvangen door [guest@localhost];
  • in [2], de 5 bijlagen van de e-mail [test-localhost-2.eml] van [1];
  • in [3], de 4 bijlagen van de e-mail [test-localhost.eml] van [2];

De console-uitvoer is als volgt:


------------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.

Als we [message_1.HTML] van [3] in een browser bekijken, krijgen we het volgende te zien:

Image