16. کارکردهای شبکه
اکنون به توابع شبکهای PHP میپردازیم که به ما امکان برنامهریزی TCP / IP (پروتکل کنترل انتقال / پروتکل اینترنت) را میدهد.

16.1. مبانی برنامهنویسی اینترنت
16.1.1. عمومی
بیایید ارتباط بین دو ماشین راه دور، A و B را در نظر بگیریم:

وقتی یک برنامه کاربردی AppA روی ماشین A بخواهد با یک برنامه کاربردی AppB روی ماشین B در اینترنت ارتباط برقرار کند، باید چندین چیز را بداند:
- آدرس IP (پروتکل اینترنت) یا نام ماشین B؛
- شماره پورت مورد استفاده توسط برنامه AppB. این به آن دلیل است که ماشین B ممکن است میزبان برنامههای متعددی باشد که در اینترنت در حال اجرا هستند. هنگامی که اطلاعاتی را از شبکه دریافت میکند، باید بداند آن اطلاعات برای کدام برنامه در نظر گرفته شده است. برنامههای روی ماشین B از طریق رابطها، که به آنها پورتهای ارتباطی نیز گفته میشود، به شبکه دسترسی پیدا میکنند. این اطلاعات در بسته دریافتی توسط ماشین B گنجانده شده است تا بتواند به برنامه صحیح تحویل داده شود؛
- پروتکلهای ارتباطی که ماشین B از آنها پشتیبانی میکند. در مطالعه ما، تنها از پروتکل TCP-IP استفاده خواهیم کرد؛
- پروتکل گفتگوی پذیرفتهشده توسط برنامه AppB. در واقع، ماشینهای A و B با یکدیگر «صحبت» خواهند کرد. آنچه آنها میگویند در پروتکلهای TCP-IP قرار داده خواهد شد. با این حال، زمانی که در سوی دیگر این زنجیره، برنامه AppB اطلاعاتی را که توسط برنامه AppA ارسال شده است دریافت میکند، باید قادر به تفسیر آن باشد. این مشابه وضعیتی است که در آن دو نفر، A و B، از طریق تلفن با یکدیگر ارتباط برقرار میکنند: مکالمه آنها توسط تلفن منتقل میشود. گفتار توسط تلفن A به صورت سیگنال رمزگذاری شده، از طریق خطوط تلفن منتقل شده و به تلفن B میرسد تا رمزگشایی شود. سپس شخص B صدای گفتار را میشنود. اینجاست که مفهوم پروتکل گفتگوی دوطرفه مطرح میشود: اگر A به فرانسوی صحبت کند و B آن زبان را نفهمد، A و B قادر به برقراری یک گفتگوی معنادار نخواهند بود؛
بنابراین، دو برنامه در حال ارتباط باید بر سر نوع دیالوگی که استفاده خواهند کرد، توافق کنند. برای مثال، دیالوگ با سرویس با نام ftp با دیالوگ با سرویس با نام pop یکسان نیست: این دو سرویس دستورات یکسانی را قبول نمیکنند. آنها پروتکل دیالوگ متفاوتی دارند؛
16.1.2. ویژگیهای پروتکل TCP
در اینجا، ما فقط ارتباطات شبکهای را که از پروتکل حملونقل TCP استفاده میکنند، بررسی خواهیم کرد که ویژگیهای اصلی آن به شرح زیر است:
- فرآیندی که قصد ارسال داده را دارد، ابتدا یک اتصال با فرایندی که قرار است اطلاعات مورد نظر را دریافت کند، برقرار میکند. این اتصال بین یک پورت در ماشین ارسالکننده و یک پورت در ماشین دریافتکننده برقرار میشود. بدین ترتیب یک مسیر مجازی بین دو پورت ایجاد میشود که منحصراً برای دو فرایندی که اتصال را برقرار کردهاند، رزرو خواهد شد؛
- تمام بستههای ارسالی توسط فرآیند مبدأ از این مسیر مجازی پیروی میکنند و به ترتیبی که ارسال شدهاند، میرسند؛
- دادههای ارسالی به صورت یک جریان پیوسته ظاهر میشوند. فرآیند ارسالکننده دادهها را با سرعت خود ارسال میکند. این دادهها لزوماً بلافاصله ارسال نمیشوند: پروتکل TCP منتظر میماند تا دادهی کافی برای ارسال داشته باشد. این دادهها در ساختاری به نام سگمنت TCP ذخیره میشوند. به محض پر شدن این بخش، به لایه IP منتقل میشود، جایی که در یک بسته IP جاسازی خواهد شد؛
- هر بخش ارسالی از طریق پروتکل TCP شمارهگذاری میشود. پروتکل گیرنده TCP بررسی میکند که بخشها را به ترتیب دریافت میکند. برای هر بخش که به درستی دریافت شود، یک تأیید به فرستنده ارسال میکند؛
- هنگامی که فرستنده این را دریافت میکند، به فرآیند ارسالکننده اطلاع میدهد. بنابراین فرآیند ارسالکننده میتواند تأیید کند که یک بخش با موفقیت تحویل داده شده است؛
- اگر پروتکل TCP که یک سگمنت را ارسال کرده است، پس از گذشت مدت زمان معینی، تأیید دریافت را دریافت نکند، آن سگمنت را مجدداً ارسال میکند و بدین ترتیب کیفیت سرویس تحویل اطلاعات را تضمین مینماید؛
- دورسوی مجازی ایجاد شده بین دو فرآیند ارتباطی، full-duplex است: این بدان معناست که اطلاعات میتواند در هر دو جهت جریان یابد. بدین ترتیب، فرآیند مقصد میتواند حتی زمانی که فرآیند مبدأ به ارسال اطلاعات ادامه میدهد، تأییدها را ارسال کند. این امر به پروتکل مبدأ TCP اجازه میدهد تا چندین بخش را بدون انتظار برای تأیید ارسال کند. اگر پس از گذشت مدت زمان معینی متوجه شود که برای یک سگمنت خاص با شماره n، تأیید دریافت نکرده است، ارسال سگمنتها را از آن نقطه از سر خواهد گرفت؛
16.1.3. رابطهٔ کلاینت-سرور
ارتباطات در اینترنت اغلب ناهمزمان است: ماشین A یک اتصال را برای درخواست یک سرویس از ماشین B آغاز میکند و مشخص میکند که میخواهد با سرویس SB1 روی ماشین B ارتباط برقرار کند. سپس ماشین B این درخواست را میپذیرد یا رد میکند. اگر درخواست پذیرفته شود، ماشین A میتواند درخواستهای خود را به سرویس SB1 ارسال کند. این درخواستها باید مطابق پروتکل گفتوگویی باشند که توسط سرویس SB1 درک میشود. بدین ترتیب یک دیالوگ درخواست-پاسخ بین ماشین A، که به عنوان ماشین کلاینت شناخته میشود، و ماشین B، که به عنوان ماشین سرور شناخته میشود، برقرار میگردد. یکی از این دو طرف اتصال را قطع خواهد کرد.
16.1.4. معماری کلاینت
معماری یک برنامه شبکهای که خدمات یک برنامه سرور را درخواست میکند به شرح زیر خواهد بود:
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. معماری سرور
معماری برنامهای که خدمات ارائه میدهد به شرح زیر خواهد بود:
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
برنامه سرور، درخواست اتصال اولیه یک کلاینت را به طور متفاوتی از درخواستهای بعدی او برای یک سرویس مدیریت میکند. این برنامه خود سرویس را ارائه نمیدهد. اگر این کار را انجام میداد، در حین ارائه سرویس دیگر منتظر درخواستهای اتصال نمیماند و در نتیجه به کلاینتها خدماتی ارائه نمیشد. بنابراین، روند کار به این صورت است: به محض دریافت و پذیرش یک درخواست اتصال در پورت گوشدادن، سرور وظیفهای را ایجاد میکند که مسئول ارائه خدمتی است که توسط کلاینت درخواست شده است. این خدمت در پورت دیگری روی ماشین سرور ارائه میشود که به آن پورت سرویس گفته میشود. این امر امکان سرویسدهی به چندین کلاینت را به طور همزمان فراهم میکند.
یک وظیفه سرویس ساختار زیر را خواهد داشت:
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. کشف پروتکلهای ارتباطی اینترنت
16.2.1. مقدمه
پس از آنکه یک کلاینت به یک سرور متصل شد، یک دیالوگ بین آنها برقرار میشود. ماهیت این دیالوگ همان چیزی است که به آن پروتکل ارتباطی سرور گفته میشود. از جمله رایجترین پروتکلها در اینترنت میتوان به موارد زیر اشاره کرد:
- HTTP: پروتکل انتقال HyperText – پروتکل ارتباط با یک سرور وب (سرور HTTP);
- SMTP: پروتکل انتقال ایمیل ساده (SMTP) – پروتکل ارتباط با سرور ارسال ایمیل (سرور SMTP)؛
- POP: پروتکل دفتر پستی – پروتکل ارتباط با یک سرور ذخیرهسازی ایمیل (سرور POP). این پروتکل برای بازیابی ایمیلهای دریافتی استفاده میشود، نه برای ارسال آنها؛
- IMAP: پروتکل دسترسی به پیام اینترنت – پروتکل برای ارتباط با یک سرور ذخیرهسازی ایمیل (سرور IMAP). این پروتکل به تدریج جایگزین پروتکل قدیمیتر POP شده است؛
- FTP: پروتکل انتقال فایل – پروتکل ارتباط با یک سرور ذخیرهسازی فایل (سرور FTP);
تمام این پروتکلها مبتنی بر متن هستند: کلاینت و سرور خطوط متنی را مبادله میکنند. اگر یک کلاینت قادر باشد:
- برقراری ارتباط با یک سرور TCP؛
- نمایش خطوط متنی که سرور برای آن ارسال میکند بر روی کنسول؛
- ارسال خطوط متنی که کاربر در صفحهکلید تایپ میکند به سرور؛
سپس میتوانیم با استفاده از یک پروتکل مبتنی بر متن با سرور TCP ارتباط برقرار کنیم، مشروط بر اینکه قواعد آن پروتکل را بدانیم.
16.2.2. خدمات TCP

در کد مرتبط با این سند، دو ابزار ارتباطی TCP وجود دارد:
- [RawTcpClient] به شما امکان میدهد تا به پورت P روی سرور S متصل شوید؛
- [RawTcpServer] برای ایجاد یک سرور که روی پورت P به مشتریان گوش میدهد، استفاده میشود؛
سرور TCP [RawTcpServer] استبا استفاده از دستوری با قاعده دستوری [RawTcpServeur port] فراخوانی میشود تا سرویسی با نام TCP را روی پورت [port] ماشین محلی (رایانهای که روی آن کار میکنید) ایجاد کند:
- سرور میتواند به طور همزمان به چندین مشتری خدمترسانی کند؛
- سرور دستورات وارد شده توسط کاربر از طریق صفحهکلید را اجرا میکند. این دستورات به شرح زیر هستند:
- list: فهرست کلاینتهای متصل به سرور را نمایش میدهد. اینها در قالب [id=x-nom=y] نمایش داده میشوند. فیلد [id] برای شناسایی کلاینتها استفاده میشود؛
- send x [texte]: متنی را به کلاینت شماره x (شناسه=x) ارسال میکند. کروشهها [] ارسال نمیشوند. آنها در دستور لازم هستند و برای تفکیک بصری متن ارسالی به کلاینت استفاده میشوند؛
- close x: اتصال با مشتری شماره x را قطع میکند؛
- quit: تمام اتصالات را میبندد و سرویس را متوقف میکند؛
- خطوط ارسالشده توسط کلاینت به سرور در کنسول نمایش داده میشوند؛
- تمام ارتباطات در یک فایل متنی به نام [machine-portService.txt] ثبت میشود، که در آن
- [machine] نام ماشینی است که کد روی آن در حال اجرا است؛
- [port] پورت سرویس است که به درخواستهای کلاینت پاسخ میدهد؛
کلاینت TCP [RawTcpClient] با استفاده از دستوری به شکل [RawTcpClient serveur port] برای اتصال به پورت [port] روی سرور [serveur] فراخوانی میشود:
- خطوط تایپشده توسط کاربر روی صفحهکلید به سرور ارسال میشوند؛
- خطوط ارسالشده توسط سرور روی کنسول نمایش داده میشوند؛
- تمام ارتباطات در یک فایل متنی به نام [serveur-port.txt] ثبت میشود؛
بیایید یک مثال را بررسی کنیم. دو پنجرهٔ فرمان ویندوز را باز کنید و در هر یک از آنها به پوشهٔ utilities بروید. در یکی از پنجرهها، سرور [RawTcpServer] را روی پورت ۱۰۰ راهاندازی کنید:

- در [1]، ما در پوشهٔ utilities قرار داریم؛
- در [2]، ما سرور TCP را روی پورت 100 راهاندازی میکنیم؛
- در [3]، سرور منتظر یک کلاینت TCP است؛
- در [4]، سرور منتظر دستوری است که کاربر از طریق صفحهکلید وارد میکند؛
در پنجرهٔ فرمان دیگر، کلاینت TCP را راهاندازی میکنیم:

- در [5]، به پوشهٔ ابزارها هدایت میشویم؛
- در [6]، کلاینت TCP را راهاندازی میکنیم: به آن دستور میدهیم تا به پورت ۱۰۰ روی ماشین محلی (ماشینی که روی آن کار میکنید) متصل شود؛
- در [7]، کلاینت با موفقیت به سرور متصل شده است. جزئیات کلاینت نمایش داده میشود: این کلاینت روی ماشین [DESKTOP-528I5CU] (در این مثال، ماشین محلی) قرار دارد و برای ارتباط با سرور از پورت [50405] استفاده میکند:
- در [8]، کلاینت در انتظار دستوری است که توسط کاربر از طریق صفحهکلید وارد میشود؛
بیایید به پنجرهٔ سرور بازگردیم. محتویات آن تغییر کرده است:

- در [9]، یک کلاینت شناسایی شده است. سرور شمارهٔ ۱ را به آن اختصاص داده است. سرور کلاینت راه دور (ماشین و پورت) را به درستی شناسایی کرده است؛
- در [10]، سرور اکنون منتظر یک کلاینت جدید است؛
بیایید به پنجره کلاینت بازگردیم و یک دستور به سرور ارسال کنیم:

- در [11]، فرمان به سرور ارسال شد؛
بیایید به پنجرهٔ سرور بازگردیم. محتوای آن تغییر کرده است:

- در [12]، در کروشهها، پیامی که توسط سرور دریافت شده است؛
بیایید یک پاسخ به کلاینت ارسال کنیم:

- به [13]، پاسخی که به کلاینت ۱ ارسال میشود. فقط متن داخل کروشهها ارسال میشود، نه خود کروشهها؛
بیایید به پنجرهٔ کلاینت بازگردیم:

- به [14]، پاسخی که توسط کلاینت دریافت میشود. متن دریافتشده همان متنی است که بین کروشهها قرار دارد؛
بیایید به پنجرهٔ سرور بازگردیم تا چند دستور دیگر را ببینیم:

- در [15]، فهرست کلاینتها را درخواست میکنیم؛
- در [16]، پاسخ؛
- با [17]، اتصال با مشتری شمارهٔ ۱ را قطع میکنیم؛
- در [18]، تأیید سرور؛
- در [19]، سرور را خاموش میکنیم؛
- در [20]، تأیید سرور؛
بیایید به پنجرهٔ کلاینت بازگردیم:

- در [21]، کلاینت پایان سرویس را تشخیص داده است؛
دو فایل لاگ ایجاد شدهاند، یکی برای سرور و دیگری برای کلاینت:

- در [25]، لاگهای سرور: نام فایل نام کلاینت است [machine-port];
- [26]، لاگهای کلاینت: نام فایل نام کلاینت [machine-port] است؛
لاگهای سرور به شرح زیر است:
لاگهای کلاینت به شرح زیر است:
16.3. نام یا آدرس IP یک ماشین در اینترنت را به دست آور

ماشینها در اینترنت با یک آدرس (IP, IPv4 یا IPv6) و اغلب اوقات با یک نام شناسایی میشوند. با این حال، در نهایت تنها از آدرس استفاده میشود. بنابراین گاهی لازم است آدرس IP ماشینی را که با نامش شناسایی شده است، بدانیم.
اسکریپت [ip-01.php] به شرح زیر است:
<?php
// رعایت دقیق انواع اعلامشدهٔ پارامترهای تابع
declare (strict_types=1);
//
// مدیریت خطا
error_reporting(E_ALL & E_STRICT);
ini_set("display_errors", "on");
//
// ثوابت
$HOTES = array("istia.univ-angers.fr", "www.univ-angers.fr", "www.ibm.com", "localhost", "", "xx");
// آدرسهای IP و نامهای ماشین $HOTES
for ($i = 0; $i < count($HOTES); $i++) {
getIPandName($HOTES[$i]);
}
// پایان
print "Terminé\n";
exit;
//------------------------------------------------
function getIPandName(string $nomMachine): void {
//$nomMachine: نام ماشینی که آدرس برای آن مورد نیاز است IP
//
// nomMachine-->آدرس IP
$ip = gethostbyname($nomMachine);
print "---------------\n";
if ($ip !== $nomMachine) {
print "ip[$nomMachine]=$ip\n";
// آدرس 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";
}
}
توضیحات
- خطوط ۷–۸: به PHP دستور داده شده است که تمام خطاها (E_ALL و E_STRICT) را گزارش کرده و نمایش دهد. این حالت تنها در حالت توسعه برای بهبود کد با استفاده از هشدارهای PHP توصیه میشود. در حالت تولید، در خط ۸، آن را روی «off» تنظیم میکنید. از نسخه ۵.۴ PHP، سطح E_STRICT در E_ALL گنجانده شده است؛
- خط ۱۱: لیست ماشینهایی که میخواهیم نام و آدرس IP را برای آنها داشته باشیم؛
فعالیتهای شبکهای PHP در تابع getIpandName در خط ۲۱ استفاده میشوند.
- خط ۲۵: تابع gethostbyname($nom) آدرس IP "ip3.ip2.ip1.ip0" را برای ماشین با نام $nom بازیابی میکند. اگر ماشین $nom وجود نداشته باشد، این تابع $nom را به عنوان نتیجه برمیگرداند؛
- خط ۳۰: تابع gethostbyaddr($ip) نام میزبان برای ماشین با آدرس $ip را به شکل "ip3.ip2.ip1.ip0" بازیابی میکند. اگر ماشین $ip وجود نداشته باشد، تابع $ip را به عنوان نتیجه بازمیگرداند؛
نتایج:
---------------
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. پروتکل HTTP (پروتکل انتقال HyperText)
16.4.1. مثال ۱

وقتی یک مرورگر یک URL را نمایش میدهد، بهعنوان کلاینت یک وبسرور عمل میکند، یا به عبارت دیگر، یک سرور HTTP. این مرورگر ابتکار عمل را در دست میگیرد و با ارسال تعدادی دستور به سرور شروع میکند. برای این مثال اول:
- سرور، ابزار [RawTcpServer] خواهد بود؛
- کلاینت یک مرورگر وب خواهد بود؛
ابتدا، سرور را روی پورت ۱۰۰ راهاندازی میکنیم:

سپس، با استفاده از یک مرورگر وب، URL و [localhost:100] را درخواست میکنیم؛ به عبارت دیگر، مشخص میکنیم که سرور HTTP که در حال پرسوجو است، روی پورت ۱۰۰ ماشین محلی در حال اجرا است:

بیایید به پنجره سرور بازگردیم:

- در [3]، کلاینتی که متصل شده است؛
- در [4-7]، سری خطوط متنی که ارسال کرده است:
- در [4]: این خط در قالب [GET URL HTTP/1.1] است. این خط URL را درخواست میکند و به سرور دستور میدهد که از پروتکل HTTP 1.1 استفاده کند؛
- در [5]: این خط به صورت [Host: serveur:port] قالببندی شده است. حالت دستور [Host] بیربط است. در اینجا باید توجه داشت که کلاینت در حال پرسوجو از یک سرور محلی است که روی پورت 100 در حال اجرا است؛
- دستور [User-Agent] هویت کلاینت را فراهم میکند؛
- دستور [Accept] مشخص میکند که کدام نوع اسناد توسط کلاینت پذیرفته میشوند؛
- دستور [Accept-Language] زبان ارائهی اسناد درخواستی را مشخص میکند، در صورتی که این اسناد به زبانهای متعددی موجود باشند؛
- دستور [Connection] حالت اتصال ترجیحی را مشخص میکند: [keep-alive] نشان میدهد که اتصال باید تا تکمیل تبادل دادهها حفظ شود؛
- در [7]: کلاینت دستورات خود را با یک خط خالی پایان میدهد؛
ما با خاموش کردن سرور، اتصال را قطع میکنیم:

