summaryrefslogtreecommitdiffstats
path: root/response.go
diff options
context:
space:
mode:
Diffstat (limited to 'response.go')
-rw-r--r--response.go249
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)
+}