课程设计周互联网协议分析课程需要写一个与协议相关的程序 我们小组挑选到了邮件收发器 刚好顺便练习一下python

程序代码

main.py为主函数 send.py为发送函数 look.py为接收类
程序很简单学习使用勿喷

main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import send
import look
imap_host = 'imap.qq.com'
imap_port = '993'
imap_user = None
imap_pwd = None
email_index = 0
def imap_server():
global imap_host,imap_port,imap_user,imap_pwd
print("IMAP_Host: "+ imap_host+"\n"+"IMAP_Port:"+imap_port+"\n")
use_last_config = input("是否使用上方的IMAP连接 y/n,默认是 (回车): ").lower()
if use_last_config != "n":
imap_user = input("请输入IMAP邮箱: ")
imap_pwd = input("请输入IMAP邮箱密码: ")
return
print("是否要更改IMAP服务提供商 y/n,默认imap.qq.com")
change_host = input().lower()
if change_host == "yes" or change_host == "y":
imap_host = input("请输入新的IMAP服务器地址: ")
print("是否要更改IMAP端口 y/n,默认993")
change_port = input().lower()
if change_port == "yes" or change_port == "y":
imap_port = input("请输入新的IMAP端口: ")
imap_user = input("输入IMAP邮箱:")
imap_pwd = input("请输入IMAP密码")
print(f"IMAP服务器已更改为: {imap_host}")
print(f"IMAP端口已更改为: {imap_host}")

if __name__ == '__main__':
i = 0
zhiling = str.lower(input("请输入指令 按?为提示\n"))
while (zhiling != "quit"):
if (zhiling == "?" or zhiling == "help"):
print("帮助\n")
print("输入命令 getfirst 获取邮件信息\n")
print("输入命令 getnext 读取并打印当前服务器中下一条信件\n")
print("输入命令 send 发送邮件\n")
print("输入命令 quit 终止程序\n")
elif (zhiling == "getfirst"):
imap_server()
imap_jieshou = look.jieshou(imap_host,imap_port,imap_user,imap_pwd)
print("请输入搜索条件以逗号隔开\n")
print("ALL: 搜索所有邮件 UNSEEN: 搜索未读邮件。SEEN: 搜索已读邮件。\nFROM sender: 搜索特定发件人的邮件。SUBJECT keyword: 搜索包含特定关键词的邮件。\nSINCE date: 搜索在指定日期之后收到的邮件。\n可组合使用\n")
tj = input()
message_ids = imap_jieshou.getUnread(tj)[1][0].decode('utf-8').split(' ')
num = message_ids[i]
mailInfo = imap_jieshou.getMailInfo(num)
if mailInfo['attachments']:
ye = input("存在附件是否下载: y/n ")
if ( ye == "y"):
# 遍历附件列表
for attachment in mailInfo['attachments']:
fileob = open(attachment['name'], 'wb')
fileob.write(attachment['data'])
fileob.close()
elif (zhiling == "getnext"):
if i ==len(message_ids)-1:
i = 0
print("已经是最后一个啦")
print('-'*50)
else: i=i+1
num = message_ids[i]
mailInfo = imap_jieshou.getMailInfo(num)
if mailInfo['attachments']:
ye = input("存在附件是否下载: y/n ")
if (ye == "y"):
# 遍历附件列表
for attachment in mailInfo['attachments']:
fileob = open(attachment['name'], 'wb')
fileob.write(attachment['data'])
fileob.close()
elif (zhiling == "send"):
send.smtp_server()
send.smtp_todo()
elif (zhiling == "quit"):
pass
else:
print("指令错误请重新输入")
zhiling = str.lower(input("请输入指令:\n"))


send.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.mime.base import MIMEBase
from email import encoders
smtp_host = 'smtp.qq.com'
smtp_port = '587'
smtp_username = None
smtp_password = None
def smtp_server():

global smtp_host, smtp_port,smtp_username, smtp_password # 使用global关键字将全局变量引入函数
print("SMTP_host:"+smtp_host+"\n"+"SMTP_port:"+smtp_port+"\n")
use_last_config = input("是否使用上方的SMTP连接 y/n,默认是 (回车): ").lower()
if use_last_config != "n":
smtp_username = input("请输入SMTP邮箱: ")
smtp_password = input("请输入SMTP邮箱密码: ")
return

