diff options
author | clsr <clsr@clsr.net> | 2017-08-18 13:46:10 +0200 |
---|---|---|
committer | clsr <clsr@clsr.net> | 2017-08-18 13:46:10 +0200 |
commit | da77deba78c8a7447b4a38324d2422a5df293b26 (patch) | |
tree | bb94b8e18eaf27a86e9fc21c4342d9e0a605baf7 /response.go | |
download | cnp-go-da77deba78c8a7447b4a38324d2422a5df293b26.tar.gz cnp-go-da77deba78c8a7447b4a38324d2422a5df293b26.zip |
Initial commitv0.1.0
Diffstat (limited to 'response.go')
-rw-r--r-- | response.go | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/response.go b/response.go new file mode 100644 index 0000000..bf23f20 --- /dev/null +++ b/response.go @@ -0,0 +1,249 @@ +package cnp + +import ( + "bytes" + "io" + "strings" + "time" +) + +// Response represents a CNP response message. +type Response struct { + Message +} + +// NewResponse creates a new Response from a response intent and optional body +// data. +func NewResponse(intent string, body []byte) (resp *Response, err error) { + var r io.Reader + if body != nil { + r = bytes.NewReader(body) + } + resp = &Response{*NewMessage("", r)} + err = resp.SetResponseIntent(intent) + return +} + +var responseIntents = map[string]bool{ + IntentOK: true, + IntentNotModified: true, + IntentRedirect: true, + IntentError: true, +} + +// ParseResponse parses a response message. +func ParseResponse(r io.Reader) (*Response, error) { + msg, err := ParseMessage(r) + if msg == nil { + return nil, err + } + return &Response{*msg}, err +} + +// ResponseIntent retrieves the response intent. +// If the intent is unknown, the "error" intent is returned. +func (r *Response) ResponseIntent() string { + s := r.Intent() + if !responseIntents[s] { + return IntentError + } + return s +} + +// SetResponseIntent sets the response intent. +// If the provided intent is not a known response intent, an error is returned +// and the intent is set to "error". +func (r *Response) SetResponseIntent(intent string) error { + if !responseIntents[intent] { + r.SetIntent(IntentError) + return ErrorInvalid{"invalid response: unknown response intent"} + } + r.SetIntent(intent) + return nil +} + +// Name retrieves the name response parameter. +// +// If the name response parameter is not a valid filename, an empty string is +// returned. +func (r *Response) Name() string { + name, err := getFilename(&r.Message, "name") + if err != nil { + return "" + } + return name +} + +// SetName sets the name response parameter. +// +// An error is raised if the name includes characters not valid in a filename +// (slash, null byte). +func (r *Response) SetName(name string) error { + return setFilename(&r.Message, "name", name) +} + +// Type retrieves the type response parameter. +// +// If the type response parameter is invalid or empty, the default value +// "application/octet-stream" is returned. +func (r *Response) Type() string { + typ, _ := getType(&r.Message, "type") + return typ +} + +// SetType sets the type response parameter. +// +// An error is raised if typ is not a valid format for a MIME type. +func (r *Response) SetType(typ string) error { + return setType(&r.Message, "type", typ) +} + +// Time retrieves the time response parameter. +// +// If the parameter isn't a valid RFC3339 timestamp, a zero time.Time is +// returned. +func (r *Response) Time() time.Time { + t, err := getTime(&r.Message, "time") + if err != nil { + return time.Time{} + } + return t +} + +// SetTime sets the time response parameter. +// +// If t is the zero time value, the time parameter is unset. +func (r *Response) SetTime(t time.Time) { + setTime(&r.Message, "time", t) +} + +// Modified retrieves the modified Response parameter. +// +// If the parameter isn't a valid RFC3339 timestamp, a zero time.Time is +// returned. +func (r *Response) Modified() time.Time { + t, err := getTime(&r.Message, "modified") + if err != nil { + return time.Time{} + } + return t +} + +// SetModified sets the modified response parameter. +// +// If the time response parameter is empty, it's set to the current time. +// If t is the zero time value, the modified parameter is unset. +func (r *Response) SetModified(t time.Time) { + setTime(&r.Message, "modified", t) + if r.Time().IsZero() { + r.SetTime(time.Now()) + } +} + +// Location retrieves the host and path from the location response +// parameter. +// +// If the location parameter is empty, it returns empty host and path. If the +// location parameter is invalid, an error is returned. +func (r *Response) Location() (host, path string, err error) { + l := r.Param("location") + if l == "" { + return "", "", nil + } + if err := validateRequestIntent(l); err != nil { + return "", "", err + } + ss := strings.SplitN(l, "/", 2) + return ss[0], "/" + ss[1], nil +} + +// SetLocation sets the location response parameter to host and path. +// +// If the host or path are invalid +func (r *Response) SetLocation(host, path string) error { + if strings.ContainsRune(host, '/') { + return ErrorInvalid{"invalid response: invalid location parameter"} + } + l := host + path + if err := validateRequestIntent(l); err != nil { + return ErrorInvalid{"invalid response: invalid location parameter"} + } + r.SetParam("location", l) + return nil +} + +var responseErrorReasons = map[string]bool{ + ReasonSyntax: true, + ReasonVersion: true, + ReasonInvalid: true, + ReasonNotSupported: true, + ReasonTooLarge: true, + ReasonNotFound: true, + ReasonDenied: true, + ReasonRejected: true, + ReasonServerError: true, + "": true, +} + +// Reason retrieves the reason response parameter. +// +// If the reason is nonempty and unknown, "server_error" is returned. +func (r *Response) Reason() string { + reason := r.Param("reason") + if _, ok := responseErrorReasons[reason]; ok { + return reason + } + return ReasonServerError +} + +// SetReason sets the reason response parameter. +// +// If the reason is nonempty and unknown, it sets "server_error" instead. +func (r *Response) SetReason(reason string) error { + if _, ok := responseErrorReasons[reason]; !ok { + r.SetParam("reason", ReasonServerError) + return ErrorInvalid{"invalid response: unknown error reason"} + } + r.SetParam("reason", reason) + return nil +} + +// Validate validates the response intent and header parameter value format +// (length, name, type, time, modified, location, reason) +func (r *Response) Validate() error { + if !responseIntents[r.Intent()] { + return ErrorInvalid{"invalid response: unknown response intent"} + } + if err := r.Message.Validate(); err != nil { + return err + } + if _, err := getFilename(&r.Message, "name"); err != nil { + return err + } + if _, err := getType(&r.Message, "type"); err != nil { + return err + } + if _, err := getTime(&r.Message, "time"); err != nil { + return err + } + if _, err := getTime(&r.Message, "modified"); err != nil { + return err + } + if h, p, err := r.Location(); err != nil { + return err + } else if r.ResponseIntent() == IntentRedirect && h == "" && p == "" { + return ErrorInvalid{"invalid response: redirect response needs location parameter"} + } + if !responseErrorReasons[r.Param("reason")] { + return ErrorInvalid{"invalid response: unknown error reason"} + } + return nil +} + +// Write writes the response to w. +func (r *Response) Write(w io.Writer) error { + if _, ok := r.Header.Parameters["length"]; !ok { + r.TryComputeLength() + } + return r.Message.Write(w) +} |