fix(external_imap): 修复 IMAP 文件夹列表兼容性
- 在测试外部 IMAP 账号时,将文件夹列表失败提示改为更准确的错误信息。 - 为 OAuth XOAUTH2 认证和文件夹列表补充更明确的错误包装,便于定位问题。 - 对不支持 `LIST RETURN (STATUS ...)` 的 Outlook/Exchange 服务降级为普通 `LIST`,再单独查询文件夹状态。
This commit is contained in:
@@ -317,7 +317,7 @@ func (a *App) handleTestExternalIMAPAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
folders, err := client.ListFolders(r.Context())
|
folders, err := client.ListFolders(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.updateExternalIMAPStatus(r.Context(), account.ID, "error", err.Error())
|
a.updateExternalIMAPStatus(r.Context(), account.ID, "error", err.Error())
|
||||||
respondError(w, http.StatusBadRequest, "connection failed: "+err.Error())
|
respondError(w, http.StatusBadRequest, "list folders failed: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.updateExternalIMAPStatus(r.Context(), account.ID, "ok", "")
|
a.updateExternalIMAPStatus(r.Context(), account.ID, "ok", "")
|
||||||
@@ -1499,7 +1499,7 @@ func (a *App) openExternalIMAPClient(ctx context.Context, account externalIMAPAc
|
|||||||
}
|
}
|
||||||
if err := c.Authenticate(newExternalIMAPXOAUTH2Client(account.Username, token)); err != nil {
|
if err := c.Authenticate(newExternalIMAPXOAUTH2Client(account.Username, token)); err != nil {
|
||||||
c.Close()
|
c.Close()
|
||||||
return nil, err
|
return nil, fmt.Errorf("oauth xoauth2 authenticate failed: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
password, err := a.decryptExternalIMAPPassword(account.PasswordCiphertext)
|
password, err := a.decryptExternalIMAPPassword(account.PasswordCiphertext)
|
||||||
@@ -1525,13 +1525,17 @@ func newExternalIMAPXOAUTH2Client(username, token string) externalIMAPXOAUTH2Cli
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c externalIMAPXOAUTH2Client) Start() (string, []byte, error) {
|
func (c externalIMAPXOAUTH2Client) Start() (string, []byte, error) {
|
||||||
return "XOAUTH2", []byte("user=" + c.username + "\x01auth=Bearer " + c.token + "\x01\x01"), nil
|
return "XOAUTH2", externalIMAPXOAUTH2Response(c.username, c.token), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c externalIMAPXOAUTH2Client) Next(challenge []byte) ([]byte, error) {
|
func (c externalIMAPXOAUTH2Client) Next(challenge []byte) ([]byte, error) {
|
||||||
return []byte{}, nil
|
return []byte{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func externalIMAPXOAUTH2Response(username, token string) []byte {
|
||||||
|
return []byte("user=" + username + "\x01auth=Bearer " + token + "\x01\x01")
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) externalIMAPOAuthAccessToken(ctx context.Context, account externalIMAPAccountRecord) (string, error) {
|
func (a *App) externalIMAPOAuthAccessToken(ctx context.Context, account externalIMAPAccountRecord) (string, error) {
|
||||||
access, err := a.decryptExternalIMAPPassword(account.OAuthAccessTokenCiphertext)
|
access, err := a.decryptExternalIMAPPassword(account.OAuthAccessTokenCiphertext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1590,7 +1594,20 @@ func (c *goExternalIMAPClient) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *goExternalIMAPClient) ListFolders(ctx context.Context) ([]externalIMAPRemoteFolder, error) {
|
func (c *goExternalIMAPClient) ListFolders(ctx context.Context) ([]externalIMAPRemoteFolder, error) {
|
||||||
list, err := c.client.List("", "*", &imap.ListOptions{ReturnStatus: &imap.StatusOptions{NumMessages: true, NumUnseen: true}}).Collect()
|
caps := c.client.Caps()
|
||||||
|
var options *imap.ListOptions
|
||||||
|
if caps.Has(imap.CapIMAP4rev2) || caps.Has(imap.CapListStatus) {
|
||||||
|
options = &imap.ListOptions{ReturnStatus: &imap.StatusOptions{NumMessages: true, NumUnseen: true}}
|
||||||
|
}
|
||||||
|
list, err := c.client.List("", "*", options).Collect()
|
||||||
|
if err != nil && options != nil {
|
||||||
|
// Some Outlook/Exchange IMAP deployments advertise extended LIST
|
||||||
|
// capabilities but reject LIST RETURN (STATUS ...) with
|
||||||
|
// "BAD Command Argument Error. 12". Fall back to a plain LIST and
|
||||||
|
// fetch counts with STATUS separately.
|
||||||
|
options = nil
|
||||||
|
list, err = c.client.List("", "*", nil).Collect()
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1613,6 +1630,21 @@ func (c *goExternalIMAPClient) ListFolders(ctx context.Context) ([]externalIMAPR
|
|||||||
}
|
}
|
||||||
folders = append(folders, f)
|
folders = append(folders, f)
|
||||||
}
|
}
|
||||||
|
if options == nil {
|
||||||
|
statusOptions := &imap.StatusOptions{NumMessages: true, NumUnseen: true}
|
||||||
|
for i := range folders {
|
||||||
|
status, err := c.client.Status(folders[i].Name, statusOptions).Wait()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if status.NumMessages != nil {
|
||||||
|
folders[i].TotalCount = int(*status.NumMessages)
|
||||||
|
}
|
||||||
|
if status.NumUnseen != nil {
|
||||||
|
folders[i].UnreadCount = int(*status.NumUnseen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if len(folders) == 0 {
|
if len(folders) == 0 {
|
||||||
folders = append(folders, externalIMAPRemoteFolder{Name: "INBOX", Role: "Inbox"})
|
folders = append(folders, externalIMAPRemoteFolder{Name: "INBOX", Role: "Inbox"})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user