# 用户决定不使用上次的SMTP连接时,询问是否更改SMTP服务提供商
print("是否要更改SMTP服务提供商 y/n,默认smtp.qq.com")
change_host = input().lower()
if change_host == "yes" or change_host == "y":
smtp_host = input("请输入新的SMTP服务器地址: ")

# 询问是否更改SMTP端口
print("是否要更改SMTP端口 y/n,默认587")
change_port = input().lower()
if change_port == "yes" or change_port == "y":
smtp_port = input("请输入新的SMTP端口: ")

smtp_username = input("请输入SMTP邮箱: ")
smtp_password = input("请输入SMTP邮箱密码: ")

print(f"SMTP服务器已更改为: {smtp_host}")
print(f"SMTP端口已更改为: {smtp_port}")

def smtp_todo():
receiver = input("请输入要发给的用户邮箱以逗号隔开: ").split(',') # 收件人信息
mail_title = input("输入表头")
mail_type = input("请输入发送到文件类型plain(默认)或者html: ")
if (mail_type!="html"):
mail_type = "plain"
mail_content = input("请输入发送的内容") #正文内容
msg = MIMEMultipart()
msg['From'] = smtp_username
msg['To'] = ", ".join(receiver)
msg['Subject'] = Header(mail_title,'utf-8')
msg.attach(MIMEText(mail_content,mail_type,'utf-8'))
mail_fu = 'n'
mail_fu = input("是否上传附件 y/n默认否").lower()
if (mail_fu == "y" or mail_fu == "yes"):
# 添加附件(支持多种文件类型)
attachment_paths = input("请输入文件路径 以分号(;)结尾:").split(';')
for attachment_path in attachment_paths:
attachment_name = attachment_path.split("\\")[-1]
attachment = MIMEBase('application', 'octet-stream')
with open(attachment_path, 'rb') as attachment_file:
attachment.set_payload(attachment_file.read())
encoders.encode_base64(attachment)
attachment.add_header('Content-Disposition', f'attachment; filename={attachment_name}')
msg.attach(attachment)

lianjie = smtplib.SMTP(smtp_host,smtp_port)
lianjie.starttls()
lianjie.login(smtp_username,smtp_password)
lianjie.sendmail(smtp_username,receiver,msg.as_bytes())
print("发送成功")

look.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# -*- coding: utf-8 -*-
import imaplib, email
from email.utils import parseaddr
from email.header import decode_header
from email.mime.multipart import MIMEMultipart
class jieshou:

def __init__(self, imap_host,imap_port,username, password):
self.mail = imaplib.IMAP4_SSL(imap_host,imap_port)
self.mail.login(username, password)
self.select("INBOX")
print("登录成功\n")
def select(self, selector):
return self.mail.select(selector)
# 搜索邮件
def search(self, charset, *criteria):
try:
se = ",".join(criteria)
return self.mail.search(charset, se)
except Exception as e:
print(f"发生错误 更改为选择默认未读邮件:{e}")
return self.mail.search(charset, "ALL")
# 获取邮件列表
def getUnread(self,tj):
tj = tj.strip()
return self.search("utf-8",tj)

#以RFC822协议格式返回邮件详情的email对象
def getEmailFormat(self, num):
data = self.mail.fetch(num, 'RFC822')
if data[0] == 'OK':
return email.message_from_bytes(data[1][0][1])
else:
return "fetch error"

def getSenderInfo(self, msg):
name = email.utils.parseaddr(msg["from"])[0]
deName = email.header.decode_header(name)[0]
if deName[1] is not None:
name = deName[0].decode(deName[1])
address = email.utils.parseaddr(msg["from"])[1]
return (name, address)

# 返回接受者的信息——元组(邮件称呼,邮件地址)
def getReceiverInfo(self, msg):
name = email.utils.parseaddr(msg["to"])[0]
deName = email.header.decode_header(name)[0]
if deName[1] is not None:
name = deName[0].decode(deName[1])
address = email.utils.parseaddr(msg["to"])[1]
return (name, address)

# 返回邮件的主题(参数msg是email对象,可调用getEmailFormat获得)
def getSubjectContent(self, msg):
deContent = decode_header(msg['subject'])[0]
if deContent[1] is not None:
return deContent[0].decode(deContent[1])
return deContent[0]

