การตรวจสอบโดยใช้อีเมล

บทก่อนหน้านี้ได้กล่าวถึงความสำคัญและกระบวนการของนิติเครือข่ายและแนวคิดที่เกี่ยวข้อง ในบทนี้ให้เราเรียนรู้เกี่ยวกับบทบาทของอีเมลในนิติดิจิทัลและการตรวจสอบโดยใช้ Python

บทบาทของอีเมลในการตรวจสอบ

อีเมลมีบทบาทสำคัญมากในการสื่อสารทางธุรกิจและกลายเป็นหนึ่งในแอปพลิเคชันที่สำคัญที่สุดบนอินเทอร์เน็ต เป็นโหมดที่สะดวกสำหรับการส่งข้อความและเอกสารไม่เพียง แต่จากคอมพิวเตอร์เท่านั้น แต่ยังรวมถึงอุปกรณ์อิเล็กทรอนิกส์อื่น ๆ เช่นโทรศัพท์มือถือและแท็บเล็ต

ด้านลบของอีเมลคืออาชญากรอาจรั่วไหลข้อมูลสำคัญเกี่ยวกับ บริษัท ของพวกเขา ดังนั้นบทบาทของอีเมลในนิติดิจิทัลจึงเพิ่มขึ้นในช่วงไม่กี่ปีที่ผ่านมา ในทางนิติวิทยาศาสตร์อีเมลถือเป็นหลักฐานสำคัญและการวิเคราะห์ส่วนหัวอีเมลกลายเป็นสิ่งสำคัญในการรวบรวมหลักฐานในระหว่างกระบวนการทางนิติวิทยาศาสตร์

ผู้ตรวจสอบมีเป้าหมายดังต่อไปนี้ในขณะที่ดำเนินการนิติเวชอีเมล -

  • เพื่อระบุตัวคนร้ายหลัก
  • เพื่อรวบรวมหลักฐานที่จำเป็น
  • เพื่อนำเสนอข้อค้นพบ
  • ในการสร้างเคส

ความท้าทายในนิติอีเมล

นิติวิทยาศาสตร์ของอีเมลมีบทบาทสำคัญมากในการตรวจสอบเนื่องจากการสื่อสารส่วนใหญ่ในยุคปัจจุบันต้องอาศัยอีเมล อย่างไรก็ตามผู้ตรวจสอบทางนิติวิทยาศาสตร์ทางอีเมลอาจเผชิญกับความท้าทายต่อไปนี้ในระหว่างการสอบสวน -

อีเมลปลอม

ความท้าทายที่ยิ่งใหญ่ที่สุดในการพิสูจน์หลักฐานทางอีเมลคือการใช้อีเมลปลอมที่สร้างขึ้นโดยการจัดการและการเขียนสคริปต์ส่วนหัวเป็นต้นอาชญากรประเภทนี้ยังใช้อีเมลชั่วคราวซึ่งเป็นบริการที่อนุญาตให้ผู้ใช้ที่ลงทะเบียนรับอีเมลตามที่อยู่ชั่วคราวที่หมดอายุ หลังจากช่วงเวลาหนึ่ง

การปลอมแปลง

ความท้าทายอีกประการหนึ่งในการพิสูจน์หลักฐานทางอีเมลคือการปลอมแปลงซึ่งอาชญากรใช้ในการนำเสนออีเมลเป็นของคนอื่น ในกรณีนี้เครื่องจะได้รับทั้งที่อยู่ IP ปลอมและที่อยู่เดิม

การส่งอีเมลซ้ำแบบไม่ระบุตัวตน

ที่นี่เซิร์ฟเวอร์อีเมลจะดึงข้อมูลการระบุตัวตนจากข้อความอีเมลก่อนที่จะส่งต่อต่อไป สิ่งนี้นำไปสู่ความท้าทายครั้งใหญ่อีกครั้งสำหรับการตรวจสอบอีเมล

เทคนิคที่ใช้ในการตรวจสอบทางนิติวิทยาศาสตร์ทางอีเมล

นิติอีเมลคือการศึกษาแหล่งที่มาและเนื้อหาของอีเมลเพื่อเป็นหลักฐานในการระบุผู้ส่งและผู้รับที่แท้จริงของข้อความพร้อมกับข้อมูลอื่น ๆ เช่นวันที่ / เวลาในการส่งและความตั้งใจของผู้ส่ง เกี่ยวข้องกับการตรวจสอบข้อมูลเมตาการสแกนพอร์ตและการค้นหาคำหลัก

