diff --git a/Documentation/Assets/UML Diagram.svg b/Documentation/Assets/UML Diagram.svg
index 83bf4c9d..9554e0db 100644
--- a/Documentation/Assets/UML Diagram.svg
+++ b/Documentation/Assets/UML Diagram.svg
@@ -37,18 +37,18 @@ text { font-family: 'Segoe UI', Arial, sans-serif; }
-
+
-
+
-
+
-
-
-
-
+
+
+
+
_BaseAPI
@@ -160,13 +160,15 @@ text { font-family: 'Segoe UI', Arial, sans-serif; }
+appendRequest($inParam : Object)
+constructor($inProvider : cs.OAuth2Provider; $inParam : Object)
+sendRequestAndWaitResponse() : Collection
-
+
_GraphAPI
-_copyGraphMessage($inMessage : Object) : Object
-_getURLParamsFromObject($inParameters : Object; $inCount : Boolean) : Text
-_loadFromObject($inObject : Object)
-+constructor($inProvider : cs.OAuth2Provider)
+-_validateGraphMessageProperties($inPayload : Object; $inFunction : Text) : Boolean
+-_validateJMAPMessageProperties($inMail : Object; $inFunction : Text) : Boolean
++constructor($inProvider : cs.OAuth2Provider)
_GraphBaseList
@@ -631,7 +633,7 @@ text { font-family: 'Segoe UI', Arial, sans-serif; }
+mailType : Text
+userId : Text
--_postJSONMessage($inURL : Text; $inMail : Object; $bSkipMessageEncapsulation : Boolean; $inHeaders : Object) : Object
+-_postJSONMessage($inFunction : Text; $inURL : Text; $inMail : Object; $bSkipMessageEncapsulation : Boolean; $inHeaders : Object) : Object
-_postMailMIMEMessage($inURL : Text; $inMail : Variant) : Object
-_postMessage($inFunction : Text; $inURL : Text; $inMail : Variant; $bSkipMessageEncapsulation : Boolean; $inHeader : Object) : Object
+append($inMail : Variant; $inFolderId : Text) : Object
diff --git a/Documentation/Classes/Office365.md b/Documentation/Classes/Office365.md
index 694b9952..a833a474 100644
--- a/Documentation/Classes/Office365.md
+++ b/Documentation/Classes/Office365.md
@@ -595,7 +595,7 @@ The `event` object used with Microsoft Calendar methods includes the following m
| hideAttendees | | Boolean | (Default: false) If `true`, attendees will only see themselves.|Yes|
## Mail
-
+
### Office365.mail.append()
**Office365.mail.append**( *email* : Object ; *folderId* : Text) : Object
diff --git a/Project/Sources/Classes/Office365Mail.4dm b/Project/Sources/Classes/Office365Mail.4dm
index 75cb8316..93d23840 100644
--- a/Project/Sources/Classes/Office365Mail.4dm
+++ b/Project/Sources/Classes/Office365Mail.4dm
@@ -31,10 +31,11 @@ Class constructor($inProvider : cs.OAuth2Provider; $inParameters : Object)
// ----------------------------------------------------
-Function _postJSONMessage($inURL : Text; $inMail : Object; $bSkipMessageEncapsulation : Boolean; $inHeaders : Object) : Object
+Function _postJSONMessage($inFunction : Text; $inURL : Text; $inMail : Object; $bSkipMessageEncapsulation : Boolean; $inHeaders : Object) : Object
/**
* @function _postJSONMessage
* @private
+ * @param {Text} $inFunction - Caller name for error reporting (e.g. `"office365.mail.send"`)
* @param {Text} $inURL - Target Graph API endpoint URL
* @param {Object} $inMail - Mail object in Microsoft Graph JSON format
* @param {Boolean} $bSkipMessageEncapsulation - When `True`, sends `$inMail` as-is;
@@ -54,6 +55,10 @@ Function _postJSONMessage($inURL : Text; $inMail : Object; $bSkipMessageEncapsul
End if
$headers["Content-Type"]:="application/json"
+ If (Not(This._validateGraphMessageProperties($inMail; $inFunction)))
+ return This._returnStatus()
+ End if
+
var $message : Object
var $messageCopy : Object:=This._copyGraphMessage($inMail)
If (Not(OB Is defined($inMail; "message")) && Not($bSkipMessageEncapsulation))
@@ -128,7 +133,9 @@ Function _postMessage($inFunction : Text; $inURL : Text; $inMail : Variant; $bSk
* @param {Object} $inHeader - Additional HTTP headers forwarded to `_postJSONMessage`
* @returns {Object} Status object
* @description Dispatches to `_postJSONMessage` or `_postMailMIMEMessage` based on `mailType`
- * and the actual type of `$inMail`; throws error 10 on type mismatch
+ * and the actual type of `$inMail`; throws error 10 on type mismatch.
+ * JMAP objects are validated via `_validateJMAPMessageProperties` before conversion;
+ * Microsoft Graph objects are validated via `_validateGraphMessageProperties`.
*/
var $status : Object
@@ -146,10 +153,14 @@ Function _postMessage($inFunction : Text; $inURL : Text; $inMail : Variant; $bSk
$status:=This._postMailMIMEMessage($inURL; $inMail)
: ((This.mailType="JMAP") && (Value type($inMail)=Is object))
- $status:=This._postMailMIMEMessage($inURL; $inMail)
+ If (This._validateJMAPMessageProperties($inMail; $inFunction))
+ $status:=This._postMailMIMEMessage($inURL; $inMail)
+ Else
+ $status:=This._returnStatus()
+ End if
: ((This.mailType="Microsoft") && (Value type($inMail)=Is object))
- $status:=This._postJSONMessage($inURL; $inMail; $bSkipMessageEncapsulation; $inHeader)
+ $status:=This._postJSONMessage($inFunction; $inURL; $inMail; $bSkipMessageEncapsulation; $inHeader)
Else
Super._throwError(10; {which: 1; function: $inFunction})
@@ -542,6 +553,10 @@ Function update($inMailId : Text; $inMail : Object) : Object
If ((Type($inMail)=Is object) && (Type($inMailId)=Is text) && (Length(String($inMailId))>0))
+ If (Not(This._validateGraphMessageProperties($inMail; "office365.mail.update")))
+ return This._returnStatus()
+ End if
+
var $response : Object
var $URL : Text:=Super._getURL()
diff --git a/Project/Sources/Classes/_GraphAPI.4dm b/Project/Sources/Classes/_GraphAPI.4dm
index 7e6cc751..60faaa76 100644
--- a/Project/Sources/Classes/_GraphAPI.4dm
+++ b/Project/Sources/Classes/_GraphAPI.4dm
@@ -76,6 +76,107 @@ Function _copyGraphMessage($inMessage : Object) : Object
// ----------------------------------------------------
+Function _validateGraphMessageProperties($inPayload : Object; $inFunction : Text) : Boolean
+/**
+ * @function _validateGraphMessageProperties
+ * @private
+ * @param {Object} $inPayload - Graph message object or request envelope (`{message: ...}`)
+ * @param {Text} $inFunction - Caller function name for error reporting
+ * @returns {Boolean} `True` when all properties are supported; otherwise `False`
+ * @description Validates mail payload keys before sending to Microsoft Graph.
+ * Adds an error to the stack for each unsupported property (for example `attachment`
+ * instead of `attachments`). Uses a Collection-based lookup for O(1)-style
+ * maintainability and readability over a `Case of` chain.
+ */
+
+ var $isValid : Boolean:=True
+
+ If (($inPayload#Null) && (Value type($inPayload)=Is object))
+
+ var $message : Object
+ var $allowedPayloadKeys : Object:={message: True; saveToSentItems: True; comment: True}
+ If (OB Is defined($inPayload; "message") && (Value type($inPayload.message)=Is object))
+ $message:=$inPayload.message
+
+ var $payloadKey : Text
+ For each ($payloadKey; OB Keys($inPayload))
+ If (Not(OB Is defined($allowedPayloadKeys; $payloadKey)))
+ This._pushError(12; {function: $inFunction; message: "Unsupported property \""+$payloadKey+"\" in mail payload."})
+ $isValid:=False
+ End if
+ End for each
+ Else
+ $message:=$inPayload
+ End if
+
+ var $allowedMessageKeys : Collection:=New collection(\
+ "attachments"; "bccRecipients"; "body"; "bodyPreview"; \
+ "categories"; "ccRecipients"; "changeKey"; "conversationId"; \
+ "conversationIndex"; "createdDateTime"; "extensions"; "flag"; \
+ "from"; "hasAttachments"; "id"; "importance"; "inferenceClassification"; \
+ "internetMessageHeaders"; "internetMessageId"; "isDeliveryReceiptRequested"; \
+ "isDraft"; "isRead"; "isReadReceiptRequested"; "lastModifiedDateTime"; \
+ "multiValueExtendedProperties"; "parentFolderId"; "receivedDateTime"; \
+ "replyTo"; "sender"; "sentDateTime"; "singleValueExtendedProperties"; \
+ "subject"; "toRecipients"; "uniqueBody"; "webLink")
+
+ var $messageKey : Text
+ For each ($messageKey; OB Keys($message))
+ If ($allowedMessageKeys.indexOf($messageKey)<0)
+ This._pushError(12; {function: $inFunction; message: "Unsupported property \""+$messageKey+"\" in mail object."})
+ $isValid:=False
+ End if
+ End for each
+
+ End if
+
+ return $isValid
+
+
+ // ----------------------------------------------------
+
+
+Function _validateJMAPMessageProperties($inMail : Object; $inFunction : Text) : Boolean
+/**
+ * @function _validateJMAPMessageProperties
+ * @private
+ * @param {Object} $inMail - 4D JMAP email object (as produced by `MAIL Convert from MIME`)
+ * @param {Text} $inFunction - Caller function name for error reporting
+ * @returns {Boolean} `True` when all properties are valid RFC 8621 / 4D JMAP properties;
+ * otherwise `False`
+ * @description Validates a JMAP mail object against the set of properties supported by
+ * 4D's `MAIL Convert to MIME` command (RFC 8621). Adds an error to the stack for each
+ * unsupported property.
+ */
+
+ var $isValid : Boolean:=True
+
+ If (($inMail#Null) && (Value type($inMail)=Is object))
+
+ var $allowedKeys : Collection:=New collection(\
+ "from"; "to"; "cc"; "bcc"; "replyTo"; "sender"; \
+ "subject"; "sentAt"; "receivedAt"; \
+ "textBody"; "htmlBody"; "bodyValues"; \
+ "attachments"; "keywords"; "headers"; \
+ "messageId"; "inReplyTo"; "references"; \
+ "id"; "threadId"; "preview"; "size"; "hasAttachment"; "mailboxIds")
+
+ var $key : Text
+ For each ($key; OB Keys($inMail))
+ If ($allowedKeys.indexOf($key)<0)
+ This._pushError(12; {function: $inFunction; message: "Unsupported property \""+$key+"\" in JMAP mail object."})
+ $isValid:=False
+ End if
+ End for each
+
+ End if
+
+ return $isValid
+
+
+ // ----------------------------------------------------
+
+
Function _loadFromObject($inObject : Object)
/**
* @function _loadFromObject