Fix MS Exchange EWS: add support for version negotiation and other enhancements (closes #6)
This commit is contained in:
@@ -94,7 +94,7 @@ impl DistinguishedFolderId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum ServerVersion {
|
pub enum ServerVersion {
|
||||||
Exchange2007,
|
Exchange2007,
|
||||||
Exchange2010,
|
Exchange2010,
|
||||||
|
|||||||
+28
-6
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::exchange_ews::soap::write_escaped;
|
use crate::exchange_ews::soap::write_escaped;
|
||||||
use crate::exchange_ews::types::{DistinguishedFolderId, FolderId, ItemId};
|
use crate::exchange_ews::types::{DistinguishedFolderId, FolderId, ItemId, ServerVersion};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum FolderRef<'a> {
|
pub enum FolderRef<'a> {
|
||||||
@@ -124,7 +124,7 @@ pub enum ItemShape {
|
|||||||
Contact,
|
Contact,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_item_body(shape: ItemShape, ids: &[ItemId]) -> String {
|
pub fn get_item_body(shape: ItemShape, ids: &[ItemId], version: ServerVersion) -> String {
|
||||||
let mut out = String::with_capacity(256 + ids.len() * 128);
|
let mut out = String::with_capacity(256 + ids.len() * 128);
|
||||||
out.push_str("<m:GetItem><m:ItemShape>");
|
out.push_str("<m:GetItem><m:ItemShape>");
|
||||||
match shape {
|
match shape {
|
||||||
@@ -134,11 +134,13 @@ pub fn get_item_body(shape: ItemShape, ids: &[ItemId]) -> String {
|
|||||||
out.push_str("<t:BodyType>Best</t:BodyType>");
|
out.push_str("<t:BodyType>Best</t:BodyType>");
|
||||||
out.push_str("<t:AdditionalProperties>");
|
out.push_str("<t:AdditionalProperties>");
|
||||||
out.push_str("<t:FieldURI FieldURI=\"item:DateTimeReceived\"/>");
|
out.push_str("<t:FieldURI FieldURI=\"item:DateTimeReceived\"/>");
|
||||||
out.push_str("<t:FieldURI FieldURI=\"item:IsRead\"/>");
|
out.push_str("<t:FieldURI FieldURI=\"message:IsRead\"/>");
|
||||||
out.push_str("<t:FieldURI FieldURI=\"item:IsDraft\"/>");
|
out.push_str("<t:FieldURI FieldURI=\"item:IsDraft\"/>");
|
||||||
out.push_str("<t:FieldURI FieldURI=\"item:Categories\"/>");
|
out.push_str("<t:FieldURI FieldURI=\"item:Categories\"/>");
|
||||||
out.push_str("<t:FieldURI FieldURI=\"item:ParentFolderId\"/>");
|
out.push_str("<t:FieldURI FieldURI=\"item:ParentFolderId\"/>");
|
||||||
|
if version >= ServerVersion::Exchange2013 {
|
||||||
out.push_str("<t:FieldURI FieldURI=\"item:Flag\"/>");
|
out.push_str("<t:FieldURI FieldURI=\"item:Flag\"/>");
|
||||||
|
}
|
||||||
out.push_str("<t:FieldURI FieldURI=\"message:IsReadReceiptRequested\"/>");
|
out.push_str("<t:FieldURI FieldURI=\"message:IsReadReceiptRequested\"/>");
|
||||||
out.push_str("</t:AdditionalProperties>");
|
out.push_str("</t:AdditionalProperties>");
|
||||||
}
|
}
|
||||||
@@ -302,15 +304,34 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn get_item_message_shape_requests_mime() {
|
fn get_item_message_shape_requests_mime() {
|
||||||
let ids = vec![ItemId::new("I1", "CK1"), ItemId::new("I2", "")];
|
let ids = vec![ItemId::new("I1", "CK1"), ItemId::new("I2", "")];
|
||||||
let body = get_item_body(ItemShape::Message, &ids);
|
let body = get_item_body(ItemShape::Message, &ids, ServerVersion::Exchange2013Sp1);
|
||||||
assert!(body.contains("<t:IncludeMimeContent>true</t:IncludeMimeContent>"));
|
assert!(body.contains("<t:IncludeMimeContent>true</t:IncludeMimeContent>"));
|
||||||
assert!(body.contains("<t:ItemId Id=\"I1\" ChangeKey=\"CK1\"/>"));
|
assert!(body.contains("<t:ItemId Id=\"I1\" ChangeKey=\"CK1\"/>"));
|
||||||
assert!(body.contains("<t:ItemId Id=\"I2\"/>"));
|
assert!(body.contains("<t:ItemId Id=\"I2\"/>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn item_flag_requested_only_on_exchange_2013_and_later() {
|
||||||
|
let ids = [ItemId::new("I1", "")];
|
||||||
|
let modern = get_item_body(ItemShape::Message, &ids, ServerVersion::Exchange2013Sp1);
|
||||||
|
assert!(modern.contains("item:Flag"));
|
||||||
|
let legacy = get_item_body(ItemShape::Message, &ids, ServerVersion::Exchange2010Sp2);
|
||||||
|
assert!(
|
||||||
|
!legacy.contains("item:Flag"),
|
||||||
|
"item:Flag is an Exchange 2013 schema addition; must be omitted on 2010"
|
||||||
|
);
|
||||||
|
assert!(legacy.contains("item:DateTimeReceived"));
|
||||||
|
assert!(legacy.contains("message:IsReadReceiptRequested"));
|
||||||
|
assert!(
|
||||||
|
modern.contains("\"message:IsRead\"") && !modern.contains("\"item:IsRead\""),
|
||||||
|
"IsRead is a MessageType property; its FieldURI is message:IsRead"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_item_calendar_shape_lists_calendar_fields() {
|
fn get_item_calendar_shape_lists_calendar_fields() {
|
||||||
let body = get_item_body(ItemShape::CalendarItem, &[ItemId::new("X", "")]);
|
let body =
|
||||||
|
get_item_body(ItemShape::CalendarItem, &[ItemId::new("X", "")], ServerVersion::Exchange2010Sp2);
|
||||||
assert!(body.contains("calendar:Recurrence"));
|
assert!(body.contains("calendar:Recurrence"));
|
||||||
assert!(body.contains("calendar:ModifiedOccurrences"));
|
assert!(body.contains("calendar:ModifiedOccurrences"));
|
||||||
assert!(body.contains("calendar:DeletedOccurrences"));
|
assert!(body.contains("calendar:DeletedOccurrences"));
|
||||||
@@ -320,7 +341,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_item_contact_shape_requests_all_properties() {
|
fn get_item_contact_shape_requests_all_properties() {
|
||||||
let body = get_item_body(ItemShape::Contact, &[ItemId::new("X", "")]);
|
let body =
|
||||||
|
get_item_body(ItemShape::Contact, &[ItemId::new("X", "")], ServerVersion::Exchange2013Sp1);
|
||||||
assert!(body.contains("<t:BaseShape>AllProperties</t:BaseShape>"));
|
assert!(body.contains("<t:BaseShape>AllProperties</t:BaseShape>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -286,11 +286,12 @@ pub fn get_items(
|
|||||||
) -> Result<GetItemBatchOutcome, EwsError> {
|
) -> Result<GetItemBatchOutcome, EwsError> {
|
||||||
let batch = ctx.batch_size.max(1);
|
let batch = ctx.batch_size.max(1);
|
||||||
let workers = ctx.connections.clamp(1, 8);
|
let workers = ctx.connections.clamp(1, 8);
|
||||||
|
let version = ctx.client.server_version();
|
||||||
let mut failed_items: u64 = 0;
|
let mut failed_items: u64 = 0;
|
||||||
if workers <= 1 || ids.len() <= batch {
|
if workers <= 1 || ids.len() <= batch {
|
||||||
let mut all = Vec::new();
|
let mut all = Vec::new();
|
||||||
for chunk in ids.chunks(batch) {
|
for chunk in ids.chunks(batch) {
|
||||||
let body = get_item_body(shape, chunk);
|
let body = get_item_body(shape, chunk, version);
|
||||||
match ctx.client.call(ctx.url, "GetItem", &body) {
|
match ctx.client.call(ctx.url, "GetItem", &body) {
|
||||||
Ok(resp) => match parse_response_messages(&resp.body, b"GetItemResponseMessage") {
|
Ok(resp) => match parse_response_messages(&resp.body, b"GetItemResponseMessage") {
|
||||||
Ok(mut msgs) => all.append(&mut msgs),
|
Ok(mut msgs) => all.append(&mut msgs),
|
||||||
@@ -326,7 +327,7 @@ pub fn get_items(
|
|||||||
);
|
);
|
||||||
let pool: crate::sync::import_jmap::pool::Pool<Vec<ItemId>, BatchResult> =
|
let pool: crate::sync::import_jmap::pool::Pool<Vec<ItemId>, BatchResult> =
|
||||||
crate::sync::import_jmap::pool::Pool::new(workers, move |chunk: Vec<ItemId>| {
|
crate::sync::import_jmap::pool::Pool::new(workers, move |chunk: Vec<ItemId>| {
|
||||||
let body = get_item_body(shape, &chunk);
|
let body = get_item_body(shape, &chunk, version);
|
||||||
let n = chunk.len();
|
let n = chunk.len();
|
||||||
let result = match client.call(&url, "GetItem", &body) {
|
let result = match client.call(&url, "GetItem", &body) {
|
||||||
Ok(resp) => parse_response_messages(&resp.body, b"GetItemResponseMessage"),
|
Ok(resp) => parse_response_messages(&resp.body, b"GetItemResponseMessage"),
|
||||||
|
|||||||
@@ -458,7 +458,7 @@ fn get_item_batches_chunk_the_id_list() {
|
|||||||
let chunks: Vec<&[ItemId]> = ids.chunks(3).collect();
|
let chunks: Vec<&[ItemId]> = ids.chunks(3).collect();
|
||||||
assert_eq!(chunks.len(), 3);
|
assert_eq!(chunks.len(), 3);
|
||||||
for chunk in &chunks {
|
for chunk in &chunks {
|
||||||
let body = get_item_body(ItemShape::Message, chunk);
|
let body = get_item_body(ItemShape::Message, chunk, ServerVersion::Exchange2013Sp1);
|
||||||
assert!(body.contains("<m:GetItem>"));
|
assert!(body.contains("<m:GetItem>"));
|
||||||
for id in *chunk {
|
for id in *chunk {
|
||||||
assert!(body.contains(&format!("Id=\"{}\"", id.id)));
|
assert!(body.contains(&format!("Id=\"{}\"", id.id)));
|
||||||
|
|||||||
Reference in New Issue
Block a user