diff --git a/common.go b/common.go
index 1303165..b8d06ca 100644
--- a/common.go
+++ b/common.go
@@ -13,11 +13,18 @@ const (
ResponseClassError ResponseClass = "Error"
)
+type ResponseItems struct {
+ Message []ItemId `xml:"Items>Message>ItemId"`
+ CalendarItem []ItemId `xml:"Items>CalendarItem>ItemId"`
+}
+
type Response struct {
ResponseClass ResponseClass `xml:"ResponseClass,attr"`
MessageText string `xml:"MessageText"`
ResponseCode string `xml:"ResponseCode"`
MessageXml MessageXml `xml:"MessageXml"`
+
+ ResponseItems
}
type EmailAddress struct {
diff --git a/create_item.go b/create_item.go
index d905255..f2bee09 100644
--- a/create_item.go
+++ b/create_item.go
@@ -32,18 +32,18 @@ type Message struct {
}
type CalendarItem struct {
- Subject string `xml:"t:Subject"`
- Body Body `xml:"t:Body"`
- ReminderIsSet bool `xml:"t:ReminderIsSet"`
- ReminderMinutesBeforeStart int `xml:"t:ReminderMinutesBeforeStart"`
- Start time.Time `xml:"t:Start"`
- End time.Time `xml:"t:End"`
- IsAllDayEvent bool `xml:"t:IsAllDayEvent"`
- LegacyFreeBusyStatus string `xml:"t:LegacyFreeBusyStatus"`
- Location string `xml:"t:Location"`
- RequiredAttendees []Attendees `xml:"t:RequiredAttendees"`
- OptionalAttendees []Attendees `xml:"t:OptionalAttendees"`
- Resources []Attendees `xml:"t:Resources"`
+ Subject string `xml:"t:Subject,omitempty"`
+ Body Body `xml:"t:Body,omitempty"`
+ ReminderIsSet bool `xml:"t:ReminderIsSet,omitempty"`
+ ReminderMinutesBeforeStart int `xml:"t:ReminderMinutesBeforeStart,omitempty"`
+ Start time.Time `xml:"t:Start,omitempty"`
+ End time.Time `xml:"t:End,omitempty"`
+ IsAllDayEvent bool `xml:"t:IsAllDayEvent,omitempty"`
+ LegacyFreeBusyStatus string `xml:"t:LegacyFreeBusyStatus,omitempty"`
+ Location string `xml:"t:Location,omitempty"`
+ RequiredAttendees []Attendees `xml:"t:RequiredAttendees,omitempty"`
+ OptionalAttendees []Attendees `xml:"t:OptionalAttendees,omitempty"`
+ Resources []Attendees `xml:"t:Resources,omitempty"`
}
type Body struct {
@@ -75,21 +75,24 @@ type createItemResponseBodyEnvelop struct {
XMLName struct{} `xml:"Envelope"`
Body createItemResponseBody `xml:"Body"`
}
+
type createItemResponseBody struct {
- CreateItemResponse CreateItemResponse `xml:"CreateItemResponse"`
+ CreateItemResponse ItemOperationResponse `xml:"CreateItemResponse"`
}
-type CreateItemResponse struct {
+type ItemOperationResponse struct {
ResponseMessages ResponseMessages `xml:"ResponseMessages"`
}
type ResponseMessages struct {
CreateItemResponseMessage Response `xml:"CreateItemResponseMessage"`
+ UpdateItemResponseMessage Response `xml:"UpdateItemResponseMessage"`
+ DeleteItemResponseMessage Response `xml:"DeleteItemResponseMessage"`
}
// CreateMessageItem
// https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/createitem-operation-email-message
-func CreateMessageItem(c Client, m ...Message) error {
+func CreateMessageItem(c Client, m ...Message) ([]ItemId, error) {
item := &CreateItem{
MessageDisposition: "SendAndSaveCopy",
@@ -99,24 +102,25 @@ func CreateMessageItem(c Client, m ...Message) error {
xmlBytes, err := xml.MarshalIndent(item, "", " ")
if err != nil {
- return err
+ return nil, err
}
bb, err := c.SendAndReceive(xmlBytes)
if err != nil {
- return err
+ return nil, err
}
- if err := checkCreateItemResponseForErrors(bb); err != nil {
- return err
+ items, err := checkCreateItemResponseForErrors(bb)
+ if err != nil {
+ return nil, err
}
- return nil
+ return items.Message, nil
}
// CreateCalendarItem
// https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/createitem-operation-calendar-item
-func CreateCalendarItem(c Client, ci ...CalendarItem) error {
+func CreateCalendarItem(c Client, ci ...CalendarItem) ([]ItemId, error) {
item := &CreateItem{
SendMeetingInvitations: "SendToAllAndSaveCopy",
@@ -126,30 +130,32 @@ func CreateCalendarItem(c Client, ci ...CalendarItem) error {
xmlBytes, err := xml.MarshalIndent(item, "", " ")
if err != nil {
- return err
+ return nil, err
}
bb, err := c.SendAndReceive(xmlBytes)
if err != nil {
- return err
+ return nil, err
}
- if err := checkCreateItemResponseForErrors(bb); err != nil {
- return err
+ items, err := checkCreateItemResponseForErrors(bb)
+ if err != nil {
+ return nil, err
}
- return nil
+ return items.CalendarItem, nil
}
-func checkCreateItemResponseForErrors(bb []byte) error {
+func checkCreateItemResponseForErrors(bb []byte) (items ResponseItems, err error) {
var soapResp createItemResponseBodyEnvelop
- if err := xml.Unmarshal(bb, &soapResp); err != nil {
- return err
+ if err = xml.Unmarshal(bb, &soapResp); err != nil {
+ return
}
resp := soapResp.Body.CreateItemResponse.ResponseMessages.CreateItemResponseMessage
if resp.ResponseClass == ResponseClassError {
- return errors.New(resp.MessageText)
+ err = errors.New(resp.MessageText)
+ return
}
- return nil
+ return resp.ResponseItems, nil
}
diff --git a/delete_item.go b/delete_item.go
new file mode 100644
index 0000000..2acc965
--- /dev/null
+++ b/delete_item.go
@@ -0,0 +1,74 @@
+package ews
+
+import (
+ "encoding/xml"
+ "errors"
+)
+
+type DeleteStrategy struct {
+ DeleteType string `xml:"DeleteType,attr,omitempty"`
+ SendMeetingCancellations string `xml:"SendMeetingCancellations,attr,omitempty"`
+}
+
+type DeleteItem struct {
+ XMLName struct{} `xml:"m:DeleteItem"`
+ ItemIds ItemIds `xml:"m:ItemIds"`
+
+ DeleteStrategy
+}
+
+type ItemIds struct {
+ XMLName struct{} `xml:"m:ItemIds"`
+
+ ItemId []ItemId `xml:"t:ItemId"`
+}
+
+type deleteItemResponseBodyEnvelop struct {
+ XMLName struct{} `xml:"Envelope"`
+ Body deleteItemResponseBody `xml:"Body"`
+}
+
+type deleteItemResponseBody struct {
+ DeleteItemResponse ItemOperationResponse `xml:"DeleteItemResponse"`
+}
+
+// DeleteItems
+// https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-delete-appointments-and-cancel-meetings-by-using-ews-in-exchange
+func DeleteItems(c Client, id []ItemId, strategy ...DeleteStrategy) error {
+
+ strategy = append(strategy, DeleteStrategy{
+ DeleteType: "MoveToDeletedItems",
+ SendMeetingCancellations: "SendToAllAndSaveCopy",
+ })
+
+ item := DeleteItem{
+ ItemIds: ItemIds{
+ ItemId: id,
+ },
+ DeleteStrategy: strategy[0],
+ }
+
+ xmlBytes, err := xml.MarshalIndent(item, "", " ")
+ if err != nil {
+ return err
+ }
+
+ bb, err := c.SendAndReceive(xmlBytes)
+ if err != nil {
+ return err
+ }
+ return checkDeleteItemResponseForErrors(bb)
+}
+
+func checkDeleteItemResponseForErrors(bb []byte) (err error) {
+ var soapResp deleteItemResponseBodyEnvelop
+ if err = xml.Unmarshal(bb, &soapResp); err != nil {
+ return
+ }
+
+ resp := soapResp.Body.DeleteItemResponse.ResponseMessages.DeleteItemResponseMessage
+ if resp.ResponseClass == ResponseClassError {
+ err = errors.New(resp.MessageText)
+ }
+ return
+}
diff --git a/delete_item_test.go b/delete_item_test.go
new file mode 100644
index 0000000..26f74aa
--- /dev/null
+++ b/delete_item_test.go
@@ -0,0 +1,31 @@
+package ews
+
+import (
+ "encoding/xml"
+ "log"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_marshal_DeleteItems(t *testing.T) {
+
+ ditem := &DeleteItem{
+ ItemIds: ItemIds{
+ ItemId: []ItemId{
+ {"ID", "Key"},
+ },
+ },
+ }
+
+ xmlBytes, err := xml.MarshalIndent(ditem, "", " ")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ assert.Equal(t, `
+
+
+
+`, string(xmlBytes))
+}
diff --git a/ews.go b/ews.go
index f7c17b8..f229079 100644
--- a/ews.go
+++ b/ews.go
@@ -4,10 +4,11 @@ import (
"bytes"
"crypto/tls"
"fmt"
- "github.com/Azure/go-ntlmssp"
"io/ioutil"
"net/http"
"net/http/httputil"
+
+ "github.com/Azure/go-ntlmssp"
)
const (
@@ -27,8 +28,18 @@ const (
type Config struct {
Dump bool
+ OAuth bool
NTLM bool
SkipTLS bool
+
+ Transport func(*http.Request) (*http.Response, error)
+}
+
+func (c *Config) RoundTrip(req *http.Request) (*http.Response, error) {
+ if c.Transport == nil {
+ c.Transport = http.DefaultClient.Transport.RoundTrip
+ }
+ return c.Transport(req)
}
type Client interface {
@@ -78,6 +89,7 @@ func (c *client) SendAndReceive(body []byte) ([]byte, error) {
req.Header.Set("Content-Type", "text/xml")
client := &http.Client{
+ Transport: c.config,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
@@ -105,7 +117,17 @@ func (c *client) SendAndReceive(body []byte) ([]byte, error) {
func applyConfig(config *Config, client *http.Client) {
if config.NTLM {
- client.Transport = ntlmssp.Negotiator{}
+ client.Transport = ntlmssp.Negotiator{
+ RoundTripper: &http.Transport{
+ TLSNextProto: map[string]func(authority string, c *tls.Conn) http.RoundTripper{},
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: config.SkipTLS},
+ },
+ }
+ }
+ if config.OAuth {
+ //To get AccessToken from MSAL: https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-authenticate-an-ews-application-by-using-oauth and pass it as PASSWORD in any circumstances.
+ //BE CAREFUL: 'Main' BRANCH OF GO SUPPORT REPOSITORY github.com/AzureAD/microsoft-authentication-library-for-go MAY NOT UP TO DATE WITH THE PASSAGE REQUIREMENT, USE 'Dev' BRANCH AND PROCEED WITH CAUTION IF YOU ARE IN PRODUCTION.
+ client.Transport = bearerNegotiator{config}
}
if config.SkipTLS {
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
diff --git a/ewsutil/create_event.go b/ewsutil/create_event.go
index 527f005..b14eac3 100644
--- a/ewsutil/create_event.go
+++ b/ewsutil/create_event.go
@@ -1,26 +1,27 @@
package ewsutil
import (
- "github.com/mhewedy/ews"
"time"
+
+ "github.com/mhewedy/ews"
)
func CreateHTMLEvent(
c ews.Client, to, optional []string, subject, body, location string, from time.Time, duration time.Duration,
-) error {
+) ([]ews.ItemId, error) {
return createEvent(c, to, optional, subject, body, location, "HTML", from, duration)
}
// CreateEvent helper method to send Message
func CreateEvent(
c ews.Client, to, optional []string, subject, body, location string, from time.Time, duration time.Duration,
-) error {
+) ([]ews.ItemId, error) {
return createEvent(c, to, optional, subject, body, location, "Text", from, duration)
}
func createEvent(
c ews.Client, to, optional []string, subject, body, location, bodyType string, from time.Time, duration time.Duration,
-) error {
+) ([]ews.ItemId, error) {
requiredAttendees := make([]ews.Attendee, len(to))
for i, tt := range to {
@@ -41,16 +42,16 @@ func createEvent(
BodyType: bodyType,
Body: []byte(body),
},
- ReminderIsSet: true,
- ReminderMinutesBeforeStart: 15,
- Start: from,
- End: from.Add(duration),
- IsAllDayEvent: false,
- LegacyFreeBusyStatus: ews.BusyTypeBusy,
- Location: location,
- RequiredAttendees: []ews.Attendees{{Attendee: requiredAttendees}},
- OptionalAttendees: []ews.Attendees{{Attendee: optionalAttendees}},
- Resources: []ews.Attendees{{Attendee: room}},
+ // ReminderIsSet: duration != 0,
+ // ReminderMinutesBeforeStart: 15,
+ Start: from,
+ End: from.Add(duration),
+ IsAllDayEvent: duration == 0,
+ LegacyFreeBusyStatus: ews.BusyTypeBusy,
+ Location: location,
+ RequiredAttendees: []ews.Attendees{{Attendee: requiredAttendees}},
+ OptionalAttendees: []ews.Attendees{{Attendee: optionalAttendees}},
+ Resources: []ews.Attendees{{Attendee: room}},
}
return ews.CreateCalendarItem(c, m)
diff --git a/ewsutil/delete_event.go b/ewsutil/delete_event.go
new file mode 100644
index 0000000..321de17
--- /dev/null
+++ b/ewsutil/delete_event.go
@@ -0,0 +1,11 @@
+package ewsutil
+
+import (
+ "github.com/mhewedy/ews"
+)
+
+func DeleteEvent(
+ c ews.Client, id ...ews.ItemId,
+) error {
+ return ews.DeleteItems(c, id)
+}
diff --git a/ewsutil/send_email.go b/ewsutil/send_email.go
index e2b153d..eb061f0 100644
--- a/ewsutil/send_email.go
+++ b/ewsutil/send_email.go
@@ -3,7 +3,7 @@ package ewsutil
import "github.com/mhewedy/ews"
// SendEmail helper method to send Message
-func SendEmail(c ews.Client, to []string, subject, body string) error {
+func SendEmail(c ews.Client, to []string, subject, body string) (err error) {
m := ews.Message{
ItemClass: "IPM.Note",
@@ -24,5 +24,6 @@ func SendEmail(c ews.Client, to []string, subject, body string) error {
}
m.ToRecipients.Mailbox = append(m.ToRecipients.Mailbox, mb...)
- return ews.CreateMessageItem(c, m)
+ _, err = ews.CreateMessageItem(c, m)
+ return
}
diff --git a/ewsutil/update_event.go b/ewsutil/update_event.go
new file mode 100644
index 0000000..c2f0266
--- /dev/null
+++ b/ewsutil/update_event.go
@@ -0,0 +1,58 @@
+package ewsutil
+
+import (
+ "time"
+
+ "github.com/mhewedy/ews"
+)
+
+func UpdateHTMLEvent(
+ c ews.Client, id ews.ItemId, to, optional []string, subject, body, location string, from time.Time, duration time.Duration,
+) ([]ews.ItemId, error) {
+ return updateEvent(c, id, to, optional, subject, body, location, "HTML", from, duration)
+}
+
+// UpdateEvent helper method to update Message
+func UpdateEvent(
+ c ews.Client, id ews.ItemId, to, optional []string, subject, body, location string, from time.Time, duration time.Duration,
+) ([]ews.ItemId, error) {
+ return updateEvent(c, id, to, optional, subject, body, location, "Text", from, duration)
+}
+
+func updateEvent(
+ c ews.Client, id ews.ItemId, to, optional []string, subject, body, location, bodyType string, from time.Time, duration time.Duration,
+) ([]ews.ItemId, error) {
+
+ requiredAttendees := make([]ews.Attendee, len(to))
+ for i, tt := range to {
+ requiredAttendees[i] = ews.Attendee{Mailbox: ews.Mailbox{EmailAddress: tt}}
+ }
+
+ optionalAttendees := make([]ews.Attendee, len(optional))
+ for i, tt := range optional {
+ optionalAttendees[i] = ews.Attendee{Mailbox: ews.Mailbox{EmailAddress: tt}}
+ }
+
+ room := make([]ews.Attendee, 1)
+ room[0] = ews.Attendee{Mailbox: ews.Mailbox{EmailAddress: location}}
+
+ m := ews.CalendarItem{
+ Subject: subject,
+ Body: ews.Body{
+ BodyType: bodyType,
+ Body: []byte(body),
+ },
+ // ReminderIsSet: duration != 0,
+ // ReminderMinutesBeforeStart: 15,
+ Start: from,
+ End: from.Add(duration),
+ IsAllDayEvent: duration == 0,
+ LegacyFreeBusyStatus: ews.BusyTypeBusy,
+ Location: location,
+ RequiredAttendees: []ews.Attendees{{Attendee: requiredAttendees}},
+ OptionalAttendees: []ews.Attendees{{Attendee: optionalAttendees}},
+ Resources: []ews.Attendees{{Attendee: room}},
+ }
+
+ return ews.UpdateCalendarItem(c, id, m)
+}
diff --git a/example_test.go b/example_test.go
index afdacfd..52bc29d 100644
--- a/example_test.go
+++ b/example_test.go
@@ -2,13 +2,14 @@ package ews_test
import (
"fmt"
- . "github.com/mhewedy/ews"
- "github.com/mhewedy/ews/ewsutil"
"io/ioutil"
"math"
"os"
"testing"
"time"
+
+ . "github.com/mhewedy/ews"
+ "github.com/mhewedy/ews/ewsutil"
)
func Test_Example(t *testing.T) {
@@ -74,7 +75,7 @@ func testCreateCalendarItem(c Client) error {
attendees := make([]Attendees, 0)
attendees = append(attendees, Attendees{Attendee: attendee})
- return CreateCalendarItem(c, CalendarItem{
+ var _, err = CreateCalendarItem(c, CalendarItem{
Subject: "Planning Meeting",
Body: Body{
BodyType: "Text",
@@ -89,6 +90,7 @@ func testCreateCalendarItem(c Client) error {
Location: "Conference Room 721",
RequiredAttendees: attendees,
})
+ return err
}
func testGetUserAvailability(c Client) error {
@@ -185,7 +187,7 @@ func testListUsersEvents(c Client) error {
func testCreateEvent(c Client) error {
- return ewsutil.CreateEvent(c,
+ var _, err = ewsutil.CreateEvent(c,
[]string{"mhewedy@mhewedy.onmicrosoft.com", "example2@mhewedy.onmicrosoft.com"},
[]string{},
"An Event subject",
@@ -194,11 +196,12 @@ func testCreateEvent(c Client) error {
time.Now().Add(24*time.Hour),
30*time.Minute,
)
+ return err
}
func testCreateHTMLEvent(c Client) error {
- return ewsutil.CreateHTMLEvent(c,
+ var _, err = ewsutil.CreateHTMLEvent(c,
[]string{"mhewedy@mhewedy.onmicrosoft.com", "example@mhewedy.onmicrosoft.com"},
[]string{},
"An Event subject",
@@ -209,6 +212,7 @@ func testCreateHTMLEvent(c Client) error {
time.Now().Add(24*time.Hour),
30*time.Minute,
)
+ return err
}
func testGetRoomLists(c Client) error {
diff --git a/go.mod b/go.mod
index 2702c37..fbe094a 100644
--- a/go.mod
+++ b/go.mod
@@ -6,5 +6,4 @@ require (
github.com/Azure/go-ntlmssp v0.0.0-20191115210519-2b2be6cc8ed4
github.com/stretchr/testify v1.4.0
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 // indirect
- golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect
)
diff --git a/go.sum b/go.sum
index 8da5354..34697c5 100644
--- a/go.sum
+++ b/go.sum
@@ -10,17 +10,10 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f h1:kDxGY2VmgABOe55qheT/TFqUMtcTHnomIPS1iv3G4Ms=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
diff --git a/oauth.go b/oauth.go
new file mode 100644
index 0000000..68c1429
--- /dev/null
+++ b/oauth.go
@@ -0,0 +1,53 @@
+package ews
+
+import (
+ "bytes"
+ "encoding/base64"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+)
+
+type bearerNegotiator struct{ http.RoundTripper }
+
+// C# Code Ref: https://github.com/OfficeDev/ews-managed-api/blob/master/Credentials/OAuthCredentials.cs
+func (b bearerNegotiator) RoundTrip(req *http.Request) (res *http.Response, err error) {
+ rt := b.RoundTripper
+ if rt == nil {
+ rt = http.DefaultTransport
+ }
+ u, p := b.authheader(req.Header.Get("Authorization"))
+ if len(u) == 0 {
+ //If it is not a simple auth, just run it as usual
+ return rt.RoundTrip(req)
+ }
+ //Set bearer header info
+ req.Header.Set("Authorization", "Bearer "+p)
+ //Set anchor mailbox info
+ req.Header.Set("X-AnchorMailbox", u)
+ //Read the body and add soap:Header element
+ if bin, _ := io.ReadAll(req.Body); len(bin) > 0 {
+ //IMPORTANT: REMOVE THIS PART YOU WILL MEET:
+ //ExchangeImpersonation SOAP header must be present for this type of OAuth token.
+ //Issue on stackoverflow: https://stackoverflow.com/questions/56148996/error-exchangeimpersonation-soap-header-must-be-present-for-this-type-of-oauth
+ //Manual by Microsoft: https://learn.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/bb204088(v=exchg.140)
+ bin = bytes.Replace(bin, []byte(``), []byte(fmt.Sprintf(`%s`, u)), 1)
+ req.Body = io.NopCloser(bytes.NewReader(bin))
+ //Update content-length information.
+ req.ContentLength = int64(len(bin))
+ }
+ return rt.RoundTrip(req)
+}
+
+func (bearerNegotiator) authheader(auth string) (u, p string) {
+ if !strings.HasPrefix(strings.ToLower(auth), "basic ") {
+ return
+ }
+ authStr, _ := base64.StdEncoding.DecodeString(auth[6:])
+ var idx = bytes.LastIndex(authStr, []byte{':'})
+ if idx != -1 {
+ u, p = string(authStr[:idx]), string(authStr[idx+1:])
+ }
+ return
+}
diff --git a/update_item.go b/update_item.go
new file mode 100644
index 0000000..06031a8
--- /dev/null
+++ b/update_item.go
@@ -0,0 +1,184 @@
+package ews
+
+import (
+ "encoding/xml"
+ "errors"
+ "reflect"
+ "strings"
+)
+
+type UpdateStrategy struct {
+ ConflictResolution string `xml:"ConflictResolution,attr,omitempty"`
+ MessageDisposition string `xml:"MessageDisposition,attr,omitempty"`
+ SendMeetingInvitationsOrCancellations string `xml:"SendMeetingInvitationsOrCancellations,attr,omitempty"`
+}
+
+type UpdateItem struct {
+ XMLName struct{} `xml:"m:UpdateItem"`
+
+ ItemChanges ItemChanges `xml:"m:ItemChanges"`
+ UpdateStrategy
+}
+
+type ItemChanges struct {
+ XMLName xml.Name `xml:"m:ItemChanges"`
+
+ ItemChanges []ItemChange
+}
+
+type ItemChange struct {
+ XMLName xml.Name `xml:"t:ItemChange"`
+
+ ItemId ItemId `xml:"t:ItemId"`
+ Updates Updates `xml:"t:Updates"`
+}
+
+type Updates struct {
+ XMLName xml.Name `xml:"t:Updates"`
+
+ Updates []SetItemField `xml:"t:Updates"`
+}
+
+type SetItemField struct {
+ XMLName xml.Name `xml:"t:SetItemField"`
+
+ FieldURI FieldURI `xml:"t:FieldURI"`
+ CalendarItem []Field `xml:"t:CalendarItem,omitempty"`
+ Message []Field `xml:"t:Message,omitempty"`
+}
+
+type Field struct {
+ Name string
+ Attributes map[string]string
+ Value interface{}
+}
+
+type updateItemResponseBodyEnvelop struct {
+ XMLName struct{} `xml:"Envelope"`
+ Body updateItemResponseBody `xml:"Body"`
+}
+
+type updateItemResponseBody struct {
+ UpdateItemResponse ItemOperationResponse `xml:"UpdateItemResponse"`
+}
+
+func (f Field) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ e.EncodeToken(start)
+ index, attr := 0, make([]xml.Attr, len(f.Attributes))
+ for k, v := range f.Attributes {
+ attr[index].Name = xml.Name{Local: k}
+ attr[index].Value = v
+ }
+ var t = xml.StartElement{Name: xml.Name{Local: f.Name}, Attr: attr}
+ // e.EncodeToken(t)
+ e.EncodeElement(f.Value, t)
+ // e.EncodeToken(xml.EndElement{Name: t.Name})
+ e.EncodeToken(xml.EndElement{Name: start.Name})
+ return e.Flush()
+}
+
+func getFields(obj interface{}) (fields []Field) {
+ val := reflect.Indirect(reflect.ValueOf(obj))
+ t := val.Type()
+ length := t.NumField()
+ for index := 0; index < length; index++ {
+ value := val.Field(index)
+ if !value.IsZero() {
+ val, ok := t.Field(index).Tag.Lookup("xml")
+ if !ok {
+ val = t.Field(index).Name
+ } else {
+ val = strings.Split(val, ",")[0]
+ }
+ fields = append(fields, Field{
+ Name: val,
+ Value: value.Interface(),
+ })
+ }
+ }
+ return
+}
+
+func (field Field) uri(replace string) string {
+ index := strings.Index(field.Name, ":")
+ if index != -1 {
+ return replace + field.Name[index:]
+ }
+ return field.Name
+}
+
+var itemFields = map[string]bool{
+ "t:Subject": true,
+ "t:Body": true,
+}
+
+func getSetItemField(prefix string, fields ...Field) []SetItemField {
+ var setFields = make([]SetItemField, len(fields))
+ for index, field := range fields {
+ setFields[index].CalendarItem = []Field{field}
+ replace := prefix
+ if itemFields[field.Name] {
+ replace = "item"
+ }
+ setFields[index].FieldURI = FieldURI{
+ FieldURI: strings.Replace(field.Name, "t", replace, 1),
+ }
+ }
+ return setFields
+}
+
+// UpdateCalendarItem
+// https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-update-appointments-and-meetings-by-using-ews-in-exchange
+func UpdateCalendarItem(c Client, id ItemId, ci CalendarItem, strategy ...UpdateStrategy) ([]ItemId, error) {
+
+ strategy = append(strategy, UpdateStrategy{
+ ConflictResolution: "AlwaysOverwrite",
+ MessageDisposition: "SaveOnly",
+ SendMeetingInvitationsOrCancellations: "SendToAllAndSaveCopy",
+ })
+
+ var setFields = getSetItemField("calendar", getFields(ci)...)
+
+ item := UpdateItem{
+ ItemChanges: ItemChanges{ItemChanges: []ItemChange{
+ ItemChange{
+ ItemId: id,
+ Updates: Updates{
+ Updates: setFields,
+ },
+ },
+ }},
+ UpdateStrategy: strategy[0],
+ }
+
+ xmlBytes, err := xml.MarshalIndent(item, "", " ")
+ if err != nil {
+ return nil, err
+ }
+
+ bb, err := c.SendAndReceive(xmlBytes)
+ if err != nil {
+ return nil, err
+ }
+
+ items, err := checkUpdateItemResponseForErrors(bb)
+ if err != nil {
+ return nil, err
+ }
+
+ return items.CalendarItem, nil
+}
+
+func checkUpdateItemResponseForErrors(bb []byte) (items ResponseItems, err error) {
+ var soapResp updateItemResponseBodyEnvelop
+ if err = xml.Unmarshal(bb, &soapResp); err != nil {
+ return
+ }
+
+ resp := soapResp.Body.UpdateItemResponse.ResponseMessages.UpdateItemResponseMessage
+ if resp.ResponseClass == ResponseClassError {
+ err = errors.New(resp.MessageText)
+ return
+ }
+ return resp.ResponseItems, nil
+}
diff --git a/update_item_test.go b/update_item_test.go
new file mode 100644
index 0000000..b25ccc2
--- /dev/null
+++ b/update_item_test.go
@@ -0,0 +1,103 @@
+package ews
+
+import (
+ "encoding/xml"
+ "log"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_marshal_UpdateItems(t *testing.T) {
+
+ attendee := make([]Attendee, 0)
+ attendee = append(attendee,
+ Attendee{Mailbox: Mailbox{EmailAddress: "User1@example.com"}},
+ Attendee{Mailbox: Mailbox{EmailAddress: "User2@example.com"}},
+ )
+ attendees := make([]Attendees, 0)
+ attendees = append(attendees, Attendees{Attendee: attendee})
+
+ start, _ := time.Parse(time.RFC3339, "2006-11-02T14:00:00Z")
+ end, _ := time.Parse(time.RFC3339, "2006-11-02T15:00:00Z")
+
+ citem := &CalendarItem{
+ Subject: "Planning Meeting",
+ Body: Body{
+ BodyType: "Text",
+ Body: []byte("Plan the agenda for next week's meeting."),
+ },
+ Start: start,
+ End: end,
+ IsAllDayEvent: false,
+ LegacyFreeBusyStatus: "Busy",
+ Location: "Conference Room 721",
+ RequiredAttendees: attendees,
+ }
+
+ uitem := &Updates{
+ Updates: getSetItemField("calendar", getFields(citem)...),
+ }
+
+ xmlBytes, err := xml.MarshalIndent(uitem, "", " ")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ assert.Equal(t, `
+
+
+
+ Planning Meeting
+
+
+
+
+
+ Plan the agenda for next week's meeting.
+
+
+
+
+
+ 2006-11-02T14:00:00Z
+
+
+
+
+
+ 2006-11-02T15:00:00Z
+
+
+
+
+
+ Busy
+
+
+
+
+
+ Conference Room 721
+
+
+
+
+
+
+
+
+ User1@example.com
+
+
+
+
+ User2@example.com
+
+
+
+
+
+`, string(xmlBytes))
+}