first commit
This commit is contained in:
commit
19dfc51360
85 files changed
+25504
No files matched your search
@@ -0,0 +1,763 @@
|
||||
// Generated from Z-Push wbxmldefs.php with MS-ASWBXML standard tag names.
|
||||
pub const NAMESPACES: &[&str] = &[
|
||||
"AirSync",
|
||||
"Contacts",
|
||||
"Email",
|
||||
"AirNotify",
|
||||
"Calendar",
|
||||
"Move",
|
||||
"GetItemEstimate",
|
||||
"FolderHierarchy",
|
||||
"MeetingResponse",
|
||||
"Tasks",
|
||||
"ResolveRecipients",
|
||||
"ValidateCert",
|
||||
"Contacts2",
|
||||
"Ping",
|
||||
"Provision",
|
||||
"Search",
|
||||
"GAL",
|
||||
"AirSyncBase",
|
||||
"Settings",
|
||||
"DocumentLibrary",
|
||||
"ItemOperations",
|
||||
"ComposeMail",
|
||||
"Email2",
|
||||
"Notes",
|
||||
"RightsManagement",
|
||||
"Find",
|
||||
];
|
||||
|
||||
pub const PAGES: &[&[(u8, &str)]] = &[
|
||||
// 0 AirSync
|
||||
&[
|
||||
(0x05, "Sync"),
|
||||
(0x06, "Responses"),
|
||||
(0x07, "Add"),
|
||||
(0x08, "Change"),
|
||||
(0x09, "Delete"),
|
||||
(0x0A, "Fetch"),
|
||||
(0x0B, "SyncKey"),
|
||||
(0x0C, "ClientId"),
|
||||
(0x0D, "ServerId"),
|
||||
(0x0E, "Status"),
|
||||
(0x0F, "Collection"),
|
||||
(0x10, "Class"),
|
||||
(0x11, "Version"),
|
||||
(0x12, "CollectionId"),
|
||||
(0x13, "GetChanges"),
|
||||
(0x14, "MoreAvailable"),
|
||||
(0x15, "WindowSize"),
|
||||
(0x16, "Commands"),
|
||||
(0x17, "Options"),
|
||||
(0x18, "FilterType"),
|
||||
(0x19, "Truncation"),
|
||||
(0x1A, "RTFTruncation"),
|
||||
(0x1B, "Conflict"),
|
||||
(0x1C, "Collections"),
|
||||
(0x1D, "ApplicationData"),
|
||||
(0x1E, "DeletesAsMoves"),
|
||||
(0x1F, "NotifyGUID"),
|
||||
(0x20, "Supported"),
|
||||
(0x21, "SoftDelete"),
|
||||
(0x22, "MIMESupport"),
|
||||
(0x23, "MIMETruncation"),
|
||||
(0x24, "Wait"),
|
||||
(0x25, "Limit"),
|
||||
(0x26, "Partial"),
|
||||
(0x27, "ConversationMode"),
|
||||
(0x28, "MaxItems"),
|
||||
(0x29, "HeartbeatInterval"),
|
||||
],
|
||||
// 1 Contacts
|
||||
&[
|
||||
(0x05, "Anniversary"),
|
||||
(0x06, "AssistantName"),
|
||||
(0x07, "AssistantPhoneNumber"),
|
||||
(0x08, "Birthday"),
|
||||
(0x09, "Body"),
|
||||
(0x0A, "BodySize"),
|
||||
(0x0B, "BodyTruncated"),
|
||||
(0x0C, "Business2PhoneNumber"),
|
||||
(0x0D, "BusinessCity"),
|
||||
(0x0E, "BusinessCountry"),
|
||||
(0x0F, "BusinessPostalCode"),
|
||||
(0x10, "BusinessState"),
|
||||
(0x11, "BusinessStreet"),
|
||||
(0x12, "BusinessFaxNumber"),
|
||||
(0x13, "BusinessPhoneNumber"),
|
||||
(0x14, "CarPhoneNumber"),
|
||||
(0x15, "Categories"),
|
||||
(0x16, "Category"),
|
||||
(0x17, "Children"),
|
||||
(0x18, "Child"),
|
||||
(0x19, "CompanyName"),
|
||||
(0x1A, "Department"),
|
||||
(0x1B, "Email1Address"),
|
||||
(0x1C, "Email2Address"),
|
||||
(0x1D, "Email3Address"),
|
||||
(0x1E, "FileAs"),
|
||||
(0x1F, "FirstName"),
|
||||
(0x20, "Home2PhoneNumber"),
|
||||
(0x21, "HomeCity"),
|
||||
(0x22, "HomeCountry"),
|
||||
(0x23, "HomePostalCode"),
|
||||
(0x24, "HomeState"),
|
||||
(0x25, "HomeStreet"),
|
||||
(0x26, "HomeFaxNumber"),
|
||||
(0x27, "HomePhoneNumber"),
|
||||
(0x28, "JobTitle"),
|
||||
(0x29, "LastName"),
|
||||
(0x2A, "MiddleName"),
|
||||
(0x2B, "MobilePhoneNumber"),
|
||||
(0x2C, "OfficeLocation"),
|
||||
(0x2D, "OtherCity"),
|
||||
(0x2E, "OtherCountry"),
|
||||
(0x2F, "OtherPostalCode"),
|
||||
(0x30, "OtherState"),
|
||||
(0x31, "OtherStreet"),
|
||||
(0x32, "PagerNumber"),
|
||||
(0x33, "RadioPhoneNumber"),
|
||||
(0x34, "Spouse"),
|
||||
(0x35, "Suffix"),
|
||||
(0x36, "Title"),
|
||||
(0x37, "WebPage"),
|
||||
(0x38, "YomiCompanyName"),
|
||||
(0x39, "YomiFirstName"),
|
||||
(0x3A, "YomiLastName"),
|
||||
(0x3B, "Rtf"),
|
||||
(0x3C, "Picture"),
|
||||
(0x3D, "Alias"),
|
||||
(0x3E, "WeightedRank"),
|
||||
],
|
||||
// 2 Email
|
||||
&[
|
||||
(0x05, "Attachment"),
|
||||
(0x06, "Attachments"),
|
||||
(0x07, "AttName"),
|
||||
(0x08, "AttSize"),
|
||||
(0x09, "AttOid"),
|
||||
(0x0A, "AttMethod"),
|
||||
(0x0B, "AttRemoved"),
|
||||
(0x0C, "Body"),
|
||||
(0x0D, "BodySize"),
|
||||
(0x0E, "BodyTruncated"),
|
||||
(0x0F, "DateReceived"),
|
||||
(0x10, "DisplayName"),
|
||||
(0x11, "DisplayTo"),
|
||||
(0x12, "Importance"),
|
||||
(0x13, "MessageClass"),
|
||||
(0x14, "Subject"),
|
||||
(0x15, "Read"),
|
||||
(0x16, "To"),
|
||||
(0x17, "Cc"),
|
||||
(0x18, "From"),
|
||||
(0x19, "ReplyTo"),
|
||||
(0x1A, "AllDayEvent"),
|
||||
(0x1B, "Categories"),
|
||||
(0x1C, "Category"),
|
||||
(0x1D, "DtStamp"),
|
||||
(0x1E, "EndTime"),
|
||||
(0x1F, "InstanceType"),
|
||||
(0x20, "BusyStatus"),
|
||||
(0x21, "Location"),
|
||||
(0x22, "MeetingRequest"),
|
||||
(0x23, "Organizer"),
|
||||
(0x24, "RecurrenceId"),
|
||||
(0x25, "Reminder"),
|
||||
(0x26, "ResponseRequested"),
|
||||
(0x27, "Recurrences"),
|
||||
(0x28, "Recurrence"),
|
||||
(0x29, "Type"),
|
||||
(0x2A, "Until"),
|
||||
(0x2B, "Occurrences"),
|
||||
(0x2C, "Interval"),
|
||||
(0x2D, "DayOfWeek"),
|
||||
(0x2E, "DayOfMonth"),
|
||||
(0x2F, "WeekOfMonth"),
|
||||
(0x30, "MonthOfYear"),
|
||||
(0x31, "StartTime"),
|
||||
(0x32, "Sensitivity"),
|
||||
(0x33, "TimeZone"),
|
||||
(0x34, "GlobalObjId"),
|
||||
(0x35, "ThreadTopic"),
|
||||
(0x36, "MIMEData"),
|
||||
(0x37, "MIMETruncated"),
|
||||
(0x38, "MIMESize"),
|
||||
(0x39, "InternetCPID"),
|
||||
(0x3A, "Flag"),
|
||||
(0x3B, "FlagStatus"),
|
||||
(0x3C, "ContentClass"),
|
||||
(0x3D, "FlagType"),
|
||||
(0x3E, "CompleteTime"),
|
||||
(0x3F, "DisallowNewTimeProposal"),
|
||||
],
|
||||
// 3 AirNotify
|
||||
&[
|
||||
(0x05, "Notify"),
|
||||
(0x06, "Notification"),
|
||||
(0x07, "Version"),
|
||||
(0x08, "Lifetime"),
|
||||
(0x09, "DeviceInfo"),
|
||||
(0x0A, "Enable"),
|
||||
(0x0B, "Folder"),
|
||||
(0x0C, "ServerEntryId"),
|
||||
(0x0D, "DeviceAddress"),
|
||||
(0x0E, "ValidCarrierProfiles"),
|
||||
(0x0F, "CarrierProfile"),
|
||||
(0x10, "Status"),
|
||||
(0x11, "Replies"),
|
||||
(0x12, "Devices"),
|
||||
(0x13, "Device"),
|
||||
(0x14, "Id"),
|
||||
(0x15, "Expiry"),
|
||||
(0x16, "NotifyGUID"),
|
||||
],
|
||||
// 4 Calendar
|
||||
&[
|
||||
(0x05, "TimeZone"),
|
||||
(0x06, "AllDayEvent"),
|
||||
(0x07, "Attendees"),
|
||||
(0x08, "Attendee"),
|
||||
(0x09, "Email"),
|
||||
(0x0A, "Name"),
|
||||
(0x0B, "Body"),
|
||||
(0x0C, "BodyTruncated"),
|
||||
(0x0D, "BusyStatus"),
|
||||
(0x0E, "Categories"),
|
||||
(0x0F, "Category"),
|
||||
(0x10, "Rtf"),
|
||||
(0x11, "DtStamp"),
|
||||
(0x12, "EndTime"),
|
||||
(0x13, "Exception"),
|
||||
(0x14, "Exceptions"),
|
||||
(0x15, "Deleted"),
|
||||
(0x16, "ExceptionStartTime"),
|
||||
(0x17, "Location"),
|
||||
(0x18, "MeetingStatus"),
|
||||
(0x19, "OrganizerEmail"),
|
||||
(0x1A, "OrganizerName"),
|
||||
(0x1B, "Recurrence"),
|
||||
(0x1C, "Type"),
|
||||
(0x1D, "Until"),
|
||||
(0x1E, "Occurrences"),
|
||||
(0x1F, "Interval"),
|
||||
(0x20, "DayOfWeek"),
|
||||
(0x21, "DayOfMonth"),
|
||||
(0x22, "WeekOfMonth"),
|
||||
(0x23, "MonthOfYear"),
|
||||
(0x24, "Reminder"),
|
||||
(0x25, "Sensitivity"),
|
||||
(0x26, "Subject"),
|
||||
(0x27, "StartTime"),
|
||||
(0x28, "UID"),
|
||||
(0x29, "AttendeeStatus"),
|
||||
(0x2A, "AttendeeType"),
|
||||
(0x2B, "Attachment"),
|
||||
(0x2C, "Attachments"),
|
||||
(0x2D, "AttName"),
|
||||
(0x2E, "AttSize"),
|
||||
(0x2F, "AttOid"),
|
||||
(0x30, "AttMethod"),
|
||||
(0x31, "AttRemoved"),
|
||||
(0x32, "DisplayName"),
|
||||
(0x33, "DisallowNewTimeProposal"),
|
||||
(0x34, "ResponseRequested"),
|
||||
(0x35, "AppointmentReplyTime"),
|
||||
(0x36, "ResponseType"),
|
||||
(0x37, "CalendarType"),
|
||||
(0x38, "IsLeapMonth"),
|
||||
(0x39, "FirstDayOfWeek"),
|
||||
(0x3A, "OnlineMeetingConfLink"),
|
||||
(0x3B, "OnlineMeetingExternalLink"),
|
||||
(0x3C, "ClientUid"),
|
||||
],
|
||||
// 5 Move
|
||||
&[
|
||||
(0x05, "MoveItems"),
|
||||
(0x06, "Move"),
|
||||
(0x07, "SrcMsgId"),
|
||||
(0x08, "SrcFldId"),
|
||||
(0x09, "DstFldId"),
|
||||
(0x0A, "Response"),
|
||||
(0x0B, "Status"),
|
||||
(0x0C, "DstMsgId"),
|
||||
],
|
||||
// 6 GetItemEstimate
|
||||
&[
|
||||
(0x05, "GetItemEstimate"),
|
||||
(0x06, "Version"),
|
||||
(0x07, "Collections"),
|
||||
(0x08, "Collection"),
|
||||
(0x09, "Class"),
|
||||
(0x0A, "CollectionId"),
|
||||
(0x0B, "DateTime"),
|
||||
(0x0C, "Estimate"),
|
||||
(0x0D, "Response"),
|
||||
(0x0E, "Status"),
|
||||
],
|
||||
// 7 FolderHierarchy
|
||||
&[
|
||||
(0x05, "Folders"),
|
||||
(0x06, "Folder"),
|
||||
(0x07, "DisplayName"),
|
||||
(0x08, "ServerId"),
|
||||
(0x09, "ParentId"),
|
||||
(0x0A, "Type"),
|
||||
(0x0B, "Response"),
|
||||
(0x0C, "Status"),
|
||||
(0x0D, "ContentClass"),
|
||||
(0x0E, "Changes"),
|
||||
(0x0F, "Add"),
|
||||
(0x10, "Delete"),
|
||||
(0x11, "Update"),
|
||||
(0x12, "SyncKey"),
|
||||
(0x13, "FolderCreate"),
|
||||
(0x14, "FolderDelete"),
|
||||
(0x15, "FolderUpdate"),
|
||||
(0x16, "FolderSync"),
|
||||
(0x17, "Count"),
|
||||
(0x18, "Version"),
|
||||
],
|
||||
// 8 MeetingResponse
|
||||
&[
|
||||
(0x05, "CalendarId"),
|
||||
(0x06, "CollectionId"),
|
||||
(0x07, "MeetingResponse"),
|
||||
(0x08, "RequestId"),
|
||||
(0x09, "Request"),
|
||||
(0x0A, "Result"),
|
||||
(0x0B, "Status"),
|
||||
(0x0C, "UserResponse"),
|
||||
(0x0D, "Version"),
|
||||
(0x0E, "InstanceId"),
|
||||
(0x10, "ProposedStartTime"),
|
||||
(0x11, "ProposedEndTime"),
|
||||
(0x12, "SendResponse"),
|
||||
],
|
||||
// 9 Tasks
|
||||
&[
|
||||
(0x05, "Body"),
|
||||
(0x06, "BodySize"),
|
||||
(0x07, "BodyTruncated"),
|
||||
(0x08, "Categories"),
|
||||
(0x09, "Category"),
|
||||
(0x0A, "Complete"),
|
||||
(0x0B, "DateCompleted"),
|
||||
(0x0C, "DueDate"),
|
||||
(0x0D, "UtcDueDate"),
|
||||
(0x0E, "Importance"),
|
||||
(0x0F, "Recurrence"),
|
||||
(0x10, "Type"),
|
||||
(0x11, "Start"),
|
||||
(0x12, "Until"),
|
||||
(0x13, "Occurrences"),
|
||||
(0x14, "Interval"),
|
||||
(0x15, "DayOfMonth"),
|
||||
(0x16, "DayOfWeek"),
|
||||
(0x17, "WeekOfMonth"),
|
||||
(0x18, "MonthOfYear"),
|
||||
(0x19, "Regenerate"),
|
||||
(0x1A, "DeadOccur"),
|
||||
(0x1B, "ReminderSet"),
|
||||
(0x1C, "ReminderTime"),
|
||||
(0x1D, "Sensitivity"),
|
||||
(0x1E, "StartDate"),
|
||||
(0x1F, "UtcStartDate"),
|
||||
(0x20, "Subject"),
|
||||
(0x21, "Rtf"),
|
||||
(0x22, "OrdinalDate"),
|
||||
(0x23, "SubOrdinalDate"),
|
||||
(0x24, "CalendarType"),
|
||||
(0x25, "IsLeapMonth"),
|
||||
(0x26, "FirstDayOfWeek"),
|
||||
],
|
||||
// 10 ResolveRecipients
|
||||
&[
|
||||
(0x05, "ResolveRecipients"),
|
||||
(0x06, "Response"),
|
||||
(0x07, "Status"),
|
||||
(0x08, "Type"),
|
||||
(0x09, "Recipient"),
|
||||
(0x0A, "DisplayName"),
|
||||
(0x0B, "EmailAddress"),
|
||||
(0x0C, "Certificates"),
|
||||
(0x0D, "Certificate"),
|
||||
(0x0E, "MiniCertificate"),
|
||||
(0x0F, "Options"),
|
||||
(0x10, "To"),
|
||||
(0x11, "CertificateRetrieval"),
|
||||
(0x12, "RecipientCount"),
|
||||
(0x13, "MaxCertificates"),
|
||||
(0x14, "MaxAmbiguousRecipients"),
|
||||
(0x15, "CertificateCount"),
|
||||
(0x16, "Availability"),
|
||||
(0x17, "StartTime"),
|
||||
(0x18, "EndTime"),
|
||||
(0x19, "MergedFreeBusy"),
|
||||
(0x1A, "Picture"),
|
||||
(0x1B, "MaxSize"),
|
||||
(0x1C, "Data"),
|
||||
(0x1D, "MaxPictures"),
|
||||
],
|
||||
// 11 ValidateCert
|
||||
&[
|
||||
(0x05, "ValidateCert"),
|
||||
(0x06, "Certificates"),
|
||||
(0x07, "Certificate"),
|
||||
(0x08, "CertificateChain"),
|
||||
(0x09, "CheckCRL"),
|
||||
(0x0A, "Status"),
|
||||
],
|
||||
// 12 Contacts2
|
||||
&[
|
||||
(0x05, "CustomerId"),
|
||||
(0x06, "GovernmentId"),
|
||||
(0x07, "IMAddress"),
|
||||
(0x08, "IMAddress2"),
|
||||
(0x09, "IMAddress3"),
|
||||
(0x0A, "ManagerName"),
|
||||
(0x0B, "CompanyMainPhone"),
|
||||
(0x0C, "AccountName"),
|
||||
(0x0D, "NickName"),
|
||||
(0x0E, "MMS"),
|
||||
],
|
||||
// 13 Ping
|
||||
&[
|
||||
(0x05, "Ping"),
|
||||
(0x06, "AutdState"),
|
||||
(0x07, "Status"),
|
||||
(0x08, "HeartbeatInterval"),
|
||||
(0x09, "Folders"),
|
||||
(0x0A, "Folder"),
|
||||
(0x0B, "Id"),
|
||||
(0x0C, "Class"),
|
||||
(0x0D, "MaxFolders"),
|
||||
(0x0E, "Version"),
|
||||
],
|
||||
// 14 Provision
|
||||
&[
|
||||
(0x05, "Provision"),
|
||||
(0x06, "Policies"),
|
||||
(0x07, "Policy"),
|
||||
(0x08, "PolicyType"),
|
||||
(0x09, "PolicyKey"),
|
||||
(0x0A, "Data"),
|
||||
(0x0B, "Status"),
|
||||
(0x0C, "RemoteWipe"),
|
||||
(0x0D, "EASProvisionDoc"),
|
||||
(0x0E, "DevicePasswordEnabled"),
|
||||
(0x0F, "AlphanumericDevicePasswordRequired"),
|
||||
(0x10, "DeviceEncryptionEnabled"),
|
||||
(0x11, "PasswordRecoveryEnabled"),
|
||||
(0x12, "DocumentBrowseEnabled"),
|
||||
(0x13, "AttachmentsEnabled"),
|
||||
(0x14, "MinDevicePasswordLength"),
|
||||
(0x15, "MaxInactivityTimeDeviceLock"),
|
||||
(0x16, "MaxDevicePasswordFailedAttempts"),
|
||||
(0x17, "MaxAttachmentSize"),
|
||||
(0x18, "AllowSimpleDevicePassword"),
|
||||
(0x19, "DevicePasswordExpiration"),
|
||||
(0x1A, "DevicePasswordHistory"),
|
||||
(0x1B, "AllowStorageCard"),
|
||||
(0x1C, "AllowCamera"),
|
||||
(0x1D, "RequireDeviceEncryption"),
|
||||
(0x1E, "AllowUnsignedApplications"),
|
||||
(0x1F, "AllowUnsignedInstallationPackages"),
|
||||
(0x20, "MinDevicePasswordComplexCharacters"),
|
||||
(0x21, "AllowWiFi"),
|
||||
(0x22, "AllowTextMessaging"),
|
||||
(0x23, "AllowPOPIMAPEmail"),
|
||||
(0x24, "AllowBluetooth"),
|
||||
(0x25, "AllowIrDA"),
|
||||
(0x26, "RequireManualSyncWhenRoaming"),
|
||||
(0x27, "AllowDesktopSync"),
|
||||
(0x28, "MaxCalendarAgeFilter"),
|
||||
(0x29, "AllowHTMLEmail"),
|
||||
(0x2A, "MaxEmailAgeFilter"),
|
||||
(0x2B, "MaxEmailBodyTruncationSize"),
|
||||
(0x2C, "MaxEmailHTMLBodyTruncationSize"),
|
||||
(0x2D, "RequireSignedSMIMEMessages"),
|
||||
(0x2E, "RequireEncryptedSMIMEMessages"),
|
||||
(0x2F, "RequireSignedSMIMEAlgorithm"),
|
||||
(0x30, "RequireEncryptionSMIMEAlgorithm"),
|
||||
(0x31, "AllowSMIMEEncryptionAlgorithmNegotiation"),
|
||||
(0x32, "AllowSMIMESoftCerts"),
|
||||
(0x33, "AllowBrowser"),
|
||||
(0x34, "AllowConsumerEmail"),
|
||||
(0x35, "AllowRemoteDesktop"),
|
||||
(0x36, "AllowInternetSharing"),
|
||||
(0x37, "UnapprovedInROMApplicationList"),
|
||||
(0x38, "ApplicationName"),
|
||||
(0x39, "ApprovedApplicationList"),
|
||||
(0x3A, "Hash"),
|
||||
(0x3B, "AccountOnlyRemoteWipe"),
|
||||
],
|
||||
// 15 Search
|
||||
&[
|
||||
(0x05, "Search"),
|
||||
(0x07, "Store"),
|
||||
(0x08, "Name"),
|
||||
(0x09, "Query"),
|
||||
(0x0A, "Options"),
|
||||
(0x0B, "Range"),
|
||||
(0x0C, "Status"),
|
||||
(0x0D, "Response"),
|
||||
(0x0E, "Result"),
|
||||
(0x0F, "Properties"),
|
||||
(0x10, "Total"),
|
||||
(0x11, "EqualTo"),
|
||||
(0x12, "Value"),
|
||||
(0x13, "And"),
|
||||
(0x14, "Or"),
|
||||
(0x15, "FreeText"),
|
||||
(0x17, "DeepTraversal"),
|
||||
(0x18, "LongId"),
|
||||
(0x19, "RebuildResults"),
|
||||
(0x1A, "LessThan"),
|
||||
(0x1B, "GreaterThan"),
|
||||
(0x1C, "Schema"),
|
||||
(0x1D, "Supported"),
|
||||
(0x1E, "UserName"),
|
||||
(0x1F, "Password"),
|
||||
(0x20, "ConversationId"),
|
||||
(0x21, "Picture"),
|
||||
(0x22, "MaxSize"),
|
||||
(0x23, "MaxPictures"),
|
||||
],
|
||||
// 16 GAL
|
||||
&[
|
||||
(0x05, "DisplayName"),
|
||||
(0x06, "Phone"),
|
||||
(0x07, "Office"),
|
||||
(0x08, "Title"),
|
||||
(0x09, "Company"),
|
||||
(0x0A, "Alias"),
|
||||
(0x0B, "FirstName"),
|
||||
(0x0C, "LastName"),
|
||||
(0x0D, "HomePhone"),
|
||||
(0x0E, "MobilePhone"),
|
||||
(0x0F, "EmailAddress"),
|
||||
(0x10, "Picture"),
|
||||
(0x11, "Status"),
|
||||
(0x12, "Data"),
|
||||
],
|
||||
// 17 AirSyncBase
|
||||
&[
|
||||
(0x05, "BodyPreference"),
|
||||
(0x06, "Type"),
|
||||
(0x07, "TruncationSize"),
|
||||
(0x08, "AllOrNone"),
|
||||
(0x0A, "Body"),
|
||||
(0x0B, "Data"),
|
||||
(0x0C, "EstimatedDataSize"),
|
||||
(0x0D, "Truncated"),
|
||||
(0x0E, "Attachments"),
|
||||
(0x0F, "Attachment"),
|
||||
(0x10, "DisplayName"),
|
||||
(0x11, "FileReference"),
|
||||
(0x12, "Method"),
|
||||
(0x13, "ContentId"),
|
||||
(0x14, "ContentLocation"),
|
||||
(0x15, "IsInline"),
|
||||
(0x16, "NativeBodyType"),
|
||||
(0x17, "ContentType"),
|
||||
(0x18, "Preview"),
|
||||
(0x19, "BodyPartPreference"),
|
||||
(0x1A, "BodyPart"),
|
||||
(0x1B, "Status"),
|
||||
(0x1C, "Add"),
|
||||
(0x1D, "Delete"),
|
||||
(0x1E, "ClientId"),
|
||||
(0x1F, "Content"),
|
||||
(0x20, "Location"),
|
||||
(0x21, "Annotation"),
|
||||
(0x22, "Street"),
|
||||
(0x23, "City"),
|
||||
(0x24, "State"),
|
||||
(0x25, "Country"),
|
||||
(0x26, "PostalCode"),
|
||||
(0x27, "Latitude"),
|
||||
(0x28, "Longitude"),
|
||||
(0x29, "Accuracy"),
|
||||
(0x2A, "Altitude"),
|
||||
(0x2B, "AltitudeAccuracy"),
|
||||
(0x2C, "LocationUri"),
|
||||
(0x2D, "InstanceId"),
|
||||
],
|
||||
// 18 Settings
|
||||
&[
|
||||
(0x05, "Settings"),
|
||||
(0x06, "Status"),
|
||||
(0x07, "Get"),
|
||||
(0x08, "Set"),
|
||||
(0x09, "Oof"),
|
||||
(0x0A, "OofState"),
|
||||
(0x0B, "StartTime"),
|
||||
(0x0C, "EndTime"),
|
||||
(0x0D, "OofMessage"),
|
||||
(0x0E, "AppliesToInternal"),
|
||||
(0x0F, "AppliesToExternalKnown"),
|
||||
(0x10, "AppliesToExternalUnknown"),
|
||||
(0x11, "Enabled"),
|
||||
(0x12, "ReplyMessage"),
|
||||
(0x13, "BodyType"),
|
||||
(0x14, "DevicePassword"),
|
||||
(0x15, "Password"),
|
||||
(0x16, "DeviceInformation"),
|
||||
(0x17, "Model"),
|
||||
(0x18, "IMEI"),
|
||||
(0x19, "FriendlyName"),
|
||||
(0x1A, "OS"),
|
||||
(0x1B, "OSLanguage"),
|
||||
(0x1C, "PhoneNumber"),
|
||||
(0x1D, "UserInformation"),
|
||||
(0x1E, "EmailAddresses"),
|
||||
(0x1F, "SmtpAddress"),
|
||||
(0x20, "UserAgent"),
|
||||
(0x21, "EnableOutboundSMS"),
|
||||
(0x22, "MobileOperator"),
|
||||
(0x23, "PrimarySmtpAddress"),
|
||||
(0x24, "Accounts"),
|
||||
(0x25, "Account"),
|
||||
(0x26, "AccountId"),
|
||||
(0x27, "AccountName"),
|
||||
(0x28, "UserDisplayName"),
|
||||
(0x29, "SendDisabled"),
|
||||
(0x2B, "RightsManagementInformation"),
|
||||
],
|
||||
// 19 DocumentLibrary
|
||||
&[
|
||||
(0x05, "LinkId"),
|
||||
(0x06, "DisplayName"),
|
||||
(0x07, "IsFolder"),
|
||||
(0x08, "CreationDate"),
|
||||
(0x09, "LastModifiedDate"),
|
||||
(0x0A, "IsHidden"),
|
||||
(0x0B, "ContentLength"),
|
||||
(0x0C, "ContentType"),
|
||||
],
|
||||
// 20 ItemOperations
|
||||
&[
|
||||
(0x05, "ItemOperations"),
|
||||
(0x06, "Fetch"),
|
||||
(0x07, "Store"),
|
||||
(0x08, "Options"),
|
||||
(0x09, "Range"),
|
||||
(0x0A, "Total"),
|
||||
(0x0B, "Properties"),
|
||||
(0x0C, "Data"),
|
||||
(0x0D, "Status"),
|
||||
(0x0E, "Response"),
|
||||
(0x0F, "Version"),
|
||||
(0x10, "Schema"),
|
||||
(0x11, "Part"),
|
||||
(0x12, "EmptyFolderContents"),
|
||||
(0x13, "DeleteSubFolders"),
|
||||
(0x14, "UserName"),
|
||||
(0x15, "Password"),
|
||||
(0x16, "Move"),
|
||||
(0x17, "DstFldId"),
|
||||
(0x18, "ConversationId"),
|
||||
(0x19, "MoveAlways"),
|
||||
],
|
||||
// 21 ComposeMail
|
||||
&[
|
||||
(0x05, "SendMail"),
|
||||
(0x06, "SmartForward"),
|
||||
(0x07, "SmartReply"),
|
||||
(0x08, "SaveInSentItems"),
|
||||
(0x09, "ReplaceMime"),
|
||||
(0x0A, "Type"),
|
||||
(0x0B, "Source"),
|
||||
(0x0C, "FolderId"),
|
||||
(0x0D, "ItemId"),
|
||||
(0x0E, "LongId"),
|
||||
(0x0F, "InstanceId"),
|
||||
(0x10, "MIME"),
|
||||
(0x11, "ClientId"),
|
||||
(0x12, "Status"),
|
||||
(0x13, "AccountId"),
|
||||
(0x15, "Forwardees"),
|
||||
(0x16, "Forwardee"),
|
||||
(0x17, "Name"),
|
||||
(0x18, "Email"),
|
||||
],
|
||||
// 22 Email2
|
||||
&[
|
||||
(0x05, "UmCallerId"),
|
||||
(0x06, "UmUserNotes"),
|
||||
(0x07, "UmAttDuration"),
|
||||
(0x08, "UmAttOrder"),
|
||||
(0x09, "ConversationId"),
|
||||
(0x0A, "ConversationIndex"),
|
||||
(0x0B, "LastVerbExecuted"),
|
||||
(0x0C, "LastVerbExecutionTime"),
|
||||
(0x0D, "ReceivedAsBcc"),
|
||||
(0x0E, "Sender"),
|
||||
(0x0F, "CalendarType"),
|
||||
(0x10, "IsLeapMonth"),
|
||||
(0x11, "AccountId"),
|
||||
(0x12, "FirstDayOfWeek"),
|
||||
(0x13, "MeetingMessageType"),
|
||||
(0x15, "IsDraft"),
|
||||
(0x16, "Bcc"),
|
||||
(0x17, "Send"),
|
||||
],
|
||||
// 23 Notes
|
||||
&[
|
||||
(0x05, "Subject"),
|
||||
(0x06, "MessageClass"),
|
||||
(0x07, "LastModifiedDate"),
|
||||
(0x08, "Categories"),
|
||||
(0x09, "Category"),
|
||||
],
|
||||
// 24 RightsManagement
|
||||
&[
|
||||
(0x05, "RightsManagementSupport"),
|
||||
(0x06, "RightsManagementTemplates"),
|
||||
(0x07, "RightsManagementTemplate"),
|
||||
(0x08, "RightsManagementLicense"),
|
||||
(0x09, "EditAllowed"),
|
||||
(0x0A, "ReplyAllowed"),
|
||||
(0x0B, "ReplyAllAllowed"),
|
||||
(0x0C, "ForwardAllowed"),
|
||||
(0x0D, "ModifyRecipientsAllowed"),
|
||||
(0x0E, "ExtractAllowed"),
|
||||
(0x0F, "PrintAllowed"),
|
||||
(0x10, "ExportAllowed"),
|
||||
(0x11, "ProgrammaticAccessAllowed"),
|
||||
(0x12, "Owner"),
|
||||
(0x13, "ContentExpiryDate"),
|
||||
(0x14, "TemplateID"),
|
||||
(0x15, "TemplateName"),
|
||||
(0x16, "TemplateDescription"),
|
||||
(0x17, "ContentOwner"),
|
||||
(0x18, "RemoveRightsManagementProtection"),
|
||||
],
|
||||
// 25 Find
|
||||
&[
|
||||
(0x05, "Find"),
|
||||
(0x06, "SearchId"),
|
||||
(0x07, "ExecuteSearch"),
|
||||
(0x08, "MailBoxSearchCriterion"),
|
||||
(0x09, "Query"),
|
||||
(0x0A, "Status"),
|
||||
(0x0B, "FreeText"),
|
||||
(0x0C, "Options"),
|
||||
(0x0D, "Range"),
|
||||
(0x0E, "DeepTraversal"),
|
||||
(0x11, "Response"),
|
||||
(0x12, "Result"),
|
||||
(0x13, "Properties"),
|
||||
(0x14, "Preview"),
|
||||
(0x15, "HasAttachments"),
|
||||
(0x16, "Total"),
|
||||
(0x17, "DisplayCc"),
|
||||
(0x18, "DisplayBcc"),
|
||||
(0x19, "GalSearchCriterion"),
|
||||
(0x20, "MaxPictures"),
|
||||
(0x21, "MaxSize"),
|
||||
(0x22, "Picture"),
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,556 @@
|
||||
//! WBXML (MS-ASWBXML) encoder/decoder with a small element tree.
|
||||
//!
|
||||
//! Elements are addressed by `"Namespace:Tag"` names, e.g. `"AirSync:SyncKey"`.
|
||||
|
||||
mod codepages;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write as _;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
pub use codepages::NAMESPACES;
|
||||
|
||||
const SWITCH_PAGE: u8 = 0x00;
|
||||
const END: u8 = 0x01;
|
||||
const ENTITY: u8 = 0x02;
|
||||
const STR_I: u8 = 0x03;
|
||||
const LITERAL: u8 = 0x04;
|
||||
const STR_T: u8 = 0x83;
|
||||
const OPAQUE: u8 = 0xC3;
|
||||
const WITH_CONTENT: u8 = 0x40;
|
||||
const WITH_ATTRIBUTES: u8 = 0x80;
|
||||
|
||||
/// Hard limits protecting the decoder against hostile input.
|
||||
const MAX_DEPTH: usize = 64;
|
||||
const MAX_ELEMENTS: usize = 500_000;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum WbxmlError {
|
||||
#[error("unexpected end of WBXML stream")]
|
||||
Eof,
|
||||
#[error("invalid WBXML: {0}")]
|
||||
Invalid(&'static str),
|
||||
#[error("unknown WBXML tag {0}")]
|
||||
UnknownName(String),
|
||||
}
|
||||
|
||||
struct Tables {
|
||||
by_name: HashMap<String, (u8, u8)>,
|
||||
}
|
||||
|
||||
fn tables() -> &'static Tables {
|
||||
static T: OnceLock<Tables> = OnceLock::new();
|
||||
T.get_or_init(|| {
|
||||
let mut by_name = HashMap::new();
|
||||
for (page, tags) in codepages::PAGES.iter().enumerate() {
|
||||
let ns = codepages::NAMESPACES[page];
|
||||
for (code, tag) in tags.iter() {
|
||||
by_name.insert(format!("{ns}:{tag}"), (page as u8, *code));
|
||||
}
|
||||
}
|
||||
Tables { by_name }
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolve a `Namespace:Tag` name into its (page, token) pair.
|
||||
pub fn lookup(name: &str) -> Option<(u8, u8)> {
|
||||
tables().by_name.get(name).copied()
|
||||
}
|
||||
|
||||
fn tag_name(page: u8, code: u8) -> (&'static str, &'static str) {
|
||||
let ns = codepages::NAMESPACES
|
||||
.get(page as usize)
|
||||
.copied()
|
||||
.unwrap_or("Unknown");
|
||||
let tag = codepages::PAGES
|
||||
.get(page as usize)
|
||||
.and_then(|t| t.iter().find(|(c, _)| *c == code).map(|(_, n)| *n))
|
||||
.unwrap_or("Unknown");
|
||||
(ns, tag)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Content {
|
||||
None,
|
||||
Text(String),
|
||||
Opaque(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Element {
|
||||
pub page: u8,
|
||||
pub code: u8,
|
||||
pub children: Vec<Element>,
|
||||
pub content: Content,
|
||||
}
|
||||
|
||||
impl Element {
|
||||
/// Create a new element. Panics on unknown names: tag names are compile-time literals
|
||||
/// in this code base and covered by tests.
|
||||
pub fn new(name: &str) -> Self {
|
||||
let (page, code) = lookup(name).unwrap_or_else(|| panic!("unknown WBXML tag name {name}"));
|
||||
Element {
|
||||
page,
|
||||
code,
|
||||
children: Vec::new(),
|
||||
content: Content::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ns(&self) -> &'static str {
|
||||
tag_name(self.page, self.code).0
|
||||
}
|
||||
|
||||
pub fn tag(&self) -> &'static str {
|
||||
tag_name(self.page, self.code).1
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
let (ns, tag) = tag_name(self.page, self.code);
|
||||
format!("{ns}:{tag}")
|
||||
}
|
||||
|
||||
pub fn is(&self, name: &str) -> bool {
|
||||
lookup(name) == Some((self.page, self.code))
|
||||
}
|
||||
|
||||
// ----- builder helpers -----
|
||||
|
||||
pub fn text(name: &str, value: impl Into<String>) -> Self {
|
||||
let mut e = Element::new(name);
|
||||
e.content = Content::Text(value.into());
|
||||
e
|
||||
}
|
||||
|
||||
pub fn opaque(name: &str, value: Vec<u8>) -> Self {
|
||||
let mut e = Element::new(name);
|
||||
e.content = Content::Opaque(value);
|
||||
e
|
||||
}
|
||||
|
||||
pub fn with(mut self, child: Element) -> Self {
|
||||
self.children.push(child);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_text(mut self, name: &str, value: impl Into<String>) -> Self {
|
||||
self.children.push(Element::text(name, value));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_opt(mut self, name: &str, value: Option<impl Into<String>>) -> Self {
|
||||
if let Some(v) = value {
|
||||
self.children.push(Element::text(name, v));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_empty(mut self, name: &str) -> Self {
|
||||
self.children.push(Element::new(name));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn push(&mut self, child: Element) {
|
||||
self.children.push(child);
|
||||
}
|
||||
|
||||
pub fn push_text(&mut self, name: &str, value: impl Into<String>) {
|
||||
self.children.push(Element::text(name, value));
|
||||
}
|
||||
|
||||
pub fn push_opt(&mut self, name: &str, value: Option<impl Into<String>>) {
|
||||
if let Some(v) = value {
|
||||
self.children.push(Element::text(name, v));
|
||||
}
|
||||
}
|
||||
|
||||
// ----- query helpers -----
|
||||
|
||||
pub fn child(&self, name: &str) -> Option<&Element> {
|
||||
let key = lookup(name)?;
|
||||
self.children.iter().find(|c| (c.page, c.code) == key)
|
||||
}
|
||||
|
||||
pub fn children_named<'a>(&'a self, name: &str) -> impl Iterator<Item = &'a Element> + 'a {
|
||||
let key = lookup(name);
|
||||
self.children
|
||||
.iter()
|
||||
.filter(move |c| Some((c.page, c.code)) == key)
|
||||
}
|
||||
|
||||
/// Text of a direct child element.
|
||||
pub fn child_text(&self, name: &str) -> Option<String> {
|
||||
self.child(name).map(|c| c.text_value())
|
||||
}
|
||||
|
||||
pub fn child_u32(&self, name: &str) -> Option<u32> {
|
||||
self.child_text(name).and_then(|t| t.trim().parse().ok())
|
||||
}
|
||||
|
||||
pub fn has(&self, name: &str) -> bool {
|
||||
self.child(name).is_some()
|
||||
}
|
||||
|
||||
/// Follow a path of direct children.
|
||||
pub fn path(&self, names: &[&str]) -> Option<&Element> {
|
||||
let mut cur = self;
|
||||
for n in names {
|
||||
cur = cur.child(n)?;
|
||||
}
|
||||
Some(cur)
|
||||
}
|
||||
|
||||
pub fn text_value(&self) -> String {
|
||||
match &self.content {
|
||||
Content::Text(s) => s.clone(),
|
||||
Content::Opaque(b) => String::from_utf8_lossy(b).into_owned(),
|
||||
Content::None => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes_value(&self) -> Vec<u8> {
|
||||
match &self.content {
|
||||
Content::Text(s) => s.as_bytes().to_vec(),
|
||||
Content::Opaque(b) => b.clone(),
|
||||
Content::None => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Human readable XML rendering for debug logs (data truncated).
|
||||
pub fn to_xml(&self) -> String {
|
||||
let mut out = String::new();
|
||||
self.write_xml(&mut out, 0);
|
||||
out
|
||||
}
|
||||
|
||||
fn write_xml(&self, out: &mut String, depth: usize) {
|
||||
let pad = " ".repeat(depth);
|
||||
let (ns, tag) = tag_name(self.page, self.code);
|
||||
let name = format!("{ns}:{tag}");
|
||||
match (&self.content, self.children.is_empty()) {
|
||||
(Content::None, true) => {
|
||||
let _ = writeln!(out, "{pad}<{name}/>");
|
||||
}
|
||||
(Content::Text(t), _) => {
|
||||
let shown = if tag == "Password" || tag == "Data" && t.len() > 256 {
|
||||
format!("[{} chars]", t.len())
|
||||
} else if t.len() > 256 {
|
||||
format!("{}…", &t[..t.floor_char_boundary_compat(256)])
|
||||
} else {
|
||||
t.clone()
|
||||
};
|
||||
let _ = writeln!(out, "{pad}<{name}>{}</{name}>", xml_escape(&shown));
|
||||
}
|
||||
(Content::Opaque(b), _) => {
|
||||
let _ = writeln!(out, "{pad}<{name}>[opaque {} bytes]</{name}>", b.len());
|
||||
}
|
||||
(Content::None, false) => {
|
||||
let _ = writeln!(out, "{pad}<{name}>");
|
||||
for c in &self.children {
|
||||
c.write_xml(out, depth + 1);
|
||||
}
|
||||
let _ = writeln!(out, "{pad}</{name}>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait FloorBoundary {
|
||||
fn floor_char_boundary_compat(&self, idx: usize) -> usize;
|
||||
}
|
||||
|
||||
impl FloorBoundary for String {
|
||||
fn floor_char_boundary_compat(&self, idx: usize) -> usize {
|
||||
let mut i = idx.min(self.len());
|
||||
while i > 0 && !self.is_char_boundary(i) {
|
||||
i -= 1;
|
||||
}
|
||||
i
|
||||
}
|
||||
}
|
||||
|
||||
fn xml_escape(s: &str) -> String {
|
||||
s.replace('&', "&")
|
||||
.replace('<', "<")
|
||||
.replace('>', ">")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- encoder
|
||||
|
||||
pub fn encode(root: &Element) -> Vec<u8> {
|
||||
let mut out = Vec::with_capacity(512);
|
||||
out.extend_from_slice(&[0x03, 0x01, 0x6A, 0x00]);
|
||||
let mut page = 0u8;
|
||||
encode_element(root, &mut out, &mut page);
|
||||
out
|
||||
}
|
||||
|
||||
fn encode_element(e: &Element, out: &mut Vec<u8>, page: &mut u8) {
|
||||
if e.page != *page {
|
||||
out.push(SWITCH_PAGE);
|
||||
out.push(e.page);
|
||||
*page = e.page;
|
||||
}
|
||||
let has_content = !e.children.is_empty() || !matches!(e.content, Content::None);
|
||||
out.push(if has_content {
|
||||
e.code | WITH_CONTENT
|
||||
} else {
|
||||
e.code
|
||||
});
|
||||
if !has_content {
|
||||
return;
|
||||
}
|
||||
match &e.content {
|
||||
Content::Text(t) => {
|
||||
out.push(STR_I);
|
||||
// NUL bytes would terminate the inline string; strip them.
|
||||
out.extend(t.bytes().filter(|b| *b != 0));
|
||||
out.push(0);
|
||||
}
|
||||
Content::Opaque(b) => {
|
||||
out.push(OPAQUE);
|
||||
write_mb_u32(out, b.len() as u32);
|
||||
out.extend_from_slice(b);
|
||||
}
|
||||
Content::None => {}
|
||||
}
|
||||
for c in &e.children {
|
||||
encode_element(c, out, page);
|
||||
}
|
||||
out.push(END);
|
||||
}
|
||||
|
||||
fn write_mb_u32(out: &mut Vec<u8>, mut v: u32) {
|
||||
let mut buf = [0u8; 5];
|
||||
let mut i = 4;
|
||||
buf[i] = (v & 0x7f) as u8;
|
||||
v >>= 7;
|
||||
while v > 0 {
|
||||
i -= 1;
|
||||
buf[i] = 0x80 | (v & 0x7f) as u8;
|
||||
v >>= 7;
|
||||
}
|
||||
out.extend_from_slice(&buf[i..]);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- decoder
|
||||
|
||||
struct Reader<'a> {
|
||||
data: &'a [u8],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a> Reader<'a> {
|
||||
fn byte(&mut self) -> Result<u8, WbxmlError> {
|
||||
let b = *self.data.get(self.pos).ok_or(WbxmlError::Eof)?;
|
||||
self.pos += 1;
|
||||
Ok(b)
|
||||
}
|
||||
|
||||
fn peek(&self) -> Option<u8> {
|
||||
self.data.get(self.pos).copied()
|
||||
}
|
||||
|
||||
fn mb_u32(&mut self) -> Result<u32, WbxmlError> {
|
||||
let mut v: u32 = 0;
|
||||
for _ in 0..5 {
|
||||
let b = self.byte()?;
|
||||
v = v
|
||||
.checked_shl(7)
|
||||
.ok_or(WbxmlError::Invalid("mb_u_int32 overflow"))?
|
||||
| (b & 0x7f) as u32;
|
||||
if b & 0x80 == 0 {
|
||||
return Ok(v);
|
||||
}
|
||||
}
|
||||
Err(WbxmlError::Invalid("mb_u_int32 too long"))
|
||||
}
|
||||
|
||||
fn cstr(&mut self) -> Result<Vec<u8>, WbxmlError> {
|
||||
let start = self.pos;
|
||||
let rel = self.data[start..]
|
||||
.iter()
|
||||
.position(|b| *b == 0)
|
||||
.ok_or(WbxmlError::Eof)?;
|
||||
self.pos = start + rel + 1;
|
||||
Ok(self.data[start..start + rel].to_vec())
|
||||
}
|
||||
|
||||
fn take(&mut self, n: usize) -> Result<&'a [u8], WbxmlError> {
|
||||
if self.pos + n > self.data.len() {
|
||||
return Err(WbxmlError::Eof);
|
||||
}
|
||||
let s = &self.data[self.pos..self.pos + n];
|
||||
self.pos += n;
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode(data: &[u8]) -> Result<Element, WbxmlError> {
|
||||
let mut r = Reader { data, pos: 0 };
|
||||
let _version = r.byte()?;
|
||||
let _public_id = r.mb_u32()?;
|
||||
let _charset = r.mb_u32()?;
|
||||
let strtbl_len = r.mb_u32()? as usize;
|
||||
let strtbl = r.take(strtbl_len)?.to_vec();
|
||||
let mut page = 0u8;
|
||||
let mut count = 0usize;
|
||||
// skip leading switch pages
|
||||
loop {
|
||||
match r.peek() {
|
||||
Some(SWITCH_PAGE) => {
|
||||
r.byte()?;
|
||||
page = r.byte()?;
|
||||
}
|
||||
Some(_) => break,
|
||||
None => return Err(WbxmlError::Eof),
|
||||
}
|
||||
}
|
||||
decode_element(&mut r, &mut page, &strtbl, 0, &mut count)
|
||||
}
|
||||
|
||||
fn decode_element(
|
||||
r: &mut Reader,
|
||||
page: &mut u8,
|
||||
strtbl: &[u8],
|
||||
depth: usize,
|
||||
count: &mut usize,
|
||||
) -> Result<Element, WbxmlError> {
|
||||
if depth > MAX_DEPTH {
|
||||
return Err(WbxmlError::Invalid("nesting too deep"));
|
||||
}
|
||||
*count += 1;
|
||||
if *count > MAX_ELEMENTS {
|
||||
return Err(WbxmlError::Invalid("too many elements"));
|
||||
}
|
||||
let tok = r.byte()?;
|
||||
if tok == LITERAL {
|
||||
return Err(WbxmlError::Invalid("literal tags not supported"));
|
||||
}
|
||||
if tok & WITH_ATTRIBUTES != 0 {
|
||||
return Err(WbxmlError::Invalid("attributes not supported"));
|
||||
}
|
||||
let mut el = Element {
|
||||
page: *page,
|
||||
code: tok & 0x3f,
|
||||
children: Vec::new(),
|
||||
content: Content::None,
|
||||
};
|
||||
if tok & WITH_CONTENT == 0 {
|
||||
return Ok(el);
|
||||
}
|
||||
let mut text: Option<Vec<u8>> = None;
|
||||
let mut opaque: Option<Vec<u8>> = None;
|
||||
loop {
|
||||
let next = r.peek().ok_or(WbxmlError::Eof)?;
|
||||
match next {
|
||||
END => {
|
||||
r.byte()?;
|
||||
break;
|
||||
}
|
||||
SWITCH_PAGE => {
|
||||
r.byte()?;
|
||||
*page = r.byte()?;
|
||||
}
|
||||
STR_I => {
|
||||
r.byte()?;
|
||||
let s = r.cstr()?;
|
||||
text.get_or_insert_with(Vec::new).extend_from_slice(&s);
|
||||
}
|
||||
STR_T => {
|
||||
r.byte()?;
|
||||
let off = r.mb_u32()? as usize;
|
||||
let rest = strtbl
|
||||
.get(off..)
|
||||
.ok_or(WbxmlError::Invalid("bad string table offset"))?;
|
||||
let end = rest.iter().position(|b| *b == 0).unwrap_or(rest.len());
|
||||
text.get_or_insert_with(Vec::new)
|
||||
.extend_from_slice(&rest[..end]);
|
||||
}
|
||||
ENTITY => {
|
||||
r.byte()?;
|
||||
let cp = r.mb_u32()?;
|
||||
if let Some(c) = char::from_u32(cp) {
|
||||
let mut b = [0u8; 4];
|
||||
text.get_or_insert_with(Vec::new)
|
||||
.extend_from_slice(c.encode_utf8(&mut b).as_bytes());
|
||||
}
|
||||
}
|
||||
OPAQUE => {
|
||||
r.byte()?;
|
||||
let len = r.mb_u32()? as usize;
|
||||
let b = r.take(len)?;
|
||||
opaque.get_or_insert_with(Vec::new).extend_from_slice(b);
|
||||
}
|
||||
_ => {
|
||||
let child = decode_element(r, page, strtbl, depth + 1, count)?;
|
||||
el.children.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(o) = opaque {
|
||||
el.content = Content::Opaque(o);
|
||||
} else if let Some(t) = text {
|
||||
el.content = Content::Text(String::from_utf8_lossy(&t).into_owned());
|
||||
}
|
||||
Ok(el)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn roundtrip() {
|
||||
let root = Element::new("AirSync:Sync").with(
|
||||
Element::new("AirSync:Collections").with(
|
||||
Element::new("AirSync:Collection")
|
||||
.with_text("AirSync:SyncKey", "{abc}1")
|
||||
.with_text("AirSync:CollectionId", "5")
|
||||
.with_empty("AirSync:GetChanges")
|
||||
.with(
|
||||
Element::new("AirSync:Options")
|
||||
.with(
|
||||
Element::new("AirSyncBase:BodyPreference")
|
||||
.with_text("AirSyncBase:Type", "2"),
|
||||
)
|
||||
.with(Element::opaque("ComposeMail:MIME", vec![0, 1, 2, 255])),
|
||||
),
|
||||
),
|
||||
);
|
||||
let bytes = encode(&root);
|
||||
let back = decode(&bytes).unwrap();
|
||||
assert_eq!(root, back);
|
||||
let col = back
|
||||
.path(&["AirSync:Collections", "AirSync:Collection"])
|
||||
.unwrap();
|
||||
assert_eq!(col.child_text("AirSync:SyncKey").unwrap(), "{abc}1");
|
||||
assert!(col.has("AirSync:GetChanges"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mb_u32() {
|
||||
for v in [0u32, 1, 127, 128, 300, 16383, 16384, 1 << 28, u32::MAX] {
|
||||
let mut out = vec![];
|
||||
write_mb_u32(&mut out, v);
|
||||
let mut r = Reader { data: &out, pos: 0 };
|
||||
assert_eq!(r.mb_u32().unwrap(), v);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_names_unique_per_page() {
|
||||
for (p, tags) in codepages::PAGES.iter().enumerate() {
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for (c, _) in tags.iter() {
|
||||
assert!(seen.insert(*c), "duplicate token {c:#x} on page {p}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_garbage() {
|
||||
assert!(decode(&[0x03, 0x01, 0x6a, 0x00, 0x45]).is_err());
|
||||
assert!(decode(&[]).is_err());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user