C0 code coverage information

Generated on 2009-08-09 21:22:56 with etap 0.3.4.

Name Total lines Lines of code Total coverage Code coverage
couch_httpd_db ?? ?? ??
18% 
....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_httpd_db).
...14 -include("couch_db.hrl").
...15 
...16 -export([handle_request/1, handle_compact_req/2, handle_design_req/2,
...17     db_req/2, couch_doc_open/4,handle_changes_req/2,
...18     update_doc_result_to_json/1, update_doc_result_to_json/2,
...19     handle_design_info_req/2, handle_view_cleanup_req/2]).
...20 
...21 -import(couch_httpd,
...22     [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
...23     start_json_response/2,send_chunk/2,end_json_response/1,
...24     start_chunked_response/3, absolute_uri/2]).
...25 
...26 -record(doc_query_args, {
...27     options = [],
...28     rev = nil,
...29     open_revs = [],
...30     show = nil
...31 }).
...32 
...33 % Database request handlers
...34 handle_request(#httpd{path_parts=[DbName|RestParts],method=Method,
...35         db_url_handlers=DbUrlHandlers}=Req)->
...36     case {Method, RestParts} of
...37     {'PUT', []} ->
...38         create_db_req(Req, DbName);
...39     {'DELETE', []} ->
...40         delete_db_req(Req, DbName);
...41     {_, []} ->
...42         do_db_req(Req, fun db_req/2);
...43     {_, [SecondPart|_]} ->
...44         Handler = couch_util:dict_find(SecondPart, DbUrlHandlers, fun db_req/2),
...45         do_db_req(Req, Handler)
...46     end.
...47 
...48 get_changes_timeout(Req, Resp) ->
...49     DefaultTimeout = list_to_integer(
...50             couch_config:get("httpd", "changes_timeout", "60000")),
...51     case couch_httpd:qs_value(Req, "heartbeat") of
...52     undefined ->
...53         case couch_httpd:qs_value(Req, "timeout") of
...54         undefined ->
...55             {DefaultTimeout, fun() -> stop end};
...56         TimeoutList ->
...57             {lists:min([DefaultTimeout, list_to_integer(TimeoutList)]),
...58                 fun() -> stop end}
...59         end;
...60     "true" ->
...61         {DefaultTimeout, fun() -> send_chunk(Resp, "\n"), ok end};
...62     TimeoutList ->
...63         {lists:min([DefaultTimeout, list_to_integer(TimeoutList)]),
...64             fun() -> send_chunk(Resp, "\n"), ok end}
...65     end.
...66 
...67 
...68 handle_changes_req(#httpd{method='GET',path_parts=[DbName|_]}=Req, Db) ->
...69     StartSeq = list_to_integer(couch_httpd:qs_value(Req, "since", "0")),
...70     {ok, Resp} = start_json_response(Req, 200),
...71     send_chunk(Resp, "{\"results\":[\n"),
...72     case couch_httpd:qs_value(Req, "feed", "normal") of
...73     ResponseType when ResponseType == "continuous" orelse ResponseType == "longpoll"->
...74         Self = self(),
...75         {ok, Notify} = couch_db_update_notifier:start_link(
...76             fun({_, DbName0}) when DbName0 == DbName ->
...77                 Self ! db_updated;
...78             (_) ->
...79                 ok
...80             end),
...81         {Timeout, TimeoutFun} = get_changes_timeout(Req, Resp),
...82         couch_stats_collector:track_process_count(Self,
...83                             {httpd, clients_requesting_changes}),
...84         try
...85             keep_sending_changes(Req, Resp, Db, StartSeq, <<"">>, Timeout, TimeoutFun, ResponseType)
...86         after
...87             couch_db_update_notifier:stop(Notify),
...88             get_rest_db_updated() % clean out any remaining update messages
...89         end;
...90     "normal" ->
...91         {ok, {LastSeq, _Prepend}} =
...92                 send_changes(Req, Resp, Db, StartSeq, <<"">>),
...93         end_sending_changes(Resp, LastSeq)
...94     end;
...95 
...96 handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
...97     send_method_not_allowed(Req, "GET,HEAD").
...98 
...99 % waits for a db_updated msg, if there are multiple msgs, collects them.
..100 wait_db_updated(Timeout, TimeoutFun) ->
..101     receive db_updated -> get_rest_db_updated()
..102     after Timeout ->
..103         case TimeoutFun() of
..104         ok -> wait_db_updated(Timeout, TimeoutFun);
..105         stop -> stop
..106         end
..107     end.
..108 
..109 get_rest_db_updated() ->
..110     receive db_updated -> get_rest_db_updated()
..111     after 0 -> updated
..112     end.
..113     
..114 end_sending_changes(Resp, EndSeq) ->
..115     send_chunk(Resp, io_lib:format("\n],\n\"last_seq\":~w}\n", [EndSeq])),
..116     end_json_response(Resp).
..117 
..118 keep_sending_changes(#httpd{user_ctx=UserCtx,path_parts=[DbName|_]}=Req, Resp, Db, StartSeq, Prepend, Timeout, TimeoutFun, ResponseType) ->
..119     {ok, {EndSeq, Prepend2}} = send_changes(Req, Resp, Db, StartSeq, Prepend),
..120     couch_db:close(Db),
..121     if
..122     EndSeq > StartSeq, ResponseType == "longpoll" ->
..123         end_sending_changes(Resp, EndSeq);
..124     true ->
..125         case wait_db_updated(Timeout, TimeoutFun) of
..126         updated ->
..127             {ok, Db2} = couch_db:open(DbName, [{user_ctx, UserCtx}]),
..128             keep_sending_changes(Req, Resp, Db2, EndSeq, Prepend2, Timeout, TimeoutFun, ResponseType);
..129         stop ->
..130             end_sending_changes(Resp, EndSeq)
..131         end
..132     end.
..133 
..134 send_changes(Req, Resp, Db, StartSeq, Prepend0) ->
..135     Style = list_to_existing_atom(
..136             couch_httpd:qs_value(Req, "style", "main_only")),
..137     {FilterFun, EndFilterFun} = make_filter_funs(Req, Db),
..138     try
..139         couch_db:changes_since(Db, Style, StartSeq,
..140         fun([#doc_info{id=Id, high_seq=Seq}|_]=DocInfos, {_, Prepend}) ->
..141             Results0 = [FilterFun(DocInfo) || DocInfo <- DocInfos],
..142             Results = [Result || Result <- Results0, Result /= null],
..143             case Results of
..144             [] ->
..145                 {ok, {Seq, Prepend}};
..146             _ ->
..147                 send_chunk(Resp,
..148                     [Prepend, ?JSON_ENCODE({[{seq,Seq}, {id, Id},
..149                                               {changes,Results}]})]),
..150                 {ok, {Seq, <<",\n">>}}
..151             end
..152         end, {StartSeq, Prepend0})
..153     after
..154         EndFilterFun()
..155     end.
..156 
..157 make_filter_funs(Req, Db) ->
..158     Filter = couch_httpd:qs_value(Req, "filter", ""),
..159     case [list_to_binary(couch_httpd:unquote(Part))
..160             || Part <- string:tokens(Filter, "/")] of
..161     [] ->
..162     {fun(#doc_info{revs=[#rev_info{rev=Rev}|_]}) ->
..163             {[{rev, couch_doc:rev_to_str(Rev)}]}
..164         end,
..165         fun() -> ok end};
..166     [DName, FName] ->
..167         DesignId = <<"_design/", DName/binary>>,
..168         #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []),
..169         Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
..170         FilterSrc = couch_util:get_nested_json_value({Props}, [<<"filters">>, FName]),
..171         {ok, Pid} = couch_query_servers:start_filter(Lang, FilterSrc),
..172         FilterFun = fun(DInfo = #doc_info{revs=[#rev_info{rev=Rev}|_]}) ->
..173             {ok, Doc} = couch_db:open_doc(Db, DInfo),
..174             {ok, Pass} = couch_query_servers:filter_doc(Pid, Doc, Req, Db),
..175             case Pass of
..176             true ->
..177                 {[{rev, couch_doc:rev_to_str(Rev)}]};
..178             false ->
..179                 null
..180             end
..181         end,
..182         EndFilterFun = fun() ->
..183             couch_query_servers:end_filter(Pid)
..184         end,
..185         {FilterFun, EndFilterFun};
..186     _Else ->
..187         throw({bad_request, 
..188             "filter parameter must be of the form `designname/filtername`"})
..189     end.  
..190 
..191 handle_compact_req(#httpd{method='POST',path_parts=[DbName,_,Id|_]}=Req, _Db) ->
..192     ok = couch_view_compactor:start_compact(DbName, Id),
..193     send_json(Req, 202, {[{ok, true}]});
..194 
..195 handle_compact_req(#httpd{method='POST'}=Req, Db) ->
..196     ok = couch_db:start_compact(Db),
..197     send_json(Req, 202, {[{ok, true}]});
..198 
..199 handle_compact_req(Req, _Db) ->
..200     send_method_not_allowed(Req, "POST").
..201 
..202 handle_view_cleanup_req(#httpd{method='POST'}=Req, Db) ->
..203     % delete unreferenced index files
..204     ok = couch_view:cleanup_index_files(Db),
..205     send_json(Req, 202, {[{ok, true}]});
..206 
..207 handle_view_cleanup_req(Req, _Db) ->
..208     send_method_not_allowed(Req, "POST").
..209 
..210 
..211 handle_design_req(#httpd{
..212         path_parts=[_DbName,_Design,_DesName, <<"_",_/binary>> = Action | _Rest],
..213         design_url_handlers = DesignUrlHandlers
..214     }=Req, Db) ->
..215     Handler = couch_util:dict_find(Action, DesignUrlHandlers, fun db_req/2),
..216     Handler(Req, Db);
..217 
..218 handle_design_req(Req, Db) ->
..219     db_req(Req, Db).
..220 
..221 handle_design_info_req(#httpd{
..222             method='GET',
..223             path_parts=[_DbName, _Design, DesignName, _]
..224         }=Req, Db) ->
..225     DesignId = <<"_design/", DesignName/binary>>,
..226     {ok, GroupInfoList} = couch_view:get_group_info(Db, DesignId),
..227     send_json(Req, 200, {[
..228         {name, DesignName},
..229         {view_index, {GroupInfoList}}
..230     ]});
..231 
..232 handle_design_info_req(Req, _Db) ->
..233     send_method_not_allowed(Req, "GET").
..234 
..235 create_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) ->
..236     ok = couch_httpd:verify_is_server_admin(Req),
..237     case couch_server:create(DbName, [{user_ctx, UserCtx}]) of
..238     {ok, Db} ->
..239         couch_db:close(Db),
..240         DocUrl = absolute_uri(Req, "/" ++ couch_util:url_encode(DbName)),
..241         send_json(Req, 201, [{"Location", DocUrl}], {[{ok, true}]});
..242     Error ->
..243         throw(Error)
..244     end.
..245 
..246 delete_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) ->
..247     ok = couch_httpd:verify_is_server_admin(Req),
..248     case couch_server:delete(DbName, [{user_ctx, UserCtx}]) of
..249     ok ->
..250         send_json(Req, 200, {[{ok, true}]});
..251     Error ->
..252         throw(Error)
..253     end.
..254 
..255 do_db_req(#httpd{user_ctx=UserCtx,path_parts=[DbName|_]}=Req, Fun) ->
..256     case couch_db:open(DbName, [{user_ctx, UserCtx}]) of
..257     {ok, Db} ->
..258         try
..259             Fun(Req, Db)
..260         after
..261             catch couch_db:close(Db)
..262         end;
..263     Error ->
..264         throw(Error)
..265     end.
..266 
..267 db_req(#httpd{method='GET',path_parts=[_DbName]}=Req, Db) ->
..268     {ok, DbInfo} = couch_db:get_db_info(Db),
..269     send_json(Req, {DbInfo});
..270 
..271 db_req(#httpd{method='POST',path_parts=[DbName]}=Req, Db) ->
..272     Doc = couch_doc:from_json_obj(couch_httpd:json_body(Req)),
..273     DocId = couch_util:new_uuid(),
..274     case couch_httpd:qs_value(Req, "batch") of
..275     "ok" ->
..276         % batch
..277         ok = couch_batch_save:eventually_save_doc(Db#db.name,
..278                 Doc#doc{id=DocId}, Db#db.user_ctx),
..279         send_json(Req, 202, [], {[
..280             {ok, true},
..281             {id, DocId}
..282         ]});
..283     _Normal ->
..284         % normal
..285         {ok, NewRev} = couch_db:update_doc(Db, Doc#doc{id=DocId}, []),
..286         DocUrl = absolute_uri(Req,
..287             binary_to_list(<<"/",DbName/binary,"/",DocId/binary>>)),
..288         send_json(Req, 201, [{"Location", DocUrl}], {[
..289             {ok, true},
..290             {id, DocId},
..291             {rev, couch_doc:rev_to_str(NewRev)}
..292         ]})
..293     end;
..294 
..295 
..296 db_req(#httpd{path_parts=[_DbName]}=Req, _Db) ->
..297     send_method_not_allowed(Req, "DELETE,GET,HEAD,POST");
..298 
..299 db_req(#httpd{method='POST',path_parts=[_,<<"_ensure_full_commit">>]}=Req, Db) ->
..300     % make the batch save
..301     committed = couch_batch_save:commit_now(Db#db.name, Db#db.user_ctx),
..302     {ok, DbStartTime} = couch_db:ensure_full_commit(Db),
..303     send_json(Req, 201, {[
..304             {ok, true},
..305             {instance_start_time, DbStartTime}
..306         ]});
..307 
..308 db_req(#httpd{path_parts=[_,<<"_ensure_full_commit">>]}=Req, _Db) ->
..309     send_method_not_allowed(Req, "POST");
..310 
..311 db_req(#httpd{method='POST',path_parts=[_,<<"_bulk_docs">>]}=Req, Db) ->
..312     couch_stats_collector:increment({httpd, bulk_requests}),
..313     {JsonProps} = couch_httpd:json_body_obj(Req),
..314     DocsArray = proplists:get_value(<<"docs">>, JsonProps),
..315     case couch_httpd:header_value(Req, "X-Couch-Full-Commit", "false") of
..316     "true" ->
..317         Options = [full_commit];
..318     _ ->
..319         Options = []
..320     end,
..321     case proplists:get_value(<<"new_edits">>, JsonProps, true) of
..322     true ->
..323         Docs = lists:map(
..324             fun({ObjProps} = JsonObj) ->
..325                 Doc = couch_doc:from_json_obj(JsonObj),
..326                 validate_attachment_names(Doc),
..327                 Id = case Doc#doc.id of
..328                     <<>> -> couch_util:new_uuid();
..329                     Id0 -> Id0
..330                 end,
..331                 case proplists:get_value(<<"_rev">>, ObjProps) of
..332                 undefined ->
..333                     Revs = {0, []};
..334                 Rev  ->
..335                     {Pos, RevId} = couch_doc:parse_rev(Rev),
..336                     Revs = {Pos, [RevId]}
..337                 end,
..338                 Doc#doc{id=Id,revs=Revs}
..339             end,
..340             DocsArray),
..341         Options2 =
..342         case proplists:get_value(<<"all_or_nothing">>, JsonProps) of
..343         true  -> [all_or_nothing|Options];
..344         _ -> Options
..345         end,
..346         case couch_db:update_docs(Db, Docs, Options2) of
..347         {ok, Results} ->
..348             % output the results
..349             DocResults = lists:zipwith(fun update_doc_result_to_json/2,
..350                 Docs, Results),
..351             send_json(Req, 201, DocResults);
..352         {aborted, Errors} ->
..353             ErrorsJson =
..354                 lists:map(fun update_doc_result_to_json/1, Errors),
..355             send_json(Req, 417, ErrorsJson)
..356         end;
..357     false ->
..358         Docs = [couch_doc:from_json_obj(JsonObj) || JsonObj <- DocsArray],
..359         {ok, Errors} = couch_db:update_docs(Db, Docs, Options, replicated_changes),
..360         ErrorsJson =
..361             lists:map(fun update_doc_result_to_json/1, Errors),
..362         send_json(Req, 201, ErrorsJson)
..363     end;
..364 db_req(#httpd{path_parts=[_,<<"_bulk_docs">>]}=Req, _Db) ->
..365     send_method_not_allowed(Req, "POST");
..366 
..367 db_req(#httpd{method='POST',path_parts=[_,<<"_purge">>]}=Req, Db) ->
..368     {IdsRevs} = couch_httpd:json_body_obj(Req),
..369     IdsRevs2 = [{Id, couch_doc:parse_revs(Revs)} || {Id, Revs} <- IdsRevs],
..370 
..371     case couch_db:purge_docs(Db, IdsRevs2) of
..372     {ok, PurgeSeq, PurgedIdsRevs} ->
..373         PurgedIdsRevs2 = [{Id, couch_doc:rev_to_strs(Revs)} || {Id, Revs} <- PurgedIdsRevs],
..374         send_json(Req, 200, {[{<<"purge_seq">>, PurgeSeq}, {<<"purged">>, {PurgedIdsRevs2}}]});
..375     Error ->
..376         throw(Error)
..377     end;
..378 
..379 db_req(#httpd{path_parts=[_,<<"_purge">>]}=Req, _Db) ->
..380     send_method_not_allowed(Req, "POST");
..381 
..382 db_req(#httpd{method='GET',path_parts=[_,<<"_all_docs">>]}=Req, Db) ->
..383     all_docs_view(Req, Db, nil);
..384 
..385 db_req(#httpd{method='POST',path_parts=[_,<<"_all_docs">>]}=Req, Db) ->
..386     {Fields} = couch_httpd:json_body_obj(Req),
..387     case proplists:get_value(<<"keys">>, Fields, nil) of
..388     nil ->
..389         ?LOG_DEBUG("POST to _all_docs with no keys member.", []),
..390         all_docs_view(Req, Db, nil);
..391     Keys when is_list(Keys) ->
..392         all_docs_view(Req, Db, Keys);
..393     _ ->
..394         throw({bad_request, "`keys` member must be a array."})
..395     end;
..396 
..397 db_req(#httpd{path_parts=[_,<<"_all_docs">>]}=Req, _Db) ->
..398     send_method_not_allowed(Req, "GET,HEAD,POST");
..399 
..400 db_req(#httpd{method='GET',path_parts=[_,<<"_all_docs_by_seq">>]}=Req, Db) ->
..401     #view_query_args{
..402         start_key = StartKey,
..403         limit = Limit,
..404         skip = SkipCount,
..405         direction = Dir
..406     } = QueryArgs = couch_httpd_view:parse_view_params(Req, nil, map),
..407 
..408     {ok, Info} = couch_db:get_db_info(Db),
..409     CurrentEtag = couch_httpd:make_etag(proplists:get_value(update_seq, Info)),
..410     couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
..411         TotalRowCount = proplists:get_value(doc_count, Info),
..412         FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
..413             TotalRowCount, #view_fold_helper_funs{
..414                 reduce_count = fun couch_db:enum_docs_since_reduce_to_count/1
..415             }),
..416         StartKey2 = case StartKey of
..417             nil -> 0;
..418             <<>> -> 100000000000;
..419             {} -> 100000000000;
..420             StartKey when is_integer(StartKey) -> StartKey
..421         end,
..422         {ok, FoldResult} = couch_db:enum_docs_since(Db, StartKey2, Dir,
..423             fun(DocInfo, Offset, Acc) ->
..424                 #doc_info{
..425                     id=Id,
..426                     high_seq=Seq,
..427                     revs=[#rev_info{rev=Rev,deleted=Deleted} | RestInfo]
..428                 } = DocInfo,
..429                 ConflictRevs = couch_doc:rev_to_strs(
..430                     [Rev1 || #rev_info{deleted=false, rev=Rev1} <- RestInfo]),
..431                 DelConflictRevs = couch_doc:rev_to_strs(
..432                     [Rev1 || #rev_info{deleted=true, rev=Rev1} <- RestInfo]),
..433                 Json = {
..434                     [{<<"rev">>, couch_doc:rev_to_str(Rev)}] ++
..435                     case ConflictRevs of
..436                     []  -> [];
..437                     _   -> [{<<"conflicts">>, ConflictRevs}]
..438                     end ++
..439                     case DelConflictRevs of
..440                     []  ->  [];
..441                     _   ->  [{<<"deleted_conflicts">>, DelConflictRevs}]
..442                     end ++
..443                     case Deleted of
..444                     true -> [{<<"deleted">>, true}];
..445                     false -> []
..446                     end
..447                 },
..448                 FoldlFun({{Seq, Id}, Json}, Offset, Acc)
..449             end, {Limit, SkipCount, undefined, [], nil}),
..450         couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})
..451     end);
..452 
..453 db_req(#httpd{path_parts=[_,<<"_all_docs_by_seq">>]}=Req, _Db) ->
..454     send_method_not_allowed(Req, "GET,HEAD");
..455 
..456 db_req(#httpd{method='POST',path_parts=[_,<<"_missing_revs">>]}=Req, Db) ->
..457     {JsonDocIdRevs} = couch_httpd:json_body_obj(Req),
..458     JsonDocIdRevs2 = [{Id, [couch_doc:parse_rev(RevStr) || RevStr <- RevStrs]} || {Id, RevStrs} <- JsonDocIdRevs],
..459     {ok, Results} = couch_db:get_missing_revs(Db, JsonDocIdRevs2),
..460     Results2 = [{Id, [couch_doc:rev_to_str(Rev) || Rev <- Revs]} || {Id, Revs} <- Results],
..461     send_json(Req, {[
..462         {missing_revs, {Results2}}
..463     ]});
..464 
..465 db_req(#httpd{path_parts=[_,<<"_missing_revs">>]}=Req, _Db) ->
..466     send_method_not_allowed(Req, "POST");
..467 
..468 db_req(#httpd{method='PUT',path_parts=[_,<<"_admins">>]}=Req,
..469         Db) ->
..470     Admins = couch_httpd:json_body(Req),
..471     ok = couch_db:set_admins(Db, Admins),
..472     send_json(Req, {[{<<"ok">>, true}]});
..473 
..474 db_req(#httpd{method='GET',path_parts=[_,<<"_admins">>]}=Req, Db) ->
..475     send_json(Req, couch_db:get_admins(Db));
..476 
..477 db_req(#httpd{path_parts=[_,<<"_admins">>]}=Req, _Db) ->
..478     send_method_not_allowed(Req, "PUT,GET");
..479 
..480 db_req(#httpd{method='PUT',path_parts=[_,<<"_revs_limit">>]}=Req,
..481         Db) ->
..482     Limit = couch_httpd:json_body(Req),
..483     ok = couch_db:set_revs_limit(Db, Limit),
..484     send_json(Req, {[{<<"ok">>, true}]});
..485 
..486 db_req(#httpd{method='GET',path_parts=[_,<<"_revs_limit">>]}=Req, Db) ->
..487     send_json(Req, couch_db:get_revs_limit(Db));
..488 
..489 db_req(#httpd{path_parts=[_,<<"_revs_limit">>]}=Req, _Db) ->
..490     send_method_not_allowed(Req, "PUT,GET");
..491 
..492 % Special case to enable using an unencoded slash in the URL of design docs,
..493 % as slashes in document IDs must otherwise be URL encoded.
..494 db_req(#httpd{method='GET',mochi_req=MochiReq, path_parts=[DbName,<<"_design/",_/binary>>|_]}=Req, _Db) ->
..495     PathFront = "/" ++ couch_httpd:quote(binary_to_list(DbName)) ++ "/",
..496     RawSplit = regexp:split(MochiReq:get(raw_path),"_design%2F"),
..497     {ok, [PathFront|PathTail]} = RawSplit,
..498     couch_httpd:send_redirect(Req, PathFront ++ "_design/" ++
..499         mochiweb_util:join(PathTail, "_design%2F"));
..500 
..501 db_req(#httpd{path_parts=[_DbName,<<"_design">>,Name]}=Req, Db) ->
..502     db_doc_req(Req, Db, <<"_design/",Name/binary>>);
..503 
..504 db_req(#httpd{path_parts=[_DbName,<<"_design">>,Name|FileNameParts]}=Req, Db) ->
..505     db_attachment_req(Req, Db, <<"_design/",Name/binary>>, FileNameParts);
..506 
..507 
..508 db_req(#httpd{path_parts=[_, DocId]}=Req, Db) ->
..509     db_doc_req(Req, Db, DocId);
..510 
..511 db_req(#httpd{path_parts=[_, DocId | FileNameParts]}=Req, Db) ->
..512     db_attachment_req(Req, Db, DocId, FileNameParts).
..513 
..514 all_docs_view(Req, Db, Keys) ->
..515     #view_query_args{
..516         start_key = StartKey,
..517         start_docid = StartDocId,
..518         end_key = EndKey,
..519         limit = Limit,
..520         skip = SkipCount,
..521         direction = Dir
..522     } = QueryArgs = couch_httpd_view:parse_view_params(Req, Keys, map),
..523     {ok, Info} = couch_db:get_db_info(Db),
..524     CurrentEtag = couch_httpd:make_etag(proplists:get_value(update_seq, Info)),
..525     couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
..526 
..527         TotalRowCount = proplists:get_value(doc_count, Info),
..528         StartId = if is_binary(StartKey) -> StartKey;
..529         true -> StartDocId
..530         end,
..531         FoldAccInit = {Limit, SkipCount, undefined, [], nil},
..532 
..533         case Keys of
..534         nil ->
..535             PassedEndFun =
..536             case Dir of
..537             fwd ->
..538                 fun(ViewKey, _ViewId) ->
..539                     couch_db_updater:less_docid(EndKey, ViewKey)
..540                 end;
..541             rev->
..542                 fun(ViewKey, _ViewId) ->
..543                     couch_db_updater:less_docid(ViewKey, EndKey)
..544                 end
..545             end,
..546 
..547             FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
..548                 TotalRowCount, #view_fold_helper_funs{
..549                     reduce_count = fun couch_db:enum_docs_reduce_to_count/1,
..550                     passed_end = PassedEndFun
..551                 }),
..552             AdapterFun = fun(#full_doc_info{id=Id}=FullDocInfo, Offset, Acc) ->
..553                 case couch_doc:to_doc_info(FullDocInfo) of
..554                 #doc_info{revs=[#rev_info{deleted=false, rev=Rev}|_]} ->
..555                     FoldlFun({{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}]}}, Offset, Acc);
..556                 #doc_info{revs=[#rev_info{deleted=true}|_]} ->
..557                     {ok, Acc}
..558                 end
..559             end,
..560             {ok, FoldResult} = couch_db:enum_docs(Db, StartId, Dir,
..561                 AdapterFun, FoldAccInit),
..562             couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
..563         _ ->
..564             FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
..565                 TotalRowCount, #view_fold_helper_funs{
..566                     reduce_count = fun(Offset) -> Offset end
..567                 }),
..568             KeyFoldFun = case Dir of
..569             fwd ->
..570                 fun lists:foldl/3;
..571             rev ->
..572                 fun lists:foldr/3
..573             end,
..574             {ok, FoldResult} = KeyFoldFun(
..575                 fun(Key, {ok, FoldAcc}) ->
..576                     DocInfo = (catch couch_db:get_doc_info(Db, Key)),
..577                     Doc = case DocInfo of
..578                     {ok, #doc_info{id=Id, revs=[#rev_info{deleted=false, rev=Rev}|_]}} ->
..579                         {{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}]}};
..580                     {ok, #doc_info{id=Id, revs=[#rev_info{deleted=true, rev=Rev}|_]}} ->
..581                         {{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}, {deleted, true}]}};
..582                     not_found ->
..583                         {{Key, error}, not_found};
..584                     _ ->
..585                         ?LOG_ERROR("Invalid DocInfo: ~p", [DocInfo]),
..586                         throw({error, invalid_doc_info})
..587                     end,
..588                     Acc = (catch FoldlFun(Doc, 0, FoldAcc)),
..589                     case Acc of
..590                     {stop, Acc2} ->
..591                         {ok, Acc2};
..592                     _ ->
..593                         Acc
..594                     end
..595                 end, {ok, FoldAccInit}, Keys),
..596             couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})
..597         end
..598     end).
..599 
..600 db_doc_req(#httpd{method='DELETE'}=Req, Db, DocId) ->
..601     % check for the existence of the doc to handle the 404 case.
..602     couch_doc_open(Db, DocId, nil, []),
..603     case couch_httpd:qs_value(Req, "rev") of
..604     undefined ->
..605         update_doc(Req, Db, DocId, {[{<<"_deleted">>,true}]});
..606     Rev ->
..607         update_doc(Req, Db, DocId, {[{<<"_rev">>, ?l2b(Rev)},{<<"_deleted">>,true}]})
..608     end;
..609 
..610 db_doc_req(#httpd{method='GET'}=Req, Db, DocId) ->
..611     #doc_query_args{
..612         show = Format,
..613         rev = Rev,
..614         open_revs = Revs,
..615         options = Options
..616     } = parse_doc_query(Req),
..617     case Format of
..618     nil ->
..619         case Revs of
..620         [] ->
..621             Doc = couch_doc_open(Db, DocId, Rev, Options),
..622             DiskEtag = couch_httpd:doc_etag(Doc),
..623             case Doc#doc.meta of
..624             [] ->
..625                 % output etag only when we have no meta
..626                 couch_httpd:etag_respond(Req, DiskEtag, fun() -> 
..627                     send_json(Req, 200, [{"Etag", DiskEtag}], couch_doc:to_json_obj(Doc, Options))
..628                 end);
..629             _ ->
..630                 send_json(Req, 200, [], couch_doc:to_json_obj(Doc, Options))
..631             end;
..632         _ ->
..633             {ok, Results} = couch_db:open_doc_revs(Db, DocId, Revs, Options),
..634             {ok, Resp} = start_json_response(Req, 200),
..635             send_chunk(Resp, "["),
..636             % We loop through the docs. The first time through the separator
..637             % is whitespace, then a comma on subsequent iterations.
..638             lists:foldl(
..639                 fun(Result, AccSeparator) ->
..640                     case Result of
..641                     {ok, Doc} ->
..642                         JsonDoc = couch_doc:to_json_obj(Doc, Options),
..643                         Json = ?JSON_ENCODE({[{ok, JsonDoc}]}),
..644                         send_chunk(Resp, AccSeparator ++ Json);
..645                     {{not_found, missing}, RevId} ->
..646                         Json = ?JSON_ENCODE({[{"missing", RevId}]}),
..647                         send_chunk(Resp, AccSeparator ++ Json)
..648                     end,
..649                     "," % AccSeparator now has a comma
..650                 end,
..651                 "", Results),
..652             send_chunk(Resp, "]"),
..653             end_json_response(Resp)
..654         end;
..655     _ ->
..656         {DesignName, ShowName} = Format,
..657         couch_httpd_show:handle_doc_show(Req, DesignName, ShowName, DocId, Db)
..658     end;
..659 
..660 db_doc_req(#httpd{method='POST'}=Req, Db, DocId) ->
..661     couch_doc:validate_docid(DocId),
..662     case couch_httpd:header_value(Req, "content-type") of
..663     "multipart/form-data" ++  _Rest ->
..664         ok;
..665     _Else ->
..666         throw({bad_ctype, <<"Invalid Content-Type header for form upload">>})
..667     end,
..668     Form = couch_httpd:parse_form(Req),
..669     Rev = couch_doc:parse_rev(list_to_binary(proplists:get_value("_rev", Form))),
..670     {ok, [{ok, Doc}]} = couch_db:open_doc_revs(Db, DocId, [Rev], []),
..671 
..672     UpdatedAtts = [
..673         #att{name=validate_attachment_name(Name),
..674             type=list_to_binary(ContentType),
..675             data=Content} ||
..676         {Name, {ContentType, _}, Content} <-
..677         proplists:get_all_values("_attachments", Form)
..678     ],
..679     #doc{atts=OldAtts} = Doc,
..680     OldAtts2 = lists:flatmap(
..681         fun(#att{name=OldName}=Att) ->
..682             case [1 || A <- UpdatedAtts, A#att.name == OldName] of
..683             [] -> [Att]; % the attachment wasn't in the UpdatedAtts, return it
..684             _ -> [] % the attachment was in the UpdatedAtts, drop it
..685             end
..686         end, OldAtts),
..687     NewDoc = Doc#doc{
..688         atts = UpdatedAtts ++ OldAtts2
..689     },
..690     {ok, NewRev} = couch_db:update_doc(Db, NewDoc, []),
..691 
..692     send_json(Req, 201, [{"Etag", "\"" ++ ?b2l(couch_doc:rev_to_str(NewRev)) ++ "\""}], {[
..693         {ok, true},
..694         {id, DocId},
..695         {rev, couch_doc:rev_to_str(NewRev)}
..696     ]});
..697 
..698 db_doc_req(#httpd{method='PUT'}=Req, Db, DocId) ->
..699     couch_doc:validate_docid(DocId),
..700     Json = couch_httpd:json_body(Req),
..701     case couch_httpd:qs_value(Req, "batch") of
..702     "ok" ->
..703         % batch
..704         Doc = couch_doc_from_req(Req, DocId, Json),
..705         ok = couch_batch_save:eventually_save_doc(Db#db.name, Doc, Db#db.user_ctx),
..706         send_json(Req, 202, [], {[
..707             {ok, true},
..708             {id, DocId}
..709         ]});
..710     _Normal ->
..711         % normal
..712         Location = absolute_uri(Req, "/" ++ ?b2l(Db#db.name) ++ "/" ++ ?b2l(DocId)),
..713         update_doc(Req, Db, DocId, Json, [{"Location", Location}])
..714     end;
..715 
..716 db_doc_req(#httpd{method='COPY'}=Req, Db, SourceDocId) ->
..717     SourceRev =
..718     case extract_header_rev(Req, couch_httpd:qs_value(Req, "rev")) of
..719         missing_rev -> nil;
..720         Rev -> Rev
..721     end,
..722     {TargetDocId, TargetRevs} = parse_copy_destination_header(Req),
..723     % open old doc
..724     Doc = couch_doc_open(Db, SourceDocId, SourceRev, []),
..725     % save new doc
..726     {ok, NewTargetRev} = couch_db:update_doc(Db,
..727         Doc#doc{id=TargetDocId, revs=TargetRevs}, []),
..728     % respond
..729     send_json(Req, 201,
..730         [{"Etag", "\"" ++ ?b2l(couch_doc:rev_to_str(NewTargetRev)) ++ "\""}],
..731         update_doc_result_to_json(TargetDocId, {ok, NewTargetRev}));
..732 
..733 db_doc_req(Req, _Db, _DocId) ->
..734     send_method_not_allowed(Req, "DELETE,GET,HEAD,POST,PUT,COPY").
..735 
..736 
..737 update_doc_result_to_json({{Id, Rev}, Error}) ->
..738         {_Code, Err, Msg} = couch_httpd:error_info(Error),
..739         {[{id, Id}, {rev, couch_doc:rev_to_str(Rev)},
..740             {error, Err}, {reason, Msg}]}.
..741 
..742 update_doc_result_to_json(#doc{id=DocId}, Result) ->
..743     update_doc_result_to_json(DocId, Result);
..744 update_doc_result_to_json(DocId, {ok, NewRev}) ->
..745     {[{id, DocId}, {rev, couch_doc:rev_to_str(NewRev)}]};
..746 update_doc_result_to_json(DocId, Error) ->
..747     {_Code, ErrorStr, Reason} = couch_httpd:error_info(Error),
..748     {[{id, DocId}, {error, ErrorStr}, {reason, Reason}]}.
..749 
..750 
..751 update_doc(Req, Db, DocId, Json) ->
..752     update_doc(Req, Db, DocId, Json, []).
..753 
..754 update_doc(Req, Db, DocId, Json, Headers) ->
..755     #doc{deleted=Deleted} = Doc = couch_doc_from_req(Req, DocId, Json),
..756 
..757     case couch_httpd:header_value(Req, "X-Couch-Full-Commit", "false") of
..758     "true" ->
..759         Options = [full_commit];
..760     _ ->
..761         Options = []
..762     end,
..763     {ok, NewRev} = couch_db:update_doc(Db, Doc, Options),
..764     NewRevStr = couch_doc:rev_to_str(NewRev),
..765     ResponseHeaders = [{"Etag", <<"\"", NewRevStr/binary, "\"">>}] ++ Headers,
..766     send_json(Req, if Deleted -> 200; true -> 201 end,
..767         ResponseHeaders, {[
..768             {ok, true},
..769             {id, DocId},
..770             {rev, NewRevStr}]}).
..771 
..772 couch_doc_from_req(Req, DocId, Json) ->
..773     Doc = couch_doc:from_json_obj(Json),
..774     validate_attachment_names(Doc),
..775     ExplicitDocRev =
..776     case Doc#doc.revs of
..777         {Start,[RevId|_]} -> {Start, RevId};
..778         _ -> undefined
..779     end,
..780     case extract_header_rev(Req, ExplicitDocRev) of
..781     missing_rev ->
..782         Revs = {0, []};
..783     {Pos, Rev} ->
..784         Revs = {Pos, [Rev]}
..785     end,
..786     Doc#doc{id=DocId, revs=Revs}.
..787 
..788 
..789 % Useful for debugging
..790 % couch_doc_open(Db, DocId) ->
..791 %   couch_doc_open(Db, DocId, nil, []).
..792 
..793 couch_doc_open(Db, DocId, Rev, Options) ->
..794     case Rev of
..795     nil -> % open most recent rev
..796         case couch_db:open_doc(Db, DocId, Options) of
..797         {ok, Doc} ->
..798             Doc;
..799          Error ->
..800              throw(Error)
..801          end;
..802   _ -> % open a specific rev (deletions come back as stubs)
..803       case couch_db:open_doc_revs(Db, DocId, [Rev], Options) of
..804           {ok, [{ok, Doc}]} ->
..805               Doc;
..806           {ok, [Else]} ->
..807               throw(Else)
..808       end
..809   end.
..810 
..811 % Attachment request handlers
..812 
..813 db_attachment_req(#httpd{method='GET'}=Req, Db, DocId, FileNameParts) ->
..814     FileName = list_to_binary(mochiweb_util:join(lists:map(fun binary_to_list/1, FileNameParts),"/")),
..815     #doc_query_args{
..816         rev=Rev,
..817         options=Options
..818     } = parse_doc_query(Req),
..819     #doc{
..820         atts=Atts
..821     } = Doc = couch_doc_open(Db, DocId, Rev, Options),
..822     case [A || A <- Atts, A#att.name == FileName] of
..823     [] ->
..824         throw({not_found, "Document is missing attachment"});
..825     [#att{type=Type}=Att] ->
..826         Etag = couch_httpd:doc_etag(Doc),
..827         couch_httpd:etag_respond(Req, Etag, fun() ->
..828             {ok, Resp} = start_chunked_response(Req, 200, [
..829                 {"ETag", Etag},
..830                 {"Cache-Control", "must-revalidate"},
..831                 {"Content-Type", binary_to_list(Type)}%,
..832                 % My understanding of http://www.faqs.org/rfcs/rfc2616.html
..833                 % says that we should not use Content-Length with a chunked
..834                 % encoding. Turning this off makes libcurl happy, but I am
..835                 % open to discussion. (jchris)
..836                 %
..837                 % Can you point to the section that makes you think that? (jan)
..838                 % {"Content-Length", integer_to_list(couch_doc:bin_size(Bin))}
..839                 ]),
..840             couch_doc:att_foldl(Att,
..841                     fun(BinSegment, _) -> send_chunk(Resp, BinSegment) end,[]),
..842             send_chunk(Resp, "")
..843         end)
..844     end;
..845 
..846 
..847 db_attachment_req(#httpd{method=Method}=Req, Db, DocId, FileNameParts)
..848         when (Method == 'PUT') or (Method == 'DELETE') ->
..849     FileName = validate_attachment_name(
..850                     mochiweb_util:join(
..851                         lists:map(fun binary_to_list/1,
..852                             FileNameParts),"/")),
..853 
..854     NewAtt = case Method of
..855         'DELETE' ->
..856             [];
..857         _ ->
..858             [#att{
..859                 name=FileName,
..860                 type = case couch_httpd:header_value(Req,"Content-Type") of
..861                     undefined ->
..862                         % We could throw an error here or guess by the FileName.
..863                         % Currently, just giving it a default.
..864                         <<"application/octet-stream">>;
..865                     CType ->
..866                         list_to_binary(CType)
..867                     end,
..868                 data = case couch_httpd:header_value(Req,"Content-Length") of
..869                     undefined ->
..870                         fun(MaxChunkSize, ChunkFun, InitState) ->
..871                             couch_httpd:recv_chunked(Req, MaxChunkSize,
..872                                 ChunkFun, InitState)
..873                         end;
..874                     Length ->
..875                         fun() -> couch_httpd:recv(Req, 0) end
..876                     end,
..877                 len = case couch_httpd:header_value(Req,"Content-Length") of
..878                     undefined ->
..879                         undefined;
..880                     Length ->
..881                         list_to_integer(Length)
..882                     end
..883                     }]
..884     end,
..885 
..886     Doc = case extract_header_rev(Req, couch_httpd:qs_value(Req, "rev")) of
..887         missing_rev -> % make the new doc
..888             #doc{id=DocId};
..889         Rev ->
..890             case couch_db:open_doc_revs(Db, DocId, [Rev], []) of
..891             {ok, [{ok, Doc0}]}  -> Doc0;
..892             {ok, [Error]}       -> throw(Error)
..893             end
..894     end,
..895 
..896     #doc{atts=Atts} = Doc,
..897     DocEdited = Doc#doc{
..898         atts = NewAtt ++ [A || A <- Atts, A#att.name /= FileName]
..899     },
..900     {ok, UpdatedRev} = couch_db:update_doc(Db, DocEdited, []),
..901     #db{name=DbName} = Db,
..902 
..903     {Status, Headers} = case Method of
..904         'DELETE' ->
..905             {200, []};
..906         _ ->
..907             {201, [{"Location", absolute_uri(Req, "/" ++
..908                 binary_to_list(DbName) ++ "/" ++
..909                 binary_to_list(DocId) ++ "/" ++
..910                 binary_to_list(FileName)
..911             )}]}
..912         end,
..913     send_json(Req,Status, Headers, {[
..914         {ok, true},
..915         {id, DocId},
..916         {rev, couch_doc:rev_to_str(UpdatedRev)}
..917     ]});
..918 
..919 db_attachment_req(Req, _Db, _DocId, _FileNameParts) ->
..920     send_method_not_allowed(Req, "DELETE,GET,HEAD,PUT").
..921 
..922 parse_doc_format(FormatStr) when is_binary(FormatStr) ->
..923     parse_doc_format(?b2l(FormatStr));
..924 parse_doc_format(FormatStr) when is_list(FormatStr) ->
..925     SplitFormat = lists:splitwith(fun($/) -> false; (_) -> true end, FormatStr),
..926     case SplitFormat of
..927         {DesignName, [$/ | ShowName]} -> {?l2b(DesignName), ?l2b(ShowName)};
..928         _Else -> throw({bad_request, <<"Invalid doc format">>})
..929     end;
..930 parse_doc_format(_BadFormatStr) ->
..931     throw({bad_request, <<"Invalid doc format">>}).
..932 
..933 parse_doc_query(Req) ->
..934     lists:foldl(fun({Key,Value}, Args) ->
..935         case {Key, Value} of
..936         {"attachments", "true"} ->
..937             Options = [attachments | Args#doc_query_args.options],
..938             Args#doc_query_args{options=Options};
..939         {"meta", "true"} ->
..940             Options = [revs_info, conflicts, deleted_conflicts | Args#doc_query_args.options],
..941             Args#doc_query_args{options=Options};
..942         {"revs", "true"} ->
..943             Options = [revs | Args#doc_query_args.options],
..944             Args#doc_query_args{options=Options};
..945         {"local_seq", "true"} ->
..946             Options = [local_seq | Args#doc_query_args.options],
..947             Args#doc_query_args{options=Options};
..948         {"revs_info", "true"} ->
..949             Options = [revs_info | Args#doc_query_args.options],
..950             Args#doc_query_args{options=Options};
..951         {"conflicts", "true"} ->
..952             Options = [conflicts | Args#doc_query_args.options],
..953             Args#doc_query_args{options=Options};
..954         {"deleted_conflicts", "true"} ->
..955             Options = [deleted_conflicts | Args#doc_query_args.options],
..956             Args#doc_query_args{options=Options};
..957         {"rev", Rev} ->
..958             Args#doc_query_args{rev=couch_doc:parse_rev(Rev)};
..959         {"open_revs", "all"} ->
..960             Args#doc_query_args{open_revs=all};
..961         {"open_revs", RevsJsonStr} ->
..962             JsonArray = ?JSON_DECODE(RevsJsonStr),
..963             Args#doc_query_args{open_revs=[couch_doc:parse_rev(Rev) || Rev <- JsonArray]};
..964         {"show", FormatStr} ->
..965             Args#doc_query_args{show=parse_doc_format(FormatStr)};
..966         _Else -> % unknown key value pair, ignore.
..967             Args
..968         end
..969     end, #doc_query_args{}, couch_httpd:qs(Req)).
..970 
..971 
..972 extract_header_rev(Req, ExplicitRev) when is_binary(ExplicitRev) or is_list(ExplicitRev)->
..973     extract_header_rev(Req, couch_doc:parse_rev(ExplicitRev));
..974 extract_header_rev(Req, ExplicitRev) ->
..975     Etag = case couch_httpd:header_value(Req, "If-Match") of
..976         undefined -> undefined;
..977         Value -> couch_doc:parse_rev(string:strip(Value, both, $"))
..978     end,
..979     case {ExplicitRev, Etag} of
..980     {undefined, undefined} -> missing_rev;
..981     {_, undefined} -> ExplicitRev;
..982     {undefined, _} -> Etag;
..983     _ when ExplicitRev == Etag -> Etag;
..984     _ ->
..985         throw({bad_request, "Document rev and etag have different values"})
..986     end.
..987 
..988 
..989 parse_copy_destination_header(Req) ->
..990     Destination = couch_httpd:header_value(Req, "Destination"),
..991     case regexp:match(Destination, "\\?") of
..992     nomatch ->
..993         {list_to_binary(Destination), {0, []}};
..994     {match, _, _} ->
..995         {ok, [DocId, RevQueryOptions]} = regexp:split(Destination, "\\?"),
..996         {ok, [_RevQueryKey, Rev]} = regexp:split(RevQueryOptions, "="),
..997         {Pos, RevId} = couch_doc:parse_rev(Rev),
..998         {list_to_binary(DocId), {Pos, [RevId]}}
..999     end.
.1000 
.1001 validate_attachment_names(Doc) ->
.1002     lists:foreach(fun(#att{name=Name}) ->
.1003         validate_attachment_name(Name)
.1004     end, Doc#doc.atts).
.1005 
.1006 validate_attachment_name(Name) when is_list(Name) ->
.1007     validate_attachment_name(list_to_binary(Name));
.1008 validate_attachment_name(<<"_",_/binary>>) ->
.1009     throw({bad_request, <<"Attachment name can't start with '_'">>});
.1010 validate_attachment_name(Name) ->
.1011     case is_valid_utf8(Name) of
.1012         true -> Name;
.1013         false -> throw({bad_request, <<"Attachment name is not UTF-8 encoded">>})
.1014     end.
.1015 
.1016 %% borrowed from mochijson2:json_bin_is_safe()
.1017 is_valid_utf8(<<>>) ->
.1018     true;
.1019 is_valid_utf8(<>) ->
.1020     case C of
.1021         $\" ->
.1022             false;
.1023         $\\ ->
.1024             false;
.1025         $\b ->
.1026             false;
.1027         $\f ->
.1028             false;
.1029         $\n ->
.1030             false;
.1031         $\r ->
.1032             false;
.1033         $\t ->
.1034             false;
.1035         C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
.1036             false;
.1037         C when C < 16#7f ->
.1038             is_valid_utf8(Rest);
.1039         _ ->
.1040             false
.1041     end.

Generated using etap 0.3.4.