เทคนิคทั่วไปบางประการที่สามารถใช้สำหรับการตรวจสอบทางนิติวิทยาศาสตร์ทางอีเมล ได้แก่

  • การวิเคราะห์ส่วนหัว
  • การตรวจสอบเซิร์ฟเวอร์
  • การตรวจสอบอุปกรณ์เครือข่าย
  • ลายนิ้วมือของผู้ส่งจดหมาย
  • Software Embedded Identifiers

ในส่วนต่อไปนี้เราจะเรียนรู้วิธีดึงข้อมูลโดยใช้ Python เพื่อจุดประสงค์ในการตรวจสอบอีเมล

การแยกข้อมูลจากไฟล์ EML

ไฟล์ EML เป็นอีเมลในรูปแบบไฟล์ที่ใช้กันอย่างแพร่หลายในการจัดเก็บข้อความอีเมล ไฟล์เหล่านี้เป็นไฟล์ข้อความที่มีโครงสร้างซึ่งเข้ากันได้กับไคลเอนต์อีเมลหลายตัวเช่น Microsoft Outlook, Outlook Express และ Windows Live Mail

ไฟล์ EML จะจัดเก็บส่วนหัวของอีเมลเนื้อหาเนื้อหาข้อมูลไฟล์แนบเป็นข้อความธรรมดา ใช้ base64 เพื่อเข้ารหัสข้อมูลไบนารีและการเข้ารหัส Quoted-Printable (QP) เพื่อจัดเก็บข้อมูลเนื้อหา สคริปต์ Python ที่สามารถใช้ดึงข้อมูลจากไฟล์ EML ได้รับด้านล่าง -

ขั้นแรกให้นำเข้าไลบรารี Python ต่อไปนี้ตามที่แสดงด้านล่าง -

from __future__ import print_function
from argparse import ArgumentParser, FileType
from email import message_from_file

import os
import quopri
import base64

ในไลบรารีข้างต้น quopriใช้เพื่อถอดรหัสค่าที่เข้ารหัส QP จากไฟล์ EML ข้อมูลที่เข้ารหัส base64 ใด ๆ สามารถถอดรหัสได้ด้วยความช่วยเหลือของbase64 ห้องสมุด.

ต่อไปให้เราจัดเตรียมอาร์กิวเมนต์สำหรับตัวจัดการบรรทัดคำสั่ง โปรดทราบว่าที่นี่จะยอมรับเพียงอาร์กิวเมนต์เดียวซึ่งจะเป็นเส้นทางไปยังไฟล์ EML ดังที่แสดงด้านล่าง -

if __name__ == '__main__':
   parser = ArgumentParser('Extracting information from EML file')
   parser.add_argument("EML_FILE",help="Path to EML File", type=FileType('r'))
   args = parser.parse_args()
   main(args.EML_FILE)

ตอนนี้เราต้องกำหนด main() ซึ่งเราจะใช้เมธอดชื่อ message_from_file()จากไลบรารีอีเมลเพื่ออ่านไฟล์เช่น object ที่นี่เราจะเข้าถึงส่วนหัวเนื้อหาเนื้อหาไฟล์แนบและข้อมูลเพย์โหลดอื่น ๆ โดยใช้ตัวแปรผลลัพธ์ที่ชื่อemlfile ดังแสดงในรหัสที่ระบุด้านล่าง -

def main(input_file):
   emlfile = message_from_file(input_file)
   for key, value in emlfile._headers:
      print("{}: {}".format(key, value))
print("\nBody\n")

if emlfile.is_multipart():
   for part in emlfile.get_payload():
      process_payload(part)
else:
   process_payload(emlfile[1])

ตอนนี้เราต้องกำหนด process_payload() วิธีการที่เราจะแยกเนื้อหาของข้อความโดยใช้ get_payload()วิธี. เราจะถอดรหัสข้อมูลที่เข้ารหัส QP โดยใช้ไฟล์quopri.decodestring()ฟังก์ชัน นอกจากนี้เราจะตรวจสอบประเภท MIME ของเนื้อหาเพื่อให้สามารถจัดการการจัดเก็บอีเมลได้อย่างถูกต้อง สังเกตรหัสที่ระบุด้านล่าง -