16.4.2. مثال ۲
اکنون که دستورات ارسالشده توسط یک مرورگر برای درخواست یک URL را میدانیم، این URL را با استفاده از کلاینت TCP [RawTcpClient] خود درخواست خواهیم کرد. سرور آپاچی لارگون وبسرور ما خواهد بود.
بیایید Laragon و سپس وبسرور Apache را راهاندازی کنیم:


اکنون، با استفاده از یک مرورگر وب، بیایید URL و [http://localhost:80] را درخواست کنیم. در اینجا، ما فقط سرور [localhost:80] را مشخص میکنیم و نه یک سند خاص مانند URL. در این مورد، URL / درخواست میشود، یعنی ریشه وبسرور:

- به [1]، URL درخواستی. ما در ابتدا [http://localhost:80] را تایپ کردیم و مرورگر (در این مورد فایرفاکس) به سادگی آن را به [localhost] تبدیل کرد، زیرا وقتی پروتکلی مشخص نمیشود، پروتکل [http] بهطور ضمنی در نظر گرفته میشود، و وقتی پورت مشخص نمیشود، پورت [80] بهطور ضمنی در نظر گرفته میشود؛
- به [2]، صفحهٔ ریشهٔ / وبسرور مورد پرسوجو؛
اکنون، بیایید نگاهی به متنی که توسط مرورگر دریافت شده است بیندازیم:

- روی صفحهای که دریافت کردهاید کلیک راست کرده و گزینه [2] را انتخاب کنید. سپس کد منبع زیر را مشاهده خواهید کرد:
اکنون با استفاده از کلاینت TCP خود، URL و [http://localhost:80] را درخواست کنیم:
![]()
- در [1]، ما به پورت ۸۰ سرور localhost متصل میشویم. این همان جایی است که وبسرور Laragon در آن اجرا میشود؛
اکنون دستوراتی را که در پاراگراف قبلی کشف کردیم وارد میکنیم:

- در [1]، دستور [GET]. ما دایرکتوری ریشه / وبسرور را درخواست میکنیم؛
- در [2]، فرمان [Host]؛
- اینها تنها دو دستور ضروری هستند. برای سایر دستورات، وبسرور از مقادیر پیشفرض استفاده خواهد کرد؛
- در [3]، خط خالی که باید دستورات کلاینت را پایان دهد؛
- زیر خط ۳ پاسخ وبسرور میآید؛
- از [4] تا خط خالی [5]، سربرگهای HTTP پاسخ سرور هستند؛
- پس از خط [5]، سند درخواستی HTML ([6]) میآید؛
ما [quit] را وارد میکنیم تا کلاینت را خاتمه دهیم و فایل لاگ [localhost-80.txt] را بارگذاری کنیم:
- خطوط ۱۱–۷۹: سند دریافتشده HTML. در مثال قبلی، فایرفاکس همان سند را دریافت کرده بود؛
اکنون ما اصول اولیه برنامهنویسی یک کلاینت TCP را داریم که یک URL را درخواست میکند.
16.4.3. مثال ۳

اسکریپت [http-01.php] یک کلاینت HTTP است که توسط فایل jSON [config-http-01.json] پیکربندی شده است. محتویات این فایل به شرح زیر است:
- خط ۲: نام ماشینی که میزبان وبسرور مورد دسترسی است؛
- خط ۳: پورتی که این وبسرور روی آن کار میکند؛
- خط ۴: URL سند مورد نظر؛
- خط ۵: ماشین مقصد به فرمت ماشین:پورت؛
- خط ۶: شناسهی مشتری HTTP: میتوانید هر چیزی که دوست دارید وارد کنید؛
- خط ۷: نوع سند مورد قبول کلاینت، در این مورد متن HTML;
- خط ۸: زبان مورد نظر برای سند درخواستشده؛
- خط ۹: کاراکتر پایان خط برای دستورات ارسالشده توسط کلاینت: این ممکن است بسته به اینکه سرور روی یک سیستم یونیکس (Unix) (\n) یا ویندوز (\r\n) اجرا میشود، متفاوت باشد؛
اسکریپت [http-01.php] به شرح زیر است:
<?php
// پابندی سختگیرانه به انواع اعلامشدهٔ پارامترهای تابع
declare (strict_types=1);
//
// مدیریت خطا
// error_reporting(E_ALL & E_STRICT);
// ini_set("display_errors", "on");
//
//ثابتها
const CONFIG_FILE_NAME = "config-http-01.json";
//
//پیکربندی را بازیابی کنید
$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);
// متن را از HTML در URL در فایل پیکربندی بازیابی میکند
foreach ($config as $site => $protocole) {
//صفحهٔ نمایهٔ سایت را میخواند $ite
$résultat = getURL($site, $protocole);
//نمایش نتیجه
print "$résultat\n";
}//برای
//پایان
exit;
//-----------------------------------------------------------------------
function getURL(string $site, array $protocole, $suivi = TRUE): string {
// فایل URL $site["GET"] را میخواند و آن را در فایل $site.HTML ذخیره میکند.
//دیالوگ کلاینت/سرور با استفاده از پروتکل $protocole انجام میشود
//
// باز کردن یک اتصال روی پورت $site
$erreurNumber = 0;
$erreur = "";
$connexion = fsockopen($site, $protocole["port"], $erreurNumber, $erreur);
// در صورت رخ دادن خطا بازگرداندن
if ($connexion === FALSE) {
return "Echec de la connexion au site (" . $site . " ," . $protocole["port"] . " : $erreur";
}
// $connexion نمایانگر یک جریان ارتباطی دوطرفه است
//بین کلاینت (این برنامه) و وبسروری که در حال تماس با آن است
// این کانال برای تبادل دستورات و اطلاعات استفاده میشود
//پروتکل ارتباطی HTTP است
//
//ایجاد فایل $site.HTML
$HTML = fopen("output/$site.HTML", "w");
if ($HTML === FALSE) {
//بستن اتصال مشتری/سرور
fclose($connexion);
//خطای بازگشتی
return "Erreur lors de la création du fichier $site.HTML";
}
// کلاینت دیالوگ HTTP را با سرور آغاز خواهد کرد
if ($suivi) {
print "Client : début de la communication avec le serveur [$site] ----------------------------\n";
}
//بسته به سرور، خطوط کلاینت باید با \n یا \r\n پایان یابند
$endOfLine = $protocole["endOfLine"];
// برای سادگی، ما موارد خطا در ارتباط کلاینت/سرور را بررسی نمیکنیم
// کلاینت فرمان GET را برای درخواست URL $protocole["GET"] ارسال میکند
//نحو: GET URL HTTP/1.1
$commande = "GET " . $protocole["GET"] . " HTTP/1.1$endOfLine";
// ردیابی؟
if ($suivi) {
print "--> $commande";
}
// دستور به سرور ارسال میشود
fputs($connexion, $commande);
//ارسال سایر سربرگها HTTP
foreach ($protocole as $verb => $value) {
if ($verb !== "GET" && $verb != "port"" && $verb !="endOfLine") {
//فرمان در حال ساخت است
$commande = "$verb: $value$endOfLine";
//پیگیری؟
if ($suivi) {
print "--> $commande";
}
// دستور به سرور ارسال میشود
fputs($connexion, $commande);
}
}
//سربرگهای پروتکل HTTP باید با یک خط خالی پایان یابند
fputs($connexion, $endOfLine);
//
// سرور اکنون در کانال $connexion پاسخ خواهد داد. آن همه را ارسال خواهد کرد
//دادههای آن را میخواند و سپس کانال را میبندد. بنابراین کلاینت همه چیز را که از $connexion میرسد میخواند
//تا زمانی که کانال بسته شود
//
// ابتدا سربرگهای HTTP ارسالشده توسط سرور خوانده میشوند
// اینها نیز با یک خط خالی پایان مییابند
if ($suivi) {
print "Réponse du serveur [$site] ----------------------------\n";
}
$fini = FALSE;
while (!$fini && $ligne = fgets($connexion, 1000)) {
//آیا خط خالی وجود دارد؟
$champs = [];
preg_match("/^(.*?)\s+$/", $ligne, $champs);
if ($champs[1] !== "") {
if ($suivi) {
// ما سربرگ HTTP را نمایش میدهیم
print "<-- " . $champs[1] . "\n";
}
} else {
// آن خط خالی بود – سربرگهای HTTP پایان یافتهاند
$fini = TRUE;
}
}
//سند HTML که پس از خط خالی میآید، خوانده میشود
while ($ligne = fgets($connexion, 1000)) {
//خط در فایل HTML در سایت ذخیره میشود
fputs($HTML, $ligne);
}
// سرور اتصال را قطع کرده است – کلاینت نیز به نوبه خود آن را میبندد
fclose($connexion);
//فایل $HTML بسته میشود
fclose($HTML);
//بازگشت
return "Fin de la communication avec le site [$site]. Vérifiez le fichier [$site.HTML]";
}
توضیحات کد:
- خط ۱۴: فایل پیکربندی برای ایجاد یک دیکشنری استفاده میشود:
- کلیدهای دیکشنری، سرورهای وبی هستند که باید از آنها پرسوجو شود؛
- مقادیر پروتکل HTTP را که باید دنبال شود مشخص میکنند؛
- خطوط ۱۶–۲۱: برنامه به صورت حلقهای لیست سرورهای وب را در پیکربندی بررسی میکند؛
- خط ۲۶: تابع getURL($site,$protocole,$suivi) یک سند را از وبسایت $site درخواست میکند و آن را در فایل متنی $site ذخیره میکند.HTML.Par: به طور پیشفرض، مبادلات کلاینت/سرور در کنسول ثبت میشوند ($suivi=TRUE);
- خط ۳۳: تابع fsockopen($site,$port,$errNumber,$erreur) یک اتصال با سرویس TCP / IP که روی پورت $port روی ماشین $site در حال اجرا است، برقرار میکند. اگر اتصال ناموفق باشد، [$errNumber] شماره خطا و [$erreur] پیام خطای مربوطه است. پس از برقراری اتصال کلاینت/سرور، چندین سرویس TCP / IP خطوط متنی را مبادله میکنند. این امر در مورد پروتکل HTTP (پروتکل انتقال HyperText) نیز صدق میکند. سپس میتوان جریان داده از سرور به کلاینت را بهعنوان یک فایل متنی خواندهشده با استفاده از [fgets] در نظر گرفت. همین امر در مورد جریان داده از کلاینت به سرور نیز صدق میکند که میتوان آن را با استفاده از [fputs] نوشت؛
- خطوط ۴۴–۵۰: ایجاد فایل [$site.HTML]، که در آن سند دریافتی HTML ذخیره خواهد شد؛
- خط ۶۰: اولین فرمان کلاینت باید فرمان [GET URL HTTP/1.1] باشد؛
- خط ۶۶: تابع fputs به کلاینت اجازه میدهد دادهها را به سرور ارسال کند. در اینجا، خط متنی که ارسال شده است معنای زیر را دارد: «من میخواهم (GET) صفحه [URL] را از وبسایتی که به آن متصل هستم دریافت کنم. من از پروتکل HTTP، نسخه ۱.۱ استفاده میکنم؛
- خطوط ۶۸–۷۹: خطوط باقیمانده از پروتکل HTTP [Host, User-Agent, Accept, Accept-Language] ارسال میشوند. ترتیب آنها اهمیتی ندارد؛
- خط ۸۱: یک خط خالی برای سرور ارسال میشود تا نشان دهد که کلاینت ارسال هدرهای HTTP خود را به پایان رسانده و اکنون منتظر سند درخواستی است؛
- خطوط ۹۲–۱۰۶: سرور ابتدا مجموعهای از هدرها HTTP را ارسال میکند که جزئیات مختلفی در مورد سند درخواستشده ارائه میدهند. این هدرها با یک خط خالی پایان مییابند؛
- خط ۹۳: یک خط ارسالشده توسط سرور با استفاده از تابع PHP [fgets] خوانده میشود؛
- خط ۹۶: محتوای خط بازیابی میشود، بدون در نظر گرفتن هرگونه فضای خالی (فاصلههای سفید، کاراکترهای پایان خط) در انتهای خط؛
- خط ۹۷: بررسی میکنیم که آیا خط خالی علامتگذاری پایان سرآیندهای HTTP ارسالشده توسط سرور را بازیابی کردهایم یا خیر؛
- خطوط ۹۸–۱۰۱: اگر در حالت [suivi] باشد، هدر دریافتشده HTTP در کنسول نمایش داده میشود؛
- خطوط ۱۰۸–۱۱۱: خطوط متنی پاسخ سرور را میتوان خط به خط با استفاده از یک حلقه while خواند و در فایل متنی [output/$site.HTML] ذخیره کرد. به محض اینکه وبسرور کل صفحهٔ درخواستی را ارسال کرد، اتصال خود را با کلاینت قطع میکند. در سمت کلاینت، این امر به عنوان پایان فایل تشخیص داده میشود؛
نتایج:
کنسول لاگهای زیر را نمایش میدهد:
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]
در مثال ما، فایل دریافتی [output/localhost.HTML] به شرح زیر است:
<!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>
ما در واقع همان سند را که با مرورگر فایرفاکس دریافت شده بود، به دست آوردیم.
16.4.4. مثال ۴
در این مثال نشان خواهیم داد که کلاینت HTTP که نوشتهایم ناکافی است. بیایید فایل پیکربندی [config-http-01.json] را به شرح زیر اصلاح کنیم:
در اینجا، ما URL [http://tahe.developpez.com:443/] را درخواست خواهیم کرد. پورت ۴۴۳ روی ماشین [tahe.developpez.com] پورتی است که برای پروتکل امن HTTP معروف به HTTPS استفاده میشود. در این پروتکل، دیالوگ کلاینت/سرور با تبادل اطلاعاتی که اتصال را ایمن میکند، آغاز میشود. بنابراین کلاینت باید از پروتکل [HTTPS] به جای پروتکل [HTTP] استفاده کند، که کلاینت ما این کار را انجام نمیدهد.
با این فایل پیکربندی، خروجی کنسول به شرح زیر است:
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]
- خط ۸: سرور [tahe.developpez.com] پاسخ داد که درخواست کلاینت نادرست بود؛
محتویات فایل [output/tahe.developpez.com.HTML] به شرح زیر است:
<!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>
سرور بهوضوح اعلام میکند که از پروتکل صحیح استفاده نکردهایم.
اکنون بیایید از فایل پیکربندی زیر استفاده کنیم:
خروجی کنسول در ادامه به این صورت است:
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]
- خط ۱۱ نشان میدهد که سرور سند را به صورت تکهها ارسال میکند؛
این موضوع با حضور اعداد در جریانی که به کلاینت ارسال میشود، منعکس شده است؛ هر عدد به کلاینت تعداد کاراکترهای بلوک بعدی ارسالشده توسط سرور را اطلاع میدهد. در فایل [output/sergetahe.com.HTML] این وضعیت به این شکل دیده میشود:

