web: Do not assume error responses are valid, or our, JSON.

Partially fixes #24815.
This commit is contained in:
Alex Vandiver 2023-07-18 18:19:22 +00:00 committed by Tim Abbott
parent 7efe989a72
commit ba7492a314
21 changed files with 109 additions and 83 deletions

View File

@ -80,7 +80,9 @@ export function create_ajax_request(
},
error(xhr) {
$(form_loading).hide();
$(form_error).show().text(xhr.responseJSON.msg);
if (xhr.responseJSON?.msg) {
$(form_error).show().text(xhr.responseJSON.msg);
}
$(form_input_section).show();
$(zulip_limited_section).show();
$(free_trial_alert_message).show();

View File

@ -105,19 +105,16 @@ function call(args) {
return;
}
} else if (xhr.status === 403) {
try {
if (
xhr.responseJSON.code === "CSRF_FAILED" &&
reload_state.csrf_failed_handler !== undefined
) {
reload_state.csrf_failed_handler();
}
} catch (error) {
blueslip.error(
"Unexpected 403 response from server",
{xhr: xhr.responseText, args},
error,
);
if (xhr.responseJSON === undefined) {
blueslip.error("Unexpected 403 response from server", {
xhr: xhr.responseText,
args,
});
} else if (
xhr.responseJSON.code === "CSRF_FAILED" &&
reload_state.csrf_failed_handler !== undefined
) {
reload_state.csrf_failed_handler();
}
}
orig_error(xhr, error_type, xhn);
@ -189,7 +186,7 @@ export function patch(options) {
}
export function xhr_error_message(message, xhr) {
if (xhr.status.toString().charAt(0) === "4") {
if (xhr.status.toString().charAt(0) === "4" && xhr.responseJSON?.msg) {
// Only display the error response for 4XX, where we've crafted
// a nice response.
const server_response_html = _.escape(xhr.responseJSON.msg);

View File

@ -583,10 +583,14 @@ export function initialize() {
$invite_row.remove();
}
function failure(error_msg) {
function xhr_failure(xhr) {
let error_message = "Failed to subscribe user!";
if (xhr.responseJSON?.msg) {
error_message = xhr.responseJSON.msg;
}
clear_invites();
compose_banner.show_error_message(
error_msg,
error_message,
compose_banner.CLASSNAMES.generic_compose_error,
$banner_container,
$("#compose-textarea"),
@ -594,11 +598,6 @@ export function initialize() {
$(event.target).prop("disabled", true);
}
function xhr_failure(xhr) {
const error = xhr.responseJSON;
failure(error.msg);
}
const sub = sub_store.get(stream_id);
subscriber_api.add_user_ids_to_stream([user_id], sub, success, xhr_failure);

View File

@ -66,10 +66,13 @@ export function post_hotspot_as_read(hotspot_name) {
url: "/json/users/me/hotspots",
data: {hotspot: hotspot_name},
error(err) {
blueslip.error("Failed to fetch hotspots", {
status: err.status,
body: err.responseText,
});
if (err.readyState !== 0) {
blueslip.error("Failed to fetch hotspots", {
readyState: err.readyState,
status: err.status,
body: err.responseText,
});
}
},
});
}

View File

@ -114,7 +114,7 @@ function submit_invitation_form() {
}
},
error(xhr) {
if (xhr.responseJSON.errors === undefined) {
if (xhr.responseJSON?.errors === undefined) {
// There was a fatal error, no partial processing occurred.
ui_report.error("", xhr, $invite_status);
} else {

View File

@ -693,13 +693,15 @@ export function toggle_resolve_topic(message_id, old_topic_name, report_errors_i
url: "/json/messages/" + message_id,
data: request,
error(xhr) {
if (xhr.responseJSON.code === "MOVE_MESSAGES_TIME_LIMIT_EXCEEDED") {
handle_resolve_topic_failure_due_to_time_limit(topic_is_resolved);
return;
}
if (xhr.responseJSON) {
if (xhr.responseJSON.code === "MOVE_MESSAGES_TIME_LIMIT_EXCEEDED") {
handle_resolve_topic_failure_due_to_time_limit(topic_is_resolved);
return;
}
if (report_errors_in_global_banner) {
ui_report.generic_embed_error(xhr.responseJSON.msg, 3500);
if (report_errors_in_global_banner) {
ui_report.generic_embed_error(xhr.responseJSON.msg, 3500);
}
}
},
});
@ -842,7 +844,7 @@ export function save_inline_topic_edit($row) {
},
error(xhr) {
const $spinner = $row.find(".topic_edit_spinner");
if (xhr.responseJSON.code === "MOVE_MESSAGES_TIME_LIMIT_EXCEEDED") {
if (xhr.responseJSON?.code === "MOVE_MESSAGES_TIME_LIMIT_EXCEEDED") {
const allowed_message_id = xhr.responseJSON.first_message_id_allowed_to_move;
const send_notification_to_old_thread = false;
const send_notification_to_new_thread = false;
@ -1223,7 +1225,7 @@ export function move_topic_containing_message_to_stream(
},
error(xhr) {
reset_modal_ui();
if (xhr.responseJSON.code === "MOVE_MESSAGES_TIME_LIMIT_EXCEEDED") {
if (xhr.responseJSON?.code === "MOVE_MESSAGES_TIME_LIMIT_EXCEEDED") {
const allowed_message_id = xhr.responseJSON.first_message_id_allowed_to_move;
function handle_confirm() {
move_topic_containing_message_to_stream(

View File

@ -196,11 +196,13 @@ function hide_catalog_show_integration() {
dataType: "html",
success: hide_catalog,
error(err) {
blueslip.error(`Integration documentation for '${state.integration}' not found.`, {
readyState: err.readyState,
status: err.status,
responseText: err.responseText,
});
if (err.readyState !== 0) {
blueslip.error(`Integration documentation for '${state.integration}' not found.`, {
readyState: err.readyState,
status: err.status,
responseText: err.responseText,
});
}
},
});
}

View File

@ -180,11 +180,10 @@ function update_url() {
// API callers: These methods handle communicating with the Python backend API.
function handle_unsuccessful_response(response) {
try {
if (response.responseJSON?.msg) {
const status_code = response.statusCode().status;
response = JSON.parse(response.responseText);
set_results_notice(`Result: (${status_code}) ${response.msg}`, "warning");
} catch {
set_results_notice(`Result: (${status_code}) ${response.responseJSON.msg}`, "warning");
} else {
// If the response is not a JSON response, then it is probably
// Django returning an HTML response containing a stack trace
// with useful debugging information regarding the backend

View File

@ -85,14 +85,16 @@ function update_ui_and_send_reaction_ajax(message_id, reaction_info) {
},
error(xhr) {
view.waiting_for_server_request_ids.delete(reaction_request_id);
if (
xhr.responseJSON?.code === "REACTION_ALREADY_EXISTS" ||
xhr.responseJSON?.code === "REACTION_DOES_NOT_EXIST"
) {
// Don't send error report for simple precondition failures caused by race
// conditions; the user already got what they wanted
} else {
blueslip.error(channel.xhr_error_message("Error sending reaction", xhr));
if (xhr.readyState !== 0) {
if (
xhr.responseJSON?.code === "REACTION_ALREADY_EXISTS" ||
xhr.responseJSON?.code === "REACTION_DOES_NOT_EXIST"
) {
// Don't send error report for simple precondition failures caused by race
// conditions; the user already got what they wanted
} else {
blueslip.error(channel.xhr_error_message("Error sending reaction", xhr));
}
}
},
};

View File

@ -207,7 +207,7 @@ function get_events({dont_block = false} = {}) {
get_events_xhr = undefined;
// If we're old enough that our message queue has been
// garbage collected, immediately reload.
if (xhr.status === 400 && xhr.responseJSON.code === "BAD_EVENT_QUEUE_ID") {
if (xhr.status === 400 && xhr.responseJSON?.code === "BAD_EVENT_QUEUE_ID") {
page_params.event_queue_expired = true;
reload.initiate({
immediate: true,

View File

@ -118,9 +118,11 @@ function upload_avatar($file_input) {
if (page_params.avatar_source === "G") {
$("#user-avatar-source").show();
}
const $error = $("#user-avatar-upload-widget .image_file_input_error");
$error.text(xhr.responseJSON.msg);
$error.show();
if (xhr.responseJSON?.msg) {
const $error = $("#user-avatar-upload-widget .image_file_input_error");
$error.text(xhr.responseJSON.msg);
$error.show();
}
},
});
}
@ -490,7 +492,9 @@ export function set_up() {
$("#api_key_value").text(data.api_key);
},
error(xhr) {
$("#user_api_key_error").text(xhr.responseJSON.msg).show();
if (xhr.responseJSON?.msg) {
$("#user_api_key_error").text(xhr.responseJSON.msg).show();
}
},
});
e.preventDefault();
@ -798,7 +802,7 @@ export function set_up() {
},
);
let rendered_error_msg;
if (xhr.responseJSON.code === "CANNOT_DEACTIVATE_LAST_USER") {
if (xhr.responseJSON?.code === "CANNOT_DEACTIVATE_LAST_USER") {
if (xhr.responseJSON.is_last_owner) {
rendered_error_msg = error_last_owner;
} else {

View File

@ -580,8 +580,10 @@ export function set_up() {
$row.find("api_key_error").hide();
},
error(xhr) {
const $row = $(e.currentTarget).closest("li");
$row.find(".api_key_error").text(xhr.responseJSON.msg).show();
if (xhr.responseJSON?.msg) {
const $row = $(e.currentTarget).closest("li");
$row.find(".api_key_error").text(xhr.responseJSON.msg).show();
}
},
});
});

View File

@ -73,8 +73,7 @@ function open_linkifier_edit_form(linkifier_id) {
},
error_continuation(xhr) {
$change_linkifier_button.prop("disabled", false);
const response_text = xhr.responseJSON;
if (response_text.errors !== undefined) {
if (xhr.responseJSON?.errors) {
handle_linkifier_api_error(
xhr,
$pattern_status,
@ -239,12 +238,14 @@ export function build_page() {
},
error(xhr) {
$add_linkifier_button.prop("disabled", false);
handle_linkifier_api_error(
xhr,
$pattern_status,
$template_status,
$linkifier_status,
);
if (xhr.responseJSON?.errors) {
handle_linkifier_api_error(
xhr,
$pattern_status,
$template_status,
$linkifier_status,
);
}
},
});
});

View File

@ -1101,7 +1101,9 @@ function get_chart_data(data, callback) {
update_last_full_update(data.end_times);
},
error(xhr) {
$("#id_stats_errors").show().text(xhr.responseJSON.msg);
if (xhr.responseJSON?.msg) {
$("#id_stats_errors").show().text(xhr.responseJSON.msg);
}
},
});
}

View File

@ -280,8 +280,7 @@ function create_stream() {
// The rest of the work is done via the subscribe event we will get
},
error(xhr) {
const msg = xhr.responseJSON.msg;
if (msg.includes("access")) {
if (xhr.responseJSON?.msg?.includes("access")) {
// If we can't access the stream, we can safely
// assume it's a duplicate stream that we are not invited to.
//

View File

@ -196,9 +196,12 @@ function subscribe_new_users({pill_user_ids}) {
}
function invite_failure(xhr) {
const error = xhr.responseJSON;
let message = "Failed to subscribe user!";
if (xhr.responseJSON?.msg) {
message = xhr.responseJSON.msg;
}
show_stream_subscription_request_result({
message: error.msg,
message,
add_class: "text-error",
remove_class: "text-success",
});

View File

@ -24,7 +24,9 @@ function send_typing_notification_ajax(user_ids_array, operation) {
},
success() {},
error(xhr) {
blueslip.warn("Failed to send typing event: " + xhr.responseText);
if (xhr.readyState !== 0) {
blueslip.warn("Failed to send typing event: " + xhr.responseText);
}
},
});
}

View File

@ -38,7 +38,7 @@ export function error(
status_box: JQuery,
remove_after?: number,
): void {
if (xhr && xhr.status >= 400 && xhr.status < 500) {
if (xhr && xhr.status >= 400 && xhr.status < 500 && xhr.responseJSON?.msg) {
// Only display the error response for 4XX, where we've crafted
// a nice response.
const server_response_html = _.escape(xhr.responseJSON.msg);
@ -78,7 +78,7 @@ export function generic_embed_error(error_html: string, remove_after: number): v
}
export function generic_row_button_error(xhr: JQuery.jqXHR, $btn: JQuery): void {
if (xhr.status >= 400 && xhr.status < 500) {
if (xhr.status >= 400 && xhr.status < 500 && xhr.responseJSON?.msg) {
const $error = $("<p>").addClass("text-error").text(xhr.responseJSON.msg);
$btn.closest("td").empty().append($error);
} else {

View File

@ -142,8 +142,10 @@ export function mark_all_as_read(args = {}) {
dialog_widget.close_modal();
},
error(xhr) {
// If we hit the rate limit, just continue without showing any error.
if (xhr.responseJSON.code === "RATE_LIMIT_HIT") {
if (xhr.readyState === 0) {
// client cancelled the request
} else if (xhr.responseJSON?.code === "RATE_LIMIT_HIT") {
// If we hit the rate limit, just continue without showing any error.
const milliseconds_to_wait = 1000 * xhr.responseJSON["retry-after"];
setTimeout(() => mark_all_as_read(args), milliseconds_to_wait);
} else {
@ -241,8 +243,10 @@ export function mark_as_unread_from_here(
}
},
error(xhr) {
// If we hit the rate limit, just continue without showing any error.
if (xhr.responseJSON.code === "RATE_LIMIT_HIT") {
if (xhr.readyState === 0) {
// client cancelled the request
} else if (xhr.responseJSON?.code === "RATE_LIMIT_HIT") {
// If we hit the rate limit, just continue without showing any error.
const milliseconds_to_wait = 1000 * xhr.responseJSON["retry-after"];
setTimeout(
() =>

View File

@ -223,9 +223,12 @@ function add_new_members({pill_user_ids}) {
}
function invite_failure(xhr) {
const error = xhr.responseJSON;
let message = "Failed to subscribe user!";
if (xhr.responseJSON?.msg) {
message = xhr.responseJSON.msg;
}
show_user_group_membership_request_result({
message: error.msg,
message,
add_class: "text-error",
remove_class: "text-success",
});

View File

@ -268,7 +268,7 @@ test("sending", ({override, override_rewire}) => {
// Since this path calls blueslip.warn, we need to handle it.
blueslip.expect("error", "XHR error message.");
channel.xhr_error_message = () => "XHR error message.";
args.error({responseJSON: {msg: "Some error message"}});
args.error({readyState: 4, responseJSON: {msg: "Some error message"}});
}
emoji_name = "alien"; // not set yet
{