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())
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
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 {
|
||||
c.Close()
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("oauth xoauth2 authenticate failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
password, err := a.decryptExternalIMAPPassword(account.PasswordCiphertext)
|
||||
@@ -1525,13 +1525,17 @@ func newExternalIMAPXOAUTH2Client(username, token string) externalIMAPXOAUTH2Cli
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
access, err := a.decryptExternalIMAPPassword(account.OAuthAccessTokenCiphertext)
|
||||
if err != nil {
|
||||
@@ -1590,7 +1594,20 @@ func (c *goExternalIMAPClient) Close() 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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1613,6 +1630,21 @@ func (c *goExternalIMAPClient) ListFolders(ctx context.Context) ([]externalIMAPR
|
||||
}
|
||||
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 {
|
||||
folders = append(folders, externalIMAPRemoteFolder{Name: "INBOX", Role: "Inbox"})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user