- در [1] و [2]، اندازه هگزادسیمال تکههای ۱ و ۲ سند؛
یک کلاینت صحیح HTTP نباید این اعداد را در سند نهایی HTML باقی بگذارد.
در اینجا یک مثال دیگر آورده شده است:
این شبیه مثال قبلی است، اما URL در خط ۴ با کاراکتر / پایان نمییابد. اینها URL یکسان نیستند. اجرای کلاینت HTTP سپس خروجی کنسول زیر را تولید میکند:
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/programming-tutorials/
<-- 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]
- خط ۸ نشان میدهد که سند درخواستی به URL تغییر کرده است. URL جدید در خط ۱۳ ارائه شده است. توجه کنید که این بار کاراکتر / پایاندهنده URL جدید است؛
سپس فایل [output/serge.tahe.com.HTML] به صورت زیر است:
<!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>
یک مشتری با شناسه HTTP باید بتواند ریدایرکتها را دنبال کند. در این مورد، باید بهطور خودکار درخواستهای جدید URL و [http://sergetahe.com/cours-tutoriels-de-programmation/] را ارسال کند.
16.4.5. مثال ۵
مثالهای قبلی به ما نشان دادند که کلاینت ما HTTP ناکافی بود. اکنون ابزاری به نام [curl] را معرفی میکنیم که امکان بازیابی اسناد وب را در حالی که با دشواریهای ذکر شده مانند پروتکل HTTPS، ارسال اسناد به صورت تکهای، ریدایرکتها و غیره مقابله میکند، فراهم میآورد. ابزار [curl] با Laragon نصب شده است:

بیایید یک ترمینال Laragon را برای [1] باز کنیم:

در ترمینال، دستور زیر را تایپ میکنیم:

- در [1]، دستور زیر را در کنسول تایپ کنید؛
- [2]، دایرکتوری فعلی. این دایرکتوری ویژه است: این همان جایی است که سرور آپاچی لارگون اسنادی را که از آن خواسته میشود، بازیابی میکند. بنابراین باید از شلوغ کردن این دایرکتوری خودداری کنیم؛
- در [3]، دستوری که وارد شده است؛
دستور [curl --help] ممکن است خطایی ایجاد کند. محتملترین دلیل این است که شما نوع صحیح ترمینال را ندارید. در این صورت، با استفاده از دستورات [4-6] یک ترمینال دیگر باز کنید؛
دستور [curl --help] تمام گزینههای پیکربندی [curl] را نمایش میدهد. دهها گزینه وجود دارد. ما از تعداد بسیار کمی از آنها استفاده خواهیم کرد. برای درخواست یک URL، کافی است دستور [curl URL] را تایپ کنید. این دستور سند درخواستی را در کنسول نمایش خواهد داد. اگر همچنین میخواهید مبادلات HTTP بین کلاینت و سرور را مشاهده کنید، دستور [curl --verbose URL] را تایپ میکنید. در نهایت، برای ذخیره سند درخواستی HTML در یک فایل، دستور [curl --verbose --output fichier URL] را تایپ میکنید.
برای جلوگیری از شلوغ شدن پوشه [www] در Laragon، بیایید به مکان دیگری در سیستم فایل برویم:

- در [1]، به پوشه [c:\temp] بروید. اگر این پوشه وجود ندارد، میتوانید آن را ایجاد کنید یا پوشه دیگری را انتخاب کنید؛
- در [2]، پوشهای به نام [curl] ایجاد کنید؛
- در [3]، آن را انتخاب کنید؛
- در [4]، محتویات آن را فهرست کنید. خالی است؛
اطمینان حاصل کنید که سرور لارگون آپاچی در حال اجرا است و از [curl]، با استفاده از دستور [curl –verbose –output localhost.HTML http://localhost/]، URL و [http://localhost/] را درخواست کنید. نتایج زیر به دست میآیند:
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 تا میزبان localhost بدون تغییر باقی ماند
- خطوط ۸–۱۲: خطوط ارسالشده توسط [curl] به سرور [localhost]. پروتکل HTTP قابل تشخیص است؛
- خطوط ۱۳–۱۹: خطوط ارسالشده در پاسخ توسط سرور؛
- خط ۱۳: نشان میدهد که سند درخواستی با موفقیت دریافت شده است؛
فایل [localhost.HTML] حاوی سند درخواستی است. میتوانید با باز کردن این فایل در یک ویرایشگر متن این موضوع را بررسی کنید.
اکنون بیایید URL و [https://tahe.developpez.com:443/] را درخواست کنیم. برای دریافت این URL، کلاینت HTTP باید بتواند با HTTPS ارتباط برقرار کند. این مورد برای کلاینت [curl] صدق میکند.
خروجی کنسول به شرح زیر است:
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 به میزبان tahe.developpez.com بدون تغییر باقی ماند
- خطوط ۱۰–۴۰: تبادلهای کلاینت/سرور برای ایمنسازی اتصال: این بخش رمزگذاری خواهد شد؛
- خطوط ۴۲–۴۵: سربرگهای HTTP که توسط کلاینت [curl] به سرور ارسال شدهاند؛
- خط ۴۸: سند درخواستشده یافت شد؛
- خط ۵۳: سند به صورت تکهها ارسال میشود؛
[curl] بهدرستی هم پروتکل امن HTTPS و هم این واقعیت که سند بهصورت تکهای ارسال میشود را مدیریت میکند. سند ارسالی را میتوان در اینجا در فایل [tahe.developpez.com.HTML] یافت.
حال بیایید به URL و [http://sergetahe.com/cours-tutoriels-de-programmation] نگاهی بیندازیم. ما دیده بودیم که برای این URL، یک هدایت به URL [http://sergetahe.com/cours-tutoriels-de-programmation/] (با یک / در انتها) وجود داشت.
خروجی کنسول سپس به شرح زیر است:
c:\Temp\curl
λ curl --verbose --output sergetahe.com.HTML --location http://sergetahe.com/programming-tutorials
% 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/programming-courses-and-tutorials/
< 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 به میزبان sergetahe.com بدون تغییر باقی ماند
* Issue another request to this URL: 'http://sergetahe.com/programming-courses-and-tutorials/'
* Found bundle for host sergetahe.com: 0x1c88548 [can pipeline]
* Could pipeline, but not asked to!
* Re-using existing connection! (#0) با میزبان 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 به میزبان sergetahe.com بدون تغییر باقی ماند
- خط ۲: گزینه [--location] برای نشان دادن اینکه میخواهیم ریدایرکتهای ارسالشده توسط سرور را دنبال کنیم، استفاده میشود؛
- خط ۱۳: سرور نشان میدهد که سند درخواستی به URL تغییر کرده است؛
- خط ۱۸: URL جدید را برای سند درخواستشده مشخص میکند؛
- خط ۲۷: [curl] یک درخواست جدید ارسال میکند، این بار به URL جدید؛
- خط ۳۳: از URL جدید استفاده میشود؛
- خط ۳۸: سرور پاسخ میدهد که سند مورد نظر را پیدا کرده است؛
- خط ۴۱: آن را به صورت تکهها ارسال میکند؛
سند درخواستی در فایل [sergetahe.com.HTML] یافت خواهد شد.
16.4.6. مثال ۶
PHP دارای افزونهای به نام [libcurl] است که امکان استفاده از قابلیتهای ابزار [curl] را در یک برنامه PHP فراهم میکند. ابتدا باید اطمینان حاصل کنید که این افزونه در فایل [php.ini] که در بخش لینکها توضیح داده شده است، فعال باشد:

اطمینان حاصل کنید که خط ۸۸۹ بالا از حالت توضیحی خارج شده باشد.
ما قصد داریم یک اسکریپت به نام [http-02.php] بنویسیم که از فایل پیکربندی زیر، jSON، استفاده خواهد کرد:
هر ورودی در فرهنگ لغت [clé, valeur] ساختار زیر را دارد:
- clé: نام یک سرور وب؛
- valeur یک فرهنگ لغت با کلیدهای زیر است:
- timeout: حداکثر زمان انتظار برای پاسخ از سرور. پس از این زمان، کلاینت قطع ارتباط خواهد کرد؛
- url: URL سند درخواستی؛
کد اسکریپت برای [http-02.php] به شرح زیر است:
<?php
// پابندی سختگیرانه به انواع اعلامشدهٔ پارامترهای تابع
declare (strict_types=1);
//
// مدیریت خطا
//error_reporting(E_ALL & E_STRICT);
//ini_set("display_errors", "on");
//
//ثابتها
const CONFIG_FILE_NAME = "config-http-02.json";
//
//پیکربندی را بازیابی میکند
$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);
//متن HTML را از URL در فایل پیکربندی بازیابی میکند
foreach ($config as $site => $infos) {
// خواندن URL از سایت $ite
$résultat = getUrl($site, $infos["url"], $infos["timeout"]);
//نمایش نتایج
print "$résultat\n";
}//برای
//پایان
exit;
//-----------------------------------------------------------------------
function getUrl(string $site, string $url, int $timeout, $suivi = TRUE): string {
// خوانش میکند URL $url و آن را در فایل output/$site ذخیره میکند.HTML
//
//پیگیری
print "Client : début de la communication avec le serveur [$site] ----------------------------\n";
// آغاز یک جلسه cURL
$curl = curl_init($url);
if ($curl === FALSE) {
// یک خطا رخ داده است
return "Erreur lors de l'initialisation de la session cURL pour le site [$site]";
}
// گزینههای curl
$options = [
// حالت تفصیلی
CURLOPT_VERBOSE => true,
// اتصال جدید – بدون کش
CURLOPT_FRESH_CONNECT => true,
// زمانبندی درخواست (به ثانیه)
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => $timeout,
// اعتبارسنجی گواهیها را انجام نده SSL
CURLOPT_SSL_VERIFYPEER => false,
// دنبال کردن هدایتها
CURLOPT_FOLLOWLOCATION => true,
// بازیابی سند درخواستی به صورت یک رشته
CURLOPT_RETURNTRANSFER => true
];
//پیکربندی curl
curl_setopt_array($curl, $options);
// اجرای درخواست
$page_content = curl_exec($curl);
//بستن جلسه cURL
curl_close($curl);
//پردازش نتیجه
if ($page_content !== FALSE) {
//ذخیرهٔ نتیجه در $site.HTML
$result = file_put_contents("output/$site.HTML", $page_content);
if ($result === FALSE) {
// بازگشت خطا
return "Erreur lors de la création du fichier [output/$site.HTML]";
}
// پاسخ موفقیتآمیز
return "Fin de la communication avec le serveur [$site]. Vérifiez le fichier [output/$site.HTML]";
} else {
// یک خطای ارتباطی رخ داده است
return "Erreur de communication avec le serveur [$site]";
}
}
نظرات
- خط ۱۴: از فایل پیکربندی برای ایجاد فرهنگ لغت [$config] استفاده میشود؛
- خطوط 17–22: یک حلقه بر روی لیست سایتهای یافتشده در پیکربندی اجرا میشود؛
- خط ۱۹: برای هر سایت، تابع [getUrl] فراخوانی میشود که دانلود میکندURL $infos[«url»] با یک تایماوت $infos[«timeout»];
- خط ۳۴: یک جلسه آغاز میشود: [curl]. [curl_init] هنوز به سرور وب متصل نمیشود. این یک منبع، [$curl]، را بازمیگرداند که بهعنوان پارامتر برای تمامی توابع بعدی [curl] خواهد بود؛
- خطوط ۳۵–۳۸: اگر inicialization جلسه [curl] ناموفق باشد، تابع [curl_init] مقدار بولین FALSE را برمیگرداند؛
- خطوط ۴۰–۵۴: دیکشنری [$options] اتصال [curl] را به سرور پیکربندی میکند؛
- خط ۵۷: گزینههای اتصال به منبع [$curl] ارسال میشوند؛
- خط ۵۹: یک اتصال به URL با گزینههای تعریفشده درخواست میشود. به دلیل گزینه [CURLOPT_RETURNTRANSFER => true]، تابع [curl_exec] سند ارسالشده توسط سرور را به صورت یک رشته بازمیگرداند. توابع [curl_exec] در صورتی که اتصال ناموفق باشد، مقدار بولی FALSE را برمیگرداند؛
- خط ۶۴: نتیجه تابع [curl_exec] تحلیل میشود؛
- خط ۶۶: صفحه دریافتشده در یک فایل محلی ذخیره میشود؛
- خطوط ۶۹، ۷۲، ۷۵: نتیجه تابع [getUrl] بازگردانده میشود؛
وقتی اسکریپت [http-02.php] اجرا میشود، خروجی کنسول زیر تولید میشود:
* 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/programming-tutorials
< Set-Cookie: SERVERID68971=2621236|XN/Gc|XN/Gc; path=/
< X-IPLB-Instance: 17097
<
* Ignoring the response-body
* Connection #0 به میزبان sergetahe.com بدون تغییر باقی ماند
* Issue another request to this URL: 'http://sergetahe.com/programming-courses-and-tutorials'
* Found bundle for host sergetahe.com: 0x1fee4ebe090 [can pipeline]
* Re-using existing connection! (#0) با میزبان 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/programming-courses-and-tutorials/
< Set-Cookie: SERVERID68971=2621236|XN/Gc|XN/Gc; path=/
< Cache-control: private
< X-IPLB-Instance: 17097
<
* Ignoring the response-body
* Connection #0 به میزبان sergetahe.com بدون تغییر باقی ماند
* Issue another request to this URL: 'http://sergetahe.com/programming-courses-and-tutorials/'
* Found bundle for host sergetahe.com: 0x1fee4ebe090 [can pipeline]
* Re-using existing connection! (#0) با میزبان 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/programming-courses-and-tutorials/wp-json/>; rel="https://api.w.org/"
< Link: <http://sergetahe.com/programming-courses-and-tutorials/>; 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 برای میزبان sergetahe.com دستنخورده باقی مانده است
* 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 به میزبان tahe.developpez.com دستنخورده باقی ماند
* 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 تا میزبان www.polytech-angers.fr دستنخورده باقی مانده
* 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) با میزبان 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 به میزبان www.polytech-angers.fr بدون تغییر باقی ماند
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 به میزبان localhost بدون تغییر باقی ماند
Fin de la communication avec le serveur [localhost]. Vérifiez le fichier [output/localhost.HTML]
توضیحات
- خروجی مشابه با ابزار [curl] تولید میشود؛
- به رنگ سبز، خروجیهای اسکریپت؛
- به رنگ آبی، دستورات ارسالشده به سرور؛
- به رنگ زرد، دستورات دریافتی توسط کلاینت در پاسخ؛
16.4.7. نتیجهگیری
در این بخش، پروتکل HTTP را بررسی کرده و یک اسکریپت [http-02.php] نوشتهایم که قادر به دانلود یک فایل URL از وب است.
16.5. پروتکل SMTP (پروتکل ساده انتقال ایمیل)
16.5.1. مقدمه

در این فصل:
- [Serveur B] یک سرور محلی SMTP خواهد بود که ما آن را نصب خواهیم کرد؛
- [Client A] یک کلاینت SMTP در اشکال مختلف خواهد بود:
- کلاینت [RawTcpClient] برای کشف پروتکل SMTP؛
- یک اسکریپت PHP که پروتکل SMTP مشتری [RawTcpClient] را بازسازی میکند؛
- یک اسکریپت PHP که با استفاده از کتابخانه [SwiftMailServer] انواع ایمیلها را ارسال میکند؛
16.5.2. ایجاد یک آدرس ایمیل [gmail]
برای انجام آزمایشهای SMTP خود، به یک آدرس ایمیل نیاز داریم تا ایمیلها را به آن ارسال کنیم. برای این کار، یک آدرس در جیمیل ایجاد خواهیم کرد:

- در [5]، کاربر [php7parlexemple] را ایجاد میکنیم (چیزی دیگر انتخاب کنید)؛
- برای [6]، رمز عبور [PHP7parlexemple] خواهد بود (یک رمز دیگر انتخاب کنید)؛
- برای [7]، این اطلاعات را تأیید میکنیم؛

- میدانهای مربوط به [9-10] را پر کنید، سپس تأیید کنید (11);
- شرایط استفاده گوگل را بپذیرید (۱۲–۱۳) و سپس تأیید کنید (۱۴);

- در [15]، صندوق ورودی کاربر [PHP7] (16);
- در [17]، این کاربر یک صندوق ورودی خالی دارد؛
- در [18-19]، به حساب گوگل کاربر [php7parlexemple@gmail.com] وارد شوید. ما قصد داریم امنیت حساب را پیکربندی کنیم؛

- در [21]، برنامههای غیر از برنامههای گوگل را برای دسترسی به حساب [php7parlexemple] مجاز کنید. اگر این کار را انجام ندهیم، سرور ایمیل محلی ما [hMailServer] قادر به برقراری ارتباط با سرور جیمیل SMTP نخواهد بود؛

16.5.3. نصب سرور SMTP
برای آزمایشهای خود، سرور ایمیل [hMailServer] را نصب خواهیم کرد که هم یک سرور SMTP برای ارسال ایمیلها است، یک سرور POP3 (پروتکل دفتر پستی) برای خواندن ایمیلهای ذخیرهشده روی سرور، و یک سرور IMAP (پروتکل دسترسی به پیام اینترنتی) که همچنین به شما امکان میدهد ایمیلهای ذخیرهشده روی سرور را بخوانید اما قابلیتهای اضافی ارائه میدهد. بهویژه، این پروتکل مدیریت ذخیرهسازی ایمیل روی سرور را امکانپذیر میسازد.
سرور ایمیل [hMailServer] در URL [https://www.hmailserver.com/] (مه ۲۰۱۹) در دسترس است.

در طول نصب، از شما خواسته میشود تا جزئیات خاصی را ارائه دهید:

- در [1-2]، هم سرور ایمیل و هم ابزارهای مدیریت آن را انتخاب کنید؛
- در طول نصب، از شما رمز عبور مدیر خواسته میشود: آن را یادداشت کنید، زیرا به آن نیاز خواهید داشت؛
[hMailServer] به عنوان یک سرویس ویندوز نصب میشود که هنگام راهاندازی کامپیوتر به طور خودکار شروع میشود. ترجیح داده میشود که راهاندازی دستی را انتخاب کنید:
- در [3]، [services] را در کادر متنی نوار وضعیت تایپ کنید؛

- برای [4-8]، سرویس را روی حالت [manuel] (۶) تنظیم کرده، سپس آن را راهاندازی کنید (۷)؛
پس از راهاندازی، سرور [hMailServer] باید پیکربندی شود. این سرور با برنامهٔ مدیریتی [hMailServer Administrator] نصب شده است:

- در [2]، در کادر ورودی نوار وضعیت، [hmailserver] را تایپ کنید؛
- در [3]، مدیر را راهاندازی کنید؛
- در [4]، مدیر را به سرور [hMailServer] متصل کنید؛
- در [5]، رمز عبوری را که هنگام نصب [hMailServer] وارد کردهاید، وارد کنید؛

ما قصد داریم یک حساب کاربری ایجاد کنیم:
- روی [Accounts] (7) کلیک راست کرده و سپس (8) را برای افزودن کاربر جدید انتخاب کنید؛
- در زبانه [General] (9)، یک کاربر [guest] (10) را با رمز عبور [guest] (11) ایجاد میکنیم. آنها آدرس ایمیل [guest@localhost] (10) را خواهند داشت؛
- در [12]، کاربر [guest] فعال شده است؛


- در [15]، پروتکل SMTP سرور ایمیل را پیکربندی کنید؛
- در [16]، توزیع ایمیل را پیکربندی کنید؛
- در [17]، پیکربندی تحویل ایمیل به ماشین میزبان (localhost)؛
- در [18]، نام ماشین محلی (localhost). اسکریپت در بخش «link» به شما امکان میدهد این نام را به دست آورید؛
- در [19]، ما یک سرور رله SMTP را پیکربندی میکنیم: این سروری است که توزیع ایمیلهایی را که برای ماشین محلی (localhost) در نظر گرفته نشدهاند، مدیریت خواهد کرد؛
- در [20]، سرور جیمیل SMTP. ما از جیمیل استفاده میکنیم زیرا در بخش «لینک» یک حساب کاربری در آنجا ایجاد کردهایم؛
- در [21]، پورت جیمیل SMTP؛
- در [22]، سرویس Gmail SMTP یک سرویس امن است: برای دسترسی به آن به یک حساب Gmail نیاز دارید؛
- در [23]، کاربر [php7parlexemple] در بخش «لینک» ایجاد شده است؛
- در [24]، رمز عبور این کاربر: [PHP7parlexemple]، در پاراگراف «لینک» ایجاد شده است؛
- در [25]، نوع پروتکل امنیتی مورد استفاده توسط جیمیل مشخص شده است؛

- در [27]، پورت برای سرویس SMTP؛
- در [28]، این سرویس به احراز هویت نیاز ندارد؛
- در [30]، پیام خوشآمدگویی را که سرور SMTP برای مشتریان خود ارسال میکند، وارد کنید؛
16.5.4. پروتکل SMTP

اکنون پروتکل SMTP را با استفاده از محیط زیر بررسی خواهیم کرد:
- کلاینت A کلاینت عمومی TCP ([RawTcpClient]) خواهد بود؛
- سرور B، سرور ایمیل [hMailServer] خواهد بود؛
- کلاینت A از سرور B درخواست میکند تا ایمیلی را به کاربر [php7parlexemple@gmail.com] تحویل دهد؛
- ما بررسی خواهیم کرد که این کاربر واقعاً ایمیل ارسالشده را دریافت کرده است؛
ما کلاینت را به شرح زیر راهاندازی میکنیم:
![]()
- به عنوان [1]، به پورت ۲۵ روی ماشین محلی متصل میشویم، جایی که سرویس SMTP از [hMailServer] در حال اجرا است. آرگومان [--quit bye] نشان میدهد که کاربر با وارد کردن فرمان [bye] از برنامه خارج خواهد شد. بدون این آرگومان، فرمان خروج از برنامه [quit] است. با این حال، [quit] همچنین یک فرمان در پروتکل SMTP است. بنابراین باید از این ابهام اجتناب کنیم؛
- در [2]، کلاینت واقعاً متصل است؛
- در [3]، کلاینت منتظر دستورات وارد شده از طریق صفحهکلید است؛
- در [4]، سرور پیام خوشآمدگویی خود را برای کلاینت ارسال میکند؛

- در [5]، کلاینت فرمان [EHLO nom-de-la-machine-client] را ارسال میکند. سرور با مجموعهای از پیامها در قالب [250-xx] (6) پاسخ میدهد. کد [250] نشان میدهد که فرمان ارسالشده توسط مشتری موفق بوده است؛
- در [7]، کلاینت فرستنده پیام را مشخص میکند، در این مورد [guest@localhost]. این کاربر باید روی سرور ایمیل [hMailServer] وجود داشته باشد. این مورد در اینجا صادق است زیرا ما قبلاً این کاربر را ایجاد کردهایم؛
- در [8]، پاسخ سرور؛
- در [9]، گیرنده پیام مشخص میشود؛ در اینجا، این کاربر جیمیل [php7parlexemple@gmail.com] است؛
- در [10]، پاسخ سرور؛
- در [11]، فرمان [DATA] به سرور اطلاع میدهد که کلاینت در آستانه ارسال محتوای پیام است؛
- در [12]، پاسخ سرور؛
- در [13-16]، مشتری باید فهرستی از خطوط متنی را ارسال کند که با خطی پایان مییابد که تنها شامل یک نقطه باشد. پیام میتواند شامل [Subject :, From :, To :] (۱۳) خط برای تعریف موضوع پیام، فرستنده و گیرنده بهترتیب باشد؛
- در [14]، سربرگهای قبلی باید با یک خط خالی دنبال شود؛
- در [15]، بدنه پیام؛
- در [16]، خطی حاوی یک نقطه کامل، که پایان پیام را نشان میدهد؛
- در [17]، به محض اینکه سرور خط حاوی یک نقطه کامل را دریافت کرد، پیام را در صف قرار میدهد؛
- در [18]، کلاینت به سرور اطلاع میدهد که کارش تمام شده است؛
- در [19]، پاسخ سرور؛
- در [20]، میبینیم که سرور اتصال به کلاینت را قطع کرده است؛
اکنون بررسی کنیم که کاربر [php7parlexemple@gmail.com] واقعاً پیام را دریافت کرده است:

- در [2]، میتوانیم ببینیم که کاربر [php7parlexemple@gmail.com] واقعاً پیام را دریافت کرده است؛



- در [7]، فرستنده ایمیل. میتوانیم ببینیم که این [guest@localhost] نیست. این به این دلیل است که سرور رله تعریفشده در پیکربندی برای [hmailServer] پیام را تحویل داده است. با این حال، این سرور رله [smtp.gmail.com] است که با اعتبارنامههای کاربر جیمیل [php7parlexemple@gmail.com] مرتبط است. هر ایمیلی که از [hMailServer] ارسال شود، به نظر میرسد از طرف کاربر [php7parlexemple@gmail.com] ارسال شده است. این چیزی نیست که ما در اینجا میخواستیم، اما اگر از این سرور رله استفاده نکنیم، سرویس SMTP جیمیل ایمیلهای ارسالشده توسط [hMailServer] را رد خواهد کرد زیرا سرویس SMTP جیمیل احراز هویت را میطلبد که [hMailServer] آن را فراهم نمیکند. بیشک راهی برای دور زدن این مشکل وجود دارد، اما من آن را پیدا نکردهام؛
- در [8]، میتوانیم ببینیم که ایمیل از ماشین [DESKTOP-528I5CU] دریافت شده است، که میزبان سرور ایمیل [hMailServer] است؛
- در [9]، فرستندهٔ پیام. میتوانیم ببینیم که این [guest@localhost] نیست؛
- [10] فرستنده اصلی پیام است. این بار، واقعاً [guest@localhost] است؛
- در [11]، موضوع؛
- [12]، گیرنده؛
- در [13]، پیام؛
در نهایت، کلاینت ما [RawTcpClient] موفق شد پیام را ارسال کند، هرچند با فرستنده مشکلی مواجه شدیم. اکنون ما پایههای لازم برای ایجاد یک کلاینت SMTP را داریم که در PHP نوشته شده است.
16.5.5. یک کلاینت پایه SMTP نوشته شده در PHP
اکنون آنچه را که قبلاً در مورد پروتکل SMTP آموختهایم، بر PHP اعمال خواهیم کرد.

اسکریپت [smtp-01.php] توسط فایل زیر 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] یک آرایه است که هر عنصر آن یک دیکشنری از نوع [nom=>infos] است. مقدار [infos] خود یک دیکشنری با کلیدها و مقادیر زیر است:
- [smtp-server]: نام سرور SMTP که باید استفاده شود؛
- [smtp-port]: شماره پورت سرویس SMTP؛
- [from]: فرستنده پیام؛
- [to]: گیرنده پیام؛
- [subject]: موضوع پیام؛
- [message]: پیامی که باید ارسال شود؛
- عنصر اول از سرور SMTP [localhost] برای ارسال ایمیل به کاربری در [localhost] استفاده میکند؛
- عنصر دوم از سرور SMTP [localhost] برای ارسال ایمیل به کاربری در [Gmail] استفاده میکند؛
- عنصر سوم از سرور SMTP [Gmail] برای ارسال ایمیل به کاربر [Gmail] استفاده میکند؛
کد [smtp-01.php] برای مشتری SMTP به شرح زیر است:
<?php
// کلاینت SMTP (SendMail پروتکل انتقال) برای ارسال یک پیام استفاده شد
//پروتکل ارتباطی کلاینت-سرور SMTP
// -> کلاینت به پورت ۲۵ سرور SMTP متصل میشود
//<- سرور یک پیام خوشآمدگویی ارسال میکند
// -> کلاینت فرمان EHLO را ارسال میکند و پس از آن نام میزبان خود را میفرستد
// <- سرور با OK پاسخ میدهد یا خیر
//-> کلاینت فرمان MAIL را ارسال میکند FROM: <ارسالکننده>
// <- سرور با OK پاسخ میدهد یا خیر
// -> کلاینت فرمان RCPT را ارسال میکند TO: <recipient>
// <- سرور با OK پاسخ میدهد یا خیر
// -> کلاینت فرمان DATA را ارسال میکند
// <- سرور با OK پاسخ میدهد یا خیر
//-> کلاینت تمام خطوط پیام خود را ارسال میکند و با خطی حاوی پایان میدهد
//یک کاراکتر واحد.
// <- سرور با OK پاسخ میدهد یا خیر
// -> کلاینت فرمان QUIT را ارسال میکند
// <- سرور با OK پاسخ میدهد یا خیر
//پاسخهای سرور به شکل xxx text هستند، که در آن xxx یک عدد سهرقمی است. هر
//اعداد xxx >=500 نشاندهنده خطا هستند.
//پاسخ ممکن است شامل چندین خط باشد که همه با xxx شروع میشوند به جز خط آخر
// به شکل xxx (فاصله)
//خطوط متن مبادله شده باید با کاراکترهای RC (#13) و LF (#10) پایان یابند
//
// SMTP کلاینت (SendMail پروتکل انتقال) برای ارسال یک پیام
//
// پردازش خطا
//ini_set ("error_reporting"، E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// رعایت دقیق انواع اعلامشده پارامترهای تابع
declare (strict_types=1);
//
//پارامترها برای ارسال ایمیل
const CONFIG_FILE_NAME = "config-smtp-01.json";
// بازیابی پیکربندی
$mails = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);
//ارسال ایمیلها
foreach ($mails as $name => $infos) {
//ردیابی
print "Envoi du mail [$name]\n";
//ارسال ایمیل
$résultat = sendmail($name, $infos, TRUE);
// نتیجه نمایش داده شده
print "$résultat\n";
}//برای
//پایان
exit;
//ارسال ایمیل
//-----------------------------------------------------------------------
function sendmail(string $name, array $infos, bool $verbose = TRUE): string {
// پیام را ارسال میکند [$name,$infos]. اگر $verbose=TRUE ، ردیابی مبادلات کلاینت-سرور
//نام مشتری را بازیابی میکند
$client = gethostbyaddr(gethostbyname(""));
//یک اتصال به سرور SMTP باز میکند
$connexion = fsockopen($infos["smtp-server"], (int) $infos["smtp-port"]);
// در صورت رخ دادن خطا بازمیگردد
if ($connexion === FALSE) {
return sprintf("Echec de la connexion au site (%s,%s) : %s", $infos["smtp-server"], $infos["smtp-port"]);
}
// $connexion نشاندهنده یک جریان ارتباط دوطرفه است
//بین کلاینت (این برنامه) و سرور SMTP مورد تماس
//این کانال برای تبادل دستورات و اطلاعات استفاده میشود
// پس از برقراری اتصال، سرور یک پیام خوشآمدگویی ارسال میکند که خوانده میشود
$erreur = sendCommand($connexion, "", $verbose, TRUE);
if ($erreur !== "") {
// اتصال بسته شد
fclose($connexion);
// بازگشت
return $erreur;
}
// دستور EHLO
$erreur = sendCommand($connexion, "EHLO $client", $verbose, TRUE);
if ($erreur !== "") {
//اتصال قطع شد
fclose($connexion);
//بازگشت
return $erreur;
}
//فرمان MAIL FROM:
$erreur = sendCommand($connexion, sprintf("MAIL FROM: <%s>", $infos["from"]), $verbose, TRUE);
if ($erreur !== "") {
// اتصال بسته شد
fclose($connexion);
//بازگشت
return $erreur;
}
// دستور RCPT TO:
$erreur = sendCommand($connexion, sprintf("RCPT TO: <%s>", $infos["to"]), $verbose, TRUE);
if ($erreur !== "") {
//اتصال بسته شد
fclose($connexion);
//بازگشت
return $erreur;
}
// دستور DATA
$erreur = sendCommand($connexion, "DATA", $verbose, TRUE);
if ($erreur !== "") {
//اتصال بسته شد
fclose($connexion);
//بازگشت
return $erreur;
}
// در حال آمادهسازی پیام برای ارسال
// باید شامل خطوط زیر باشد
// از: فرستنده
// به: گیرنده
// موضوع:
// خط خالی
// پیام
// .
$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 !== "") {
// اتصال بسته شد
fclose($connexion);
// بازگشت
return $erreur;
}
// دستور خروج
$erreur = sendCommand($connexion, "QUIT", $verbose, TRUE);
if ($erreur !== "") {
// اتصال بسته شد
fclose($connexion);
//بازگشت
return $erreur;
}
//پایان
fclose($connexion);
return "Message envoyé";
}
// --------------------------------------------------------------------------
function sendCommand($connexion, string $commande, bool $verbose, bool $withRCLF): string {
//ارسال میکند $commande به کانال $connexion
// حالت تفصیلی اگر $verbose=1
//اگر $withRCLF=1 باشد، توالی RCLF را به تبادل اضافه میکند
// دادهها
if ($withRCLF) {
$RCLF = "\r\n";
} else {
$RCLF = "";
}
// دستور ارسال را ارسال کنید اگر $commande خالی نباشد
if ($commande!=="") {
fputs($connexion, "$commande$RCLF");
//اکو احتمالی
if ($verbose) {
affiche($commande, 1);
}
}//اگر
// خواندن پاسخ
$réponse = fgets($connexion, 1000);
// اکوی احتمالی
if ($verbose) {
affiche($réponse, 2);
}
//بازیابی کد خطا
$codeErreur = (int) substr($réponse, 0, 3);
// آخرین خط پاسخ؟
while (substr($réponse, 3, 1) === "-") {
// خواندن پاسخ
$réponse = fgets($connexion, 1000);
// بازتاب احتمالی
if ($verbose) {
affiche($réponse, 2);
}
}//در حالی که
// پاسخ کامل
// خطایی که توسط سرور بازگردانده شده؟
if ($codeErreur >= 500) {
return substr($réponse, 4);
}
//بدون خطا بازگردانده شد
return "";
}
// --------------------------------------------------------------------------
function affiche($échange, $sens) {
// $échange را روی صفحه نمایش میدهد
// اگر $sens=1 باشد، نمایش میدهد -->$echange
// اگر $sens=2 باشد، نمایش میدهد <-- $échange بدون دو کاراکتر آخر RCLF
switch ($sens) {
case 1:
print "--> [$échange]\n";
break;
case 2:
$L = strlen($échange);
print "<-- [" . substr($échange, 0, $L - 2) . "]\n";
break;
}//سوئیچ
}
نظرات
- خط ۳۹: فایل پیکربندی پردازش میشود؛
- خط ۴۲: یک حلقه بر روی عناصر آرایه [mails] اجرا میشود. هر عنصر یک دیکشنری [name=>infos] است، که در آن [name] نامی است که میتواند هر چیزی باشد و [infos] دیکشنریای حاوی اطلاعات مورد نیاز برای ارسال ایمیل است؛
- خط ۴۶: ایمیل توسط تابع [sendmail] ارسال میشود که سه پارامتر میگیرد:
- $name: نام اختصاصدادهشده به این ایمیل؛
- $infos: دیکشنری حاوی اطلاعات مورد نیاز برای ارسال؛
- verbose: یک بول که نشان میدهد آیا تعاملات کلاینت/سرور باید در کنسول ثبت شوند یا خیر؛
- خط ۴۶: تابع [sendmail] یک پیام خطا را بازمیگرداند که در صورت عدم وقوع خطا خالی است؛
- خط ۵۶: تابع [sendmail] دستورات مختلفی را که یک کلاینت باید ارسال کند، ارسال میکند SMTP:
- خطوط ۷۷–۸۴: فرمان EHLO;
- خطوط ۸۵–۹۲: فرمان MAIL FROM: ;
- خطوط ۹۳–۱۰۰: دستور RCPT TO: ;
- خطوط ۱۰۱–۱۰۸: فرمان DATA;
- خطوط ۱۱۷–۱۲۴: ارسال پیام (From, To, Subject, متن);
- خطوط ۱۲۵–۱۳۲: فرمان QUIT;
- خط ۱۴۰: تابع [sendCommand] مسئول ارسال دستورات مشتری به سرور SMTP است. این تابع چهار پارامتر را میپذیرد:
- [$connexion]: اتصال رابط بین کلاینت و سرور؛
- [$commande]: دستوری که باید ارسال شود؛
- [$verbose]: اگر TRUE باشد، آنگاه تعاملات کلاینت/سرور در کنسول ثبت میشود؛
- [$withRCLF]: اگر TRUE باشد، فرمان را با دنباله \r\n ارسال کنید. این برای تمام دستورات در پروتکل SMTP الزامی است، اما [sendCommand] نیز برای ارسال پیام استفاده میشود. در این حالت، توالی \r\n اضافه نمیشود؛
- خطوط ۱۵۰–۱۵۷: فرمان به سرور ارسال میشود؛
- خطوط 158–163: اولین خط پاسخ خوانده میشود. پاسخ ممکن است شامل چندین خط باشد. هر خط به شکل XXX-YYY است، که در آن XXX یک کد عددی است، به جز آخرین خط پاسخ که به شکل XXX YYY (بدون خط تیره) است؛
- خطوط ۱۶۷–۱۷۴: تمام خطوط پاسخ را بخوانید؛
- خط ۱۷۷: اگر کد عددی XXX بزرگتر از ۵۰۰ باشد، آنگاه سرور خطایی را بازگردانده است؛
نتایج
اجرای اسکریپت خروجی کنسول زیر را تولید میکند:
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.
- خطوط ۱–۲۶: استفاده از سرور SMTP [hMailServer] برای ارسال ایمیل به [guest@localhost] موفقیتآمیز است؛
- خطوط ۲۷–۵۲: استفاده از سرور SMTP [hMailServer] برای ارسال ایمیل به [php7parlexemple@gmail.com] موفقیتآمیز است؛
- خطوط ۵۳–۶۵: تلاش برای استفاده از سرور SMTP [Gmail] برای ارسال ایمیل به [php7parlexemple@gmail.com] به درستی کار نمیکند: در خط ۶۵، سرور SMTP کد خطای ۵۳۰ را همراه با یک پیام خطا بازمیگرداند. این نشان میدهد که کلاینت SMTP ابتدا باید از طریق یک اتصال امن احراز هویت کند. کلاینت ما این کار را انجام نداده و بنابراین رد میشود؛
16.5.6. یک کلاینت دوم، SMTP، با استفاده از کتابخانه [SwiftMailer] مینویسد
کلاینت قبلی حداقل دو کاستی دارد:
- اگر سرور اتصال امن را الزامی کند، نمیتواند از آن استفاده کند؛
- نمیتواند فایلها را به پیام پیوست کند؛
در اسکریپت جدید ما، از کتابخانههای [SwiftMailer] و [https://swiftmailer.symfony.com/] (مه ۲۰۱۹) استفاده خواهیم کرد. روند نصب [SwiftMailer] در URL [https://swiftmailer.symfony.com/docs/introduction.HTML] (مه ۲۰۱۹) توضیح داده شده است.
ابتدا، Laragon را اجرا کنید:

- در [1]، یک ترمینال باز کنید؛

- به [3]، بررسی کنید که در پوشه [<laragon>/www] هستید، که در آن <laragon> پوشه نصب Laragon است؛
- در [3]، دستور نمایشدادهشده را تایپ کنید (مه ۲۰۱۹). دستور دقیق را در URL و [https://swiftmailer.symfony.com/docs/introduction.HTML] بررسی کنید؛
- در [4]، ذکر شده است که هیچ نصب یا بهروزرسانی انجام نشده است. این به این دلیل است که کتابخانه قبلاً روی این رایانه نصب شده بود؛
- در [5]، پوشه نصب برای [swiftmailer] و [6]؛
- در [7]، فایلی که در اسکریپت خود به آن نیاز خواهیم داشت؛
پس از انجام این کار، بررسی کنید که پوشه [<laragon>/www/vendor] [5] واقعاً در شاخه [Include Path] در NetBeans وجود دارد (به بخش «لینک» مراجعه کنید).
در نهایت، کتابخانه [SwiftMailer] نیازمند فعال بودن افزونه PHP [mbstring] است. برای این کار، فایل [php.ini] را بررسی کنید (به بخش «لینک» مراجعه کنید):

اسکریپت [smtp-02.php] از فایل پیکربندی زیر استفاده خواهد کرد: jSON [config-smtp-02.json]:
این فایل شامل همان بخشهای موجود در [config-smtp-01.json] است، به همراه دو بخش اضافی:
- [tls]: در TRUE، این نشان میدهد که باید از اتصال امن با سرور SMTP استفاده شود. اگر [tls] روی TRUE تنظیم شده باشد، دو فیلد باید اضافه شوند:
- [user]: نام کاربری مورد استفاده برای احراز هویت اتصال؛
- [password]: رمز عبور آنها؛
در مثال ما، از اطلاعات کاربری [php7parlexemple@gmail.com] برای ورود به سرور جیمیل استفاده کردیم. از اطلاعات کاربری خود استفاده کنید؛
- [attachments]: نام فایلهایی را که باید به ایمیل پیوست شوند، مشخص میکند؛
کد اسکریپت برای [smtp-02.php] به شرح زیر است:
<?php
// کلاینت SMTP (پروتکل انتقال SendMail) برای ارسال یک پیام
//
// مدیریت خطا
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
//وابستگیها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
//
// تنظیمات ارسال ایمیل
const CONFIG_FILE_NAME = "config-smtp-02.json";
// بازیابی پیکربندی
$mails = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);
//ارسال ایمیلها
foreach ($mails as $name => $infos) {
// ردیابی
print "Envoi du mail [$name]\n";
//ارسال ایمیل
$résultat = sendmail($name, $infos);
//نمایش نتایج
print "$résultat\n";
}//برای
// پایان
exit;
//-----------------------------------------------------------------------
function sendmail($name, $infos) {
//ارسال میکند $infos[message] به سرور SMTP $infos[smtp-server] روی پورت $infos[smt-port]
//اگر $infos[tls] درست باشد، از قالب TLS استفاده خواهد شد
//ایمیل به نمایندگی از $infos[from] ارسال میشود
// برای گیرنده $infos['to']
//سند $info[attachment] به پیام پیوست شده است
//پیام دارای موضوع $infos[subject]
//
//پیام در قالب HTML
$messageHTML = str_replace("\n", "<br/>", $infos["message"]);
try {
// ایجاد پیام
$message = (new \Swift_Message())
// موضوع پیام
->setSubject($infos["subject"])
// فرستنده
->setFrom($infos["from"])
// گیرندگان با استفاده از یک فرهنگ لغت (setTo/setCc/setBcc)
->setTo($infos["to"])
// متن پیام
->setBody($infos["message"])
//نسخهٔ HTML
->addPart("<b>$messageHTML</b>", 'text/html')
;
// پیوستها
foreach ($infos["attachments"] as $attachment) {
// مسیر پیوست
$fileName = __DIR__ . $attachment;
//بررسی وجود فایل
if (file_exists($fileName)) {
// پیوست کردن سند به پیام
$message->attach(\Swift_Attachment::fromPath($fileName));
} else {
// خطا
print "L'attachement [$fileName] n'existe pas\n";
}
}
// TLS پروتکل؟
if ($infos["tls"] === "TRUE") {
// TLS
$transport = (new \Swift_SmtpTransport($infos["smtp-server"], $infos["smtp-port"], 'tls'))
->setUsername($infos["user"])
->setPassword($infos["password"]);
} else {
// خیر TLS
$transport = (new \Swift_SmtpTransport($infos["smtp-server"], $infos["smtp-port"]));
}
// مدیر ارسال
$mailer = new \Swift_Mailer($transport);
// پیام ارسال شد
$result = $mailer->send($message);
// پایان
return "Message [$name] envoyé";
} catch (\Throwable $ex) {
// خطا
return "Erreur lors de l'envoi du message [$name] : " . $ex->getMessage();
}
}
توضیحات
- خط ۱۰: ما فایل [autoload.php] را که در پوشه [<lagagon>/www/vendor] قرار دارد، بارگذاری میکنیم، جایی که <laragon> پوشه نصب Laragon است. این فایل باعث میشود فایلهای تعریف کلاس از [SwiftMailer] در اولین بار استفاده از این کلاسها بارگذاری شوند. این کار ما را از ایجاد به همان تعداد فایل [require] که تعداد کلاسها و رابطها در SwiftMailer است، بینیاز میکند؛
- خط ۳۲: تابع جدید [sendmail]، که دو پارامتر دارد:
- [$name]، که برای تمایز بین پیامهای مختلف استفاده میشود؛
- [$infos]: اطلاعات مورد نیاز برای ارسال پیام به گیرندهٔ آن؛
- خط ۴۲: ما دو نسخه از پیام خواهیم داشت: یکی به صورت متن ساده و دیگری در HTML. در اینجا، کاراکترهای پایان خط را به کد HTML <br/> تغییر میدهیم؛
- خطوط ۴۵–۶۹: ما پیام را با استفاده از کلاس [\SwiftMessage] تعریف میکنیم؛
- خط ۴۷: متد [SwiftMessage→setSubject] برای تعیین موضوع پیام استفاده میشود؛
- خط ۴۹: متد [SwiftMessage→setFrom] برای تعیین فرستنده پیام استفاده میشود؛
- خط ۵۱: متد [SwiftMessage→setTo] برای تعیین گیرنده پیام استفاده میشود؛
- خط ۵۳: متد [SwiftMessage→setBody] برای تنظیم متن پیام استفاده میشود؛
- خط ۵۵: متد [SwiftMessage→addPart] برای مشخص کردن نسخههای مختلف پیام استفاده میشود، در این مورد پیام در قالب HTML. هنگامی که پیام دارای گونههای مختلفی باشد، کلاینتهای ایمیل گونه مورد نظر کاربر را نمایش میدهند؛
- خطوط ۵۸–۶۹: متد [SwiftMessage→addAttachment] (۶۴) برای پیوست کردن یک فایل به پیام استفاده میشود؛
- خطوط ۷۰–۷۹: پس از تعریف پیام ارسالی، روش ارسال آن باید مشخص شود. حالت انتقال پیام توسط کلاس [\Swift_SmtpTransport] تعریف میشود. حداقل باید دو مورد اطلاعات ارائه شود: nom و port برای سرور SMTP. همچنین یک مورد اطلاعاتی سوم وجود دارد: آیا سرور SMTP به احراز هویت امن نیاز دارد؟
- خطوط ۷۳–۷۵: نمونه [\Swift_SmtpTransport] برای یک اتصال امن به سرور SMTP؛
- خط ۷۸: نمونه [\Swift_SmtpTransport] برای یک اتصال ناامن به سرور SMTP؛
- خط ۸۱: کلاس [\SwiftMailer] پیامها را ارسال میکند. حالت حملونقل انتخابشده باید به آن پاس داده شود؛
- خط ۸۳: پیام [\SwiftMessage] از طریق روش انتقال انتخابشده [\Swift_SmtpTransport] ارسال میشود. متد [SwiftMailer→send] مقدار بولی FALSE را در صورتی که پیام ارسال نشده باشد، برمیگرداند؛
- خطوط ۸۶–۸۹: کتابخانه [SwiftMailer] به محض اینکه مشکلی پیش بیاید، یک استثنا پرتاب میکند؛
توجه: باید توجه داشت که فضای نام کلاسها در کتابخانه [SwiftMailer] ریشه \ است. کلاسهای [\SwiftMessage, \Swift_SmtpTransport, \SwiftMailer] به طور صریح ذکر شدهاند تا به عنوان یادآوری عمل کنند؛
نتایج
وقتی اسکریپت [smtp-02.php] اجرا میشود، خروجی کنسول زیر تولید میشود:
اگر حساب جیمیل کاربر [php7parlexemple] را بررسی کنیم، خروجی زیر را مشاهده میکنیم:

- [1] به عنوان موضوع؛
- در [2]، موضوع؛
- [3]، گیرنده؛
- در [4]، پیام؛
- در [5-10]، پیوستها؛
اگر بخواهید پیام اصلی را ببینید، سند زیر را دریافت میکنید:
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_=_--
- خط ۹: موضوع؛
- خط ۱۰: فرستنده؛
- خط ۱۱: گیرنده؛
- خط ۱۳: پیام شامل چندین بخش است که با برچسبهای [--_=_swift_xx] جدا شدهاند؛
- خطوط ۱۹–۲۴: پیام به صورت متن ساده؛
- خطوط ۲۷–۳۰: پیام در HTML;
- خطوط ۳۴–۳۶: فایل پیوست [Hello from SwiftMailer.docx];
- خطوط ۴۰–۴۲: فایل پیوست [Hello from SwiftMailer.pdf];
- خطوط ۴۶–۴۸: فایل پیوست [Hello from SwiftMailer.odt];
- خطوط ۵۸–۶۰: فایل پیوست [Cours-Tutoriels-Serge-Tahé-1568x268.png];
- خطوط ۵۸–۶۰: فایل پیوست [test-localhost.eml];
- خطوط ۶۲–۱۱۴: فایل پیوست [test-localhost.eml] خود یک پیام است که محتوای آن در خطوط ۶۲–۱۱۴ نمایش داده میشود. مشاهده میشود که این پیام خود حاوی پیوستها است؛
16.6. پروتکلهای POP3 (پروتکل دفتر پست) و IMAP (پروتکل دسترسی به پیام اینترنتی)
16.6.1. مقدمه
برای خواندن ایمیلهای ذخیرهشده روی سرور ایمیل دو پروتکل وجود دارد:
- پروتکل POP3 (پروتکل دفتر پستی)، که از نظر تاریخی اولین پروتکل بود اما اکنون به ندرت استفاده میشود؛
- پروتکل IMAP (پروتکل دسترسی به پیام اینترنتی)، که جدیدتر از POP3 است و در حال حاضر بیشترین استفاده را دارد؛
برای بررسی پروتکل POP3، از معماری زیر استفاده خواهیم کرد:

- [Serveur B] یک سرور محلی POP3 / IMAP خواهد بود که توسط سرور ایمیل [hMailServer] پیادهسازی شده است؛
- [Client A] یک کلاینت POP3 / IMAP در اشکال مختلف خواهد بود:
- کلاینت [RawTcpClient] برای کشف پروتکل POP3؛
- یک اسکریپت PHP که پروتکل POP3 را از سمت کلاینت [RawTcpClient] مجدداً اجرا میکند؛
- یک اسکریپت PHP که از کتابخانه IMAP در PHP استفاده میکند، که پیادهسازی هر دو کلاینت IMAP و POP3 را امکانپذیر میسازد؛
16.6.2. مقدمهای بر پروتکل POP3
ابتدا، از اسکریپت [smtp-01.php] برای ارسال ایمیل به کاربر [guest@localhost] استفاده میکنیم. اگر تستهای مرتبط با اسکریپت را انجام داده باشید، این کاربر معمولاً ایمیلهایی دریافت کرده است، اما ما نتوانستهایم این موضوع را تأیید کنیم. برای ارسال یک ایمیل جدید به او، برای مثال از فایل پیکربندی زیر [config-smtp-01.json] استفاده کنید:
اکنون بیایید ببینیم چگونه میتوانیم از کلاینت [RawTcpClient] برای مشاهده صندوق ورودی کاربر [guest@localhost] استفاده کنیم:
C:\Data\st-2019\dev\php7\php5-exemples\exemples\inet\utilitaires>RawTcpClient --quit bye localhost 110
Client [DESKTOP-528I5CU:55593] connecté au serveur [localhost-110]
Tapez vos commandes (bye pour arrêter) :
<-- [+OK Bienvenue sur sergetahe@localhost]
USER guest@localhost
<-- [+OK Send your password]
PASS guest
<-- [+OK Mailbox locked and ready]
LIST
<-- [+OK 2 messages (610 octets)]
<-- [1 305]
<-- [2 305]
<-- [.]
RETR 1
<-- [+OK 305 octets]
<-- [Return-Path: guest@localhost]
<-- [Received: from DESKTOP-528I5CU.home (localhost [127.0.0.1])]
<-- [ by DESKTOP-528I5CU with ESMTP]
<-- [ ; Tue, 21 May 2019 12:59:11 +0200]
<-- [Message-ID: <1356373A-33C9-4F31-BA43-2B119E128CE3@DESKTOP-528I5CU>]
<-- [From: guest@localhost]
<-- [To: guest@localhost]
<-- [Subject: to localhost via localhost]
<-- []
<-- [ligne 1]
<-- [ligne 2]
<-- [ligne 3]
<-- [.]
DELE 1
<-- [+OK msg deleted]
LIST
<-- [+OK 1 messages (305 octets)]
<-- [2 305]
<-- [.]
DELE 2
<-- [+OK msg deleted]
LIST
<-- [+OK 0 messages (0 octets)]
<-- [.]
QUIT
<-- [+OK POP3 server saying goodbye…]
Perte de la connexion avec le serveur…
- خط ۱: سرور POP3 معمولاً روی پورت ۱۱۰ کار میکند. در اینجا نیز همینطور است؛
- خط ۵: دستور [USER] برای مشخص کردن کاربری که میخواهید صندوق پستی او را بخوانید، استفاده میشود؛
- خط ۷: دستور [PASS] برای مشخص کردن رمز عبور کاربر استفاده میشود؛
- خط ۹: فرمان [LIST] فهرست پیامهای صندوق پستی کاربر را بازیابی میکند؛
- خط ۱۴: فرمان [RETR] پیام مشخصشده با شماره ارائهشده را بازیابی میکند؛
- خط ۲۹: دستور [DELE] درخواست حذف پیامی را که با شمارهٔ ارائهشده مشخص شده است، میدهد؛
- خط ۴۰: فرمان [QUIT] به سرور اطلاع میدهد که کاربر کار خود را پایان داده است؛
پاسخ سرور ممکن است چندین شکل داشته باشد:
- یک خط واحد که با [+OK] آغاز میشود تا نشان دهد فرمان قبلی مشتری موفق بوده است؛
- یک خط واحد که با [-ERR] شروع میشود تا نشان دهد فرمان قبلی مشتری ناموفق بوده است؛
- چند خط که:
- خط اول با [+OK] آغاز میشود؛
- آخرین خط شامل یک نقطه است؛
16.6.3. یک اسکریپت پایه که پروتکل POP3 را پیادهسازی میکند

از آنجایی که پروتکل POP3 ساختار مشابهی با پروتکل SMTP دارد، اسکریپت [pop3-01.php] یک پورت از اسکریپت [smtp-01.php] است. این فایل پیکربندی زیر را خواهد داشت: [config-pop3-01.json]:
- خطوط ۳–۴: سرور POP3 که در حال پرسوجو است، سرور محلی [hMailServer] است؛
- خطوط ۵–۶: ما میخواهیم صندوق پستی کاربر [guest@localhost] را بخوانیم؛
- خط ۷: حداکثر ۵ ایمیل بازیابی خواهد شد؛
اسکریپت [pop3-01.php] به شرح زیر است:
<?php
// POP3 کلاینت (پروتکل دفتر پستی) برای خواندن پیامها از صندوق پستی
//پروتکل ارتباطی POP3 کلاینت-سرور
// -> کلاینت به پورت 110 روی سرور SMTP متصل میشود
//<- سرور یک پیام خوشآمدگویی ارسال میکند
// -> کلاینت فرمان USER username را ارسال میکند
// <- سرور با OK پاسخ میدهد یا خیر
// -> مشتری فرمان PASS mot_de_passe را ارسال میکند
// <- سرور با OK پاسخ میدهد یا خیر
// -> کلاینت فرمان LIST را ارسال میکند
// <- سرور با OK پاسخ میدهد یا خیر
//-> کلاینت فرمان RETR را با یک شماره منحصر به فرد برای هر ایمیل ارسال میکند
// <- سرور با OK پاسخ میدهد یا خیر. اگر OK باشد، محتوای ایمیل درخواستی را ارسال میکند
//-> سرور تمام خطوط ایمیل را ارسال میکند و با خطی که حاوی ... است پایان مییابد
// یک کاراکتر واحد.
//-> کلاینت فرمان DELE [شماره] را برای حذف یک ایمیل ارسال میکند
// <- سرور با OK یا نه پاسخ میدهد
// // -> کلاینت فرمان QUIT را برای پایان دادن به مکالمه با سرور ارسال میکند
// <- سرور با OK پاسخ میدهد یا خیر
//پاسخهای سرور به شکل +OK متن یا -ERR متن هستند
//پاسخ ممکن است شامل چندین خط باشد. در این صورت، آخرین خط شامل یک نقطهٔ کامل است
//خطوط متن مبادله شده باید با کاراکترهای RC (#13) و LF (#10) پایان یابند
//
// POP3 کلاینت (SendMail پروتکل انتقال) برای خواندن ایمیلها
//
// رسیدگی به خطا
//ini_set ("error_reporting"، E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// رعایت دقیق انواع اعلامشده پارامترهای تابع
declare (strict_types=1);
//
//پارامترها برای ارسال ایمیل
const CONFIG_FILE_NAME = "config-pop3-01.json";
// بازیابی پیکربندی
$mailboxes = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);
// خواندن صندوقهای پستی
foreach ($mailboxes as $name => $infos) {
// ردیابی
print "Lecture de la boîte à lettres [$name]\n";
// خواندن صندوق پستی
$résultat = readmail($name, $infos, TRUE);
//نمایش نتایج
print "$résultat\n";
}//برای
//پایان
exit;
//خواندن ایمیل
//-----------------------------------------------------------------------
function readmail(string $name, array $infos, bool $verbose = TRUE): string {
// محتویات صندوق پستی را میخواند [$name]
//تمام پیامها را وارد میکند
//هر پیام پس از خوانده شدن حذف میشود
// اگر $verbose=1 باشد، تعاملات کلاینت-سرور را ردیابی میکند
//
// یک اتصال به سرور SMTP باز میکند
$connexion = fsockopen($infos["server"], (int) $infos["port"]);
// در صورت رخ دادن خطا بازگرداندن
if ($connexion === FALSE) {
return sprintf("Echec de la connexion au site (%s,%s) : %s", $infos["smtp-server"], $infos["smtp-port"]);
}
// $connexion نمایانگر یک جریان ارتباطی دوطرفه است
//بین کلاینت (این برنامه) و سرور POP3 مورد تماس
//این کانال برای تبادل دستورات و اطلاعات استفاده میشود
// پس از برقراری اتصال، سرور یک پیام خوشآمدگویی ارسال میکند که خوانده میشود
$erreur = sendCommand($connexion, "", $verbose, TRUE);
if ($erreur !== "") {
// اتصال بسته شد
fclose($connexion);
// بازگشت
return $erreur;
}
// دستور USER
$erreur = sendCommand($connexion, "USER {$infos["user"]}", $verbose, TRUE);
if ($erreur !== "") {
// اتصال قطع شد
fclose($connexion);
// بازگشت
return $erreur;
}
// دستور PASS
$erreur = sendCommand($connexion, "PASS {$infos["password"]}", $verbose, TRUE);
if ($erreur !== "") {
// اتصال بسته شد
fclose($connexion);
//بازگشت
return $erreur;
}
// فرمان LIST
$premièreLigne = "";
$erreur = sendCommand($connexion, "LIST", $verbose, TRUE, $premièreLigne);
if ($erreur !== "") {
//اتصال بسته شد
fclose($connexion);
//بازگشت
return $erreur;
}
// اولین خط را تحلیل میکند تا تعداد پیامها را تعیین کند
$champs = [];
preg_match("/^\+OK (\d+)/", $premièreLigne, $champs);
$nbMessages = (int) $champs[1];
//حلقه روی پیامها
$iMessage = 0;
while ($iMessage < $nbMessages && $iMessage < $infos["maxmails"]) {
// دستور RETR
$erreur = sendCommand($connexion, "RETR " . ($iMessage + 1), $verbose, TRUE);
if ($erreur !== "") {
// بستن اتصال
fclose($connexion);
//بازگشت
return $erreur;
}
// دستور DELE
$erreur = sendCommand($connexion, "DELE " . ($iMessage + 1), $verbose, TRUE);
if ($erreur !== "") {
// اتصال بسته شد
fclose($connexion);
//بازگشت
return $erreur;
}
// پیام بعدی
$iMessage++;
}
// فرمان QUIT
$erreur = sendCommand($connexion, "QUIT", $verbose, TRUE);
if ($erreur !== "") {
// اتصال بسته شد
fclose($connexion);
//بازگشت
return $erreur;
}
// پایان
fclose($connexion);
return "Terminé";
}
// --------------------------------------------------------------------------
function sendCommand($connexion, string $commande, bool $verbose, bool $withRCLF, string &$premièreLigne = ""): string {
//ارسال $commande به کانال $connexion
// حالت تفصیلی اگر $verbose=1
//اگر $withRCLF=1 باشد، توالی RCLF را به تبادل اضافه میکند
// اولین خط پاسخ را در [$premièreLigne
// ]
//داده
if ($withRCLF) {
$RCLF = "\r\n";
} else {
$RCLF = "";
}
// دستور ارسال را ارسال میکند اگر $commande خالی نباشد
if ($commande !== "") {
fputs($connexion, "$commande$RCLF");
// اکوی احتمالی
if ($verbose) {
affiche($commande, 1);
}
}//اگر
// پاسخ را بخوانید
$réponse = fgets($connexion, 1000);
// ذخیره خط اول
$premièreLigne = $réponse;
// اکوی احتمالی
if ($verbose) {
affiche($réponse, 2);
}
//بازیابی کد خطا
$codeErreur = substr($réponse, 0, 1);
if ($codeErreur === "-") {
//خطایی رخ داده است
return substr($réponse, 5);
}
//موارد ویژه برای دستورات RETR و LIST که پاسخهای چندخطی دارند
$commande = substr(strtolower($commande), 0, 4);
if ($commande === "list" || $commande === "retr") {
// آخرین خط پاسخ؟
$champs = [];
$match = preg_match("/^\.\s+$/", $réponse, $champs);
while (!$match) {
// خواندن پاسخ
$réponse = fgets($connexion, 1000);
// اکوی احتمالی
if ($verbose) {
affiche($réponse, 2);
}
// تحلیل پاسخ
$champs = [];
$match = preg_match("/^\.\s+$/", $réponse, $champs);
}//در حالی که
}
//بازگشت بدون خطا
return "";
}
// --------------------------------------------------------------------------
function affiche($échange, $sens) {
// نمایش $échange روی صفحه
// اگر $sens=1 باشد، -->$echange را نمایش میدهد
// اگر $sens=2 باشد، نمایش میدهد <-- $échange بدون دو کاراکتر آخر RCLF
switch ($sens) {
case 1:
print "--> [$échange]\n";
break;
case 2:
$L = strlen($échange);
print "<-- [" . substr($échange, 0, $L - 2) . "]\n";
break;
}//سوئیچ
}
توضیحات
همانطور که ذکر شد، [pop3-01.php] یک نسخهٔ بازسازیشده از اسکریپت [smtp-01.php] است که قبلاً در مورد آن بحث کردهایم. ما فقط به تفاوتهای اصلی اشاره خواهیم کرد:
- خط ۵۵: تابع [readmail] مسئول خواندن ایمیلها از صندوق پستی است. جزئیات مورد نیاز برای اتصال به این صندوق پستی در دیکشنری [$infos] قرار دارد؛
- خطوط ۶۱–۶۶: برقراری ارتباط با سرور POP3؛
- خطوط ۷۱–۷۷: پیام خوشآمدگویی ارسالشده توسط سرور را میخواند؛
- خطوط ۷۸–۸۵: فرمان [USER] برای شناسایی کاربری که ایمیلهای او مورد نیاز است، ارسال میشود؛
- خطوط ۸۶–۹۳: دستور [PASS] برای ارائه رمز عبور آن کاربر ارسال میشود؛
- خطوط ۹۴–۱۰۲: دستور [LIST] ارسال میشود تا مشخص شود چند ایمیل در صندوق پستی آن کاربر وجود دارد.
- خط ۹۶: پارامتر [$premièreLigne] را به پارامترهای تابع [readmail] اضافه کنید. در اولین خط پاسخ خود به فرمان LIST، سرور تعداد پیامهای موجود در صندوق پستی را نشان میدهد؛
- خطوط ۱۰۴–۱۰۶: تعداد پیامها از خط اول پاسخ استخراج میشود؛
- خطوط ۱۰۹–۱۲۸: ما روی هر یک از پیامها حلقه میزنیم. برای هر کدام، دو دستور صادر میکنیم:
- RETR i: برای بازیابی پیام شمارهٔ i (خطوط 111–117);
- DELE i: برای حذف آن پس از خوانده شدن (خطوط 118–125);
- خطوط ۱۲۹–۱۳۶: دستور [QUIT] برای اطلاعرسانی به سرور که کار ما تمام شده است، ارسال میشود؛
- خطوط 178–194: برای دستورات [LIST] و [RETR]، پاسخ سرور از چندین خط تشکیل شده است که آخرین خط آن یک نقطه کامل است؛
نتایج
پس از اجرا، نتایج زیر به دست میآیند:
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.
این یک کلاینت پایه POP3 است که فاقد برخی قابلیتها میباشد:
- توانایی برقراری ارتباط با یک سرور امن POP3؛
- توانایی خواندن پیوستهای یک پیام؛
ما قابلیت اول را با استفاده از توابع [imap] و PHP پیادهسازی خواهیم کرد.
16.6.4. کلاینت POP3 / IMAP با استفاده از توابع [imap] از PHP پیادهسازی شده است
ابتدا باید بررسی کنیم که توابع [imap] در نسخهای از PHP که استفاده میکنیم، موجود باشند. فایل [php.ini] را که در بخش «link» توصیف شده است باز میکنیم و خطوطی را که به [imap] اشاره دارند، جستجو میکنیم:

خط ۸۹۵: بررسی کنید که افزونه [imap] فعال باشد.
اسکریپت [imap-01.php] فایلهای زیر jSON و [config-imap-01.json] را پردازش خواهد کرد:
فایل [config-imap-01.json] آرایهای از سرورهای IMAP / POP3 را برای تماس تعریف میکند. هر عنصر یک ساختار [clé:valeur] است، که در آن:
- [clé]: سروری است که باید با آن تماس گرفته شود. در اینجا دو مورد از این وجود دارد:
- [{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX]: به سرور [imap.gmail.com] اشاره دارد که در پورت 993 در حال گوش دادن است. پروتکل کلاینت/سرور IMAP است. پارامتر /ssl نشان میدهد که ارتباط کلاینت/سرور امن است. پارامتر /novalidate-cert به کلاینت دستور میدهد که گواهی امنیتی را که سرور برایش ارسال میکند، اعتبارسنجی نکند. در نهایت، سرور با نام IMAP مجموعهای از صندوقهای پستی را برای یک کاربر واحد مدیریت میکند. با مشخص کردن INBOX در URL سرور IMAP، ما نشان میدهیم که به صندوق پستی با نام INBOX علاقهمند هستیم، که معمولاً جایی است که پیامهای جدید به آن میرسند؛
- [{localhost:110/pop3}INBOX]: به سرور [localhost] اشاره دارد که در پورت 110 در حال گوش دادن است. پروتکل کلاینت/سرور در اینجا POP3 است؛
- [valeur]: یک فرهنگ لغت است که موارد زیر را مشخص میکند:
- [imap-server]: نام سرور IMAP یا POP3؛
- [imap-port]: پورت سرور IMAP یا POP3;
- [user]: صاحب صندوق پستی که میخواهید آن را بخوانید؛
- [password]: رمز عبور آنها؛
- [output-dir]: پوشهای که پیامها در آن ذخیره میشوند؛
- [prefix]: نام فایلهایی که پیامها با آنها ذخیره میشوند به شکل prefixN خواهد بود، که در آن N شماره پیام است؛
- [pop3]: یک مقدار بولی که روی TRUE تنظیم میشود تا نشان دهد پروتکل مورد استفاده POP3 است. در این حالت، پس از خوانده شدن یک پیام، آن پیام حذف خواهد شد. این روشی است که سرورهای POP3 معمولاً کار میکنند: یک پیام خوانده شده روی سرور نگه داشته نمیشود؛
اسکریپت [imap-01.php] به شرح زیر است:
<?php
// کلاینت IMAP (پروتکل دسترسی به پیام اینترنتی) برای خواندن ایمیلها
//
// پابندی سختگیرانه به انواع اعلامشده پارامترهای تابع
declare (strict_types=1);
// مدیریت خطا
error_reporting(E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
//
//پارامترهای بازیابی ایمیل
const CONFIG_FILE_NAME = "config-imap-01.json";
//بازیابی پیکربندی
$mailboxes = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true);
// خواندن صندوقهای پستی
foreach ($mailboxes as $name => $infos) {
// ردیابی
print "------------Lecture de la boîte à lettres [$name]\n";
// خواندن صندوق پستی
readmailbox($name, $infos);
}
//پایان
exit;
//-----------------------------------------------------------------------
function readmailbox(string $name, array $infos): void {
// تلاش برای اتصال
$imapResource = imap_open($name, $infos["user"], $infos["password"]);
// آزمایش مقدار بازگشتی تابع imap_open()
if (!$imapResource) {
// ناموفق
print "La connexion au serveur [$name] a échoué : " . imap_last_error() . "\n";
} else {
//اتصال برقرار شد
print "Connexion établie avec le serveur [$name].\n";
// تعداد کل پیامها در صندوق پستی
$nbmsg = imap_num_msg($imapResource);
print "Il y a [$nbmsg] messages dans la boîte à lettres [$name]\n";
// پیامهای نخوانده در صندوق پستی جاری
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) {
// در حال بازیابی اطلاعات درباره پیام شماره $msgNumber
$infosMail = imap_headerinfo($imapResource, $msgNumber);
if ($infosMail === FALSE) {
print "Statut du message n° [$msgNumber] de la boîte à lettres [$name] non récupéré : " . imap_last_error() . "\n";
} else {
print "Statut du message n° [$msgNumber] de la boîte à lettres [$name]\n";
print_r($infosMail);
}
// بازیابی متن پیام شماره $msgNumber
getMailBody($imapResource, $msgNumber, $infos);
// اگر پروتکل POP3 باشد، پیام حذف میشود
$pop3 = $infos["pop3"];
if ($pop3 !== NULL) {
// پیام در دو مرحله حذف میشود
imap_delete($imapResource, $msgNumber);
imap_expunge($imapResource);
}
}
}
}
}
//اتصال بسته شد
$imapClose = imap_close($imapResource);
if (!$imapClose) {
// ناموفق
print "La fermeture de la connexion a échoué : " . imap_last_error() . "\n";
} else {
// موفقیت
print "Fermeture de la connexion réussie.\n";
}
}
function getMailBody($imapResource, int $msgNumber, array $infos): void {
//بازیابی بدنه پیام شماره $msgNumber
$corpsMail = imap_body($imapResource, $msgNumber);
print "Enregistrement du message dans le fichier {$infos["output-dir"]}/{$infos["prefix"]}$msgNumber\n";
// در صورت لزوم پوشه را ایجاد کنید
if (!file_exists($infos["output-dir"])) {
mkdir($infos["output-dir"]);
}
//ذخیره پیام
if (!file_put_contents($infos["output-dir"] . "/" . $infos["prefix"] . $msgNumber, $corpsMail)) {
print "Echec de l'enregistrement\n";
}
}
نظرات
- خطوط ۱۹–۲۴: اسکریپت به صورت حلقهای از میان تمام سرورهای یافتشده در فایل پیکربندی عبور میکند؛
- خط ۳۲: تابع [raedmailbox] صندوق پستی مشخصشده در [$name] را میخواند؛
- خط ۳۲: یک اتصال IMAP باز میشود؛
- پارامتر اول، URL IMAP مربوط به صندوق پستیای است که باید خوانده شود؛
- پارامتر دوم نام کاربری صاحب صندوق پستی است؛
- پارامتر سوم رمز عبور آنها است؛
تابع [imap_open] در صورتی که URL IMAP صندوق پستی دارای پارامتر /ssl باشد، اتصال امن را مدیریت میکند؛
- خط ۴۱: تابع [imap_num_msg] تعداد کل پیامهای موجود در صندوق پستی را بازیابی میکند؛
- خط ۴۶: تابع [imap_search] برای جستجوی پیامهای خاص استفاده میشود. در اینجا، ما به دنبال پیامهایی هستیم که هنوز خوانده نشدهاند (UNSEEN). پارامتر دوم یک معیار انتخاب است. حدود بیست مورد از این معیارها وجود دارد. تابع [imap_search] آرایهای از شمارههای پیام را بازمیگرداند. اینها میتوانند دو شکل داشته باشند: شمارههای ترتیبی یا شناسههای پیام (UID). بهطور پیشفرض، تابع [imap_search] آرایهای از شمارههای ترتیبی را بازمیگرداند. اگر پارامتر سومی، [SE_UID]، اضافه شود، شناسههای پیام (UID) بازگردانده خواهند شد؛
- خط ۴۷: تابع [imap_search] اگر هیچ پیامی پیدا نکرده باشد، مقدار بولی FALSE را برمیگرداند؛
- خط ۵۰: ما روی تمام پیامهای نخوانده حلقه میزنیم؛
- خط ۵۲: یک پیام دارای سربرگها است که میتوان آنها را با استفاده از تابع [imap_headerinfo] بازیابی کرد. پارامتر دوم آن معمولاً شماره توالی پیام است. اگر میخواهید یک شناسه پیام را به UID تنظیم کنید، باید پارامتر سوم را روی [FT_UID] تنظیم کنید؛
- خط ۵۳: تابع [imap_headerinfo] اگر نتواند وظیفهاش را تکمیل کند، مقدار بولی FALSE را برمیگرداند. در غیر این صورت، یک شیء پیچیده را بازمیگرداند که با استفاده از تابع [print_r]، خط ۵۷، نمایش داده میشود؛
- خط ۶۰: پس از سربرگها، بدنه پیام اکنون با استفاده از تابع [imap_body] بازیابی میشود. این تابع در صورتی که نتوانسته باشد وظیفهاش را به پایان برساند، مقدار NULL را برمیگرداند؛
- خطوط ۸۴–۸۷: بدنه پیام در یک فایل محلی ذخیره میشود؛
- خطوط ۶۳–۶۸: اگر پروتکل مورد استفاده POP3 باشد، پیامی که به تازگی خوانده شده است حذف میشود:
- تابع [imap_delete] پیام را بهعنوان «برای حذف» علامتگذاری میکند اما آن را حذف نمیکند؛
- تابع [imap_expunge] تمام پیامهایی را که بهعنوان «برای حذف» علامتگذاری شدهاند، بهطور فیزیکی حذف میکند؛
- خط ۷۴: اتصال به سرور با استفاده از تابع IMAP بسته میشود. تابع [imap_close] برای این کار استفاده میشود؛
- خط ۸۶: تابع [imap_body] بدنهٔ پیامی را که با شمارهٔ آن شناسایی شده است، بازیابی میکند؛
بیایید اسکریپت [smtp-02.json] را اجرا کنیم تا کاربر Gmail با شناسه [php7parlexemple] و کاربر [guest] از [localhost] پیامهای جدیدی دریافت کنند. پس از انجام این کار، بیایید اسکریپت [imap-01.php] را برای خواندن صندوقهای پستی آنها اجرا کنیم.
خروجی کنسول به شرح زیر است:
------------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.
اگر بلافاصله پس از این نتایج، اسکریپت [imap-01.php] را دوباره اجرا کنیم، نتایج به شرح زیر است:
------------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.
- خط ۳: تعداد پیامها در صندوق ورودی جیمیل همچنان یکسان است، اما دیگر هیچ پیام جدید نخواندهای وجود ندارد (خط ۵). این نشان میدهد که اجرای قبلی وضعیت پیامهای خوانده شده را از «نخوانده» به «خوانده» تغییر داده است؛
- خط ۹: دیگر هیچ پیامی در صندوق ورودی کاربر [guest@localhost] وجود ندارد. این به این دلیل است که در اجرای قبلی، پیامهای خوانده شده در [localhost] به دنبال آن حذف شدند؛
پیامها بهصورت محلی ذخیره شدهاند:

اگر برای مثال به محتوای پیام شماره ۲۶ در جیمیل نگاه کنیم، موارد زیر را مشاهده میکنیم:
--_=_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_=_--
- خطوط ۱۱–۱۳: پیام به صورت متن ساده؛
- خط ۱۹: پیام HTML;
- خط ۲۵: پیوست؛
بیایید سعی کنیم این اسکریپت را بهبود بخشیم تا انواع مختلف پیامها و پیوستها در فایلهای جداگانه ذخیره شوند.
16.6.5. کلاینت بهبودیافته POP3 / IMAP
در اسکریپت [imap-01.php]، بدنه پیام شماره i به صورت یک فایل متنی نمایش داده میشود که شامل انواع مختلف پیام و محتوای رمزگذاریشده پیوستهای گوناگون است. میتوان ساختار پیام را به دست آورد تا این بخشهای مختلف را شناسایی کرد. در اسکریپت [imap-02.php]، تابع [getMailBody] را به شرح زیر اصلاح میکنیم:
function getMailBody($imapResource, int $msgNumber, array $infos): void {
//بازیابی ساختار پیام
$structure=imap_fetchstructure($imapResource, $msgNumber);
//آن را نمایش دهید
print_r($structure);
}
- خط ۳: ساختار پیام را درخواست میکنیم؛
- خط ۵: آن را نمایش میدهیم؛
هدف شناسایی اطلاعات موجود در ساختار پیام است تا ببینیم چگونه میتوانیم بخشهای مختلف آن را استخراج کنیم. در مثال ما، پیام توسط اسکریپت [smtp-02.php] با پیکربندی زیر ارسال میشود: [config-smtp-02.json]:
بنابراین این یک پیام با پنج پیوست است که به [guest@localhost] (خطوط ۱۱–۱۵) ارسال میشود. اسکریپت [imap-02.php] با پیکربندی زیر اجرا میشود: [config-imap-01.json]:
بنابراین صندوق پستی [guest@localhost] مورد بهرهبرداری قرار میگیرد (خط ۵). سپس اسکریپت [imap-02.php] ساختار پیامی را که توسط [smtp-02.php] ارسال شده است نمایش میدهد. این ساختار که در کنسول نمایش داده شده است به شرح زیر است:
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
(
…
)
)
)
)
نظرات
- مستندات PHP برای تابع [imap_fetchstructure] معنای فیلدهای مختلف در ابجکت بازگردانده شده توسط تابع را توضیح میدهد:

