// parseIntoBSO takes JSON and turns into a syncstorage.PutBSOInput
func parseIntoBSO(jsonData json.RawMessage, bso *syncstorage.PutBSOInput) *parseError {
	// make sure JSON BSO data *only* has the keys that are allowed
	var bkeys map[string]json.RawMessage
	err := json.Unmarshal(jsonData, &bkeys)

	if err != nil {
		return &parseError{field: "-", msg: "Could not parse into object"}
	} else {
		for k, _ := range bkeys {
			switch k {
			case "id", "payload", "ttl", "sortindex":
				// it's ok
			case "modified":
				// to pass the python test_meta_global_sanity functional test
			default:
				return &parseError{field: k, msg: "invalid field"}
			}
		}
	}

	var bId string

	// check to make sure values are appropriate
	if r, ok := bkeys["id"]; ok {
		err := json.Unmarshal(r, &bId)
		if err != nil {
			return &parseError{field: "id", msg: "Invalid format"}
		} else {
			bso.Id = bId
		}
	}

	if r, ok := bkeys["payload"]; ok {
		var payload string
		err := json.Unmarshal(r, &payload)
		if err != nil {
			return &parseError{bId: bId, field: "payload", msg: "Invalid format"}
		} else {
			bso.Payload = &payload
		}
	}

	if r, ok := bkeys["ttl"]; ok {
		var ttl int
		err := json.Unmarshal(r, &ttl)
		if err != nil {
			return &parseError{bId: bId, field: "ttl", msg: "Invalid format"}
		} else {
			bso.TTL = &ttl
		}
	}

	if r, ok := bkeys["sortindex"]; ok {
		var sortindex int
		err := json.Unmarshal(r, &sortindex)
		if err != nil {
			return &parseError{bId: bId, field: "sortindex", msg: "Invalid format"}
		} else {
			bso.SortIndex = &sortindex
		}
	}

	return nil
}
func (s *SyncUserHandler) hBsoPUT(w http.ResponseWriter, r *http.Request) {
	if !AcceptHeaderOk(w, r) {
		return
	}

	// accept text/plain from old (broken) clients
	ct := getMediaType(r.Header.Get("Content-Type"))
	if ct != "application/json" && ct != "text/plain" && ct != "application/newlines" {
		sendRequestProblem(w, r, http.StatusUnsupportedMediaType, errors.Errorf("Not acceptable Content-Type: %s", ct))
		return
	}

	var (
		bId      string
		ok       bool
		cId      int
		modified int
		err      error
	)

	if bId, ok = extractBsoIdFail(w, r); !ok {
		return
	}

	cId, err = s.getcid(r, true)
	if err != nil {
		InternalError(w, r, err)
		return
	}

	modified, err = s.db.GetBSOModified(cId, bId)
	if err != nil {
		if err != syncstorage.ErrNotFound {
			InternalError(w, r, errors.Wrap(err, "Could not get Modified ts"))
			return
		}
	}

	if sentNotModified(w, r, modified) {
		return
	}

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		InternalError(w, r, errors.New("PUT could not read JSON body"))
		return
	}

	var bso syncstorage.PutBSOInput
	if err := parseIntoBSO(body, &bso); err != nil {
		WeaveInvalidWBOError(w, r, errors.Wrap(err, "Could not parse body into BSO"))
		return
	}

	if bso.Payload != nil && len(*bso.Payload) > s.config.MaxRecordPayloadBytes {
		sendRequestProblem(w, r,
			http.StatusRequestEntityTooLarge,
			errors.New("Payload too big"))
		return
	}

	// change bso.TTL to milliseconds (what the db uses)
	// from seconds (what client's send)
	if bso.TTL != nil {
		tmp := *bso.TTL * 1000
		bso.TTL = &tmp
	}

	modified, err = s.db.PutBSO(cId, bId, bso.Payload, bso.SortIndex, bso.TTL)

	if err != nil {
		sendRequestProblem(w, r, http.StatusBadRequest, err)
		return
	}
	m := syncstorage.ModifiedToString(modified)
	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("X-Last-Modified", m)
	w.Write([]byte(m))
}