A user should also always be warned when a post request is being resubmitted, since the HTTP RFC's only says get, head, put and delete should be idempotent.
RFC2616:
In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe". This allows user agents to represent other methods, such as POST, PUT and DELETE, in a special way, so that the user is made aware of the fact that a possibly unsafe action is being requested.
While I agree with your sentiment here (I would prefer my browser to just not save any POST data at all for tab recovery since its so extremely rare that doing so would actually be useful), the RFC section you quote does not specify anything about persistence of resources to backing storage. It simply indicates the way browsers should treat these resources in a general sense. And according to the verbiage you have provided, Safari, Chrome and Firefox are all working as expected, because that paragraph is actually telling the browsers that automatic resubmission of non-GET requests can (and almost always does) have side effects (such as doing a duplicate post to a forum for instance).
Indeed my original post indicates briefly that I was already well-aware of this distinction between GET requests being idempotent (ie has no side effects) and other requests not being safe for resubmission. All modern standards-compliant browsers warn the users before resending the results of POST requests (Are you sure you want to resubmit?). Furthermore, none of these three major browsers (Chrome, Safari, Firefox) will act upon POST requests when they are pulled out of the session recovery feature.
Although respecting idempotency of non-GET requests is not at issue in this topic, I nonetheless tested this as a side effect (huzzah) of seeing how common the storage of POST data for recovery actually is.
Chrome
Chrome has an interesting behavior when it comes to storing POST data for reopening later, it appears to save the response content of an HTTP POST and does not resubmit the POST request. The user is presented with the content they saw when they initially sent the POST request. Nonetheless one can refresh (with Are you sure you want to resubmit) and the data will be resent in a new POST request. So Chrome is storing HTTP POST data for reuse when the tab is restored.
For HTTPS POST resources however, Chrome will _not_ automatically restore the page content from its backing store (and by implication likely does not save it) and also will not resend the request until the user manually refreshes the page. HOWEVER, at that point all of the original data from the first POST request is resent. Thus HTTPS POST data _is_ persisted by Chrome.
Firefox
Firefox has completely different behavior. From my testing it will save POST resources as GET resources, and when they are restored, I receive a new fresh GET request from my test server. So, Firefox is NOT saving POST data for reuse upon tab recovery.
Conclusion
I'm not saying that the RFC doesn't matter because it does. Chrome should be changed to be more in line with it. Regardless of whether it is best practice for browser makers to persist this data or not, my argument about the unreliable exploitation of this problem stands because the only time this is a problem is when a website delivers a POST response without an immediate redirect, which all sites SHOULD be doing.
The only time this isn't the case is when the resource presenting the login HTML form is also the action of the form it contains (or some variant of this situation). Now *that* is a bad practice that remains bad no matter what browser you are using.
Test Methods
Browser versions I tested with:
- Chrome: 30.0.1599.114
- Firefox: 24.0
I created a simple PHP script (post.php) and hosted it on my HTTPS-capable test server:
<?php
echo "foo is: ".(isset($_POST['foo']) ? $_POST['foo'] : '<em>not present</em>')."<br/>";
echo "user input is: ".(isset($_POST['user']) ? $_POST['user'] : '<em>not present</em>')."<br/>";
echo "random number: ".mt_rand()."<br/>";
echo "request method: ".$_SERVER['REQUEST_METHOD']."<br/>";
?>
<form action="?" method="post">
<input type="hidden" name="foo" value="bar:<?= mt_rand() ?>" /><br/>
User input: <input type="text" name="user" value="" /><br/>
<button>Go</button>
</form>
I then submitted a request using this test page, saved the content outside the browser in a scratchpad, then force killed my local browsers and restored them. I repeated the tests both with HTTP and HTTPS to see the difference. The random numbers act as nonces so that I can immediately tell whether a request has been resent without looking in the logs (though I did watch the logs for verification)