مقادیر عددی در فیلد [type] معانی زیر را دارند:

مقادیر عددی در فیلد [encoding] معانی زیر را دارند:

پیامی که توسط [imap-01.php] ضبط شده است با متن زیر آغاز میشود:
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_=_
- خطوط ۱۵) و ۳۳) پیام از نوع [multipart/mixed] (خط m) را جدا میکنند؛
- خطوط 18) و 16) بخش اول پیام را جدا میکنند: پیام متن ساده؛
- خطوط 26) و 32) بخش دوم پیام را جدا میکنند: پیام HTML؛
ما قطعات مختلف اطلاعات پیام فوق را در ابجکت بازگردانده شده توسط [imap_fetchstructure] پیدا میکنیم:
stdClass Object
(
[type] => 1
[encoding] => 0
[ifsubtype] => 1
[subtype] => MIXED
[ifdescription] => 0
[ifid] => 0
[bytes] => 253599
[ifdisposition] => 0
[ifdparameters] => 0
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => BOUNDARY
[value] => _=_swift_1558872295_5bc8ee2ca8b3723c0b39ca8bbfbebdeb_=_
)
)
[parts] => Array
(
[0] => stdClass Object
(
[type] => 1
[encoding] => 0
[ifsubtype] => 1
[subtype] => ALTERNATIVE
[ifdescription] => 0
[ifid] => 0
[bytes] => 429
[ifdisposition] => 0
[ifdparameters] => 0
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => BOUNDARY
[value] => _=_swift_1558872296_1e51aae79dfca4e7e0af112489fe8734_=_
)
)
[parts] => Array
(
[0] => stdClass Object
(
[type] => 0
[encoding] => 4
[ifsubtype] => 1
[subtype] => PLAIN
[ifdescription] => 0
[ifid] => 0
[lines] => 3
[bytes] => 27
[ifdisposition] => 0
[ifdparameters] => 0
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => CHARSET
[value] => utf-8
)
)
)
[1] => stdClass Object
(
[type] => 0
[encoding] => 4
[ifsubtype] => 1
[subtype] => HTML
[ifdescription] => 0
[ifid] => 0
[lines] => 1
[bytes] => 40
[ifdisposition] => 0
[ifdparameters] => 0
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => CHARSET
[value] => utf-8
)
)
)
)
)
- خط ۳: پیام از نوع MIME (افزونه های پست الکترونیکی چندمنظوره اینترنت) [multipart];
- خط ۴: پیام با ۷ بیت رمزگذاری شده است؛
- خط ۵: [ifsubtype]=1 نشان میدهد که یک فیلد [subtype] در ساختار وجود دارد؛
- خط ۶: فیلد [subtype] به زیرنوع MIME اشاره دارد، در این مورد نوع [mixed]. در مجموع، نوع سند MIME برابر با [multipart/mixed] است؛
- خط ۷: [ifdescription]=0 نشان میدهد که هیچ فیلدی با نام [description] در ساختار وجود ندارد؛
- خط ۸: [ifid]=0 نشان میدهد که هیچ فیلدی با نام [id] در ساختار وجود ندارد؛
- خط ۱۰: [ifdisposition]=0 نشان میدهد که هیچ فیلد [disposition] در ساختار وجود ندارد؛
- خط ۱۱: [ifdparameters]=0 نشان میدهد که هیچ فیلدی به نام [dparameters] در ساختار وجود ندارد؛
- خط ۱۲: [ifparameters]=1 نشان میدهد که یک فیلد [parameters] در ساختار وجود دارد؛
- خط ۱۳: فیلد [parameters] پارامترهای پیام را توصیف میکند. در این مورد، تنها یک پارامتر وجود دارد؛
- خطوط ۱۵–۱۹: این شیء خط بعدی پیام متنی را توصیف میکند:
این خطوط برای جدا کردن پیام استفاده میشوند. در پیامی که توسط [imap-01.php] بازیابی شده است، بخشی از پیام که به تازگی توصیف شد، با خط m) مطابقت دارد. ویژگی [boundary] یکسان نیست زیرا اسکرینشاتها مربوط به یک پیام یکسان هستند اما در زمانهای مختلف ارسال شدهاند؛
- خط ۲۳: اینجاست که ساختار بخشهای مختلف پیام آغاز میشود؛
- خطوط ۲۵ تا ۴۵: این بخش اول از نوع [multipart/alternative] است. این بخش با خط p) متن پیام مطابقت دارد؛
- خط ۴۷: این بخش اول خود دارای زیربخشهایی است؛
- خطوط ۴۷–۷۰: این زیربخش اول از نوع [text/plain] است (خطوط ۵۱، ۵۴)، به عنوان نوع [ENCQUOTEDPRINTABLE] (خط ۵۲) رمزگذاری شده و دارای پارامتر [charset=utf-8] (خطوط ۶۶–۶۷) است؛
- خطوط ۴۹–۷۲ خطوط s–x پیام متنی را توصیف میکنند؛
- خطوط ۷۴–۹۹: بخش دوم زیربخش [multipart/alternative] را توصیف میکنند؛
- خطوط ۷۴–۹۹: این زیربخش دوم از نوع [text/HTML] (خطوط ۷۶، ۷۹) است، به نوع [ENCQUOTEDPRINTABLE] (خط ۷۷) رمزگذاری شده و دارای پارامتر [charset=utf-8] است (خطوط ۸۹–۹۳)؛
- خطوط ۷۴–۹۹ خطوط aa–ad پیام متنی را توصیف میکنند؛
بخش [multipart/alternative] اکنون کامل است. بخش [application/vnd.openxmlformats-officedocument.wordprocessingml.document] آغاز میشود، که با متن زیر توصیف شده است:
بار دیگر، این اطلاعات در ابجکتی که توسط تابع [imap_fetchstructure] بازگردانده میشود، وجود دارد:
[1] => stdClass Object
(
[type] => 3
[encoding] => 3
[ifsubtype] => 1
[subtype] => VND.OPENXMLFORMATS-OFFICEDOCUMENT.WORDPROCESSINGML.DOCUMENT
[ifdescription] => 0
[ifid] => 0
[bytes] => 16302
[ifdisposition] => 1
[disposition] => ATTACHMENT
[ifdparameters] => 1
[dparameters] => Array
(
[0] => stdClass Object
(
[attribute] => FILENAME
[value] => Hello from SwiftMailer.docx
)
)
[ifparameters] => 1
[parameters] => Array
(
[0] => stdClass Object
(
[attribute] => NAME
[value] => Hello from SwiftMailer.docx
)
)
)
- خط ۱: این بخش دوم از پیام کلی است. به یادآوری، بخش اول از نوع [multipart/alternative] بود؛
- خطوط ۳–۶: این بخش دوم از نوع [application/vnd.openxmlformats-officedocument.wordprocessingml.document] (خطوط ۳ و ۶) است و به صورت Base64 رمزگذاری شده است (خط ۴)؛
- خط ۱۱: این بخش دوم یک پیوست است (خط ۱۱) و دو پارامتر دارد: [filename=Hello from SwiftMailer.docx] (خطوط ۱۵–۲۱) و [name=Hello from SwiftMailer.docx] (خطوط ۲۶–۳۲). شایان ذکر است که این پارامتر اخیر در پیام متنی وجود ندارد. بنابراین در تابع [imap_fetchstructure] اضافه شده است؛
خطوط ۱ تا ۳۶ برای هر یک از پنج پیوست پیام تکرار میشوند.
بنابراین، تابع [imap_fetch_structure] به ما امکان میدهد ساختار یک پیام را به دست آوریم. این ساختار، بخشهایی را تعریف میکند که خود ممکن است شامل زیربخشهایی باشند. برای بازیابی متن یک بخش یا زیربخش، از تابع [imap_fetchbody] استفاده میکنیم.
ما تابع [getMailBody] را که امکان بازیابی بدنهٔ یک پیام را برای ما فراهم میکند، به شرح زیر تغییر میدهیم:
function getMailBody($imapResource, int $msgNumber, array $infos, object $infosMail): void {
//بازیابی ساختار پیام
$structure = imap_fetchstructure($imapResource, $msgNumber);
if ($structure !== FALSE) {
// بازیابی این بخشهای مختلف
getParts($imapResource, $msgNumber, $infos, $infosMail, $structure);
}
}
function getParts($imapResource, int $msgNumber, array $infos, object $infosMail, stdclass $part, string $sectionNumber = "0"): void {
// شماره بخش را محاسبه کنید
if (substr($sectionNumber, 0, 2) === "0.") {
$sectionNumber = substr($sectionNumber, 2);
}
print "-----contenu de la partie n° [$sectionNumber]\n";
//نوع محتوا
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;
}
// نوع رمزگذاری
$encodings=["7 bits", "8 bits", "binaire", "base 64", "quoted-printable", "autre"];
print "Transfer-Encoding : ".$encodings[$part->encoding]."\n";
// به هر زیربخش بعدی میرویم
if (isset($part->parts)) {
for ($i = 1; $i <= count($part->parts); $i++) {
// بخشی جدید از پیام
$subpart = $part->parts[$i - 1];
// فراخوانی بازگشتی – درخواست بدنهٔ بخش [$subpart]
getParts($imapResource, $msgNumber, $infos, $infosMail, $subpart, "$sectionNumber.$i");
}
}
}
نظرات
- خط ۳: ساختار پیام را بازیابی میکنیم؛
- خط ۶: ما بخشهای مختلف پیام را که در آرایه [parts] درون ساختار قرار دارند، درخواست میکنیم؛
- خط ۱۰: تابع [getParts] پارامترهای زیر را دریافت میکند:
- [$imapResource]: اتصال به سرور IMAP;
- [$msgNumber]: شماره توالی پیامی که قطعات از آن مورد نیاز هستند؛
- [$infos]: اطلاعاتی که مشخص میکند قطعات یافتشده در کجا از سیستم فایل محلی ذخیره شوند؛
- [$infosMail]: اطلاعات عمومی در مورد ایمیل (فرستنده، گیرنده(گان)، موضوع و غیره)؛
- [$part]: ابجکتی که نمایانگر بخشی از پیام است؛
- [$sectionNumber]: شماره بخش (یا قسمت) پیام؛
- خطوط 17–34: نوع محتوا برای بخش شماره [$section] پیام نمایش داده میشود. برای این کار، از فیلدهای [$part→type] و [$part→subtype] از بخش [$part] استفاده میشود؛
- خطوط ۳۶–۳۷: نوع رمزگذاری بخش [$sectionNumber] نمایش داده میشود؛
- خطوط 40–47: بخشی که به تازگی اطلاعات آن نمایش داده شده است ممکن است خود شامل زیربخشهایی باشد؛
- خطوط ۴۱–۴۶: اگر چنین باشد، نوع محتوا را برای بخشهای فرعی مختلف بخشی که به تازگی نمایش دادهایم درخواست میکنیم. در اینجا، ما یک فراخوانی بازگشتی به تابع [getParts] انجام میدهیم؛
بار دیگر، ما ایمیلی به کاربر جیمیل [php7parlexemple@gmail.com] حاوی اسکریپت [smtp-02.php] ارسال میکنیم و آن را با استفاده از اسکریپت قبلی، [imap-02.php]، میخوانیم. این کار خروجی کنسول زیر را تولید میکند:
------------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.
ما بهطور موفقیتآمیز قادر به بازیابی انواع مختلف محتوا در پیام و همچنین انواع رمزگذاری آنها هستیم. شمارهگذاری بخشها از این قاعده پیروی میکند:
- خطوط ۶–۷: بخش [multipart/mixed]، که نمایانگر کل پیام است، شماره ۰ دارد. بخشهای مختلف این شیء سپس به ترتیب ۱، ۲… شمارهگذاری میشوند.
پیام در مجموع پنج بخش دارد:
- خطوط ۹–۱۰: بخش [multipart/alternative]، با شماره ۱؛
- خطوط ۱۷–۱۸: بخش [APPLICATION/VND.OPENXMLFORMATS-OFFICEDOCUMENT.WORDPROCESSINGML.DOCUMENT] که شمارهٔ ۲ را دارد. این یک فایل پیوست ورد است؛
- خطوط ۲۰–۲۱: بخش [APPLICATION/PDF]، با شمارهٔ ۳. این یک پیوست فایل با نام PDF است؛
- خطوط ۲۳–۲۴: بخش [APPLICATION/VND.OASIS.OPENDOCUMENT.TEXT] که شمارهٔ ۴ دارد. این یک پیوست فایل با نام OpenOffice است؛
- خطوط ۲۶–۲۷: بخش [UNKNOWN/PNG]، شمارهٔ ۵. این یک پیوست فایل تصویری است؛
- خطوط ۳۰–۳۱: بخش [MESSAGE/RFC822]، با شماره ۶. این یک پیوست ایمیل است؛
وقتی یک بخش دارای زیربخشها باشد، این زیربخشها با شمارههای x.1، x.2… شمارهگذاری میشوند که در آن x شماره بخش مادر است. بنابراین:
- خطوط ۱۱–۱۲: بخش اول از بخش [multipart/alternative] با شماره ۱.۱ مشخص شده است. این یک ورودی از نوع [text/plain] است: پیام ایمیل؛
- خطوط 14–15: بخش دوم از بخش [multipart/alternative] با شماره 1.2 شمارهگذاری شده است. این محتوا از نوع [text/HTML] است: پیام ایمیل در HTML;
- خطوط ۳۲–۳۳: بخش اول پیوست [MESSAGE/RFC822] با شمارهٔ ۶.۱ مشخص شده است. این از نوع [text/plain] است. در واقع، طبق استاندارد MIME، شمارهگذاری بخشهای یک پیوست ایمیل [MESSAGE/RFC822] با قاعدهی فوق متفاوت است. بنابراین، بخش اول پیوست [MESSAGE/RFC822] شماره ۶.۱ ندارد، بلکه شماره متفاوتی دارد؛
اکنون که میدانیم چگونه بخشها و زیربخشهای مختلف یک ایمیل را شناسایی کنیم، باید محتوای آنها را بازیابی کنیم.
کد اسکریپت به شرح زیر تغییر میکند:
function getParts($imapResource, int $msgNumber, array $infos, object $infosMail, stdclass $part, string $sectionNumber = "0"): void {
//محاسبه شماره بخش
if (substr($sectionNumber, 0, 2) === "0.") {
$sectionNumber = substr($sectionNumber, 2);
}
print "-----contenu de la partie n° [$sectionNumber]\n";
// نوع محتوا
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;
}
// نوع رمزگذاری
$encodings = ["7 bits", "8 bits", "binaire", "base 64", "quoted-printable", "autre"];
print "Transfer-Encoding : " . $encodings[$part->encoding] . "\n";
//آیا این یک پیام است؟
if ($part->type === TYPEMESSAGE) {
// ما زیربخشهای این پیام (ایمیل پیوست) را پردازش نخواهیم کرد
// متن ایمیل پیوست نمایش داده میشود
print imap_fetchbody($imapResource, $msgNumber, $sectionNumber);
} else {
// به بخشهای فرعی بعدی میرویم
if (isset($part->parts)) {
for ($i = 1; $i <= count($part->parts); $i++) {
// بخش جدیدی از پیام
$subpart = $part->parts[$i - 1];
//فراخوانی بازگشتی – محتوای بخش [$subpart] درخواست میشود
getParts($imapResource, $msgNumber, $infos, $infosMail, $subpart, "$sectionNumber.$i");
}
} else {
//هیچ زیربخشی وجود ندارد – بنابراین متن پیام نمایش داده میشود
print imap_fetchbody($imapResource, $msgNumber, $sectionNumber);
}
}
}
نظرات
- خط ۴۶: تابع [imap_fetchbody] بدنه بخش شماره [$sectionNumber] پیام را بازیابی میکند. شمارهگذاری بخشهای یک پیام از قاعدهای که قبلاً توضیح داده شد پیروی میکند؛
- خط ۱: ما با بخش «۰» شروع میکنیم؛
- خط ۴۱: زیربخشهای این بخش سپس با اعداد «0.1»، «0.2» شمارهگذاری شدهاند، در حالی که باید با اعداد «1»، «2» شمارهگذاری میشدند…
- خطوط ۳–۵: این ناهنجاری اصلاح میشود؛
- خطوط ۳۷–۴۳: اگر بخش فعلی دارای زیربخشها باشد، آنگاه یک حلقه بر روی هر یک از آنها اجرا میشود (خطوط ۳۸–۴۳). شماره بخش آنها [$sectionNumber.$i] است؛
- خطوط ۴۴–۴۷: هنگامی که دیگر زیربخشی وجود ندارد، محتوای بخش جاری با استفاده از تابع [imap_fetchbody] نمایش داده میشود. در مثال ما، این بخشها [text/plain]، [text/HTML] و پیوستها هستند؛
اجرای این اسکریپت نتایج زیر را تولید میکند:
------------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.
نظرات
- خطوط ۱۴–۱۶: محتوای پیام متنی که به صورت [quoted-printable] رمزگذاری شده است (خط ۱۳);
- خط ۲۰: محتوای پیام HTML رمزگذاری شده در [quoted-printable] (خط ۱۹);
- خطوط ۲۴–۲۸: محتویات فایل ورد رمزگذاریشده به صورت [base64] (خط ۲۳);
- خطوط ۳۲–۳۷: محتویات فایل PDF، رمزگذاریشده به صورت [base64] (خط ۳۱);
- خطوط ۴۱–۴۵: محتویات فایل OpenOffice، رمزگذاری شده به صورت [base64] (خط ۴۰);
- خطوط ۵۰–۵۵: محتویات فایل تصویری رمزگذاریشده به صورت [base64] (خط ۴۹);
- خطوط ۵۹–۶۳: محتویات ایمیل پیوستشده رمزگذاریشده به صورت [base64] (خط ۵۸);
اکنون که:
- ما میدانیم چگونه متن را از بخشهای مختلف یک ایمیل بازیابی کنیم؛
- ما از رمزگذاری این متون مطلع هستیم؛
میتوانیم این متون را در فایلها ذخیره کنیم.
کد به شرح زیر تغییر میکند:
function getParts($imapResource, int $msgNumber, array $infos, object $infosMail, stdclass $part, string $sectionNumber = "0"): void {
//محاسبه شماره بخش
if (substr($sectionNumber, 0, 2) === "0.") {
$sectionNumber = substr($sectionNumber, 2);
}
print "-----contenu de la partie n° [$sectionNumber]\n";
// نوع محتوا
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;
}
// نوع رمزگذاری
$encodings = ["7 bits", "8 bits", "binaire", "base 64", "quoted-printable", "autre"];
print "Transfer-Encoding : " . $encodings[$part->encoding] . "\n";
//آیا این یک پیام است؟
if ($part->type === TYPEMESSAGE) {
// ما زیربخشهای این پیام را پردازش نخواهیم کرد
savePart($imapResource, $msgNumber, $sectionNumber, $infos, $infosMail);
} else {
// به هر بخش فرعی بعدی میرویم
if (isset($part->parts)) {
for ($i = 1; $i <= count($part->parts); $i++) {
// بخشی جدید از پیام
$subpart = $part->parts[$i - 1];
//فراخوانی بازگشتی – ما محتوای بخش [$subpart] را درخواست میکنیم
getParts($imapResource, $msgNumber, $infos, $infosMail, $subpart, "$sectionNumber.$i");
}
} else {
// زیربخش وجود ندارد – بنابراین بدنه پیام ذخیره میشود
savePart($imapResource, $msgNumber, $sectionNumber, $infos, $infosMail);
}
}
}
- خطوط ۳۳ و ۴۵: نمایش متن بخشی از ایمیل [$imapResource, $msgNumber, $sectionNumber] اکنون با ذخیره آن در یک فایل جایگزین شده است؛
تابع [savePart] به شرح زیر است:
//ذخیره یک بخش پیام
function savePart($imapResource, int $msgNumber, string $sectionNumber, array $infos, object $infosMail): void {
// ذخیره پوشه
$outputDir = $infos["output-dir"] . "/message-$msgNumber";
// اگر پوشه وجود نداشته باشد، ایجاد میشود
if (!file_exists($outputDir)) {
mkdir($outputDir);
}
//ساختار بخشی که باید ذخیره شود
$struct = imap_bodystruct($imapResource, $msgNumber, $sectionNumber);
// نوع سند
$type = $struct->type;
//زیرنوع سند
$subtype = "";
if (isset($struct->subtype)) {
$subtype = strtolower($struct->subtype);
}
// تحلیل نوع بخش
switch ($type) {
case TYPETEXT:
// پیام متنی: 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:
// سایر موارد – فقط پیوستها در نظر گرفته میشوند
if (isset($struct->disposition)) {
$disposition = strtolower($struct->disposition);
if ($disposition === "attachment") {
// این یک پیوست است – آن را ذخیره کن
saveAttachment($imapResource, $msgNumber, $sectionNumber, $outputDir, $struct);
}
} else {
// این بخش پردازش نخواهد شد
print "Partie [$sectionNumber] ignorée\n";
}
break;
}
}
- خطوط ۳–۸: ایجاد فایل پشتیبان. این فایل حامل شماره پیامی است که بخشهای آن در حال تحلیل هستند؛
- خط ۱۰: بخش پیام مورد نظر برای ذخیره به طور منحصربهفرد توسط سه پارامتر [$imapResource, $msgNumber, $sectionNumber] تعریف میشود. ساختار این بخش با استفاده از تابع [imap_bodystruct] بازیابی میشود؛
- خط ۱۲: نوع اصلی بخش پیام بازیابی میشود؛
- خطوط ۱۳–۱۷: زیرنوع آن بازیابی میشود؛
- خطوط ۲۰–۳۰: دو نوع محتوا پردازش میشوند: [text/plain] (خطوط ۲۳–۲۵) و [text/HTML] (خطوط ۲۶–۲۸). سایر انواع [text/xx] نادیده گرفته میشوند؛
- خط ۲۴: متن از بخش [text/plain] خواهد بود در فایلی به نام [message.txt] ذخیره خواهد شد؛
- خط ۲۷: متن از بخش [text/HTML] خواهد بود ذخیره میشود در فایلی به نام [message.HTML];
- خطوط ۳۱–۴۳: ما بخشهایی را که نوع اصلیشان [text] نیست، مدیریت میکنیم؛
- خط ۳۵: تنها پیوستهای پیام در نظر گرفته میشوند؛
- خط ۳۷: این موارد با استفاده از تابع [saveAttachment] در یک فایل ذخیره میشوند؛
برای خلاصه کردن کد قبلی:
- قطعات [text/plain] و [text/HTML] را با استفاده از تابع [saveText] ذخیره میکند. این قطعات نمایانگر محتوای ایمیل هستند؛
- پیوستهای مختلف را با استفاده از تابع [saveAttachment] ذخیره میکند؛
تابع [saveText] به شرح زیر است:
//ذخیرهٔ متن [$text] از پیام
function saveText(string $fileName, int $type, string $text, object $infosMail, object $struct) {
//در حال آمادهسازی متن برای ذخیره شدن
// $text رمزگذاری شده است – آن را رمزگشایی کن
switch ($struct->encoding) {
case ENCBASE64:
$text = base64_decode($text);
break;
case ENCQUOTEDPRINTABLE:
$text = quoted_printable_decode($text);
break;
}
// سربرگهای پیام
// از
$from = "From: ";
foreach ($infosMail->from as $expéditeur) {
$from .= $expéditeur->mailbox . "@" . $expéditeur->host . ";";
}
// به
$to = "To: ";
foreach ($infosMail->to as $destinataire) {
$to .= $destinataire->mailbox . "@" . $destinataire->host . ";";
}
// موضوع
$subject = "Subject: " . $infosMail->subject;
// ایجاد متن برای ذخیره
switch ($type) {
case 0:
// text/plain
$contents = "$from\n$to\n$subject\n\n$text";
break;
case 1:
//متن/HTML
$contents = "$from<br/>\n$to<br/>\n$subject<br/>\n<br/>\n$text";
break;
}
// ایجاد فایل
print "sauvegarde d'un message dans [$fileName]\n";
// ایجاد فایل
if (! file_put_contents($fileName, $contents)) {
// ایجاد فایل ناموفق بود
print "Impossible de créer le fichier [$fileName]\n";
}
}
نظرات
- خط ۱:
- [$fileName] نام فایلی است که متن از [$text] در آن ذخیره خواهد شد؛
- [$type]: برای یک فایل متنی برابر 0 و برای یک فایل HTML برابر 1 است؛
- [$text]: متنی است که باید ذخیره شود. با این حال، ابتدا باید از حالت رمزگذاری خارج شود؛
- [$infosMail]: حاوی اطلاعات عمومی در مورد ایمیل است. ما از فیلدهای [from, to, subject] استفاده خواهیم کرد؛
- [$struct]: ساختار توصیفکننده بخشی از ایمیل است که در حال ذخیره آن هستیم. این به ما امکان میدهد تا نوع رمزگذاری متن مورد نظر برای ذخیره را شناسایی کنیم؛
- خطوط ۴–۱۲: متن مورد نظر برای ذخیره را رمزگشایی میکنیم؛
- خطوط ۱۳–۲۵: ما اطلاعات [from, to, subject] را از ایمیل بازیابی میکنیم؛
- خطوط ۲۷–۳۶: بسته به نوع (۰ یا ۱) متن قابل ذخیره، متن ساده (خط ۳۰) یا متن HTML (خط ۳۴) را میسازیم؛
- خط ۴۰: کل متن در فایل [$fileName] ذخیره میشود؛
پیوستها با استفاده از تابع زیر ذخیره میشوند، [saveAttachment]:
// ذخیرهٔ پیوست
function saveAttachment($imapResource, int $msgNumber, string $sectionNumber, string $outputDir, object $struct) {
// تحلیل ساختار پیوست
// تلاش برای بازیابی نام فایلی که باید پیوست در آن ذخیره شود
//این نام در فیلدهای [dparameters] ساختار یافت میشود
if (isset($struct->dparameters)) {
//بازیابی [dparameters]
$dparameters = $struct->dparameters;
$fileName = "";
// ما روی آرایهٔ [dparameters] حلقه میزنیم
foreach ($dparameters as $dparameter) {
//هر [dparameter] یک شیء با دو ویژگی است: [attribute, value]
$attribute = strtolower($dparameter->attribute);
// ویژگی [filename] معادل نام فایلی است که باید ایجاد شود
//در این حالت، نام فایل در [$dparameter->value] قرار دارد
if ($attribute === "filename") {
$fileName = $dparameter->value;
break;
}
}
//اگر نام فایلی یافت نشود، در ویژگی [parameters] ساختار جستجو میکنیم
if ($fileName === "" && isset($struct->parameters)) {
// ما [parameters] را بازیابی میکنیم
$parameters = $struct->parameters;
foreach ($parameters as $parameter) {
// هر پارامتر یک دیکشنری با دو کلید است: [attribute, value]
$attribute = strtolower($parameter->attribute);
//اگر ویژگی [name] باشد، آنگاه [value] نام فایل است
if ($attribute === "name") {
$fileName = $parameter->value;
//نام فایل ممکن است رمزگذاری شده باشد
// برای مثال =?utf-8?Q?Courses-Tutorials-Serge-Tah=C3=A9-1568x268=2Ep
// ما با استفاده از یک عبارت منظم رمزگذاری را بازیابی میکنیم
$champs = [];
$match = preg_match("/=\?(.+?)\?/", $fileName, $champs);
// اگر تطابق وجود داشته باشد، نام فایل را رمزگشایی میکنیم
if ($match) {
$fileName = iconv_mime_decode($fileName, 0, $champs[1]);
}
break;
}
}
}
}
// اگر نام فایل پیدا شد، سپس پیوست را ذخیره کنید
if ($fileName !== "") {
// ذخیرهٔ پیوست
$fileName = "$outputDir/$fileName";
print "sauvegarde de l'attachement dans [$fileName]\n";
// ایجاد فایل
if ($file = fopen($fileName, "w")) {
//بازیابی متن رمزگذاریشده از پیوست
$text = imap_fetchbody($imapResource, $msgNumber, $sectionNumber);
// پیوست رمزگذاری شده است – آن را رمزگشایی کنید
switch ($struct->encoding) {
// Base64
case ENCBASE64:
$text = base64_decode($text);
break;
// قابل چاپ با نقل قول
case ENCQUOTEDPRINTABLE:
$text = quoted_printable_decode($text);
break;
default:
// سایر موارد نادیده گرفته میشوند
break;
}
// نوشتن متن در فایل
fputs($file, $text);
// بستن فایل
fclose($file);
} else {
// ایجاد فایل ناموفق بود
print "L'attachement n'a pu être sauvegardé dans [$fileName]\n";
}
}
}
نظرات
- خط ۲: تابع [saveAttachment] پارامترهای زیر را میپذیرد:
- [$imapResource, int $msgNumber, string $sectionNumber] بهطور منحصربهفرد بخشی از IMAP را که باید ذخیره شود، تعریف میکند؛
- [string $outputDir] پوشهٔ ذخیره است؛
- [object $struct] ساختار بخش پیام را که باید ذخیره شود، توصیف میکند؛
- خطوط 6–44: نام فایل مرتبط با پیوست بازیابی میشود. همین نام فایل برای ذخیره کردن آن استفاده خواهد شد. نام فایل پیوست در جدول [$struct→dparameters] یا جدول [$struct→parameters]، یا احتمالاً هر دو، یافت میشود؛
- خطوط ۳۰–۴۰: اگر نام فایل حاوی کاراکترهایی باشد که در ۷ بیت رمزگذاری نشدهاند، آنگاه در [quoted-printable] رمزگذاری شده است. در این حالت، در [$struct→dparameters]، ویژگی به جای [fileName]، [fileName*] نامیده میشود. این بدان معناست که شرط در خط ۱۶ برقرار نشده است. سپس نام فایل در آرایه [$struct→parameters] جستجو میشود؛
- خط ۳۲: مثالی از یک نام فایل رمزگذاریشده. این به شکل زیر است: =?codage_original?codage_actuel?nom_encodé. بنابراین، نام [=?utf-8?Q?Cours-Tutoriels-Serge-Tah=C3=A9-1568x268=2Ep] به این معنی است که نام فایل قبلاً UTF-8 بوده و در حال حاضر [quoted-printable] (Q) است؛
- خط ۳۸: نام فایل با استفاده از تابع [iconv_mime_decode] رمزگشایی میشود که در اینجا سه پارامتر میگیرد:
- رشتهٔ مورد نظر برای رمزگشایی؛
- بهطور پیشفرض روی 0 تنظیم شده است؛
- مجموعه کاراکتری که باید برای نمایش رشته رمزگشاییشده استفاده شود. این پارامتر در رشتهای که باید رمزگشایی شود وجود دارد. این پارامتر با استفاده از یک عبارت منظم در خطوط 34–35 استخراج میشود؛
- خطوط ۴۵–۷۵: پیوست با نام یافتشده در یک فایل ذخیره میشود؛
برای آزمایش اسکریپت [imap-02.php]، ابتدا ایمیلی با پیکربندی زیر به [guest@localhost] ارسال میکنیم:
بنابراین پنج پیوست وجود دارد.
ما ایمیل ارسالشده را با استفاده از [imap-02.php] با پیکربندی زیر میخوانیم:
خروجی کنسول به شرح زیر است:
------------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.
فایلهای ذخیره شده را میتوان در پوشه [output/localhost-pop3/message-N] یافت:

