Annotation For src/mail.rs
Logged in as anonymous

Origin for each line in src/mail.rs from check-in f5ed284f8c:

f5ed284f8c 2025-06-21    1: use crate::{
f5ed284f8c 2025-06-21    2: 	Cursor,
f5ed284f8c 2025-06-21    3: 	telegram::{
f5ed284f8c 2025-06-21    4: 		encode,
f5ed284f8c 2025-06-21    5: 		TelegramTransport,
f5ed284f8c 2025-06-21    6: 	},
f5ed284f8c 2025-06-21    7: 	utils::{
f5ed284f8c 2025-06-21    8: 		Attachment,
f5ed284f8c 2025-06-21    9: 		RE_DOMAIN,
f5ed284f8c 2025-06-21   10: 	},
f5ed284f8c 2025-06-21   11: };
f5ed284f8c 2025-06-21   12: 
f5ed284f8c 2025-06-21   13: use std::{
f5ed284f8c 2025-06-21   14: 	borrow::Cow,
f5ed284f8c 2025-06-21   15: 	collections::{
f5ed284f8c 2025-06-21   16: 		HashMap,
f5ed284f8c 2025-06-21   17: 		HashSet,
f5ed284f8c 2025-06-21   18: 	},
f5ed284f8c 2025-06-21   19: 	io::Error,
f5ed284f8c 2025-06-21   20: };
f5ed284f8c 2025-06-21   21: 
f5ed284f8c 2025-06-21   22: use anyhow::{
f5ed284f8c 2025-06-21   23: 	bail,
f5ed284f8c 2025-06-21   24: 	Context,
f5ed284f8c 2025-06-21   25: 	Result,
f5ed284f8c 2025-06-21   26: };
f5ed284f8c 2025-06-21   27: use async_std::{
f5ed284f8c 2025-06-21   28: 	sync::Arc,
f5ed284f8c 2025-06-21   29: 	task,
f5ed284f8c 2025-06-21   30: };
f5ed284f8c 2025-06-21   31: use mailin_embedded::{
f5ed284f8c 2025-06-21   32: 	Response,
f5ed284f8c 2025-06-21   33: 	response::{
f5ed284f8c 2025-06-21   34: 		INTERNAL_ERROR,
f5ed284f8c 2025-06-21   35: 		INVALID_CREDENTIALS,
f5ed284f8c 2025-06-21   36: 		NO_MAILBOX,
f5ed284f8c 2025-06-21   37: 		OK
f5ed284f8c 2025-06-21   38: 	},
f5ed284f8c 2025-06-21   39: };
f5ed284f8c 2025-06-21   40: use regex::{
f5ed284f8c 2025-06-21   41: 	Regex,
f5ed284f8c 2025-06-21   42: 	escape,
f5ed284f8c 2025-06-21   43: };
f5ed284f8c 2025-06-21   44: use tgbot::types::ChatPeerId;
f5ed284f8c 2025-06-21   45: 
f5ed284f8c 2025-06-21   46: /// `SomeHeaders` object to store data through SMTP session
f5ed284f8c 2025-06-21   47: #[derive(Clone, Debug)]
f5ed284f8c 2025-06-21   48: struct SomeHeaders {
f5ed284f8c 2025-06-21   49: 	from: String,
f5ed284f8c 2025-06-21   50: 	to: Vec<String>,
f5ed284f8c 2025-06-21   51: }
f5ed284f8c 2025-06-21   52: 
f5ed284f8c 2025-06-21   53: /// `MailServer` Central object with TG api and configuration
f5ed284f8c 2025-06-21   54: #[derive(Clone, Debug)]
f5ed284f8c 2025-06-21   55: pub struct MailServer {
f5ed284f8c 2025-06-21   56: 	data: Vec<u8>,
f5ed284f8c 2025-06-21   57: 	headers: Option<SomeHeaders>,
f5ed284f8c 2025-06-21   58: 	relay: bool,
f5ed284f8c 2025-06-21   59: 	tg: Arc<TelegramTransport>,
f5ed284f8c 2025-06-21   60: 	fields: HashSet<String>,
f5ed284f8c 2025-06-21   61: 	address: Regex,
f5ed284f8c 2025-06-21   62: }
f5ed284f8c 2025-06-21   63: 
f5ed284f8c 2025-06-21   64: impl MailServer {
f5ed284f8c 2025-06-21   65: 	/// Initialize API and read configuration
f5ed284f8c 2025-06-21   66: 	pub fn new(settings: config::Config) -> Result<MailServer> {
f5ed284f8c 2025-06-21   67: 		let api_key = settings.get_string("api_key")
f5ed284f8c 2025-06-21   68: 			.context("[smtp2tg.toml] missing \"api_key\" parameter.\n")?;
f5ed284f8c 2025-06-21   69: 		let mut recipients = HashMap::new();
f5ed284f8c 2025-06-21   70: 		for (name, value) in settings.get_table("recipients")
f5ed284f8c 2025-06-21   71: 			.expect("[smtp2tg.toml] missing table \"recipients\".\n")
f5ed284f8c 2025-06-21   72: 		{
f5ed284f8c 2025-06-21   73: 			let value = value.into_int()
f5ed284f8c 2025-06-21   74: 				.context("[smtp2tg.toml] \"recipient\" table values should be integers.\n")?;
f5ed284f8c 2025-06-21   75: 			recipients.insert(name, value);
f5ed284f8c 2025-06-21   76: 		}
f5ed284f8c 2025-06-21   77: 		let default = settings.get_int("default")
f5ed284f8c 2025-06-21   78: 			.context("[smtp2tg.toml] missing \"default\" recipient.\n")?;
f5ed284f8c 2025-06-21   79: 
f5ed284f8c 2025-06-21   80: 		let tg = Arc::new(TelegramTransport::new(api_key, recipients, default)?);
f5ed284f8c 2025-06-21   81: 		let fields = HashSet::<String>::from_iter(settings.get_array("fields")
f5ed284f8c 2025-06-21   82: 			.expect("[smtp2tg.toml] \"fields\" should be an array")
f5ed284f8c 2025-06-21   83: 			.iter().map(|x| x.clone().into_string().expect("should be strings")));
f5ed284f8c 2025-06-21   84: 		let mut domains: HashSet<String> = HashSet::new();
f5ed284f8c 2025-06-21   85: 		let extra_domains = settings.get_array("domains").unwrap();
f5ed284f8c 2025-06-21   86: 		for domain in extra_domains {
f5ed284f8c 2025-06-21   87: 			let domain = domain.to_string().to_lowercase();
f5ed284f8c 2025-06-21   88: 			if RE_DOMAIN.is_match(&domain) {
f5ed284f8c 2025-06-21   89: 				domains.insert(domain);
f5ed284f8c 2025-06-21   90: 			} else {
f5ed284f8c 2025-06-21   91: 				panic!("[smtp2tg.toml] can't check of domains in \"domains\": {domain}");
f5ed284f8c 2025-06-21   92: 			}
f5ed284f8c 2025-06-21   93: 		}
f5ed284f8c 2025-06-21   94: 		let domains = domains.into_iter().map(|s| escape(&s))
f5ed284f8c 2025-06-21   95: 			.collect::<Vec<String>>().join("|");
f5ed284f8c 2025-06-21   96: 		let address = Regex::new(&format!("^(?P<user>[a-z0-9][-a-z0-9])(@({domains}))$")).unwrap();
f5ed284f8c 2025-06-21   97: 		let relay = match settings.get_string("unknown")
f5ed284f8c 2025-06-21   98: 			.context("[smtp2tg.toml] can't get \"unknown\" policy.\n")?.as_str()
f5ed284f8c 2025-06-21   99: 		{
f5ed284f8c 2025-06-21  100: 			"relay" => true,
f5ed284f8c 2025-06-21  101: 			"deny" => false,
f5ed284f8c 2025-06-21  102: 			_ => {
f5ed284f8c 2025-06-21  103: 				bail!("[smtp2tg.toml] \"unknown\" should be either \"relay\" or \"deny\".\n");
f5ed284f8c 2025-06-21  104: 			},
f5ed284f8c 2025-06-21  105: 		};
f5ed284f8c 2025-06-21  106: 
f5ed284f8c 2025-06-21  107: 		Ok(MailServer {
f5ed284f8c 2025-06-21  108: 			data: vec!(),
f5ed284f8c 2025-06-21  109: 			headers: None,
f5ed284f8c 2025-06-21  110: 			relay,
f5ed284f8c 2025-06-21  111: 			tg,
f5ed284f8c 2025-06-21  112: 			fields,
f5ed284f8c 2025-06-21  113: 			address,
f5ed284f8c 2025-06-21  114: 		})
f5ed284f8c 2025-06-21  115: 	}
f5ed284f8c 2025-06-21  116: 
f5ed284f8c 2025-06-21  117: 	/// Returns id for provided email address
f5ed284f8c 2025-06-21  118: 	fn get_id (&self, name: &str) -> Result<&ChatPeerId> {
f5ed284f8c 2025-06-21  119: 		// here we need to store String locally to borrow it after
f5ed284f8c 2025-06-21  120: 		let mut link = name;
f5ed284f8c 2025-06-21  121: 		let name: String;
f5ed284f8c 2025-06-21  122: 		if let Some(caps) = self.address.captures(link) {
f5ed284f8c 2025-06-21  123: 			name = caps["name"].to_string();
f5ed284f8c 2025-06-21  124: 			link = &name;
f5ed284f8c 2025-06-21  125: 		}
f5ed284f8c 2025-06-21  126: 		match self.tg.get(link) {
f5ed284f8c 2025-06-21  127: 			Ok(addr) => Ok(addr),
f5ed284f8c 2025-06-21  128: 			Err(_) => Ok(&self.tg.default),
f5ed284f8c 2025-06-21  129: 		}
f5ed284f8c 2025-06-21  130: 	}
f5ed284f8c 2025-06-21  131: 
f5ed284f8c 2025-06-21  132: 	/// Attempt to deliver one message
f5ed284f8c 2025-06-21  133: 	async fn relay_mail (&self) -> Result<()> {
f5ed284f8c 2025-06-21  134: 		if let Some(headers) = &self.headers {
f5ed284f8c 2025-06-21  135: 			let mail = mail_parser::MessageParser::new().parse(&self.data)
f5ed284f8c 2025-06-21  136: 				.context("Failed to parse mail.")?;
f5ed284f8c 2025-06-21  137: 
f5ed284f8c 2025-06-21  138: 			// Adding all known addresses to recipient list, for anyone else adding default
f5ed284f8c 2025-06-21  139: 			// Also if list is empty also adding default
f5ed284f8c 2025-06-21  140: 			let mut rcpt: HashSet<&ChatPeerId> = HashSet::new();
f5ed284f8c 2025-06-21  141: 			if headers.to.is_empty() && !self.relay {
f5ed284f8c 2025-06-21  142: 				bail!("Relaying is disabled, and there's no destination address");
f5ed284f8c 2025-06-21  143: 			}
f5ed284f8c 2025-06-21  144: 			for item in &headers.to {
f5ed284f8c 2025-06-21  145: 				rcpt.insert(self.get_id(item)?);
f5ed284f8c 2025-06-21  146: 			};
f5ed284f8c 2025-06-21  147: 			if rcpt.is_empty() {
f5ed284f8c 2025-06-21  148: 				self.tg.debug("No recipient or envelope address.").await?;
f5ed284f8c 2025-06-21  149: 				rcpt.insert(&self.tg.default);
f5ed284f8c 2025-06-21  150: 			};
f5ed284f8c 2025-06-21  151: 
f5ed284f8c 2025-06-21  152: 			// prepating message header
f5ed284f8c 2025-06-21  153: 			let mut reply: Vec<String> = vec![];
f5ed284f8c 2025-06-21  154: 			if self.fields.contains("subject") {
f5ed284f8c 2025-06-21  155: 				if let Some(subject) = mail.subject() {
f5ed284f8c 2025-06-21  156: 					reply.push(format!("__*Subject:*__ `{}`", encode(subject)));
f5ed284f8c 2025-06-21  157: 				} else if let Some(thread) = mail.thread_name() {
f5ed284f8c 2025-06-21  158: 					reply.push(format!("__*Thread:*__ `{}`", encode(thread)));
f5ed284f8c 2025-06-21  159: 				}
f5ed284f8c 2025-06-21  160: 			}
f5ed284f8c 2025-06-21  161: 			let mut short_headers: Vec<String> = vec![];
f5ed284f8c 2025-06-21  162: 			// do we need to replace spaces here?
f5ed284f8c 2025-06-21  163: 			if self.fields.contains("from") {
f5ed284f8c 2025-06-21  164: 				short_headers.push(format!("__*From:*__ `{}`", encode(&headers.from)));
f5ed284f8c 2025-06-21  165: 			}
f5ed284f8c 2025-06-21  166: 			if self.fields.contains("date") {
f5ed284f8c 2025-06-21  167: 				if let Some(date) = mail.date() {
f5ed284f8c 2025-06-21  168: 					short_headers.push(format!("__*Date:*__ `{date}`"));
f5ed284f8c 2025-06-21  169: 				}
f5ed284f8c 2025-06-21  170: 			}
f5ed284f8c 2025-06-21  171: 			reply.push(short_headers.join(" "));
f5ed284f8c 2025-06-21  172: 			let header_size = reply.join(" ").len() + 1;
f5ed284f8c 2025-06-21  173: 
f5ed284f8c 2025-06-21  174: 			let html_parts = mail.html_body_count();
f5ed284f8c 2025-06-21  175: 			let text_parts = mail.text_body_count();
f5ed284f8c 2025-06-21  176: 			let attachments = mail.attachment_count();
f5ed284f8c 2025-06-21  177: 			if html_parts != text_parts {
f5ed284f8c 2025-06-21  178: 				self.tg.debug(&format!("Hm, we have {html_parts} HTML parts and {text_parts} text parts.")).await?;
f5ed284f8c 2025-06-21  179: 			}
f5ed284f8c 2025-06-21  180: 			//let mut html_num = 0;
f5ed284f8c 2025-06-21  181: 			let mut text_num = 0;
f5ed284f8c 2025-06-21  182: 			let mut file_num = 0;
f5ed284f8c 2025-06-21  183: 			// let's display first html or text part as body
f5ed284f8c 2025-06-21  184: 			let mut body: Cow<'_, str> = "".into();
f5ed284f8c 2025-06-21  185: 			/*
f5ed284f8c 2025-06-21  186: 			 * actually I don't wanna parse that html stuff
f5ed284f8c 2025-06-21  187: 			if html_parts > 0 {
f5ed284f8c 2025-06-21  188: 				let text = mail.body_html(0).unwrap();
f5ed284f8c 2025-06-21  189: 				if text.len() < 4096 - header_size {
f5ed284f8c 2025-06-21  190: 					body = text;
f5ed284f8c 2025-06-21  191: 					html_num = 1;
f5ed284f8c 2025-06-21  192: 				}
f5ed284f8c 2025-06-21  193: 			};
f5ed284f8c 2025-06-21  194: 			*/
f5ed284f8c 2025-06-21  195: 			if body.is_empty() && text_parts > 0 {
f5ed284f8c 2025-06-21  196: 				let text = mail.body_text(0)
f5ed284f8c 2025-06-21  197: 					.context("Failed to extract text from message")?;
f5ed284f8c 2025-06-21  198: 				if text.len() < 4096 - header_size {
f5ed284f8c 2025-06-21  199: 					body = text;
f5ed284f8c 2025-06-21  200: 					text_num = 1;
f5ed284f8c 2025-06-21  201: 				}
f5ed284f8c 2025-06-21  202: 			};
f5ed284f8c 2025-06-21  203: 			reply.push("```".into());
f5ed284f8c 2025-06-21  204: 			reply.extend(body.lines().map(|x| x.into()));
f5ed284f8c 2025-06-21  205: 			reply.push("```".into());
f5ed284f8c 2025-06-21  206: 
f5ed284f8c 2025-06-21  207: 			// and let's collect all other attachment parts
f5ed284f8c 2025-06-21  208: 			let mut files_to_send = vec![];
f5ed284f8c 2025-06-21  209: 			/*
f5ed284f8c 2025-06-21  210: 			 * let's just skip html parts for now, they just duplicate text?
f5ed284f8c 2025-06-21  211: 			while html_num < html_parts {
f5ed284f8c 2025-06-21  212: 				files_to_send.push(mail.html_part(html_num).unwrap());
f5ed284f8c 2025-06-21  213: 				html_num += 1;
f5ed284f8c 2025-06-21  214: 			}
f5ed284f8c 2025-06-21  215: 			*/
f5ed284f8c 2025-06-21  216: 			while text_num < text_parts {
f5ed284f8c 2025-06-21  217: 				files_to_send.push(mail.text_part(text_num.try_into()?)
f5ed284f8c 2025-06-21  218: 					.context("Failed to get text part from message.")?);
f5ed284f8c 2025-06-21  219: 				text_num += 1;
f5ed284f8c 2025-06-21  220: 			}
f5ed284f8c 2025-06-21  221: 			while file_num < attachments {
f5ed284f8c 2025-06-21  222: 				files_to_send.push(mail.attachment(file_num.try_into()?)
f5ed284f8c 2025-06-21  223: 					.context("Failed to get file part from message.")?);
f5ed284f8c 2025-06-21  224: 				file_num += 1;
f5ed284f8c 2025-06-21  225: 			}
f5ed284f8c 2025-06-21  226: 
f5ed284f8c 2025-06-21  227: 			let msg = reply.join("\n");
f5ed284f8c 2025-06-21  228: 			for chat in rcpt {
f5ed284f8c 2025-06-21  229: 				if !files_to_send.is_empty() {
f5ed284f8c 2025-06-21  230: 					let mut files = vec![];
f5ed284f8c 2025-06-21  231: 					// let mut first_one = true;
f5ed284f8c 2025-06-21  232: 					for chunk in &files_to_send {
f5ed284f8c 2025-06-21  233: 						let data: Vec<u8> = chunk.contents().to_vec();
f5ed284f8c 2025-06-21  234: 						let mut filename: Option<String> = None;
f5ed284f8c 2025-06-21  235: 						for header in chunk.headers() {
f5ed284f8c 2025-06-21  236: 							if header.name() == "Content-Type" {
f5ed284f8c 2025-06-21  237: 								match header.value() {
f5ed284f8c 2025-06-21  238: 									mail_parser::HeaderValue::ContentType(contenttype) => {
f5ed284f8c 2025-06-21  239: 										if let Some(fname) = contenttype.attribute("name") {
f5ed284f8c 2025-06-21  240: 											filename = Some(fname.to_owned());
f5ed284f8c 2025-06-21  241: 										}
f5ed284f8c 2025-06-21  242: 									},
f5ed284f8c 2025-06-21  243: 									_ => {
f5ed284f8c 2025-06-21  244: 										self.tg.debug("Attachment has bad ContentType header.").await?;
f5ed284f8c 2025-06-21  245: 									},
f5ed284f8c 2025-06-21  246: 								};
f5ed284f8c 2025-06-21  247: 							};
f5ed284f8c 2025-06-21  248: 						};
f5ed284f8c 2025-06-21  249: 						let filename = if let Some(fname) = filename {
f5ed284f8c 2025-06-21  250: 							fname
f5ed284f8c 2025-06-21  251: 						} else {
f5ed284f8c 2025-06-21  252: 							"Attachment.txt".into()
f5ed284f8c 2025-06-21  253: 						};
f5ed284f8c 2025-06-21  254: 						files.push(Attachment {
f5ed284f8c 2025-06-21  255: 							data: Cursor::new(data),
f5ed284f8c 2025-06-21  256: 							name: filename,
f5ed284f8c 2025-06-21  257: 						});
f5ed284f8c 2025-06-21  258: 					}
f5ed284f8c 2025-06-21  259: 					self.tg.sendgroup(chat, files, &msg).await?;
f5ed284f8c 2025-06-21  260: 				} else {
f5ed284f8c 2025-06-21  261: 					self.tg.send(chat, &msg).await?;
f5ed284f8c 2025-06-21  262: 				}
f5ed284f8c 2025-06-21  263: 			}
f5ed284f8c 2025-06-21  264: 		} else {
f5ed284f8c 2025-06-21  265: 			bail!("Required headers were not found.");
f5ed284f8c 2025-06-21  266: 		}
f5ed284f8c 2025-06-21  267: 		Ok(())
f5ed284f8c 2025-06-21  268: 	}
f5ed284f8c 2025-06-21  269: }
f5ed284f8c 2025-06-21  270: 
f5ed284f8c 2025-06-21  271: impl mailin_embedded::Handler for MailServer {
f5ed284f8c 2025-06-21  272: 	/// Just deny login auth
f5ed284f8c 2025-06-21  273: 	fn auth_login (&mut self, _username: &str, _password: &str) -> Response {
f5ed284f8c 2025-06-21  274: 		INVALID_CREDENTIALS
f5ed284f8c 2025-06-21  275: 	}
f5ed284f8c 2025-06-21  276: 
f5ed284f8c 2025-06-21  277: 	/// Just deny plain auth
f5ed284f8c 2025-06-21  278: 	fn auth_plain (&mut self, _authorization_id: &str, _authentication_id: &str, _password: &str) -> Response {
f5ed284f8c 2025-06-21  279: 		INVALID_CREDENTIALS
f5ed284f8c 2025-06-21  280: 	}
f5ed284f8c 2025-06-21  281: 
f5ed284f8c 2025-06-21  282: 	/// Verify whether address is deliverable
f5ed284f8c 2025-06-21  283: 	fn rcpt (&mut self, to: &str) -> Response {
f5ed284f8c 2025-06-21  284: 		if self.relay {
f5ed284f8c 2025-06-21  285: 			OK
f5ed284f8c 2025-06-21  286: 		} else {
f5ed284f8c 2025-06-21  287: 			match self.get_id(to) {
f5ed284f8c 2025-06-21  288: 				Ok(_) => OK,
f5ed284f8c 2025-06-21  289: 				Err(_) => {
f5ed284f8c 2025-06-21  290: 					if self.relay {
f5ed284f8c 2025-06-21  291: 						OK
f5ed284f8c 2025-06-21  292: 					} else {
f5ed284f8c 2025-06-21  293: 						NO_MAILBOX
f5ed284f8c 2025-06-21  294: 					}
f5ed284f8c 2025-06-21  295: 				}
f5ed284f8c 2025-06-21  296: 			}
f5ed284f8c 2025-06-21  297: 		}
f5ed284f8c 2025-06-21  298: 	}
f5ed284f8c 2025-06-21  299: 
f5ed284f8c 2025-06-21  300: 	/// Save headers we need
f5ed284f8c 2025-06-21  301: 	fn data_start (&mut self, _domain: &str, from: &str, _is8bit: bool, to: &[String]) -> Response {
f5ed284f8c 2025-06-21  302: 		self.headers = Some(SomeHeaders{
f5ed284f8c 2025-06-21  303: 			from: from.to_string(),
f5ed284f8c 2025-06-21  304: 			to: to.to_vec(),
f5ed284f8c 2025-06-21  305: 		});
f5ed284f8c 2025-06-21  306: 		OK
f5ed284f8c 2025-06-21  307: 	}
f5ed284f8c 2025-06-21  308: 
f5ed284f8c 2025-06-21  309: 	/// Save chunk(?) of data
f5ed284f8c 2025-06-21  310: 	fn data (&mut self, buf: &[u8]) -> Result<(), Error> {
f5ed284f8c 2025-06-21  311: 		self.data.append(buf.to_vec().as_mut());
f5ed284f8c 2025-06-21  312: 		Ok(())
f5ed284f8c 2025-06-21  313: 	}
f5ed284f8c 2025-06-21  314: 
f5ed284f8c 2025-06-21  315: 	/// Attempt to send email, return temporary error if that fails
f5ed284f8c 2025-06-21  316: 	fn data_end (&mut self) -> Response {
f5ed284f8c 2025-06-21  317: 		let mut result = OK;
f5ed284f8c 2025-06-21  318: 		task::block_on(async {
f5ed284f8c 2025-06-21  319: 			// relay mail
f5ed284f8c 2025-06-21  320: 			if let Err(err) = self.relay_mail().await {
f5ed284f8c 2025-06-21  321: 				result = INTERNAL_ERROR;
f5ed284f8c 2025-06-21  322: 				// in case that fails - inform default recipient
f5ed284f8c 2025-06-21  323: 				if let Err(err) = self.tg.debug(&format!("Sending emails failed:\n{err:?}")).await {
f5ed284f8c 2025-06-21  324: 					// in case that also fails - write some logs and bail
f5ed284f8c 2025-06-21  325: 					eprintln!("{err:?}");
f5ed284f8c 2025-06-21  326: 				};
f5ed284f8c 2025-06-21  327: 			};
f5ed284f8c 2025-06-21  328: 		});
f5ed284f8c 2025-06-21  329: 		// clear - just in case
f5ed284f8c 2025-06-21  330: 		self.data = vec![];
f5ed284f8c 2025-06-21  331: 		self.headers = None;
f5ed284f8c 2025-06-21  332: 		result
f5ed284f8c 2025-06-21  333: 	}
f5ed284f8c 2025-06-21  334: }