xref: /5.5.2/couchdb/src/couchdb/couch_doc.erl (revision fc5a97ef)
1% Licensed under the Apache License, Version 2.0 (the "License"); you may not
2% use this file except in compliance with the License. You may obtain a copy of
3% the License at
4%
5%   http://www.apache.org/licenses/LICENSE-2.0
6%
7% Unless required by applicable law or agreed to in writing, software
8% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10% License for the specific language governing permissions and limitations under
11% the License.
12
13-module(couch_doc).
14
15-export([parse_rev/1,parse_revs/1,rev_to_str/1,revs_to_strs/1]).
16-export([from_json_obj/1,to_json_obj/2,to_json_obj/1,to_json_bin/1,from_binary/3]).
17-export([validate_docid/1,with_uncompressed_body/1]).
18-export([with_ejson_body/1,with_json_body/1]).
19-export([to_raw_json_binary_views/1, to_raw_json_binary_views/3]).
20-export([to_json_base64/1, to_json_obj_with_bin_body/1]).
21
22-include("couch_db.hrl").
23
24
25% helpers used by to_json_obj
26to_json_rev(0, _) ->
27    [];
28to_json_rev(Start, RevId) ->
29    [{<<"rev">>, ?l2b([integer_to_list(Start),"-",revid_to_str(RevId)])}].
30
31to_json_vbinfo(PartId, Seq) ->
32    [{<<"seq">>, integer_to_binary(Seq)}]
33    ++ [{<<"vb">>, integer_to_binary(PartId)}].
34
35to_ejson_body({Body}, _ContentMeta) ->
36    {<<"json">>, {Body}};
37to_ejson_body(<<"{}">>, ?CONTENT_META_JSON) ->
38    {<<"json">>, {[]}};
39to_ejson_body(Body, ?CONTENT_META_JSON) ->
40    {_} = R = ?JSON_DECODE(Body),
41    {<<"json">>, R};
42to_ejson_body(Body, ContentMeta)
43        when ContentMeta /= ?CONTENT_META_JSON ->
44    {<<"base64">>, iolist_to_binary(base64:encode(iolist_to_binary(Body)))}.
45
46
47revid_to_str(RevId) ->
48    ?l2b(couch_util:to_hex(RevId)).
49
50rev_to_str({Pos, RevId}) ->
51    ?l2b([integer_to_list(Pos),"-",revid_to_str(RevId)]).
52
53
54revs_to_strs([]) ->
55    [];
56revs_to_strs([{Pos, RevId}| Rest]) ->
57    [rev_to_str({Pos, RevId}) | revs_to_strs(Rest)].
58
59to_json_meta(Meta) ->
60    lists:map(
61        fun({local_seq, Seq}) ->
62            {<<"local_seq">>, Seq}
63        end, Meta).
64
65revid_to_memcached_meta(<<_Cas:64, Expiration:32, Flags:32>> = _RevId) ->
66    {Expiration, Flags};
67revid_to_memcached_meta(_) ->
68    nil.
69
70content_meta_to_memcached_meta(?CONTENT_META_JSON) ->
71    nil;
72content_meta_to_memcached_meta(?CONTENT_META_INVALID_JSON) ->
73    <<"invalid_json">>;
74content_meta_to_memcached_meta(?CONTENT_META_NON_JSON_MODE) ->
75    <<"non-JSON mode">>;
76content_meta_to_memcached_meta(_Other) ->
77    <<"raw">>.
78
79to_memcached_meta(#doc{rev={_, RevId},content_meta=Meta}) ->
80    case content_meta_to_memcached_meta(Meta) of
81    nil ->
82        [];
83    AttReason ->
84        [{<<"att_reason">>, AttReason}]
85    end ++
86    case revid_to_memcached_meta(RevId) of
87    nil ->
88        [];
89    {Exp, Flags} ->
90        [{<<"expiration">>, Exp}, {<<"flags">>, Flags}]
91    end.
92
93to_deleted_meta(true) ->
94    [{<<"deleted">>, true}];
95to_deleted_meta(_) ->
96    [].
97
98to_full_ejson_meta(#doc{id=Id,deleted=Del,rev={Start, RevId},
99        meta=Meta, content_meta=ContentMeta}=Doc, PartId, Seq, IncludeType) ->
100    {
101        [json_id(Id)]
102        ++ to_json_rev(Start, RevId)
103        ++ case {PartId, Seq} of
104           {nil, nil} ->
105               [];
106           _ ->
107               to_json_vbinfo(PartId, Seq)
108           end
109        ++ to_json_meta(Meta)
110        ++ to_memcached_meta(Doc)
111        ++ to_deleted_meta(Del)
112        ++ case {IncludeType, ContentMeta} of
113        {true, ?CONTENT_META_JSON} ->
114            [{<<"type">>, <<"json">>}];
115        {true, _} ->
116            [{<<"type">>, <<"base64">>}];
117        _ ->
118           []
119        end
120    }.
121to_full_ejson_meta(Doc, IncludeType) ->
122    to_full_ejson_meta(Doc, nil, nil, IncludeType).
123
124to_json_obj(Doc0)->
125    to_json_obj(Doc0, []).
126
127to_json_obj(Doc0, _Options)->
128    JSONBin = to_json_bin(Doc0),
129    ?JSON_DECODE(JSONBin).
130
131to_json_obj_with_bin_body(Doc0) ->
132    Doc = #doc{content_meta = ContentMeta, body = Body} = with_json_body(Doc0),
133    Meta = to_full_ejson_meta(Doc, false),
134
135    BodyTuple =
136        case ContentMeta of
137            ?CONTENT_META_JSON ->
138                {json, Body};
139            _ ->
140                {base64, base64:encode(Body)}
141        end,
142    {[{meta, Meta}, BodyTuple]}.
143
144to_json_base64(Doc)->
145    #doc{body = Body} = Doc2 = with_uncompressed_body(Doc),
146    DocMeta = ?JSON_ENCODE(to_full_ejson_meta(Doc2, false)),
147    Bin = base64:encode(iolist_to_binary(Body)),
148    iolist_to_binary([<<"{\"meta\":">>,
149                      DocMeta,
150                      <<",\"base64\":\"">>,
151                      Bin,
152                      <<"\"}">>]).
153
154to_json_bin(Doc0)->
155    Doc = with_json_body(Doc0),
156    DocMeta = ?JSON_ENCODE(to_full_ejson_meta(Doc, false)),
157    {DocBody, _DocMeta} = to_raw_json_binary_views(Doc),
158    case Doc#doc.content_meta of
159    ?CONTENT_META_JSON ->
160        <<"{\"meta\":", DocMeta/binary, ",\"json\":", DocBody/binary, "}">>;
161    _ -> % encode as raw byte array (make conditional...)
162        <<"{\"meta\":", DocMeta/binary, ",\"base64\":", DocBody/binary, "}">>
163    end.
164
165
166
167mk_json_doc_from_binary(<<?LOCAL_DOC_PREFIX, _/binary>> = Id, Value) ->
168    case ejson:validate(Value) of
169    ok ->
170        #doc{id=Id, body = Value};
171    Error ->
172        throw(Error)
173    end;
174mk_json_doc_from_binary(Id, Value) ->
175    % Docs should accept any JSON value, not just objs and arrays
176    % (this would be anything that is acceptable as a value in an array
177    case ejson:validate([<<"[{},">>, Value, <<"]">>]) of
178    {error, invalid_json} ->
179        #doc{id=Id, body = Value,
180            content_meta = ?CONTENT_META_INVALID_JSON};
181    {error, garbage_after_value} ->
182        #doc{id=Id, body = Value,
183            content_meta = ?CONTENT_META_INVALID_JSON};
184    ok ->
185        #doc{id=Id, body = Value,
186            content_meta = ?CONTENT_META_JSON}
187    end.
188
189from_binary(Id, Value, _WantJson=true) ->
190    mk_json_doc_from_binary(Id, Value);
191from_binary(Id, Value, false) ->
192    #doc{id=Id, body = Value,
193            content_meta = ?CONTENT_META_NON_JSON_MODE}.
194
195from_json_obj({Props}) ->
196    transfer_fields(Props, #doc{});
197
198from_json_obj(_Other) ->
199    throw({bad_request, "Document must be a JSON object"}).
200
201parse_revid(RevId) when is_binary(RevId) ->
202    parse_revid(?b2l(RevId));
203parse_revid(RevId) ->
204    Size = length(RevId),
205    RevInt = erlang:list_to_integer(RevId, 16),
206     <<RevInt:(Size*4)>>.
207
208
209parse_rev(Rev) when is_binary(Rev) ->
210    parse_rev(?b2l(Rev));
211parse_rev(Rev) when is_list(Rev) ->
212    SplitRev = lists:splitwith(fun($-) -> false; (_) -> true end, Rev),
213    case SplitRev of
214        {Pos, [$- | RevId]} -> {list_to_integer(Pos), parse_revid(RevId)};
215        _Else -> throw({bad_request, <<"Invalid rev format">>})
216    end;
217parse_rev(_BadRev) ->
218    throw({bad_request, <<"Invalid rev format">>}).
219
220parse_revs([]) ->
221    [];
222parse_revs([Rev | Rest]) ->
223    [parse_rev(Rev) | parse_revs(Rest)].
224
225
226validate_docid(Id) when is_binary(Id) ->
227    ok;
228
229validate_docid(Id) ->
230    ?LOG_DEBUG("Document id is not a string: ~p", [?LOG_USERDATA(Id)]),
231    throw({bad_request, <<"Document id must be a string">>}).
232
233
234transfer_meta([], Doc) ->
235    Doc;
236
237transfer_meta([{<<"id">>, Id} | Rest], Doc) when is_list(Id) ->
238    BinId = list_to_binary(Id),
239    validate_docid(BinId),
240    transfer_meta(Rest, Doc#doc{id=BinId});
241
242transfer_meta([{<<"id">>, Id} | Rest], Doc) ->
243    validate_docid(Id),
244    transfer_meta(Rest, Doc#doc{id=Id});
245
246transfer_meta([{<<"rev">>, Rev} | Rest], #doc{rev={0, _}}=Doc) ->
247    {Pos, RevId} = parse_rev(Rev),
248    transfer_meta(Rest,
249            Doc#doc{rev={Pos, RevId}});
250
251transfer_meta([{<<"rev">>, _Rev} | Rest], Doc) ->
252    % we already got the rev from the _revisions
253    transfer_meta(Rest, Doc);
254
255transfer_meta([{<<"deleted">>, B} | Rest], Doc) when is_boolean(B) ->
256    transfer_meta(Rest, Doc#doc{deleted=B});
257
258transfer_meta([{_Other, _} | Rest], Doc) ->
259    % Ignore other meta
260    transfer_meta(Rest, Doc).
261
262
263transfer_fields([], #doc{body={Fields}}=Doc) when is_list(Fields) ->
264    % convert fields back to json object
265    Doc#doc{body=?JSON_ENCODE({Fields}), content_meta=?CONTENT_META_JSON};
266
267transfer_fields([], #doc{}=Doc) ->
268    Doc;
269
270% if the body is nested we can transfer it without care for special fields.
271transfer_fields([{<<"json">>, Json} | Rest], Doc) ->
272    transfer_fields(Rest, Doc#doc{body=Json, content_meta=?CONTENT_META_JSON});
273
274% in case the body is a blob we transfer it as base64.
275transfer_fields([{<<"base64">>, Bin} | Rest], Doc) ->
276    transfer_fields(Rest, Doc#doc{body=base64:decode(Bin), content_meta=?CONTENT_META_NON_JSON_MODE}); % todo base64
277
278transfer_fields([{<<"meta">>, {Meta}} | Rest], Doc) ->
279    DocWithMeta = transfer_meta(Meta, Doc),
280    transfer_fields(Rest, DocWithMeta);
281
282% unknown top level field
283transfer_fields([{Name, _} | _], _) ->
284    throw({doc_validation,
285        ?l2b(io_lib:format("User data must be in the `json` field, please nest `~s`", [Name]))}).
286
287
288with_ejson_body(Doc) ->
289    Uncompressed = with_uncompressed_body(Doc),
290    #doc{body = Body, content_meta=Meta} = Uncompressed,
291    {_Type, EJSONBody}= to_ejson_body(Body, Meta),
292    Uncompressed#doc{body = EJSONBody}.
293
294with_json_body(Doc) ->
295    case with_uncompressed_body(Doc) of
296    #doc{body = Body} = Doc2 when is_tuple(Body)->
297        Doc2#doc{body = ?JSON_ENCODE(Body), content_meta=?CONTENT_META_JSON};
298    #doc{body = Body} = Doc2 when is_binary(Body)->
299        Doc2;
300    #doc{} = Doc2 ->
301        Doc2
302    end.
303
304
305with_uncompressed_body(#doc{body = {_}} = Doc) ->
306    Doc;
307with_uncompressed_body(#doc{body = Body, content_meta = Meta} = Doc)
308        when (Meta band ?CONTENT_META_SNAPPY_COMPRESSED) > 0 ->
309    NewMeta = Meta band (bnot ?CONTENT_META_SNAPPY_COMPRESSED),
310    Doc#doc{body = couch_compress:decompress(Body), content_meta = NewMeta};
311with_uncompressed_body(Doc) ->
312    Doc.
313
314
315json_id(Id) ->
316    case couch_util:validate_utf8(Id) of
317    false -> % encode as raw byte array
318        {<<"id">>, binary_to_list(iolist_to_binary(Id))};
319    _ ->
320        {<<"id">>, Id}
321    end.
322
323to_raw_json_binary_views(Doc0, PartId, Seq) ->
324    Doc = with_json_body(Doc0),
325    MetaBin = case {PartId, Seq} of
326    {nil, nil} ->
327        ?JSON_ENCODE(to_full_ejson_meta(Doc, true));
328    _ ->
329        ?JSON_ENCODE(to_full_ejson_meta(Doc, PartId, Seq, true))
330    end,
331    ContentBin = case Doc#doc.content_meta of
332    ?CONTENT_META_JSON ->
333        iolist_to_binary(Doc#doc.body);
334    _ -> % encode as raw byte array (make conditional...)
335        iolist_to_binary([<<"\"">>, base64:encode(iolist_to_binary(Doc#doc.body)), <<"\"">>])
336    end,
337    {ContentBin, MetaBin}.
338to_raw_json_binary_views(Doc0) ->
339    to_raw_json_binary_views(Doc0, nil, nil).
340