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. // // Returns an error 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. // // Returns an error 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 t is the zero time value, the modified parameter is unset. Otherwise, if // the time response parameter is empty, it's set to the current time. func (r *Response) SetModified(t time.Time) { setTime(&r.Message, "modified", t) if !t.IsZero() && 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. // // Returns an error if the host or path are invalid. func (r *Response) SetLocation(host, path string) error { err := ErrorInvalid{"invalid response: invalid location parameter"} if strings.ContainsRune(host, '/') { return err } l := host + path if err := validateRequestIntent(l); err != nil { return err } 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 } // Select retrieves the select response parameter. // // If the parameter isn't a valid selector, empty strings are returned. func (r *Response) Select() (selector, query string) { selector, query, _ = getSelect(&r.Message, "select") return } // SetSelect sets the select response parameter. // // If the selector name is empty, the select parameter is unset. func (r *Response) SetSelect(selector, query string) error { return setSelect(&r.Message, "select", selector, query) } // Validate validates the response intent and header parameter value format // (length, name, type, time, modified, location, reason, select) 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"} } if _, _, err := getSelect(&r.Message, "select"); err != nil { return err } 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) }