This commit is contained in:
oneao committed 2025-09-08 17:30:19 +08:00
1 parent 5136bb90bd
commit 603f938e96
17 files changed
+334 -654

No files matched your search

-38
View File
@@ -1,38 +0,0 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
-10
View File
@@ -1,10 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Environment-dependent path to Maven home directory
/mavenHomeManager.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>
-14
View File
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
</component>
</project>
-25
View File
@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.email</groupId>
<artifactId>Email-Netty</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.106.Final</version>
</dependency>
</dependencies>
</project>
@@ -1,144 +0,0 @@
package com.email;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ImapClientHandler extends SimpleChannelInboundHandler<String> {
private final String username;
private final String password;
private enum State { LOGIN, SELECT, FETCH, DONE }
private State state = State.LOGIN;
private int tagCounter = 1;
private StringBuilder headerBuffer = new StringBuilder();
private StringBuilder bodyBuffer = new StringBuilder();
private boolean inHeader = true;
private String boundary = null;
private boolean inAttachment = false;
private String currentFilename = null;
public ImapClientHandler(String username, String password) {
this.username = username;
this.password = password;
}
private String nextTag() {
return "a" + (tagCounter++);
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("Connected to IMAP server...");
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String line) {
line = line.trim();
if (line.isEmpty()) return;
System.out.println("S: " + line);
switch (state) {
case LOGIN:
if (line.startsWith("* OK")) {
ctx.writeAndFlush(nextTag() + " LOGIN " + username + " " + password + "\r\n");
} else if (line.contains("OK LOGIN")) {
state = State.SELECT;
ctx.writeAndFlush(nextTag() + " SELECT INBOX\r\n");
}
break;
case SELECT:
if (line.contains("OK [READ-WRITE]") || line.contains("OK [READ-ONLY]")) {
state = State.FETCH;
ctx.writeAndFlush(nextTag() + " FETCH 1:* (BODY.PEEK[])\r\n");
}
break;
case FETCH:
if (line.contains("OK FETCH completed")) {
state = State.DONE;
System.out.println("All emails fetched!");
ctx.close();
} else if (line.startsWith("*") && line.contains("FETCH")) {
// 新邮件开始
headerBuffer.setLength(0);
bodyBuffer.setLength(0);
inHeader = true;
inAttachment = false;
currentFilename = null;
boundary = null;
} else {
if (inHeader) {
headerBuffer.append(line).append("\r\n");
if (line.isEmpty()) { // header结束
inHeader = false;
// 解析邮件标题
Matcher subjectMatcher = Pattern.compile("(?i)^Subject: (.+)$", Pattern.MULTILINE).matcher(headerBuffer);
if (subjectMatcher.find()) {
String subject = subjectMatcher.group(1).trim();
System.out.println("Email Subject: " + subject);
}
// 解析 boundary
Matcher m = Pattern.compile("boundary=\"([^\"]+)\"").matcher(headerBuffer);
if (m.find()) boundary = m.group(1);
}
} else {
// body部分
if (boundary != null && line.contains(boundary)) {
// attachment结束
if (inAttachment && currentFilename != null) {
saveAttachment(bodyBuffer.toString(), currentFilename);
System.out.println("Attachment saved: " + currentFilename);
bodyBuffer.setLength(0);
inAttachment = false;
currentFilename = null;
}
} else {
// 检测附件开始
Matcher m = Pattern.compile("Content-Disposition: attachment; filename=\"([^\"]+)\"").matcher(line);
if (m.find()) {
currentFilename = m.group(1);
inAttachment = true;
bodyBuffer.setLength(0);
} else if (inAttachment) {
bodyBuffer.append(line);
}
}
}
}
break;
case DONE:
break;
}
}
private void saveAttachment(String base64Data, String filename) {
try {
byte[] data = Base64.getDecoder().decode(base64Data);
try (OutputStream os = new FileOutputStream(filename)) {
os.write(data);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
@@ -1,61 +0,0 @@
package com.email;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
public class NettyImapClient {
private final String host;
private final int port;
private final String username;
private final String password;
public NettyImapClient(String host, int port, String username, String password) {
this.host = host;
this.port = port;
this.username = username;
this.password = password;
}
public void start() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
SslContext sslCtx = SslContextBuilder.forClient().build();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
p.addLast(sslCtx.newHandler(ch.alloc(), host, port));
p.addLast(new LineBasedFrameDecoder(8192));
p.addLast(new StringDecoder());
p.addLast(new ImapClientHandler(username, password));
}
});
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
String host = "imap.qq.com"; // IMAP服务器
int port = 993; // SSL端口
String user = "717406575@qq.com";
String pass = "dhqesrwzgblrbcac"; // 授权码
new NettyImapClient(host, port, user, pass).start();
}
}