def process_payload(payload):
   print(payload.get_content_type() + "\n" + "=" * len(payload.get_content_type()))
   body = quopri.decodestring(payload.get_payload())
   
   if payload.get_charset():
      body = body.decode(payload.get_charset())
else:
   try:
      body = body.decode()
   except UnicodeDecodeError:
      body = body.decode('cp1252')

if payload.get_content_type() == "text/html":
   outfile = os.path.basename(args.EML_FILE.name) + ".html"
   open(outfile, 'w').write(body)
elif payload.get_content_type().startswith('application'):
   outfile = open(payload.get_filename(), 'wb')
   body = base64.b64decode(payload.get_payload())
   outfile.write(body)
   outfile.close()
   print("Exported: {}\n".format(outfile.name))
else:
   print(body)

หลังจากเรียกใช้สคริปต์ข้างต้นแล้วเราจะได้รับข้อมูลส่วนหัวพร้อมกับ payload ต่างๆบนคอนโซล

การวิเคราะห์ไฟล์ MSG โดยใช้ Python

ข้อความอีเมลมีหลายรูปแบบ MSG เป็นรูปแบบหนึ่งที่ Microsoft Outlook และ Exchange ใช้ ไฟล์ที่มีนามสกุล MSG อาจมีข้อความ ASCII ธรรมดาสำหรับส่วนหัวและเนื้อหาข้อความหลักตลอดจนไฮเปอร์ลิงก์และไฟล์แนบ

ในส่วนนี้เราจะเรียนรู้วิธีการดึงข้อมูลจากไฟล์ MSG โดยใช้ Outlook API โปรดทราบว่าสคริปต์ Python ต่อไปนี้จะใช้ได้กับ Windows เท่านั้น สำหรับสิ่งนี้เราจำเป็นต้องติดตั้งไลบรารี Python ของบุคคลที่สามที่ชื่อpywin32 ดังต่อไปนี้ -

pip install pywin32

ตอนนี้นำเข้าไลบรารีต่อไปนี้โดยใช้คำสั่งที่แสดง -

from __future__ import print_function
from argparse import ArgumentParser

import os
import win32com.client
import pywintypes

ตอนนี้ให้เราระบุอาร์กิวเมนต์สำหรับตัวจัดการบรรทัดคำสั่ง ที่นี่จะยอมรับสองอาร์กิวเมนต์หนึ่งจะเป็นพา ธ ไปยังไฟล์ MSG และอื่น ๆ จะเป็นโฟลเดอร์ผลลัพธ์ที่ต้องการดังนี้ -

if __name__ == '__main__':
   parser = ArgumentParser(‘Extracting information from MSG file’)
   parser.add_argument("MSG_FILE", help="Path to MSG file")
   parser.add_argument("OUTPUT_DIR", help="Path to output folder")
   args = parser.parse_args()
   out_dir = args.OUTPUT_DIR
   
   if not os.path.exists(out_dir):
      os.makedirs(out_dir)
   main(args.MSG_FILE, args.OUTPUT_DIR)

ตอนนี้เราต้องกำหนด main() ฟังก์ชันที่เราจะเรียกใช้ win32com ห้องสมุดสำหรับการตั้งค่า Outlook API ซึ่งช่วยให้เข้าถึงไฟล์ MAPI เนมสเปซ

def main(msg_file, output_dir):
   mapi = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
   msg = mapi.OpenSharedItem(os.path.abspath(args.MSG_FILE))
   
   display_msg_attribs(msg)
   display_msg_recipients(msg)
   
   extract_msg_body(msg, output_dir)
   extract_attachments(msg, output_dir)

ตอนนี้กำหนดฟังก์ชันต่างๆที่เราใช้ในสคริปต์นี้ รหัสที่ระบุด้านล่างแสดงการกำหนดไฟล์display_msg_attribs() ฟังก์ชันที่ช่วยให้เราสามารถแสดงคุณลักษณะต่างๆของข้อความเช่น subject, to, BCC, CC, Size, SenderName, sent, etc.

