// PostInterceptor
// version 0.7
// 2007-11-16
// Copyright (c) 2005-2007, Prakash Kailasa <pk-moz at kailasa dot net>
// Released under the GPL license
// http://www.gnu.org/copyleft/gpl.html
//
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script.
//
// To install, you need Greasemonkey: http://greasemonkey.mozdev.org/
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
//
// To uninstall, go to Tools -> Manage User Scripts,
// select "PostInterceptor", and click Uninstall.
//
// Add appropriate @include URL patterns, when installing the script.
// After installation, go to Tools -> Manage User Scripts, select
// "PostInterceptor" to add/edit URL patterns.
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @name PostIntercepter
// @description Intercept POST requests and let user modify before submit
// @namespace http://kailasa.net/prakash/greasemonkey/
// @include http*://example.com/*
// ==/UserScript==
//
// IMPORTANT: Be sure to change/add @include lines for the sites you
// want the script to work on
//
// --------------------------------------------------------------------
//
// To enable/disable POST Interceptor, click on the small '[PI] is Off'
// or '[PI] is On' button on the bottom-right corner of the page.
//
// --------------------------------------------------------------------
//
// TODO:
// - Handle radio buttons correctly
// - Implement add_form_param
// - Code needs cleanup
//
// --------------------------------------------------------------------
const POST_INTERCEPT = 'PostIntercept';
var intercept_on;
var is_modified = false;
interceptor_setup();
function toggle_intercept(flag)
{
intercept_on = flag;
GM_setValue(POST_INTERCEPT, intercept_on);
setup_pi_button();
}
function setup_pi_button()
{
var pi = document.getElementById('__pi_toggle');
if (!pi) {
pi = new_node('p', '__pi_toggle');
pi.appendChild(new_text_node(''));
document.getElementsByTagName('body')[0].appendChild(pi);
var pi_toggle_style = ' \
#__pi_toggle { \
position: fixed; \
bottom: 0; right: 0; \
padding: 2px; margin: 0; \
font: caption; \
font-weight: bold; \
cursor: crosshair; \
opacity: .5; \
} \
#__pi_toggle:hover { \
border-width: 2px 0 0 2px; \
border-style: solid none none solid; \
border-color: black; \
} \
';
add_style("__pi_toggle_style", pi_toggle_style);
pi.addEventListener('click',
function() {toggle_intercept(!intercept_on)},
false);
}
if (intercept_on) {
pi.firstChild.data = '[PI] is On';
pi.setAttribute('title', 'Click to turn POST Interceptor Off');
pi.style.backgroundColor = '#ff8';
pi.style.color = '#008';
} else {
pi.firstChild.data = '[PI] is Off';
pi.setAttribute('title', 'Click to turn POST Interceptor On');
pi.style.backgroundColor = '#ccc';
pi.style.color = '#444';
}
}
function interceptor_setup()
{
// nothing to do if no forms on the page
if (document.forms.length == 0)
return;
if (typeof GM_getValue != 'undefined') {
intercept_on = GM_getValue(POST_INTERCEPT, false);
GM_log('intercept_on = ' + intercept_on);
setup_pi_button();
} else {
intercept_on = true;
}
// override submit handling
unsafeWindow.HTMLFormElement.prototype.real_submit = unsafeWindow.HTMLFormElement.prototype.submit;
unsafeWindow.HTMLFormElement.prototype.submit = interceptor;
// trap onsubmit handler, if any, for each form.
// we will call the real onsubmit handler ourselves later
// var forms = document.getElementsByTagName('form');
// for (var i = 0; i < forms.length; i++) {
// if (forms[i].method.toLowerCase() == 'post') {
// if (forms[i].onsubmit) {
// forms[i].real_onsubmit = forms[i].onsubmit;
// forms[i].onsubmit = function(e) {
// return false;
// };
// }
// }
// }
// define our 'submit' handler on window, to avoid defining
// on individual forms
window.addEventListener('submit', function(e) {
interceptor(e);
e.preventDefault();
}, false);
}
// interceptor: called in place of form.submit()
// or as a result of submit handler on window (arg: event)
function interceptor(e) {
var target = e ? e.target : this;
//GM_log('interceptor: target = <' + target + '>');
var frm;
// target could be an INPUT element
if (target.tagName.toLowerCase() == 'input') {
frm = target.form;
} else {
frm = target;
}
if (!frm || !frm.tagName || frm.tagName.toLowerCase() != 'form') {
GM_log('interceptor: found <' + frm + (frm.tagName ? '(' + frm.tagName + ')' : '') + '> instead of form; investigate further!!!');
}
if (!interceptor_onsubmit(frm))
return false;
//alert('interceptor: intercept_on = ' + intercept_on);
if (intercept_on) {
show(frm);
return false;
} else {
frm.real_submit();
}
}
// if any form defined an onsubmit handler, it was saved earlier.
// call it now
function interceptor_onsubmit(f)
{
var rc = true;
if (f.real_onsubmit) {
rc = f.real_onsubmit();
}
return rc;
}
function show(frm)
{
var content = build(frm);
content.open();
}
function build(frm)
{
add_window_style();
var container = new_node('div', 'post_interceptor');
container.className = '__pi_window';
var title = new_node('h1');
title.className = '__pi_title';
title.appendChild(new_text_node('Intercepting POST ' + post_url(frm)));
container.appendChild(title);
var note = new_node('div', '__pi_note');
note.appendChild(new_text_node('Click on any value to modify it'));
//title.appendChild(note);
container.appendChild(note);
var data = build_post_data(frm);
container.appendChild(data);
var buttons = new_node('div', '__pi_buttons');
var btn_send_mod = new_node('button', '__pi_btn_send_mod');
btn_send_mod.className = '__pi_button';
btn_send_mod.appendChild(new_text_node('Send Modified'));
buttons.appendChild(btn_send_mod);
btn_send_mod.addEventListener('click', function(e) { submit_modified(win) }, false);
var btn_send_orig = new_node('button', '__pi_btn_send_orig');
btn_send_orig.className = '__pi_button';
btn_send_orig.appendChild(new_text_node('Send Original'));
buttons.appendChild(btn_send_orig);
btn_send_orig.addEventListener('click', function(e) { submit_original(win) }, false);
var btn_cancel = new_node('button', '__pi_btn_cancel');
btn_cancel.className = '__pi_button';
btn_cancel.appendChild(new_text_node('Cancel'));
buttons.appendChild(btn_cancel);
container.appendChild(buttons);
btn_cancel.addEventListener('click', function(e) { cancel_submit(win) }, false);
var win = Window(container);
container.form = frm;
return win;
}
// POST content
function build_post_data(f)
{
var table = new_node('table');
// heading
var thead = new_node('thead');
var th_row = new_node('tr');
var attrs = new Array('+', 'name', 'type', 'value');
for (var a = 0; a < attrs.length; a++) {
var th = new_node('th');
th.appendChild(new_text_node(attrs[a].ucFirst()));
th_row.appendChild(th);
}
th_row.firstChild.setAttribute('title', 'Click to add a new parameter');
th_row.firstChild.addEventListener('click', add_form_param, false);
thead.appendChild(th_row);
table.appendChild(thead);
// data
var tbody = new_node('tbody');
var el_count = 0;
for (var i = 0; i < f.elements.length; i++) {
if (!f.elements[i].name)
continue;
var row = new_node('tr');
row.className = el_count++ % 2 == 0 ? '__pi_row_even' : '__pi_row_odd';
var cell_ctrl = new_node('td', '__pi_cell_ctrl_' + i);
cell_ctrl.className = '__pi_cell_ctrl';
cell_ctrl.appendChild(new_text_node('X'));
cell_ctrl.setAttribute('title', 'Click to delete');
cell_ctrl.addEventListener('click', toggle_form_param, false);
row.appendChild(cell_ctrl);
//for (var a in attrs) {
for (var a = 1; a < attrs.length; a++) {
var cell = new_node('td', '__pi_cell_' + attrs[a] + '_' + i);
cell.className = '__pi_cell_' + attrs[a];
var data;
if (attrs[a] == 'value') {
//data = new_node('span', '__pi_cell_value_text_' + i);
//data.appendChild(new_text_node(f.elements[i][attrs[a]]));
data = new_node('input', '__pi_cell_value_text_' + i);
data.value = f.elements[i][attrs[a]];
data.readOnly = true;
data.className = '__pi_view_field';
data.maxLength = 1000;
cell.addEventListener("click", show_edit, false);
} else {
data = new_text_node(f.elements[i][attrs[a]]);
}
cell.appendChild(data);
row.appendChild(cell);
}
tbody.appendChild(row);
}
table.appendChild(tbody);
var data = new_node('div', '__pi_post_info');
data.className = '__pi_post_info';
data.appendChild(table);
return data;
}
// hide value and show edit field
function show_edit(e)
{
var view, cell;
if (e.target.nodeName == 'INPUT') {
view = e.target;
cell = view.parentNode;
} else {
cell = e.target;
view = cell.firstChild;
}
view.__origValue = view.value;
view.className = '__pi_edit_field';
view.readOnly = false;
view.addEventListener("blur", show_view, false);
}
// hide edit field and show modified value
function show_view(e)
{
var view = e.target;
view.className = '__pi_view_field';
view.addEventListener("click", show_edit, false);
if (view.value != view.__origValue) {
is_modified = true;
view.parentNode.parentNode.className += ' __pi_modified';
}
}
// build POST url
function post_url(f)
{
// absolute URL?
if (f.action.match(/^https?:/))
return f.action;
// relative URL; build complete URL
var url = document.location.protocol + '//' + document.location.host;
if (f.action.match(/^\//)) {
url += f.action;
} else {
url += document.location.pathname + '/' + f.action;
}
return url;
}
// delete/undelete a form parameter
function toggle_form_param(e)
{
var cell = e.target;
var deleted = cell.parentNode.getAttribute('deleted');
if (deleted && deleted == 'true') {
cell.parentNode.setAttribute('deleted', 'false');
cell.textContent = 'X';
cell.setAttribute('title', 'Click to delete');
cell.parentNode.className = cell.parentNode.className.replace(' __pi_deleted', '');
} else {
cell.parentNode.setAttribute('deleted', 'true');
cell.textContent = '+';
cell.setAttribute('title', 'Click to undo delete');
cell.parentNode.className += ' __pi_deleted';
is_modified = true;
}
}
// add a new form parameter
function add_form_param(e)
{
window.alert('Sorry! not implemented yet');
}
// cancel submit; just close the Interceptor window
function cancel_submit(win)
{
win.close();
}
// ignore form modifications and submit original form
function submit_original(win)
{
win.close();
// win.frame.form.real_submit();
// bug fix from esquifit (http://groups.google.com/group/greasemonkey-users/msg/153bc8b5081c1ab3?dmode=source)
win.frame.form.wrappedJSObject.real_submit();
}
// submit form with modified parameters
function submit_modified(win)
{
if (is_modified) {
update_form(win);
}
submit_original(win);
}
// update the form being submitted with user modifications
function update_form(win)
{
var f = win.frame.form;
var diff = 'submitting ' + f.name + ':\n';
for (var i = 0; i < f.elements.length; i++) {
if (!f.elements[i].name)
continue;
var edit = document.getElementById('__pi_cell_value_text_' + i);
if (edit && edit.value != f.elements[i].value) {
diff += f.elements[i].name + ': |' + f.elements[i].value + '| -> |' + edit.value + '|\n';
// update the original form param
f.elements[i].value = edit.value;
}
var deleted = document.getElementById('__pi_cell_ctrl_' + i).parentNode.getAttribute('deleted');
if (deleted && deleted == 'true') {
f.elements[i].disabled = true;
}
}
GM_log(diff);
}
// helper functions
function new_node(type, id)
{
var node = document.createElement(type);
if (id && id.length > 0)
node.id = id;
return node;
}
function new_text_node(txt)
{
return document.createTextNode(txt);
}
function add_style(style_id, style_rules)
{
if (document.getElementById(style_id))
return;
var style = new_node("style", style_id);
style.type = "text/css";
style.innerHTML = style_rules;
document.getElementsByTagName('head')[0].appendChild(style);
}
// style for the interceptor window
function add_window_style()
{
var pi_style_rules = ' \
.post_interceptor { \
margin: 0; padding: 0; \
} \
\
.__pi_window { \
background-color: #bfbfff; \
border-color: #000040; \
border-style: solid; \
border-width: 2px; \
/* opacity: .90; */ \
margin: 0px; \
padding: 1px 2px; \
position: absolute; \
text-align: center; \
visibility: hidden; \
z-index: 1000; \
\
-moz-border-radius: 15px; \
} \
\
.__pi_title { \
background-color: #4040ff; \
color: #ffffff; \
margin: 1px; padding: 1px; \
font: caption; \
font-weight: bold; \
text-align: center; \
white-space: nowrap; \
overflow: hidden; \
\
-moz-border-radius: 20px; \
} \
\
#__pi_note { \
border: solid 0px black; \
color: #800000; \
margin: 0; \
font: caption; \
font-weight: bold; \
text-align: center; \
} \
\
#__pi_buttons { \
width: 99%; \
text-align: center; \
position: absolute; \
bottom: 5px; \
} \
\
.__pi_button { \
background-color: #4040ff; \
color: #fff; \
margin: 0 5px; padding: 2px; \
font: icon; \
font-weight: bold; \
\
-moz-border-radius: 15px; \
} \
\
.__pi_button:hover { \
background-color: #ff4040; \
cursor: pointer; \
} \
\
.__pi_post_info { \
max-height: 335px; \
overflow: auto; \
margin: 3px 2px; padding: 0; \
border: 1px solid #008080; \
} \
\
.__pi_post_info table { \
width: 100%; \
font: bold .7em "sans serif"; \
} \
\
.__pi_post_info table thead tr { \
background-color: black; \
color: white; \
} \
\
.__pi_post_info table td { \
text-align: left; \
} \
\
.__pi_row_odd { \
background-color: #eee; \
} \
\
.__pi_row_even { \
background-color: #ccc; \
} \
\
.__pi_cell_ctrl { \
font: caption; \
font-weight: bold; \
color: white; \
background-color: black; \
text-align: center; \
width: 1.5em; \
cursor: crosshair; \
} \
\
.__pi_view_field { \
background-color: inherit; \
border: 0px solid black; \
width: 25em; \
font: bold 1em "sans serif"; \
} \
\
.__pi_edit_field { \
background-color: #ffc; \
color: blue; \
border: 1px solid black; \
padding: -1px; \
width: 25em; \
font: bold 1em "sans serif"; \
} \
\
tr.__pi_modified td, tr.__pi_modified input { \
color: red; \
} \
\
tr.__pi_deleted td, tr.__pi_deleted input { \
color: gray; \
} \
\
';
add_style("__pi_style", pi_style_rules);
}
//===============================================================
// Popup Window
function Window(el)
{
document.getElementsByTagName('body')[0].appendChild(el);
var win = {
frame: el,
open: function() {
var width = 550;
var height = 400;
this.frame.style.width = width + 'px';
this.frame.style.height = height + 'px';
this.frame.style.left = parseInt(window.scrollX + (window.innerWidth - width)/2) + 'px';
this.frame.style.top = parseInt(window.scrollY + (window.innerHeight - height)/2) + 'px';
this.frame.style.visibility = "visible";
},
close: function() {
this.frame.style.visibility = "hidden";
},
};
return win;
}
String.prototype.ucFirst = function () {
return this.charAt(0).toUpperCase() + this.substr(1);
}
/*
* Change Log:
*
* 0.7 - 2007-11-16 - Bug fix. use wrappedJSObject to get to the real function (esquifit)
* 0.6 - 2006-01-11 - Updated to work with Firefox 1.5 and Greasemonkey 0.6.4
* 0.5 - 2005-07-26 - One minor change to make this work in GM 0.4.1 (Mark Pilgrim)
* - Removed check for input type 'image', when target is determined (Umesh Shankar)
* - Form parameters can be deleted, before submitting
* - Skip form parameters without 'name' attribute (takes care of fieldsets)
* - minor style changes
* 0.4 - 2005-05-09 - Add a toggle "button" at the bottom-right corder
* - implement edit fields using 'input' elements
* - more pleasing colors (I hope) and rounded corners
* - remove Menu Commands until GM_unregisterMenuCommand is implemented
* 0.3 - 2005-04-20 - remove dependency on GM 0.3
* 0.2 - 2005-04-18 - build full POST URL
* 0.1 - 2005-04-17 - initial version
*/