getOCCValue retrieves the optimistic concurrency control value for a document (Revision ID or CV) from several possible sources: - Query parameter "rev" - If-Match header - Body field "_rev" or "_cv" (if the body is provided in the request) It also validates that the provided OCC value exactly match
(optionalBody db.Body)
| 352 | // - Body field "_rev" or "_cv" (if the body is provided in the request) |
| 353 | // It also validates that the provided OCC value exactly matches the corresponding body field if it exists in multiple places. |
| 354 | func (h *handler) getOCCValue(optionalBody db.Body) (occValue string, occValueType occVersionType, err error) { |
| 355 | // skipBodyMatchValidation can skip the validation of the occValue inside the body, since in some cases we're pulling that value out of the body anyway. |
| 356 | var skipBodyMatchValidation bool |
| 357 | |
| 358 | // occValue is the optimistic concurrency control value, which can be either the current rev ID or CV - used to prevent lost updates. |
| 359 | // we grab occValue from either query param, Etag header, or request body (in that order) |
| 360 | if revQuery := h.getQuery("rev"); revQuery != "" { |
| 361 | occValue = revQuery |
| 362 | // try to detect occ Values that are not URL Query escaped |
| 363 | // - `+` which can appear in base64 strings is converted to a space when not escaped properly |
| 364 | // other characters are difficult to correctly detect, since the value is already unescaped |
| 365 | if strings.ContainsAny(occValue, " ") { |
| 366 | return "", 0, base.HTTPErrorf(http.StatusBadRequest, "Bad rev query parameter: %q - ensure this query parameter value is URL Encoded", occValue) |
| 367 | } |
| 368 | occValueType = guessOCCVersionTypeFromValue(occValue) |
| 369 | } else if ifMatch, err := h.getEtag("If-Match"); err != nil { |
| 370 | return "", 0, err |
| 371 | } else if ifMatch != "" { |
| 372 | occValue = ifMatch |
| 373 | occValueType = guessOCCVersionTypeFromValue(occValue) |
| 374 | } else if bodyCV, ok := optionalBody[db.BodyCV]; ok { |
| 375 | if bodyCVStr, ok := bodyCV.(string); ok { |
| 376 | occValue = bodyCVStr |
| 377 | occValueType = VersionTypeCV |
| 378 | skipBodyMatchValidation = true |
| 379 | } |
| 380 | } else if bodyRev, ok := optionalBody[db.BodyRev]; ok { |
| 381 | if bodyRevStr, ok := bodyRev.(string); ok { |
| 382 | occValue = bodyRevStr |
| 383 | occValueType = VersionTypeRevTreeID |
| 384 | skipBodyMatchValidation = true |
| 385 | } |
| 386 | } else { |
| 387 | // empty occValue - treat as a create operation without any parent |
| 388 | return "", VersionTypeRevTreeID, nil |
| 389 | } |
| 390 | |
| 391 | // defensive measure against falling out of above without a type set |
| 392 | if occValueType == VersionTypeUnknown { |
| 393 | return "", 0, base.HTTPErrorf(http.StatusBadRequest, "Invalid version type for OCC value: %q", occValue) |
| 394 | } |
| 395 | |
| 396 | // ensure the value provided matches exactly the one that may also be supplied in the body |
| 397 | if !skipBodyMatchValidation { |
| 398 | switch occValueType { |
| 399 | case VersionTypeRevTreeID: |
| 400 | if optionalBody[db.BodyRev] != nil && occValue != optionalBody[db.BodyRev] { |
| 401 | return "", 0, base.HTTPErrorf(http.StatusBadRequest, "Revision IDs provided do not match") |
| 402 | } |
| 403 | case VersionTypeCV: |
| 404 | if optionalBody[db.BodyCV] != nil && occValue != optionalBody[db.BodyCV] { |
| 405 | return "", 0, base.HTTPErrorf(http.StatusBadRequest, "CVs provided do not match") |
| 406 | } |
| 407 | default: |
| 408 | return "", 0, base.HTTPErrorf(http.StatusBadRequest, "Unknown version type provided: %q", occValue) |
| 409 | } |
| 410 | } |
| 411 |
no test coverage detected