def parse_attachment(self, message_part):
content_disposition = message_part.get("Content-Disposition", None)
if content_disposition:
file_data = message_part.get_payload(decode=True)
attachment = {}
attachment["content_type"] = message_part.get_content_type()
deName = decode_header(message_part.get_filename())[0]
name = deName[0]
if deName[1] is not None:
name = deName[0].decode(deName[1])
attachment["name"] = name
attachment["data"] = file_data
return attachment
return None

def getMailInfo(self, num):
msg = self.getEmailFormat(num)
attachments = []
print("主题:" + self.getSubjectContent(msg) + "\n")
print("发件人: " + self.getSenderInfo(msg)[0] + " 发件人邮箱: " + self.getSenderInfo(msg)[1] + "\n")
print("收件人: " + self.getReceiverInfo(msg)[0] + " 收件人邮箱: " + self.getReceiverInfo(msg)[1] + "\n")
for part in msg.walk(): # 遍历邮件消息
attachment = self.parse_attachment(part)
if part.get_content_type() == "text/plain":
body = part.get_payload(decode=True).decode('utf-8', errors='ignore')
print(body)
if part.get_content_type() == "text/html":
html = part.get_payload(decode=True).decode('utf-8', errors='ignore')
print(html)
if attachment:
attachments.append(attachment)
if len(attachments) != 0:
print("附件:\n")
for idx, att in enumerate(attachments):
print(f"{idx + 1}:Name: {att['name']}\n")
print('-' * 50)
return {
'attachments': attachments,
}

说明

说明部分我就直接粘贴我在实验报告上的实验说明 emm 胡言乱语 看不懂可以自己搜索理解

主函数 main.py

在程序的主函数中需要实现当输入!或者help时出现帮助,输入 getfirst时出现接收邮件所选择的第一封邮件,当输入getnext时需要显示下一封邮件,输入send时调用发送邮件模块发送邮件,当输入quit时退出程序

因为在主函数里需要连续对接受的邮件进行处理所以对于接受邮件的实例化对象需要在主函数中创建,也就是说在主程序中需要建立imap接收邮件的连接。当用户输入getfirst时需要建立一个imap的连接,在建立连接时可以在调用的程序中来实现连接信息的输入,也可以在主函数中定义连接信息的输入,这里我们在接受时采用在主函数定义函数进行输入,在发送时在函数中定义连接信息的输入。建立连接之后在邮件收信箱中有许多文件夹我们默认采用了INBOX文件夹。当选择完文件夹之后我们需要定义了选择的条件来在文件夹中搜索所符合条件的邮件。并对邮件进行处理。当输入getnext时我们需要显示符合刚才输入条件的下一条邮件我们需要定义一个计数的变量让其用来记录现在是第几件邮件,并且当邮件是最后一封的时候将计数器归零要求变为第一封邮件。当输入send时我们调用在send.py中定义的函数来实现邮件发送效果。

发送模块send.py

我们将所有的发送内容都放在发送函数中进行处理,当发送邮件时我们需要先连接到smtp服务器然后再发送邮件所以我们需要定义一个smtp_server()函数作登录用途
发送之前我们需要先设置几个关于发送的参数首先是要发给的用户,然后是发送文件的类型,文件的类型有两种plain和html默认情况下是plain内容,接下来就是输入发送的正文内容,然后就是附件,如果用户需要上传附件则需要对附件进行处理然后构建发送体。发件的内容构建完毕可以调用函数进行连接发送。

接收模块look.py

在接受邮件中我们采用类的形式进行定义在主函数中创建实例化对象来进行操作。首先定义主类进行imap的连接。在本模块里边我们需要对主函数需要进行的接受操作进行定义包括选择文件夹,获取符合条件的邮件列表,搜索符合条件的邮件列表,除此之外我们还需要对接受到的邮件进行转换格式和内容搜索。对于邮件服务器来说所发送的邮件是以RFC822格式的我们需要对其进行转换。对于主函数搜多到的符合条件的邮件序号在本类中需要对其进行实例化并返回输出邮件信息。对于附件我们在类中定义方法实现对附件列表的遍历以及对附件列表的输出返回文件看用用户是否需要下载