def display_msg_attribs(msg):
   attribs = [
      'Application', 'AutoForwarded', 'BCC', 'CC', 'Class',
      'ConversationID', 'ConversationTopic', 'CreationTime',
      'ExpiryTime', 'Importance', 'InternetCodePage', 'IsMarkedAsTask',
      'LastModificationTime', 'Links','ReceivedTime', 'ReminderSet',
      'ReminderTime', 'ReplyRecipientNames', 'Saved', 'Sender',
      'SenderEmailAddress', 'SenderEmailType', 'SenderName', 'Sent',
      'SentOn', 'SentOnBehalfOfName', 'Size', 'Subject',
      'TaskCompletedDate', 'TaskDueDate', 'To', 'UnRead'
   ]
   print("\nMessage Attributes")
   for entry in attribs:
      print("{}: {}".format(entry, getattr(msg, entry, 'N/A')))

ตอนนี้กำหนด display_msg_recipeints() ฟังก์ชันที่วนซ้ำผ่านข้อความและแสดงรายละเอียดผู้รับ

def display_msg_recipients(msg):
   recipient_attrib = ['Address', 'AutoResponse', 'Name', 'Resolved', 'Sendable']
   i = 1
   
   while True:
   try:
      recipient = msg.Recipients(i)
   except pywintypes.com_error:
      break
   print("\nRecipient {}".format(i))
   print("=" * 15)
   
   for entry in recipient_attrib:
      print("{}: {}".format(entry, getattr(recipient, entry, 'N/A')))
   i += 1

ต่อไปเราจะกำหนด extract_msg_body() ฟังก์ชันที่แยกเนื้อหาเนื้อหา HTML และข้อความธรรมดาออกจากข้อความ

def extract_msg_body(msg, out_dir):
   html_data = msg.HTMLBody.encode('cp1252')
   outfile = os.path.join(out_dir, os.path.basename(args.MSG_FILE))
   
   open(outfile + ".body.html", 'wb').write(html_data)
   print("Exported: {}".format(outfile + ".body.html"))
   body_data = msg.Body.encode('cp1252')
   
   open(outfile + ".body.txt", 'wb').write(body_data)
   print("Exported: {}".format(outfile + ".body.txt"))

ต่อไปเราจะกำหนด extract_attachments() ฟังก์ชันที่ส่งออกข้อมูลสิ่งที่แนบไปยังไดเร็กทอรีเอาต์พุตที่ต้องการ

def extract_attachments(msg, out_dir):
   attachment_attribs = ['DisplayName', 'FileName', 'PathName', 'Position', 'Size']
   i = 1 # Attachments start at 1
   
   while True:
      try:
         attachment = msg.Attachments(i)
   except pywintypes.com_error:
      break

เมื่อกำหนดฟังก์ชันทั้งหมดแล้วเราจะพิมพ์แอตทริบิวต์ทั้งหมดไปยังคอนโซลด้วยรหัสบรรทัดต่อไปนี้ -

print("\nAttachment {}".format(i))
print("=" * 15)
   
for entry in attachment_attribs:
   print('{}: {}'.format(entry, getattr(attachment, entry,"N/A")))
outfile = os.path.join(os.path.abspath(out_dir),os.path.split(args.MSG_FILE)[-1])
   
if not os.path.exists(outfile):
os.makedirs(outfile)
outfile = os.path.join(outfile, attachment.FileName)
attachment.SaveAsFile(outfile)
   
print("Exported: {}".format(outfile))
i += 1

หลังจากเรียกใช้สคริปต์ข้างต้นเราจะได้รับคุณลักษณะของข้อความและสิ่งที่แนบมาในหน้าต่างคอนโซลพร้อมกับไฟล์ต่างๆในไดเร็กทอรีเอาต์พุต

การจัดโครงสร้างไฟล์ MBOX จาก Google Takeout โดยใช้ Python

ไฟล์ MBOX เป็นไฟล์ข้อความที่มีการจัดรูปแบบพิเศษซึ่งแบ่งข้อความที่เก็บไว้ภายใน มักพบในระบบ UNIX, Thunderbolt และ Google Takeouts

ในส่วนนี้คุณจะเห็นสคริปต์ Python ซึ่งเราจะจัดโครงสร้างไฟล์ MBOX ที่ได้รับจาก Google Takeouts แต่ก่อนหน้านั้นเราต้องรู้ว่าเราจะสร้างไฟล์ MBOX เหล่านี้ได้อย่างไรโดยใช้บัญชี Google หรือบัญชี Gmail ของเรา

การรับกล่องจดหมายของบัญชี Google เป็นรูปแบบ MBX

