github.com/cs3org/reva/v2@v2.27.7/internal/http/services/owncloud/ocs/response/response.go (about) 1 // Copyright 2018-2021 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package response 20 21 import ( 22 "bytes" 23 "context" 24 "encoding/json" 25 "encoding/xml" 26 "net/http" 27 "reflect" 28 29 "github.com/cs3org/reva/v2/pkg/appctx" 30 "github.com/go-chi/chi/v5" 31 ) 32 33 type key int 34 35 const ( 36 apiVersionKey key = 1 37 ) 38 39 var ( 40 defaultStatusCodeMapper = OcsV2StatusCodes 41 ) 42 43 // Response is the top level response structure 44 type Response struct { 45 OCS *Payload `json:"ocs"` 46 } 47 48 // Payload combines response metadata and data 49 type Payload struct { 50 XMLName struct{} `json:"-" xml:"ocs"` 51 Meta Meta `json:"meta" xml:"meta"` 52 Data interface{} `json:"data,omitempty" xml:"data,omitempty"` 53 } 54 55 var ( 56 elementStartElement = xml.StartElement{Name: xml.Name{Local: "element"}} 57 metaStartElement = xml.StartElement{Name: xml.Name{Local: "meta"}} 58 ocsName = xml.Name{Local: "ocs"} 59 dataName = xml.Name{Local: "data"} 60 ) 61 62 // MarshalXML handles ocs specific wrapping of array members in 'element' tags for the data 63 func (p Payload) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { 64 // first the easy part 65 // use ocs as the surrounding tag 66 start.Name = ocsName 67 if err = e.EncodeToken(start); err != nil { 68 return 69 } 70 71 // encode the meta tag 72 if err = e.EncodeElement(p.Meta, metaStartElement); err != nil { 73 return 74 } 75 76 // we need to use reflection to determine if p.Data is an array or a slice 77 rt := reflect.TypeOf(p.Data) 78 if rt != nil && (rt.Kind() == reflect.Array || rt.Kind() == reflect.Slice) { 79 // this is how to wrap the data elements in their own <element> tag 80 v := reflect.ValueOf(p.Data) 81 if err = e.EncodeToken(xml.StartElement{Name: dataName}); err != nil { 82 return 83 } 84 for i := 0; i < v.Len(); i++ { 85 if err = e.EncodeElement(v.Index(i).Interface(), elementStartElement); err != nil { 86 return 87 } 88 } 89 if err = e.EncodeToken(xml.EndElement{Name: dataName}); err != nil { 90 return 91 } 92 } else if err = e.EncodeElement(p.Data, xml.StartElement{Name: dataName}); err != nil { 93 return 94 } 95 96 // write the closing <ocs> tag 97 if err = e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil { 98 return 99 } 100 return 101 } 102 103 // Meta holds response metadata 104 type Meta struct { 105 Status string `json:"status" xml:"status"` 106 StatusCode int `json:"statuscode" xml:"statuscode"` 107 Message string `json:"message" xml:"message"` 108 TotalItems string `json:"totalitems,omitempty" xml:"totalitems,omitempty"` 109 ItemsPerPage string `json:"itemsperpage,omitempty" xml:"itemsperpage,omitempty"` 110 } 111 112 // MetaOK is the default ok response 113 var MetaOK = Meta{Status: "ok", StatusCode: 100, Message: "OK"} 114 115 // MetaFailure is a failure response with code 101 116 var MetaFailure = Meta{Status: "", StatusCode: 101, Message: "Failure"} 117 118 // MetaInvalidInput is an error response with code 102 119 var MetaInvalidInput = Meta{Status: "", StatusCode: 102, Message: "Invalid Input"} 120 121 // MetaForbidden is an error response with code 104 122 var MetaForbidden = Meta{Status: "", StatusCode: 104, Message: "Forbidden"} 123 124 // MetaBadRequest is used for unknown errors 125 var MetaBadRequest = Meta{Status: "error", StatusCode: 400, Message: "Bad Request"} 126 127 // MetaPathNotFound is returned when trying to share not existing resources 128 var MetaPathNotFound = Meta{Status: "failure", StatusCode: 404, Message: MessagePathNotFound} 129 130 // MetaLocked is returned when trying to share not existing resources 131 var MetaLocked = Meta{Status: "failure", StatusCode: 423, Message: "The file is locked"} 132 133 // MetaServerError is returned on server errors 134 var MetaServerError = Meta{Status: "error", StatusCode: 996, Message: "Server Error"} 135 136 // MetaUnauthorized is returned on unauthorized requests 137 var MetaUnauthorized = Meta{Status: "error", StatusCode: 997, Message: "Unauthorised"} 138 139 // MetaNotFound is returned when trying to access not existing resources 140 var MetaNotFound = Meta{Status: "error", StatusCode: 998, Message: "Not Found"} 141 142 // MetaUnknownError is used for unknown errors 143 var MetaUnknownError = Meta{Status: "error", StatusCode: 999, Message: "Unknown Error"} 144 145 // MessageUserNotFound is used when a user can not be found 146 var MessageUserNotFound = "The requested user could not be found" 147 148 // MessageGroupNotFound is used when a group can not be found 149 var MessageGroupNotFound = "The requested group could not be found" 150 151 // MessagePathNotFound is used when a file or folder can not be found 152 var MessagePathNotFound = "Wrong path, file/folder doesn't exist" 153 154 // MessageShareExists is used when a user tries to create a new share for the same user 155 var MessageShareExists = "A share for the recipient already exists" 156 157 // MessageLockedForSharing is used when a user tries to create a new share until the file is in use by at least one user 158 var MessageLockedForSharing = "The file is locked until the file is in use by at least one user" 159 160 // WriteOCSSuccess handles writing successful ocs response data 161 func WriteOCSSuccess(w http.ResponseWriter, r *http.Request, d interface{}) { 162 WriteOCSData(w, r, MetaOK, d, nil) 163 } 164 165 // WriteOCSError handles writing error ocs responses 166 func WriteOCSError(w http.ResponseWriter, r *http.Request, c int, m string, err error) { 167 WriteOCSData(w, r, Meta{Status: "error", StatusCode: c, Message: m}, nil, err) 168 } 169 170 // WriteOCSData handles writing ocs data in json and xml 171 func WriteOCSData(w http.ResponseWriter, r *http.Request, m Meta, d interface{}, err error) { 172 WriteOCSResponse(w, r, Response{ 173 OCS: &Payload{ 174 Meta: m, 175 Data: d, 176 }, 177 }, err) 178 } 179 180 // WriteOCSResponse handles writing ocs responses in json and xml 181 func WriteOCSResponse(w http.ResponseWriter, r *http.Request, res Response, err error) { 182 if err != nil { 183 appctx.GetLogger(r.Context()). 184 Debug(). 185 Err(err). 186 Str("ocs_msg", res.OCS.Meta.Message). 187 Msg("writing ocs error response") 188 } 189 190 version := APIVersion(r.Context()) 191 m := statusCodeMapper(version) 192 statusCode := m(res.OCS.Meta) 193 if version == "2" && statusCode == http.StatusOK { 194 res.OCS.Meta.StatusCode = statusCode 195 } 196 197 var encoder func(Response) ([]byte, error) 198 if r.URL.Query().Get("format") == "json" { 199 w.Header().Set("Content-Type", "application/json; charset=utf-8") 200 encoder = encodeJSON 201 } else { 202 w.Header().Set("Content-Type", "text/xml; charset=utf-8") 203 encoder = encodeXML 204 } 205 w.WriteHeader(statusCode) 206 encoded, err := encoder(res) 207 if err != nil { 208 appctx.GetLogger(r.Context()).Error().Err(err).Msg("error encoding ocs response") 209 w.WriteHeader(http.StatusInternalServerError) 210 return 211 } 212 213 _, err = w.Write(encoded) 214 if err != nil { 215 appctx.GetLogger(r.Context()).Error().Err(err).Msg("error writing ocs response") 216 w.WriteHeader(http.StatusInternalServerError) 217 } 218 } 219 220 func encodeXML(res Response) ([]byte, error) { 221 marshalled, err := xml.Marshal(res.OCS) 222 if err != nil { 223 return nil, err 224 } 225 b := new(bytes.Buffer) 226 b.WriteString(xml.Header) 227 b.Write(marshalled) 228 return b.Bytes(), nil 229 } 230 231 func encodeJSON(res Response) ([]byte, error) { 232 return json.Marshal(res) 233 } 234 235 // OcsV1StatusCodes returns the http status codes for the OCS API v1. 236 func OcsV1StatusCodes(meta Meta) int { 237 return http.StatusOK 238 } 239 240 // OcsV2StatusCodes maps the OCS codes to http status codes for the ocs API v2. 241 func OcsV2StatusCodes(meta Meta) int { 242 sc := meta.StatusCode 243 switch sc { 244 case MetaNotFound.StatusCode: 245 return http.StatusNotFound 246 case MetaUnknownError.StatusCode: 247 fallthrough 248 case MetaServerError.StatusCode: 249 return http.StatusInternalServerError 250 case MetaUnauthorized.StatusCode: 251 return http.StatusUnauthorized 252 case MetaOK.StatusCode: 253 meta.StatusCode = http.StatusOK 254 return http.StatusOK 255 case MetaForbidden.StatusCode: 256 return http.StatusForbidden 257 } 258 // any 2xx, 4xx and 5xx will be used as is 259 if sc >= 200 && sc < 600 { 260 return sc 261 } 262 263 // any error codes > 100 are treated as client errors 264 if sc > 100 && sc < 200 { 265 return http.StatusBadRequest 266 } 267 268 // TODO change this status code? 269 return http.StatusOK 270 } 271 272 // WithAPIVersion puts the api version in the context. 273 func VersionCtx(next http.Handler) http.Handler { 274 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 275 version := chi.URLParam(r, "version") 276 if version == "" { 277 WriteOCSError(w, r, MetaBadRequest.StatusCode, "unknown ocs api version", nil) 278 return 279 } 280 w.Header().Set("Ocs-Api-Version", version) 281 282 // store version in context so handlers can access it 283 ctx := context.WithValue(r.Context(), apiVersionKey, version) 284 next.ServeHTTP(w, r.WithContext(ctx)) 285 }) 286 } 287 288 // APIVersion retrieves the api version from the context. 289 func APIVersion(ctx context.Context) string { 290 value := ctx.Value(apiVersionKey) 291 if value != nil { 292 return value.(string) 293 } 294 return "" 295 } 296 297 func statusCodeMapper(version string) func(Meta) int { 298 var mapper func(Meta) int 299 switch version { 300 case "1": 301 mapper = OcsV1StatusCodes 302 case "2": 303 mapper = OcsV2StatusCodes 304 default: 305 mapper = defaultStatusCodeMapper 306 } 307 return mapper 308 }