first commit

This commit is contained in:
Mateusz Gruszczyński committed 2026-11-05 12:52:32 +01:00
commit 19dfc51360
85 files changed
+25504

No files matched your search

+763
View File
@@ -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"),
],
];
+556
View File
@@ -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('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
}
// ---------------------------------------------------------------- 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());
}
}