การได้รับกล่องจดหมายบัญชี Google หมายถึงการสำรองข้อมูลบัญชี Gmail ของเรา การสำรองข้อมูลสามารถทำได้ด้วยเหตุผลส่วนตัวหรือทางวิชาชีพต่างๆ โปรดทราบว่า Google ให้การสำรองข้อมูล Gmail ในการรับกล่องจดหมายบัญชี Google ของเราในรูปแบบ MBOX คุณต้องทำตามขั้นตอนด้านล่าง -

  • เปิด My account แผงควบคุม.

  • ไปที่ส่วนข้อมูลส่วนบุคคลและความเป็นส่วนตัวแล้วเลือกลิงก์ควบคุมเนื้อหาของคุณ

  • คุณสามารถสร้างที่เก็บถาวรใหม่หรือสามารถจัดการที่มีอยู่ได้ ถ้าเราคลิกCREATE ARCHIVE จากนั้นเราจะได้รับช่องทำเครื่องหมายสำหรับผลิตภัณฑ์ Google แต่ละรายการที่เราต้องการรวมไว้

  • หลังจากเลือกผลิตภัณฑ์แล้วเราจะได้รับอิสระในการเลือกประเภทไฟล์และขนาดสูงสุดสำหรับไฟล์เก็บถาวรของเราพร้อมกับวิธีการจัดส่งเพื่อเลือกจากรายการ

  • สุดท้ายเราจะได้รับข้อมูลสำรองนี้ในรูปแบบ MBOX

รหัส Python

ตอนนี้ไฟล์ MBOX ที่กล่าวถึงข้างต้นสามารถจัดโครงสร้างโดยใช้ Python ดังที่แสดงด้านล่าง -

ก่อนอื่นต้องนำเข้าไลบรารี Python ดังนี้ -

from __future__ import print_function
from argparse import ArgumentParser

import mailbox
import os
import time
import csv
from tqdm import tqdm

import base64

ไลบรารีทั้งหมดถูกใช้และอธิบายไว้ในสคริปต์ก่อนหน้านี้ยกเว้นไฟล์ mailbox ไลบรารีที่ใช้ในการแยกวิเคราะห์ไฟล์ MBOX

ตอนนี้ให้อาร์กิวเมนต์สำหรับตัวจัดการบรรทัดคำสั่ง ที่นี่จะยอมรับสองอาร์กิวเมนต์หนึ่งจะเป็นพา ธ ไปยังไฟล์ MBOX และอีกอันจะเป็นโฟลเดอร์ผลลัพธ์ที่ต้องการ

if __name__ == '__main__':
   parser = ArgumentParser('Parsing MBOX files')
   parser.add_argument("MBOX", help="Path to mbox file")
   parser.add_argument(
      "OUTPUT_DIR",help = "Path to output directory to write report ""and exported content")
   args = parser.parse_args()
   main(args.MBOX, args.OUTPUT_DIR)

ตอนนี้จะกำหนด main() ฟังก์ชันและการโทร mbox คลาสของไลบรารีกล่องจดหมายด้วยความช่วยเหลือซึ่งเราสามารถแยกวิเคราะห์ไฟล์ MBOX โดยระบุพา ธ -

def main(mbox_file, output_dir):
   print("Reading mbox file")
   mbox = mailbox.mbox(mbox_file, factory=custom_reader)
   print("{} messages to parse".format(len(mbox)))

ตอนนี้กำหนดวิธีการอ่านสำหรับ mailbox ห้องสมุดดังนี้ -

def custom_reader(data_stream):
   data = data_stream.read()
   try:
      content = data.decode("ascii")
   except (UnicodeDecodeError, UnicodeEncodeError) as e:
      content = data.decode("cp1252", errors="replace")
   return mailbox.mboxMessage(content)

ตอนนี้สร้างตัวแปรสำหรับการประมวลผลเพิ่มเติมดังนี้ -

parsed_data = []
attachments_dir = os.path.join(output_dir, "attachments")

if not os.path.exists(attachments_dir):
   os.makedirs(attachments_dir)
columns = [
   "Date", "From", "To", "Subject", "X-Gmail-Labels", "Return-Path", "Received", 
   "Content-Type", "Message-ID","X-GM-THRID", "num_attachments_exported", "export_path"]

ถัดไปใช้ tqdm เพื่อสร้างแถบความคืบหน้าและติดตามกระบวนการทำซ้ำดังนี้ -

for message in tqdm(mbox):
   msg_data = dict()
   header_data = dict(message._headers)
