Skip to content

feat(activesync): add EAS 16.0 Find command support#44

Merged
ralflang merged 2 commits into
FRAMEWORK_6_0from
feature/EAS16.0_Find
Jun 10, 2026
Merged

feat(activesync): add EAS 16.0 Find command support#44
ralflang merged 2 commits into
FRAMEWORK_6_0from
feature/EAS16.0_Find

Conversation

@TDannhauer

@TDannhauer TDannhauer commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Add EAS 16.0 Find command support

Summary

Implements the Exchange ActiveSync Find command (protocol version 16.0) so modern clients— notably iOS Mail— can search mailbox content via KQL instead of relying on the legacy Search command.

Find is exposed as a thin protocol layer on top of the existing IMAP search backend used by Search. A companion PR for horde/core wires getFindResults() to IMP; this PR contains all horde/activesync changes.

Motivation

iOS 16+ sends Cmd=Find for unified mailbox search. Horde previously had no Find handler, which resulted in HTTP 400 (Find not supported). This change adds full request parsing, WBXML encoding, IMAP query execution, and the ItemOperations follow-up needed to open search hits on iPhone.

Tested on iPhone (EAS 16.0): global search, subfolder hits, result list, and opening messages from search results.

Changes

Protocol and request handling

  • Add Horde_ActiveSync_Request_Find — parses Find requests (SearchId, ExecuteSearch, MailBoxSearchCriterion, GALSearchCriterion, Query, FreeText, Options, Range, DeepTraversal)
  • Register Find WBXML code page 0x19 with token order per MS-ASWBXML / Z-Push (code page 25)
  • Advertise Find in supported commands for VERSION_SIXTEEN in Horde_ActiveSync.php
  • Encode Find responses (Status, Response, Result, mail properties, Preview, HasAttachments, Range, Total)
  • Cap iPhone Range 0-100 to server maximum (100) instead of returning status 12

Find support types

File Role
lib/Horde/ActiveSync/Find/Params.php Find request DTO
lib/Horde/ActiveSync/Find/Results.php Find results DTO
lib/Horde/ActiveSync/Find/QueryMapper.php Maps Find params → Search params
lib/Horde/ActiveSync/Find/Kql.php KQL → Horde_Imap_Client_Search_Query (incl. OR for iOS patterns)

IMAP search backend

  • Extend Horde_ActiveSync_Imap_Adapter::_doQuery() for Find/Search-shared mailbox queries
  • Add _getSubMailboxes() for DeepTraversal (search folder + subfolders)
  • When no folder is specified, search all account mailboxes (global search)
  • Add resolveLongIdForUid() — locate mailbox:uid when client uses a virtual folder id

ItemOperations (iOS follow-up)

When the user taps a Find result, iOS sends ItemOperations Fetch with a virtual All Mailboxes folder id (M<uid>), which is not in the device folder cache. ItemOperations.php now falls back to UID-based long-id resolution instead of failing with “Folder not found in cache”.

Driver interface

  • Add abstract getFindResults() to Horde_ActiveSync_Driver_Base
  • Stub implementation in Horde_ActiveSync_Driver_Mock

Preview encoding fix

Find result encoding reads body preview from stream resources via _extractPreview() and requests a default preview: 255 bodypref when fetching hit metadata.

Files in this PR

New

  • lib/Horde/ActiveSync/Request/Find.php
  • lib/Horde/ActiveSync/Find/Params.php
  • lib/Horde/ActiveSync/Find/Results.php
  • lib/Horde/ActiveSync/Find/QueryMapper.php
  • lib/Horde/ActiveSync/Find/Kql.php
  • test/Horde/ActiveSync/FindKqlTest.php
  • test/Horde/ActiveSync/FindQueryMapperTest.php

Modified

  • lib/Horde/ActiveSync.php
  • lib/Horde/ActiveSync/Wbxml.php
  • lib/Horde/ActiveSync/Driver/Base.php
  • lib/Horde/ActiveSync/Driver/Mock.php
  • lib/Horde/ActiveSync/Imap/Adapter.php
  • lib/Horde/ActiveSync/Request/Base.php
  • lib/Horde/ActiveSync/Request/ItemOperations.php
  • test/Horde/ActiveSync/ServerTest.php

Architecture

Find request
  → Horde_ActiveSync_Request_Find (parse + encode)
  → driver.getFindResults()          [implemented in horde/core PR]
      → QueryMapper → getSearchResults()
      → Imap Adapter _doQuery() → IMAP SEARCH per mailbox
  → encode Results (properties + Preview)

Tap result on iOS
  → ItemOperations Fetch (virtual M<uid> folder)
  → resolveLongIdForUid() → itemOperationsFetchMailbox(mailbox:uid)

Test plan

  • Run unit tests:
    phpunit test/Horde/ActiveSync/FindKqlTest.php
    phpunit test/Horde/ActiveSync/FindQueryMapperTest.php
    phpunit test/Horde/ActiveSync/ServerTest.php
  • With horde/core Find wiring deployed, test on EAS 16.0 client (iPhone):
    • Global search returns hits from INBOX and subfolders
    • Tapping a result opens the full message (no ItemOperations 500)
    • Search with visible results includes Preview, Range, and Total in device log
  • Verify Find is not advertised for protocol versions below 16.0

Follow-up

Requires a separate horde/core PR adding Horde_Core_ActiveSync_Driver::getFindResults() and resolveLongIdForUid() delegation to the IMAP adapter. Without that PR, Find requests will fail at the driver layer.

Author

Torben Dannhauer torben@dannhauer.de

requires horde/Core#137

Implement the Find command for protocol version 16.0 so clients such as
iOS can search mail via KQL instead of the legacy Search command.
- Add Find request handler, DTOs, and QueryMapper (Find → shared IMAP search)
- Add Kql parser with OR support for iPhone query patterns
- Register Find WBXML code page 0x19 and advertise command for v16.0
- Extend IMAP adapter with deepTraversal, subfolder search, and UID resolution
- Handle iOS virtual All Mailboxes folder IDs in ItemOperations fetch
- Add unit tests for KQL and QueryMapper; update ServerTest expectations
@TDannhauer TDannhauer requested a review from ralflang June 9, 2026 22:19
Check connection_aborted() during IMAP mailbox search and result
encoding so cancelled Find requests stop scanning folders and
fetching message previews instead of running to completion.
@ralflang ralflang merged commit d97b96f into FRAMEWORK_6_0 Jun 10, 2026
0 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants