Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
)

func TestGetAccountBalance(t *testing.T) {
InitEnv()

client, err := NewClient(testAPIKey, testAPISecret)
if err != nil {
t.Error("Failed to create Client with error:", err)
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/wheniwork/gonexmo/v2

go 1.14
3 changes: 2 additions & 1 deletion gonexmo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var (
testFrom string
)

func init() {
func InitEnv() {
testAPIKey = os.Getenv("NEXMO_KEY")
if testAPIKey == "" {
fmt.Println("No API key specified. Please set NEXMO_KEY")
Expand All @@ -38,6 +38,7 @@ func init() {
}

func TestNexmoCreation(t *testing.T) {
InitEnv()
_, err := NewClient(testAPIKey, testAPISecret)
if err != nil {
t.Error("failed to create Client with error:", err)
Expand Down
274 changes: 162 additions & 112 deletions server.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package nexmo

import (
"fmt"
"net"
"net/http"
"net/url"
Expand Down Expand Up @@ -103,9 +104,92 @@ type DeliveryReceipt struct {
ClientReference string `json:"client-ref"`
}

// ParseReceivedMessage unmarshals and processes the form data in a Nexmo request
// and returns a DeliveryReceipt struct.
func ParseDeliveryReceipt(req *http.Request) (*DeliveryReceipt, error) {
err := req.ParseForm()
if err != nil {
return nil, fmt.Errorf("failed to parse form data: %v", err)
}

// Decode the form data
m := new(DeliveryReceipt)

m.To = req.FormValue("to")
m.NetworkCode = req.FormValue("network-code")
m.MessageID = req.FormValue("messageId")
m.MSISDN = req.FormValue("msisdn")
m.Status = req.FormValue("status")
m.ErrorCode = req.FormValue("err-code")
m.Price = req.FormValue("price")
m.ClientReference = req.FormValue("client-ref")

{
t, err := url.QueryUnescape(req.FormValue("scts"))
if err != nil {
return nil, fmt.Errorf("failed to unescape field 'scts': %v", err)
}

m.SCTS, err = parseSCTS(t)
if err != nil {
return nil, err
}
}

{
t, err := url.QueryUnescape(req.FormValue("message-timestamp"))
if err != nil {
return nil, fmt.Errorf("failed to unescape field 'message-timestamp': %v", err)
}

m.Timestamp, err = parseMessageTimestamp(t)
if err != nil {
return nil, err
}
}

return m, nil
}

func parseSCTS(t string) (time.Time, error) {
if t == "" {
return time.Time{}, nil
}

timestamp, err := time.Parse("0601021504", t)
if err != nil {
return time.Time{}, fmt.Errorf("failed to parse timestamp for field 'scts': %v", err)
}

return timestamp, nil
}

func parseMessageTimestamp(t string) (time.Time, error) {
if t == "" {
return time.Time{}, nil
}

// nexmo is just doing some crazy stuff lately
formats := []string{
"2006-01-02 15:04:05 -0700", // actually valid
"2006-01-02 15:04:05 0000", // where did the plus go
"2006-01-02 15:04:05 0000", // oh you forgot to URL encode it? very cool
"2006-01-02 15:04:05", // you know what, forget timezones
}

for _, f := range formats {
timestamp, err := time.Parse(f, t)
if err == nil {
return timestamp, nil
}
}

return time.Time{}, fmt.Errorf("failed to parse timestamp '%s' for field 'message-timestamp'", t)
}

// NewDeliveryHandler creates a new http.HandlerFunc that can be used to listen
// for delivery receipts from the Nexmo server. Any receipts received will be
// decoded nad passed to the out chan.
// decoded and passed to the out chan.
func NewDeliveryHandler(out chan *DeliveryReceipt, verifyIPs bool) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
if verifyIPs {
Expand All @@ -117,61 +201,106 @@ func NewDeliveryHandler(out chan *DeliveryReceipt, verifyIPs bool) http.HandlerF
}
}

var err error
// Check if the query is empty. If it is, it's just Nexmo
// making sure our service is up, so we don't want to return
// an error.
if req.URL.RawQuery == "" {
return
}

req.ParseForm()
// Decode the form data
m := new(DeliveryReceipt)

m.To = req.FormValue("to")
m.NetworkCode = req.FormValue("network-code")
m.MessageID = req.FormValue("messageId")
m.MSISDN = req.FormValue("msisdn")
m.Status = req.FormValue("status")
m.ErrorCode = req.FormValue("err-code")
m.Price = req.FormValue("price")
m.ClientReference = req.FormValue("client-ref")

t, err := url.QueryUnescape(req.FormValue("scts"))
receipt, err := ParseDeliveryReceipt(req)
if err != nil {
http.Error(w, "", http.StatusInternalServerError)
return
}

// Convert the timestamp to a time.Time.
timestamp, err := time.Parse("0601021504", t)
// Pass it out on the chan
out <- receipt
}
}

// ParseReceivedMessage unmarshals and processes the form data in a Nexmo request
// and returns a ReceivedMessage struct.
func ParseReceivedMessage(req *http.Request) (*ReceivedMessage, error) {
err := req.ParseForm()
if err != nil {
return nil, fmt.Errorf("failed to parse form data: %v", err)
}

// Decode the form data
m := new(ReceivedMessage)
switch t := req.FormValue("type"); t {
case "text":
m.Text, err = url.QueryUnescape(req.FormValue("text"))
if err != nil {
http.Error(w, "", http.StatusInternalServerError)
return
return nil, fmt.Errorf("failed to unescape field 'text': %v", err)
}
m.Type = TextMessage

m.SCTS = timestamp
case "unicode":
m.Text, err = url.QueryUnescape(req.FormValue("text"))
if err != nil {
return nil, fmt.Errorf("failed to unescape field 'text': %v", err)
}
m.Type = UnicodeMessage

t, err = url.QueryUnescape(req.FormValue("message-timestamp"))
case "binary":
// TODO: I have no idea if this data stuff works, as I'm unable to
// send data SMS messages.
data, err := url.QueryUnescape(req.FormValue("data"))
if err != nil {
http.Error(w, "", http.StatusInternalServerError)
return
return nil, fmt.Errorf("failed to unescape field 'data': %v", err)
}
m.Data = []byte(data)

// Convert the timestamp to a time.Time.
timestamp, err = time.Parse("2006-01-02 15:04:05", t)
udh, err := url.QueryUnescape(req.FormValue("udh"))
if err != nil {
http.Error(w, "", http.StatusInternalServerError)
return
return nil, fmt.Errorf("failed to unescape field 'udh': %v", err)
}
m.UDH = []byte(udh)
m.Type = BinaryMessage

m.Timestamp = timestamp
default:
//error
return nil, fmt.Errorf("unrecognized message type %s", t)
}

// Pass it out on the chan
out <- m
m.To = req.FormValue("to")
m.MSISDN = req.FormValue("msisdn")
m.NetworkCode = req.FormValue("network-code")
m.ID = req.FormValue("messageId")

m.Keyword = req.FormValue("keyword")
t, err := url.QueryUnescape(req.FormValue("message-timestamp"))
if err != nil {
return nil, fmt.Errorf("failed to unescape field 'message-timestamp': %v", err)
}

// Convert the timestamp to a time.Time.
timestamp, err := time.Parse("2006-01-02 15:04:05", t)
if err != nil {
return nil, fmt.Errorf("failed to parse timestamp for field 'message-timestamp': %v", err)
}

m.Timestamp = timestamp

// TODO: I don't know if this works as I've been unable to send an SMS
// message longer than 160 characters that doesn't get concatenated
// automatically.
if req.FormValue("concat") == "true" {
m.Concatenated = true
m.Concat.Reference = req.FormValue("concat-ref")
m.Concat.Total, err = strconv.Atoi(req.FormValue("concat-total"))
if err != nil {
return nil, fmt.Errorf("failed to convert field 'concat-total' to int: %v", err)
}
m.Concat.Part, err = strconv.Atoi(req.FormValue("concat-part"))
if err != nil {
return nil, fmt.Errorf("failed to convert field 'concat-part' to int: %v", err)
}
}

return m, nil
}

// NewMessageHandler creates a new http.HandlerFunc that can be used to listen
Expand All @@ -188,99 +317,20 @@ func NewMessageHandler(out chan *ReceivedMessage, verifyIPs bool) http.HandlerFu
}
}

var err error

// Check if the query is empty. If it is, it's just Nexmo
// making sure our service is up, so we don't want to return
// an error.
if req.URL.RawQuery == "" {
return
}

req.ParseForm()
// Decode the form data
m := new(ReceivedMessage)
switch req.FormValue("type") {
case "text":
m.Text, err = url.QueryUnescape(req.FormValue("text"))
if err != nil {
http.Error(w, "", http.StatusInternalServerError)
return
}
m.Type = TextMessage
case "unicode":
m.Text, err = url.QueryUnescape(req.FormValue("text"))
if err != nil {
http.Error(w, "", http.StatusInternalServerError)
return
}
m.Type = UnicodeMessage

// TODO: I have no idea if this data stuff works, as I'm unable to
// send data SMS messages.
case "binary":
data, err := url.QueryUnescape(req.FormValue("data"))
if err != nil {
http.Error(w, "", http.StatusInternalServerError)
return
}
m.Data = []byte(data)

udh, err := url.QueryUnescape(req.FormValue("udh"))
if err != nil {
http.Error(w, "", http.StatusInternalServerError)
return
}
m.UDH = []byte(udh)
m.Type = BinaryMessage

default:
//error
http.Error(w, "", http.StatusInternalServerError)
return
}

m.To = req.FormValue("to")
m.MSISDN = req.FormValue("msisdn")
m.NetworkCode = req.FormValue("network-code")
m.ID = req.FormValue("messageId")

m.Keyword = req.FormValue("keyword")
t, err := url.QueryUnescape(req.FormValue("message-timestamp"))
if err != nil {
http.Error(w, "", http.StatusInternalServerError)
return
}

// Convert the timestamp to a time.Time.
timestamp, err := time.Parse("2006-01-02 15:04:05", t)
message, err := ParseReceivedMessage(req)
if err != nil {
http.Error(w, "", http.StatusInternalServerError)
return
}

m.Timestamp = timestamp

// TODO: I don't know if this works as I've been unable to send an SMS
// message longer than 160 characters that doesn't get concatenated
// automatically.
if req.FormValue("concat") == "true" {
m.Concatenated = true
m.Concat.Reference = req.FormValue("concat-ref")
m.Concat.Total, err = strconv.Atoi(req.FormValue("concat-total"))
if err != nil {
http.Error(w, "", http.StatusInternalServerError)
return
}
m.Concat.Part, err = strconv.Atoi(req.FormValue("concat-part"))
if err != nil {
http.Error(w, "", http.StatusInternalServerError)
return
}
}

// Pass it out on the chan
out <- m
out <- message
}

}
Loading