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