Subject: url-http: synchronous post and 302 error response not handled properly

From: Vaidheeswaran C <[email-masked]>
Date: Thu, 09 Jul 2015 13:51:39 +0530

url-http: synchronous post and 302 error response not handled
properly.  This is seen with Oddmuse wikis.

GNU Emacs is from today's master branch.

----------------------------------------------------------------

Prerequisites:

You need an oddmuse wiki that you have write access to.

----------------------------------------------------------------

The snippet below posts the "Hello Everywhere" (the "text") on to
"TemporaryPage" (the "title").

(let* ((wiki-base-url "http://localhost/cgi-bin/wiki.pl/")
       (values
	`(("pwd" . "xxxx")
	  ("recent_edit" . "off")
	  ("summary" . "Some updates")
	  ("text" . "Hello Everywhere")
	  ("title" . "TemporaryPage")
	  ("username" . "Anonymous"))))
  (let ((url-request-method "POST")
	(url-request-extra-headers
	 '(("Content-Type" . "application/x-www-form-urlencoded")))
	(url-request-data (mm-url-encode-www-form-urlencoded values)))
    (url-retrieve-synchronously wiki-base-url)
    (message "Done")))

1. Get hold of an oddmuse wiki that you have edit access to
2. Customize `url-debug' to t
3. C-x C-e the above snippet
4. See that the "Done" message gets never printed.
5. C-g your way out.
6. Verify that Oddmuse wiki page has been updated.

----------------------------------------------------------------

The HTTP process buffer says:

    HTTP/1.1 302 Found
    Status: 302 Found
    Location: http://localhost/cgi-bin/wiki.pl/TemporaryPage
    Transfer-Encoding: chunked
    Date: Thu, 09 Jul 2015 07:32:42 GMT
    Server: lighttpd/1.4.31

----------------------------------------------------------------

The *URL-DEBUG* buffer says infinite "spinning" for headers.

    http -> Contacting host: localhost:80
    http -> Marking connection as busy: localhost:80 #<process localhost>
    retrieval -> Spinning in url-retrieve-synchronously: nil (#<buffer
*http localhost:80*>)
    http -> Request is:
    POST /cgi-bin/wiki.pl/ HTTP/1.1
    MIME-Version: 1.0
    Connection: keep-alive
    Extension: Security/Digest Security/SSL
    Host: localhost
    Accept-encoding: gzip
    Accept-charset: utf-8;q=1, gb2312;q=0.5, iso-8859-1;q=0.5,
big5;q=0.5, iso-2022-jp;q=0.5, shift_jis;q=0.5, euc-tw;q=0.5,
euc-jp;q=0.5, euc-jis-2004;q=0.5, euc-kr;q=0.5, us-ascii;q=0.5,
utf-7;q=0.5, hz-gb-2312;q=0.5, big5-hkscs;q=0.5, gbk;q=0.5,
gb18030;q=0.5, iso-8859-5;q=0.5, koi8-r;q=0.5, koi8-u;q=0.5,
cp866;q=0.5, koi8-t;q=0.5, windows-1251;q=0.5, cp855;q=0.5,
iso-8859-2;q=0.5, iso-8859-3;q=0.5, iso-8859-4;q=0.5, iso-8859-9;q=0.5,
iso-8859-10;q=0.5, iso-8859-13;q=0.5, iso-8859-14;q=0.5,
iso-8859-15;q=0.5, windows-1250;q=0.5, windows-1252;q=0.5,
windows-1254;q=0.5, windows-1257;q=0.5, cp775;q=0.5, cp850;q=0.5,
cp852;q=0.5, cp857;q=0.5, cp858;q=0.5, cp860;q=0.5, cp861;q=0.5,
cp863;q=0.5, cp865;q=0.5, cp437;q=0.5, macintosh;q=0.5, next;q=0.5,
hp-roman8;q=0.5, adobe-standard-encoding;q=0.5, iso-8859-16;q=0.5,
iso-8859-7;q=0.5, windows-1253;q=0.5, cp737;q=0.5, cp851;q=0.5,
cp869;q=0.5, iso-8859-8;q=0.5, windows-1255;q=0.5, cp862;q=0.5,
iso-2022-jp-2004;q=0.5, cp874;q=0.5, iso-8859-11;q=0.5, viscii;q=0.5,
windows-1258;q=0.5, iso-8859-6;q=0.5, windows-1256;q=0.5,
iso-2022-cn;q=0.5, iso-2022-cn-ext;q=0.5, iso-2022-jp-2;q=0.5,
iso-2022-kr;q=0.5, utf-16le;q=0.5, utf-16be;q=0.5, utf-16;q=0.5,
x-ctext;q=0.5
    Accept: */*
    User-Agent: URL/Emacs
    Cookie: Wiki=pwd%251exxxx%251eusername%251eAnonymous
    Content-Type: application/x-www-form-urlencoded
    Content-length: 101

pwd=xxxx&recent_edit=off&summary=Some+updates&text=Hello+World&title=TemporaryPage&username=Anonymous
    http -> Calling after change function
`url-http-wait-for-headers-change-function' for `#<process localhost>'
    http -> url-http-wait-for-headers-change-function ( *http localhost:80*)
    http -> Saw end of headers... ( *http localhost:80*)
    http -> url-http-parse-response called in ( *http localhost:80*)
    http -> Saw chunked encoding.
    http -> Calling initial chunked-encoding for extra data at end of
headers
    http -> Reading chunk 0 (182 183 1)
    http -> Did not see start of chunk @ 183!
    http -> Spinning waiting for headers...
    retrieval -> Spinning in url-retrieve-synchronously: nil (#<buffer
*http localhost:80*>)
    http -> Calling after change function
`url-http-chunked-encoding-after-change-function' for `#<process localhost>'
    http -> Reading chunk 0 (183 188 5)
    http -> Saw start of chunk 1 (length=0, start=183
    http -> Saw end of stream chunk!
    http -> Removing terminator of last chunk
    http -> Saw end of trailers...
    http -> Marking connection as free: localhost:80 #<process localhost>
    http -> url-http-parse-headers called in ( *http localhost:80*)
    http -> url-http-parse-response called in ( *http localhost:80*)
    http -> Parsed HTTP headers: class=3 status=302
    http -> Finished parsing HTTP headers: nil
    retrieval -> Spinning in url-retrieve-synchronously: nil (#<buffer
*http localhost:80*>)
    retrieval -> Spinning in url-retrieve-synchronously: nil (#<buffer
*http localhost:80*>)

----------------------------------------------------------------

Hint for quick isolation:

If I comment out the `unless' part below (cf. url-http.el @ line 602)
thereabouts, the spinning stops altogether.

    ((or `moved-permanently `found `temporary-redirect) ; 301 302 307
     ;; If the 301|302 status code is received in response to a
     ;; request other than GET or HEAD, the user agent MUST NOT
     ;; automatically redirect the request unless it can be
     ;; confirmed by the user, since this might change the
     ;; conditions under which the request was issued.
     (unless (member url-http-method '("HEAD" "GET"))
       (setq redirect-uri nil)))

----------------------------------------------------------------

Expected behaviour:

I am attaching an edit transaction done via firefox + emacswiki
(captured with "follow TCP stream" option of wireshark)

I see a 302 response followed by a fetch of the edited page.  Whatever
be the right behaviour, I expect that synchronous fetch doesn't loop
infinitely and make emacs unusable.