summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/Makefile.am1
-rw-r--r--share/www/script/couch_tests.js1
-rw-r--r--share/www/script/test/jsonp.js69
-rw-r--r--src/couchdb/couch_db.hrl3
-rw-r--r--src/couchdb/couch_httpd.erl64
-rw-r--r--src/couchdb/couch_httpd_view.erl2
6 files changed, 135 insertions, 5 deletions
diff --git a/share/Makefile.am b/share/Makefile.am
index d3ba2b1a..22560a68 100644
--- a/share/Makefile.am
+++ b/share/Makefile.am
@@ -107,6 +107,7 @@ nobase_dist_localdata_DATA = \
www/script/test/multiple_rows.js \
www/script/test/large_docs.js \
www/script/test/utf8.js \
+ www/script/test/jsonp.js \
www/script/test/attachments.js \
www/script/test/attachment_paths.js \
www/script/test/attachment_views.js \
diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js
index 6528835d..f7c26d01 100644
--- a/share/www/script/couch_tests.js
+++ b/share/www/script/couch_tests.js
@@ -42,6 +42,7 @@ loadTest("design_options.js");
loadTest("multiple_rows.js");
loadTest("large_docs.js");
loadTest("utf8.js");
+loadTest("jsonp.js");
loadTest("attachments.js");
loadTest("attachment_names.js");
loadTest("attachment_paths.js");
diff --git a/share/www/script/test/jsonp.js b/share/www/script/test/jsonp.js
new file mode 100644
index 00000000..5ea31180
--- /dev/null
+++ b/share/www/script/test/jsonp.js
@@ -0,0 +1,69 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// Verify callbacks ran
+var jsonp_flag = 0;
+
+// Callbacks
+function jsonp_no_chunk(doc) {
+ T(jsonp_flag == 0);
+ T(doc._id == "0");
+ jsonp_flag = 1;
+}
+
+function jsonp_chunk(doc) {
+ T(jsonp_flag == 0);
+ T(doc.total_rows == 1);
+ jsonp_flag = 1;
+}
+
+// Do some jsonp tests.
+couchTests.jsonp = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var doc = {_id:"0",a:0,b:0};
+ T(db.save(doc).ok);
+
+ // Test unchunked callbacks.
+ var xhr = CouchDB.request("GET", "/test_suite_db/0?callback=jsonp_no_chunk");
+ T(xhr.status == 200);
+ jsonp_flag = 0;
+ eval(xhr.responseText);
+ T(jsonp_flag == 1);
+ xhr = CouchDB.request("GET", "/test_suite_db/0?callback=foo\"");
+ T(xhr.status == 400);
+
+ // Test chunked responses
+ var doc = {_id:"1",a:1,b:1};
+ T(db.save(doc).ok);
+
+ var designDoc = {
+ _id:"_design/test",
+ language: "javascript",
+ views: {
+ all_docs: {map: "function(doc) {if(doc.a) emit(null, doc.a);}"}
+ }
+ }
+ T(db.save(designDoc).ok);
+
+ var url = "/test_suite_db/_design/test/_view/all_docs?callback=jsonp_chunk";
+ xhr = CouchDB.request("GET", url);
+ T(xhr.status == 200);
+ jsonp_flag = 0;
+ eval(xhr.responseText);
+ T(jsonp_flag == 1);
+ xhr = CouchDB.request("GET", url + "\'");
+ T(xhr.status == 400);
+};
diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl
index c00792a8..0f7e344e 100644
--- a/src/couchdb/couch_db.hrl
+++ b/src/couchdb/couch_db.hrl
@@ -167,7 +167,8 @@
include_docs = false,
stale = false,
multi_get = false,
- ignore = false
+ ignore = false,
+ callback = nil
}).
-record(view_fold_helper_funs, {
diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl
index 7a803c85..0f29bae7 100644
--- a/src/couchdb/couch_httpd.erl
+++ b/src/couchdb/couch_httpd.erl
@@ -386,8 +386,10 @@ send_json(Req, Code, Headers, Value) ->
{"Content-Type", negotiate_content_type(Req)},
{"Cache-Control", "must-revalidate"}
],
- send_response(Req, Code, DefaultHeaders ++ Headers,
- list_to_binary([?JSON_ENCODE(Value), $\n])).
+ Body = list_to_binary(
+ [start_jsonp(Req), ?JSON_ENCODE(Value), end_jsonp(), $\n]
+ ),
+ send_response(Req, Code, DefaultHeaders ++ Headers, Body).
start_json_response(Req, Code) ->
start_json_response(Req, Code, []).
@@ -397,12 +399,66 @@ start_json_response(Req, Code, Headers) ->
{"Content-Type", negotiate_content_type(Req)},
{"Cache-Control", "must-revalidate"}
],
- start_chunked_response(Req, Code, DefaultHeaders ++ Headers).
+ start_jsonp(Req), % Validate before starting chunked.
+ %start_chunked_response(Req, Code, DefaultHeaders ++ Headers).
+ {ok, Resp} = start_chunked_response(Req, Code, DefaultHeaders ++ Headers),
+ case start_jsonp(Req) of
+ [] -> ok;
+ Start -> send_chunk(Resp, Start)
+ end,
+ {ok, Resp}.
end_json_response(Resp) ->
- send_chunk(Resp, [$\n]),
+ send_chunk(Resp, end_jsonp() ++ [$\n]),
+ %send_chunk(Resp, [$\n]),
send_chunk(Resp, []).
+start_jsonp(Req) ->
+ case get(jsonp) of
+ undefined -> put(jsonp, qs_value(Req, "callback", no_jsonp));
+ _ -> ok
+ end,
+ case get(jsonp) of
+ no_jsonp -> [];
+ [] -> [];
+ CallBack ->
+ try
+ validate_callback(CallBack),
+ CallBack ++ "("
+ catch
+ Error ->
+ put(jsonp, no_jsonp),
+ throw(Error)
+ end
+ end.
+
+end_jsonp() ->
+ Resp = case get(jsonp) of
+ no_jsonp -> [];
+ [] -> [];
+ _ -> ");"
+ end,
+ put(jsonp, undefined),
+ Resp.
+
+validate_callback(CallBack) when is_binary(CallBack) ->
+ validate_callback(binary_to_list(CallBack));
+validate_callback([]) ->
+ ok;
+validate_callback([Char | Rest]) ->
+ case Char of
+ _ when Char >= $a andalso Char =< $z -> ok;
+ _ when Char >= $A andalso Char =< $Z -> ok;
+ _ when Char >= $0 andalso Char =< $9 -> ok;
+ _ when Char == $. -> ok;
+ _ when Char == $_ -> ok;
+ _ when Char == $[ -> ok;
+ _ when Char == $] -> ok;
+ _ ->
+ throw({bad_request, invalid_callback})
+ end,
+ validate_callback(Rest).
+
error_info({Error, Reason}) when is_list(Reason) ->
error_info({Error, ?l2b(Reason)});
diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl
index 016a391a..8db561ac 100644
--- a/src/couchdb/couch_httpd_view.erl
+++ b/src/couchdb/couch_httpd_view.erl
@@ -270,6 +270,8 @@ parse_view_param("reduce", Value) ->
[{reduce, parse_bool_param(Value)}];
parse_view_param("include_docs", Value) ->
[{include_docs, parse_bool_param(Value)}];
+parse_view_param("callback", _) ->
+ []; % Verified in the JSON response functions
parse_view_param(Key, Value) ->
[{extra, {Key, Value}}].