for hdr in columns:
   msg_data[hdr] = header_data.get(hdr, "N/A")

ตอนนี้ตรวจสอบข้อความสภาพอากาศว่ามีน้ำหนักบรรทุกหรือไม่ หากมีแล้วเราจะกำหนดwrite_payload() วิธีการดังนี้ -

if len(message.get_payload()):
   export_path = write_payload(message, attachments_dir)
   msg_data['num_attachments_exported'] = len(export_path)
   msg_data['export_path'] = ", ".join(export_path)

ตอนนี้จำเป็นต้องต่อท้ายข้อมูล แล้วเราจะโทรcreate_report() วิธีการดังนี้ -

parsed_data.append(msg_data)
create_report(
   parsed_data, os.path.join(output_dir, "mbox_report.csv"), columns)
def write_payload(msg, out_dir):
   pyld = msg.get_payload()
   export_path = []
   
if msg.is_multipart():
   for entry in pyld:
      export_path += write_payload(entry, out_dir)
else:
   content_type = msg.get_content_type()
   if "application/" in content_type.lower():
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
   elif "image/" in content_type.lower():
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))

   elif "video/" in content_type.lower():
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
   elif "audio/" in content_type.lower():
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
   elif "text/csv" in content_type.lower():
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
   elif "info/" in content_type.lower():
      export_path.append(export_content(msg, out_dir,
      msg.get_payload()))
   elif "text/calendar" in content_type.lower():
      export_path.append(export_content(msg, out_dir,
      msg.get_payload()))
   elif "text/rtf" in content_type.lower():
      export_path.append(export_content(msg, out_dir,
      msg.get_payload()))
   else:
      if "name=" in msg.get('Content-Disposition', "N/A"):
         content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
   elif "name=" in msg.get('Content-Type', "N/A"):
      content = base64.b64decode(msg.get_payload())
      export_path.append(export_content(msg, out_dir, content))
return export_path

สังเกตว่าข้อความ if-else ข้างต้นเข้าใจง่าย ตอนนี้เราต้องกำหนดวิธีการที่จะแยกชื่อไฟล์จากไฟล์msg วัตถุดังต่อไปนี้ -

def export_content(msg, out_dir, content_data):
   file_name = get_filename(msg)
   file_ext = "FILE"
   
   if "." in file_name: file_ext = file_name.rsplit(".", 1)[-1]
   file_name = "{}_{:.4f}.{}".format(file_name.rsplit(".", 1)[0], time.time(), file_ext)
   file_name = os.path.join(out_dir, file_name)

ตอนนี้ด้วยความช่วยเหลือของโค้ดต่อไปนี้คุณสามารถส่งออกไฟล์ได้จริง -

if isinstance(content_data, str):
   open(file_name, 'w').write(content_data)
else:
   open(file_name, 'wb').write(content_data)
return file_name

ตอนนี้ให้เรากำหนดฟังก์ชั่นเพื่อแยกชื่อไฟล์จากไฟล์ message เพื่อแสดงชื่อของไฟล์เหล่านี้อย่างถูกต้องดังนี้ -

def get_filename(msg):
   if 'name=' in msg.get("Content-Disposition", "N/A"):
      fname_data = msg["Content-Disposition"].replace("\r\n", " ")
      fname = [x for x in fname_data.split("; ") if 'name=' in x]
      file_name = fname[0].split("=", 1)[-1]
   elif 'name=' in msg.get("Content-Type", "N/A"):
      fname_data = msg["Content-Type"].replace("\r\n", " ")
      fname = [x for x in fname_data.split("; ") if 'name=' in x]
      file_name = fname[0].split("=", 1)[-1]
   else:
      file_name = "NO_FILENAME"
   fchars = [x for x in file_name if x.isalnum() or x.isspace() or x == "."]
   return "".join(fchars)

ตอนนี้เราสามารถเขียนไฟล์ CSV โดยกำหนดไฟล์ create_report() ฟังก์ชันดังต่อไปนี้ -

def create_report(output_data, output_file, columns):
   with open(output_file, 'w', newline="") as outfile:
      csvfile = csv.DictWriter(outfile, columns)
      csvfile.writeheader()
      csvfile.writerows(output_data)

เมื่อคุณเรียกใช้สคริปต์ที่ให้ไว้ข้างต้นเราจะได้รับรายงาน CSV และไดเรกทอรีที่เต็มไปด้วยไฟล์แนบ