16.6.6. کلاینت POP3 / IMAP با کتابخانه [php-mime-mail-parser]
در اسکریپت قبلی، [imap-02.php]، توانستیم ذخیره کنیم:
- محتویات [text/plain] و [text/HTML] ایمیل؛
- پیوستهای ایمیل؛
برای یک پیوست از نوع [message/rfc822]، ما همچنین محتوای پیوست را ذخیره کردیم. با این حال، این نوع پیوست خود یک ایمیل است که به نوبه خود حاوی محتویات [text/plain] و [text/HTML] و همچنین پیوستها است. بنابراین ممکن است خود را در وضعیت زیر بیابیم:
- یک فایل [mail 1] که ساختار آن مشابه ساختار یک پیوست از نوع [message/rfc822] است؛
- یک [mail 2] پیوست به ایمیل ۱؛
- یک فایل [mail 3] پیوست به ایمیل ۲؛
- و غیره…
اسکریپت [imap-02.php] محتویات [mail 1] (متن و پیوستها) را ذخیره میکند. این اسکریپت [mail 2] را به عنوان یک سند پیوست ذخیره میکند اما به همینجا بسنده میکند. این اسکریپت تلاش نمیکند تا [mail 2] را تحلیل کند تا متن و پیوستهای آن را استخراج نماید. ممکن است تصور شود که کافی است همان فرآیندی که برای [mail 1] به کار رفته است، روی [mail 2] نیز اعمال شود. یک فراخوانی بازگشتی به متدی که [mail 1] را پردازش کرد، ممکن است برای بازیابی محتوای تمام ایمیلهای تو در تو کافی باشد. متأسفانه، بخشهای [mail 2] با منطقی متفاوت از آنچه برای [mail 1] استفاده میشود، شمارهگذاری شدهاند، که این امر مانع از استفاده از الگوریتم یکسان در هر دو مورد میشود، مگر اینکه از منطقی نسبتاً پیچیده برای محاسبه شمارههای بخشهای یک ایمیل استفاده شود. صرفنظر از موقعیت آن در مجموعه ایمیلهای تو در تو.
اسکریپت [imap-02.php] از قبل پیچیده بود. برای اینکه از پیچیدهتر شدن آن جهت رسیدگی به محتوای ایمیلهای تو در تو جلوگیری کنیم، از کتابخانه [php-mime-mail-parser] که در گیتهاب (مه ۲۰۱۹) در URL [https://github.com/php-mime-mail-parser/php-mime-mail-parser] موجود است و توسط وینسنت داوس نوشته شده است، استفاده خواهیم کرد.
16.6.6.1. نصب کتابخانه [php-mime-mail-parser]
صفحهٔ مروری کتابخانه نحوهٔ نصب آن را در ویندوز توضیح میدهد:

برای OS در ویندوز دو مرحله وجود دارد:
télécharger une DLL ;
modifier le fichier [php.ini] qui configure PHP ;
LA DLL از کتابخانه [mailparse] در URL [http://pecl.php.net/package/mailparse] (مه ۲۰۱۹) در دسترس است؛

- در [2]، جدیدترین و پایدارترین نسخه کتابخانه را انتخاب کنید؛

- در [3]، نسخهای از PHP را که استفاده میکنید انتخاب کنید (در این سند PHP 7.2 است)؛
- برای [4]، نسخه ویندوز OS خود را انتخاب کنید (در اینجا نسخه ۶۴ بیتی ویندوز است). ما از نسخه [Thread Safe] استفاده خواهیم کرد؛
برای اطلاع از نسخه PHP دانلود شده با Laragon، [Terminal] را از پنجره Laragon باز کرده و دستور زیر را تایپ کنید:
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
نسخه PHP (7.2.11) در خط ۳ نمایش داده میشود. همین خط همچنین نسخه ویندوز مورد استفاده برای کامپایل (۳۲ بیتی یا ۷۴ بیتی) را نشان میدهد.
پس از بهدست آوردن DLL، باید آن را به پوشه [<laragon>/bin/php/<version-php>/ext] کپی کنید:

پس از انجام این کار، باید این افزونه را در فایل [php.ini] فعال کنید که PHP را پیکربندی میکند (به بخش «لینک» مراجعه کنید):

احتمالاً خط [7] وجود نخواهد داشت و شما باید خودتان آن را اضافه کنید.
پس از فعال شدن افزونه، میتوانید با وارد کردن دستور زیر در یک ترمینال Laragon، از معتبر بودن آن اطمینان حاصل کنید:
C:\myprograms\laragon-lite\www
λ php --ini
Configuration File (php.ini) Path: C:\windows
Loaded Configuration File: C:\myprograms\laragon-lite\bin\php\php-7.2.11-Win32-VC15-x64\php.ini
Scan for additional .ini files in: (none)
Additional .ini files parsed: (none)
دستور [php –-ini] فایل پیکربندی را برای خط ۴ بارگذاری میکند. سپس فایلهای DLL را برای تمام افزونههای فعال در [php.ini] بارگذاری میکند. اگر هر یک از این موارد نادرست باشد، این موضوع گزارش خواهد شد. بنابراین، اعتبار DLL که به [php_mailparse.dll] اضافه شده است، بررسی خواهد شد. ممکن است به دلایل مختلفی نادرست اعلام شود که رایجترین آنها به شرح زیر است:
- شما نسخه DLL را دانلود کردهاید که با نسخهای که از PHP در حال استفاده است مطابقت ندارد؛
- شما یک فایل DLL ۳۲ بیتی را دانلود کردهاید در حالی که سیستم شما ۶۴ بیتی است، یا برعکس؛
پس از فعالسازی و تأیید افزونه، میتوانید نصب کتابخانه [php-mime-mail-parser] را ادامه دهید:

دستور [8] باید در یک ترمینال Laragon وارد شود (به لینک در پاراگراف مراجعه کنید):

- برای [1]، بررسی کنید که در پوشه [<laragon>/www] هستید؛
- به [2]، دستور نصب کتابخانه [php-mime-mail-parser]؛
- در [3]، هیچ چیزی نصب نشده است زیرا کتابخانه [php-mime-mail-parser] قبلاً نصب شده بود؛
کتابخانه [php-mime-mail-parser] در پوشه [<laragon>/www/vendor] نصب شده است:


- در [2-3]، فایلهای منبع کتابخانه [php-mime-mail-parser]؛
اکنون که محیط کار راهاندازی شده است، میتوانیم به نوشتن اسکریپت [imap-03.php] بپردازیم.
16.6.6.2. اسکریپت [imap-03.php]
اسکریپت [imap-03.php] از همان فایل پیکربندی [config-imap-01.json] در اسکریپتهای قبلی استفاده میکند:
اسکریپت [imap-03.php] به شرح زیر است:
<?php
// IMAP کلاینت (پروتکل دسترسی به پیام اینترنتی) برای خواندن ایمیلها
//نوشته شده با استفاده از کتابخانه [php-mime-mail-parser]
// در دسترس درURL [https://github.com/php-mime-mail-parser/php-mime-mail-parser] (مه ۲۰۱۹)
//
// پابندی سختگیرانه به انواع اعلامشدهٔ پارامترهای تابع
declare (strict_types=1);
// مدیریت خطا
error_reporting(E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
//وابستگیها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
// تنظیمات بازیابی ایمیل
const CONFIG_FILE_NAME = "config-imap-01.json";
// بازیابی پیکربندی
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);
// خواندن صندوقهای پستی
foreach ($mailboxes as $name => $infos) {
// ردیابی
print "------------Lecture de la boîte à lettres [$name]\n";
// خواندن صندوق پستی
readmailbox($name, $infos);
}
//پایان
exit;
نظرات
- خطوط ۱۸–۲۳: محتویات فایل پیکربندی در دیکشنری [$mailboxes] قرار داده میشوند؛
- خطوط ۲۶–۳۱: هر صندوق پستی توسط تابع [readmailbox] (خط ۳۰) خوانده میشود. این تابع در واقع پیامهای نخوانده را از صندوق پستی میخواند. یک صندوق پستی با آدرس ایمیل کاربر مشخصی مطابقت دارد؛
تابع [readmailbox] به صورت زیر است:
function readmailbox(string $name, array $infos): void {
// ورود
$imapResource = imap_open($name, $infos["user"], $infos["password"]);
if (!$imapResource) {
// خطا
print "La connexion au serveur [$name] a échoué : " . imap_last_error() . "\n";
exit;
}
//اتصال برقرار شد
print "Connexion établie avec le serveur [$name].\n";
// تعداد کل پیامها در صندوق پستی
$nbmsg = imap_num_msg($imapResource);
print "Il y a [$nbmsg] messages dans la boîte à lettres [$name]\n";
// پیامهای نخوانده در صندوق پستی فعلی
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) {
print "---message n° [$msgNumber]\n";
// بازیابی بدنه پیام شماره $msgNumber
getMailBody($imapResource, $msgNumber, $infos);
// اگر پروتکل POP3 باشد، پیام پس از بازیابی حذف میشود
$pop3 = $infos["pop3"];
if ($pop3 !== NULL) {
// پیام بهعنوان «برای حذف» علامتگذاری میشود
imap_delete($imapResource, $msgNumber);
}
}
//پایان خواندن پیامهای نخوانده
if ($pop3 !== NULL) {
// پیامهای علامتگذاریشده بهعنوان «برای حذف» حذف میشوند
imap_expunge($imapResource);
}
}
}
// اتصال بسته شد
$imapClose = imap_close($imapResource);
if (!$imapClose) {
// نبود موفقیت
print "La fermeture de la connexion a échoué : " . imap_last_error() . "\n";
} else {
// موفقیت
print "Fermeture de la connexion réussie.\n";
}
}
توضیحات
کد تابع [readmailbox] مشابه اسکریپتهای قبلی است.
تابع [getMailBody] (خط 25)، که بدنهٔ یک پیام (محتوا + پیوستها) را تحلیل میکند، به شرح زیر است:
// تحلیل محتوای پیام
function getMailBody($imapResource, int $msgNumber, array $infos): void {
// بازیابی متن کامل پیام
$text = imap_fetchbody($imapResource, $msgNumber, "");
if ($text === FALSE) {
print "Le corps du message [$msgNumber] n'a pu être récupéré";
return;
}
//ایجاد یک تجزیهگر برای تحلیل متن پیام
$parser = (new PhpMimeMailParser\Parser())->setText($text);
//بازیابی بخشهای مختلف پیام
$outputDir = $infos["output-dir"] . "/message-$msgNumber";
getParts($parser, $msgNumber, $outputDir);
}
توضیحات
- خط ۲: تابع [getMailBody] سه پارامتر را میپذیرد:
- [$imapResource]: منبع IMAP که به آن متصل هستید؛
- [$msgNumber]: شماره پیامی (در صندوق پستی) که باید پردازش شود؛
- [$infos]: جزئیات مختلف مربوط به صندوق پستی در حال پردازش؛
- خط ۴: کل پیام با شماره [$msgNumber] بازیابی میشود؛
- خطوط ۵–۸: حالتی که محتوای پیام قابل بازیابی نبوده است؛
- خط ۱۰: ما شروع به استفاده از کتابخانه [php-mime-mail-parser] میکنیم. شیء [$parser] مسئول تجزیه و تحلیل متن پیام خواهد بود؛
- خط ۱۲: [$outputDir] پوشهای خواهد بود که محتوای متنی و پیوستهای پیام شماره [$msgNumber] در آن ذخیره میشوند؛
- خط ۱۳: به تابع [getParts] دستور داده شده است تا بخشهای مختلف (محتوای متن و پیوستها) پیام شماره [$msgNumber] را استخراج کرده و آنها را در پوشه [$outputDir] ذخیره کند؛
تابع [getParts] به شرح زیر است:
//بازیابی بخشهای مختلف یک پیام
function getParts(PhpMimeMailParser\Parser $parser, int $msgNumber, string $outputDir): void {
//پوشه پشتیبان پیام در صورت لزوم ایجاد میشود
if (!file_exists($outputDir)) {
if (!mkdir($outputDir)) {
print "Le dossier [$outputDir] n'a pu être créé\n";
return;
}
}
// سربرگهای پیام را بازیابی میکند
$arrayHeaders = $parser->getHeaders();
//پیامهای متنی ذخیره میشوند
$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");
}
//ذخیره پیامهای HTML
$parts = $parser->getInlineParts("html");
for ($i = 1; $i <= count($parts); $i++) {
print "-- Sauvegarde d'un message de type [text/html]\n";
saveMessage($parts[$i - 1], 1, $arrayHeaders, "$outputDir/message_$i.html");
}
//بازیابی پیوستهای پیام
$attachments = $parser->getAttachments();
// شماره پیوست
$iAttachment = 0;
//مرور فهرست پیوستها
foreach ($attachments as $attachment) {
// نوع پیوست
$fileType = $attachment->getContentType();
print "-- Sauvegarde d'un attachement de type [$fileType] dans le fichier [$outputDir/{$attachment->getFilename()}]\n";
//ذخیرهٔ پیوست
try {
$attachment->save($outputDir, PhpMimeMailParser\Parser::ATTACHMENT_DUPLICATE_SUFFIX);
} catch (Exception $e) {
print "L'attachement n'a pu être sauvegardé : " . $e->getMessage() . "\n";
}
// مورد ویژه نوع message/rfc822
if ($fileType === "message/rfc822") {
// پیوست خود یک پیام است – آن را نیز تجزیه خواهیم کرد
// تغییر دایرکتوری ذخیره
$iAttachment++;
$outputDir = $outputDir . "/rfc822-$iAttachment";
// محتوای مورد تجزیه را تغییر دهید
$parser->setText($attachment->getContent());
// پیام بهصورت بازگشتی تحلیل میشود
getParts($parser, $msgNumber, $outputDir);
}
}
}
نظرات
- خط ۲: تابع [getParts] سه پارامتر میگیرد:
- یک تجزیهکننده [$parser] که متن کامل پیام مورد تجزیه و تحلیل به آن ارسال شده است؛
- [$msgNumber] شماره پیامی است که در حال حاضر در حال تحلیل است؛
- [$outputDir] پوشهای است که محتوای پیام و پیوستهای آن در آن ذخیره میشوند؛
- خطوط ۴–۹: ایجاد پوشه [$outputDir]؛
- خط ۱۱: سرآیندهای پیام در حال تحلیل بازیابی میشوند (از، به، موضوع و غیره)؛
- خط ۱۳: بخشهای ایمیل با نوع [text/plain] بازیابی میشوند. یک آرایه بازیابی میشود؛
- خطوط ۱۴–۱۷: تمام عناصر آرایهٔ بازیابیشده ذخیره میشوند، هر کدام با یک نام فایل متفاوت؛
- خط ۱۹: بخشهای ایمیل با نوع [text/html] را بازیابی میکنیم. یک آرایه بازیابی میشود؛
- خطوط ۲۰–۲۳: تمام عناصر آرایهٔ بازیابیشده ذخیره میشوند، هر کدام با یک نام فایل متفاوت؛
- خط ۲۵: فهرست پیوستهای پیام تحلیلشده را بازیابی میکنیم؛
- خط ۲۹: این لیست را بهصورت بازگشتی پردازش میکنیم؛
- خط ۲۴: بازیابی نوع پیوست (ویژگی Content-Type);
- خطوط ۳۴–۳۸: پیوست در پوشه [$outputDir] ذخیره میشود. پارامتر دوم، [PhpMimeMailParser\Parser::ATTACHMENT_DUPLICATE_SUFFIX]، یک قرارداد نامگذاری برای پیوستها است. اگر [$attachment→getFilename()] روی X تنظیم شده باشد و فایل X از قبل وجود داشته باشد، آنگاه کتابخانه [php-mime-mail-parser] نامهای [X_1]، [X_2]، را امتحان میکند، و غیره تا زمانی که یک نام فایل را که وجود ندارد پیدا کند؛
- خط ۴۰: بررسی میکند که آیا فایل پیوستشده یک ایمیل است؛
- خطوط ۴۱–۴۸: اگر چنین باشد، آنگاه این ایمیل به نوبه خود برای استخراج محتویات و پیوستهای آن تحلیل میشود؛
- خط ۴۴: اگر [$outputDir] برابر X باشد و در میان پیوستهای پیام تحلیلشده دو ایمیل وجود داشته باشد، اولی در پوشه [$outputDir/rfc822-1] و دومی در پوشه [$outputDir/rfc822-2] ذخیره خواهد شد؛
- خط ۴۶: محتوای ایمیل پیوستشده به متن جدید برای پردازش تبدیل میشود؛
- خط ۴۸: تابع [getParts] بهصورت بازگشتی برای تجزیه و تحلیل متن جدید فراخوانی میشود؛
تابع [saveMessage] محتوای متنی پیام مورد تجزیه و تحلیل را ذخیره میکند:
//ذخیرهٔ یک پیام متنی
function saveMessage(string $text, int $type, array $arrayHeaders, string $filename): void {
// محتوایی که باید ذخیره شود
$contents = "";
// افزودن سربرگها
switch ($type) {
case 0:
// text/plain
foreach ($arrayHeaders as $key => $value) {
$contents .= "$key: $value\n";
}
$contents .= "\n";
break;
case 1:
//متن/HTML
foreach ($arrayHeaders as $key => $value) {
$contents .= "$key: $value<br/>\n";
}
$contents .= "<br/>\n";
}
// افزودن متن پیام
$contents .= $text;
// ذخیره همه چیز
if (!file_put_contents($filename, $contents)) {
// شکست
print "Le message n'a pu être sauvegardé dans le fichier [$filename]\n";
} else {
// موفقیت
print "Le message a été sauvegardé dans le fichier [$filename]\n";
}
}
توضیحات
- تابع [saveMessage] پارامترهای زیر را میپذیرد:
- [$text]: متنی که باید ذخیره شود؛
- [$type]: نوع متن (0: text/plain، 1: text/HTML);
- [$arrayHeaders]: سربرگهای پیام تحلیلشده؛
- [$filename]: نام فایلی که [$text] در آن ذخیره میشود؛
- خط ۴: [$contents] نمایانگر کل متنی است که باید ذخیره شود؛
- خطوط ۶–۲۰: ابتدا، تمام سرآیندهای پیام (از، به، موضوع و غیره) ذخیره خواهند شد؛
- خطوط ۱۶–۱۹: در مورد متنی مانند HTML، هر خط با تگ <br/> پایان مییابد تا هر سربرگ در مرورگر در خط خود نمایش داده شود؛
- خط ۲۲: متن پیام برای ذخیرهسازی به سربرگها اضافه میشود؛
- خطوط ۲۴–۳۰: کل مجموعه در فایل [$filename] ذخیره میشود؛
استفاده از کتابخانه [php-mime-mail-parser] نوشتن اسکریپت خواندن ایمیل را بهطور قابل توجهی آسانتر میکند.
اسکریپت [smtp-02.php] برای ارسال ایمیل به کاربر [guest@localhost] با پیکربندی زیر استفاده میشود:
- خطوط ۱۱–۱۵: پنج پیوست وجود دارد؛
- خط ۱۵: [test-localhost-2.eml] ایمیلی است که به شکل زیر ساختار یافته است:
- [test-localhost-2.eml] شامل ۴ پیوست (همانهایی که در خطوط ۱۱–۱۴ فهرست شدهاند) و یک ایمیل پیوستشده است؛
- ایمیل پیوست به [test-localhost-2.eml] حاوی ۴ پیوست (همانند موارد در خطوط ۱۱–۱۴) است؛
اسکریپت [imap-03.php] برای خواندن صندوق پستی کاربر [guest@localhost] با پیکربندی زیر استفاده میشود:
پس از اجرا، ساختار پوشه برای [output/localhost-pop3] به شرح زیر شد:

- در [1]، ۵ پیوست ایمیل دریافتی از [guest@localhost]؛
- به [2]، ۵ پیوست از ایمیل [test-localhost-2.eml] که توسط [1] ارسال شده است؛
- در [3]، ۴ پیوست از ایمیلی که توسط [2] ارسال شده و در [test-localhost.eml] قرار دارد؛
خروجی کنسول به شرح زیر است:
------------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.
اگر [message_1.HTML] را از [3] در یک مرورگر مشاهده کنید، خروجی زیر را دریافت میکنید:
