<commit_before>Decription: Cherry picked fixes from 1.466.2 to resolve
 two security issues:
    - CVE-2012-4438 jenkins remote code execution
    - CVE-2012-4439 jenkins XSS
Origin: Upstream, commits fb73bac50f22526a3d3b...43ff1688eee6ea
Bug: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=688298

--- a/core/src/main/java/hudson/model/DownloadService.java
+++ b/core/src/main/java/hudson/model/DownloadService.java
@@ -61,7 +61,8 @@ public class DownloadService extends Pag
      */
     public String generateFragment() {
     	if (neverUpdate) return "";
-    	
+        if (doesNotSupportPostMessage())  return "";
+
         StringBuilder buf = new StringBuilder();
         if(Jenkins.getInstance().hasPermission(Jenkins.READ)) {
             long now = System.currentTimeMillis();
@@ -88,6 +89,23 @@ public class DownloadService extends Pag
         return buf.toString();
     }
 
+    private boolean doesNotSupportPostMessage() {
+        StaplerRequest req = Stapler.getCurrentRequest();
+        if (req==null)      return false;
+
+        String ua = req.getHeader("User-Agent");
+        if (ua==null)       return false;
+
+        // according to http://caniuse.com/#feat=x-doc-messaging, IE <=7 doesn't support pstMessage
+        // see http://www.useragentstring.com/pages/Internet%20Explorer/ for user agents
+
+        // we want to err on the cautious side here.
+        // Because of JENKINS-15105, we can't serve signed metadata from JSON, which means we need to be
+        // using a modern browser as a vehicle to request these data. This check is here to prevent Jenkins
+        // from using older browsers that are known not to support postMessage as the vehicle.
+        return ua.contains("Windows") && (ua.contains(" MSIE 5.") || ua.contains(" MSIE 6.") || ua.contains(" MSIE 7."));
+    }
+
     private String mapHttps(String url) {
         /*
             HACKISH:
--- a/core/src/main/java/hudson/search/Search.java
+++ b/core/src/main/java/hudson/search/Search.java
@@ -84,6 +84,7 @@ public class Search {
      * See http://developer.mozilla.org/en/docs/Supporting_search_suggestions_in_search_plugins
      */
     public void doSuggestOpenSearch(StaplerRequest req, StaplerResponse rsp, @QueryParameter String q) throws IOException, ServletException {
+        rsp.setContentType(Flavor.JSON.contentType);
         DataWriter w = Flavor.JSON.createDataWriter(null, rsp);
         w.startArray();
         w.value(q);
--- a/core/src/main/java/hudson/tasks/junit/History.java
+++ b/core/src/main/java/hudson/tasks/junit/History.java
@@ -293,4 +293,12 @@ public class History {
 
     }
 
+    public static int asInt(String s, int defalutValue) {
+        if (s==null)    return defalutValue;
+        try {
+            return Integer.parseInt(s);
+        } catch (NumberFormatException e) {
+            return defalutValue;
+        }
+    }
 }
--- a/core/src/main/resources/hudson/tasks/junit/History/index.jelly
+++ b/core/src/main/resources/hudson/tasks/junit/History/index.jelly
@@ -26,8 +26,8 @@ THE SOFTWARE.
 <?jelly escape-by-default='true'?>
 <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
   <l:layout title="${%title(it.testObject.displayName)}">
-		<j:set var="start" value="${request.getParameter('start')?:0}"/>
-		<j:set var="end" value="${request.getParameter('end')?:start+24}"/>
+		<j:set var="start" value="${it.asInt(request.getParameter('start'),0)}"/>
+		<j:set var="end" value="${it.asInt(request.getParameter('end'),start+24)}"/>
 		<j:set var="rangeParameters" value="start=${start}&amp;end=${end+1}"/>
 		<script type="text/javascript">
 			function setCount() {
--- a/war/src/main/webapp/scripts/hudson-behavior.js
+++ b/war/src/main/webapp/scripts/hudson-behavior.js
@@ -2308,12 +2308,69 @@ function loadScript(href,callback) {
     head.insertBefore( script, head.firstChild );
 }
 
+/**
+ * Loads a dynamically created invisible IFRAME.
+ */
+function createIframe(src,callback) {
+    var iframe = document.createElement("iframe");
+    iframe.src = src;
+    iframe.style.display = "none";
+
+    var done = false;
+    iframe.onload = iframe.onreadystatechange = function() {
+        if ( !done && (!this.readyState ||
+                this.readyState === "loaded" || this.readyState === "complete") ) {
+            done = true;
+            callback();
+        }
+    };
+
+    document.body.appendChild(iframe);
+    return iframe;
+}
+
 var downloadService = {
     continuations: {},
 
     download : function(id,url,info, postBack,completionHandler) {
-        this.continuations[id] = {postBack:postBack,completionHandler:completionHandler};
-        loadScript(url+"?"+Hash.toQueryString(info));
+        var tag = {id:id,postBack:postBack,completionHandler:completionHandler,received:false};
+        this.continuations[id] = tag;
+
+        // use JSONP to download the data
+        function fallback() {
+            loadScript(url+"?id="+id+'&'+Hash.toQueryString(info));
+        }
+
+        if (window.postMessage) {
+            // try downloading the postMessage version of the data,
+            // if we don't receive postMessage (which probably means the server isn't ready with these new datasets),
+            // fallback to JSONP
+            tag.iframe = createIframe(url+".html?id="+id+'&'+Hash.toQueryString(info),function() {
+                window.setTimeout(function() {
+                    if (!tag.received)
+                        fallback();
+                },100); // bit of delay in case onload on our side fires first
+            });
+        } else {
+            // this browser doesn't support postMessage
+            fallback();
+        }
+
+        // NOTE:
+        //   the only reason we even try fallback() is in case our server accepts the submission without a signature
+        //   (which it really shouldn't)
+    },
+
+    /**
+     * Call back to postMessage
+     */
+    receiveMessage : function(ev) {
+        var self = this;
+        Object.values(this.continuations).each(function(tag) {
+            if (tag.iframe.contentWindow==ev.source) {
+                self.post(tag.id,JSON.parse(ev.data));
+            }
+        })
     },
 
     post : function(id,data) {
@@ -2322,15 +2379,22 @@ var downloadService = {
             data = id;
             id = data.id;
         }
-        var o = this.continuations[id];
+        var tag = this.continuations[id];
+        if (tag==undefined) {
+            console.log("Submission from update center that we don't know: "+id);
+            console.log("Likely mismatch between the registered ID vs ID in JSON");
+            return;
+        }
+        tag.received = true;
+
         // send the payload back in the body. We used to send this in as a form submission, but that hits the form size check in Jetty.
-        new Ajax.Request(o.postBack, {
+        new Ajax.Request(tag.postBack, {
             contentType:"application/json",
             encoding:"UTF-8",
             postBody:Object.toJSON(data),
             onSuccess: function() {
-                if(o.completionHandler!=null)
-                    o.completionHandler();
+                if(tag.completionHandler!=null)
+                    tag.completionHandler();
                 else if(downloadService.completionHandler!=null)
                     downloadService.completionHandler();
             }
@@ -2341,6 +2405,8 @@ var downloadService = {
 // update center service. to remain compatible with earlier version of Hudson, aliased.
 var updateCenter = downloadService;
 
+YAHOO.util.Event.addListener(window, "message", function(ev) { downloadService.receiveMessage(ev); })
+
 /*
 redirects to a page once the page is ready.
 
<commit_msg>Refreshed patch with full commit range<commit_after>Decription: Cherry picked fixes from 1.466.2 to resolve
 two security issues:
    - CVE-2012-4438 jenkins remote code execution
    - CVE-2012-4439 jenkins XSS
Origin: Upstream, commits fb73bac50f22526a3d3b...43ff1688eee6ea
Bug: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=688298

diff --git a/core/src/main/java/hudson/model/DownloadService.java b/core/src/main/java/hudson/model/DownloadService.java
index 7f8d86d..820a44a 100644
--- a/core/src/main/java/hudson/model/DownloadService.java
+++ b/core/src/main/java/hudson/model/DownloadService.java
@@ -26,18 +26,20 @@ package hudson.model;
 import hudson.Extension;
 import hudson.ExtensionList;
 import hudson.ExtensionPoint;
+import hudson.util.FormValidation;
+import hudson.util.FormValidation.Kind;
 import hudson.util.IOException2;
 import hudson.util.IOUtils;
 import hudson.util.QuotedStringTokenizer;
 import hudson.util.TextFile;
 import hudson.util.TimeUnit2;
 import jenkins.model.Jenkins;
+import jenkins.util.JSONSignatureValidator;
 import net.sf.json.JSONException;
 import org.kohsuke.stapler.Stapler;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import net.sf.json.JSONObject;
@@ -61,7 +63,8 @@ public class DownloadService extends PageDecorator {
      */
     public String generateFragment() {
     	if (neverUpdate) return "";
-    	
+        if (doesNotSupportPostMessage())  return "";
+
         StringBuilder buf = new StringBuilder();
         if(Jenkins.getInstance().hasPermission(Jenkins.READ)) {
             long now = System.currentTimeMillis();
@@ -88,6 +91,23 @@ public class DownloadService extends PageDecorator {
         return buf.toString();
     }
 
+    private boolean doesNotSupportPostMessage() {
+        StaplerRequest req = Stapler.getCurrentRequest();
+        if (req==null)      return false;
+
+        String ua = req.getHeader("User-Agent");
+        if (ua==null)       return false;
+
+        // according to http://caniuse.com/#feat=x-doc-messaging, IE <=7 doesn't support pstMessage
+        // see http://www.useragentstring.com/pages/Internet%20Explorer/ for user agents
+
+        // we want to err on the cautious side here.
+        // Because of JENKINS-15105, we can't serve signed metadata from JSON, which means we need to be
+        // using a modern browser as a vehicle to request these data. This check is here to prevent Jenkins
+        // from using older browsers that are known not to support postMessage as the vehicle.
+        return ua.contains("Windows") && (ua.contains(" MSIE 5.") || ua.contains(" MSIE 6.") || ua.contains(" MSIE 7."));
+    }
+
     private String mapHttps(String url) {
         /*
             HACKISH:
@@ -223,11 +243,24 @@ public class DownloadService extends PageDecorator {
          */
         public void doPostBack(StaplerRequest req, StaplerResponse rsp) throws IOException {
             long dataTimestamp = System.currentTimeMillis();
+            due = dataTimestamp+getInterval();  // success or fail, don't try too often
+
+            String json = IOUtils.toString(req.getInputStream(),"UTF-8");
+            JSONObject o = JSONObject.fromObject(json);
+
+            if (signatureCheck) {
+                FormValidation e = new JSONSignatureValidator("downloadable '"+id+"'").verifySignature(o);
+                if (e.kind!= Kind.OK) {
+                    LOGGER.severe(e.renderHtml());
+                    throw e;
+                }
+            }
+
             TextFile df = getDataFile();
-            df.write(IOUtils.toString(req.getInputStream(),"UTF-8"));
+            df.write(json);
             df.file.setLastModified(dataTimestamp);
-            due = dataTimestamp+getInterval();
             LOGGER.info("Obtained the updated data file for "+id);
+
             rsp.setContentType("text/plain");  // So browser won't try to parse response
         }
 
@@ -253,5 +286,10 @@ public class DownloadService extends PageDecorator {
     }
 
     public static boolean neverUpdate = Boolean.getBoolean(DownloadService.class.getName()+".never");
+
+    /**
+     * Off by default until we know this is reasonably working.
+     */
+    public static boolean signatureCheck = !Boolean.getBoolean(DownloadService.class.getName()+".noSignatureCheck");
 }
 
diff --git a/core/src/main/java/hudson/model/UpdateSite.java b/core/src/main/java/hudson/model/UpdateSite.java
index 74bccfb..578237e 100644
--- a/core/src/main/java/hudson/model/UpdateSite.java
+++ b/core/src/main/java/hudson/model/UpdateSite.java
@@ -25,7 +25,6 @@
 
 package hudson.model;
 
-import com.trilead.ssh2.crypto.Base64;
 import hudson.PluginManager;
 import hudson.PluginWrapper;
 import hudson.lifecycle.Lifecycle;
@@ -37,12 +36,9 @@ import hudson.util.IOUtils;
 import hudson.util.TextFile;
 import hudson.util.VersionNumber;
 import jenkins.model.Jenkins;
+import jenkins.util.JSONSignatureValidator;
 import net.sf.json.JSONException;
 import net.sf.json.JSONObject;
-import org.apache.commons.io.output.NullOutputStream;
-import org.apache.commons.io.output.TeeOutputStream;
-import org.jvnet.hudson.crypto.CertificateUtil;
-import org.jvnet.hudson.crypto.SignatureOutputStream;
 import org.kohsuke.stapler.DataBoundConstructor;
 import org.kohsuke.stapler.HttpResponse;
 import org.kohsuke.stapler.QueryParameter;
@@ -52,23 +48,14 @@ import org.kohsuke.stapler.StaplerResponse;
 import javax.servlet.ServletContext;
 import java.io.ByteArrayInputStream;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.security.DigestOutputStream;
 import java.security.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.security.Signature;
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.CertificateNotYetValidException;
-import java.security.cert.TrustAnchor;
-import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -174,100 +161,7 @@ public class UpdateSite {
      * Verifies the signature in the update center data file.
      */
     private FormValidation verifySignature(JSONObject o) throws IOException {
-        try {
-            FormValidation warning = null;
-
-            JSONObject signature = o.getJSONObject("signature");
-            if (signature.isNullObject()) {
-                return FormValidation.error("No signature block found in update center '"+id+"'");
-            }
-            o.remove("signature");
-
-            List<X509Certificate> certs = new ArrayList<X509Certificate>();
-            {// load and verify certificates
-                CertificateFactory cf = CertificateFactory.getInstance("X509");
-                for (Object cert : signature.getJSONArray("certificates")) {
-                    X509Certificate c = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.toString().toCharArray())));
-                    try {
-                        c.checkValidity();
-                    } catch (CertificateExpiredException e) { // even if the certificate isn't valid yet, we'll proceed it anyway
-                        warning = FormValidation.warning(e,String.format("Certificate %s has expired in update center '%s'",cert.toString(),id));
-                    } catch (CertificateNotYetValidException e) {
-                        warning = FormValidation.warning(e,String.format("Certificate %s is not yet valid in update center '%s'",cert.toString(),id));
-                    }
-                    certs.add(c);
-                }
-
-                // if we trust default root CAs, we end up trusting anyone who has a valid certificate,
-                // which isn't useful at all
-                Set<TrustAnchor> anchors = new HashSet<TrustAnchor>(); // CertificateUtil.getDefaultRootCAs();
-                Jenkins j = Jenkins.getInstance();
-                for (String cert : (Set<String>) j.servletContext.getResourcePaths("/WEB-INF/update-center-rootCAs")) {
-                    if (cert.endsWith(".txt"))  continue;       // skip text files that are meant to be documentation
-                    anchors.add(new TrustAnchor((X509Certificate)cf.generateCertificate(j.servletContext.getResourceAsStream(cert)),null));
-                }
-                File[] cas = new File(j.root, "update-center-rootCAs").listFiles();
-                if (cas!=null) {
-                    for (File cert : cas) {
-                        if (cert.getName().endsWith(".txt"))  continue;       // skip text files that are meant to be documentation
-                        FileInputStream in = new FileInputStream(cert);
-                        try {
-                            anchors.add(new TrustAnchor((X509Certificate)cf.generateCertificate(in),null));
-                        } finally {
-                            in.close();
-                        }
-                    }
-                }
-                CertificateUtil.validatePath(certs,anchors);
-            }
-
-            // this is for computing a digest to check sanity
-            MessageDigest sha1 = MessageDigest.getInstance("SHA1");
-            DigestOutputStream dos = new DigestOutputStream(new NullOutputStream(),sha1);
-
-            // this is for computing a signature
-            Signature sig = Signature.getInstance("SHA1withRSA");
-            sig.initVerify(certs.get(0));
-            SignatureOutputStream sos = new SignatureOutputStream(sig);
-
-            // until JENKINS-11110 fix, UC used to serve invalid digest (and therefore unverifiable signature)
-            // that only covers the earlier portion of the file. This was caused by the lack of close() call
-            // in the canonical writing, which apparently leave some bytes somewhere that's not flushed to
-            // the digest output stream. This affects Jenkins [1.424,1,431].
-            // Jenkins 1.432 shipped with the "fix" (1eb0c64abb3794edce29cbb1de50c93fa03a8229) that made it
-            // compute the correct digest, but it breaks all the existing UC json metadata out there. We then
-            // quickly discovered ourselves in the catch-22 situation. If we generate UC with the correct signature,
-            // it'll cut off [1.424,1.431] from the UC. But if we don't, we'll cut off [1.432,*).
-            //
-            // In 1.433, we revisited 1eb0c64abb3794edce29cbb1de50c93fa03a8229 so that the original "digest"/"signature"
-            // pair continues to be generated in a buggy form, while "correct_digest"/"correct_signature" are generated
-            // correctly.
-            //
-            // Jenkins should ignore "digest"/"signature" pair. Accepting it creates a vulnerability that allows
-            // the attacker to inject a fragment at the end of the json.
-            o.writeCanonical(new OutputStreamWriter(new TeeOutputStream(dos,sos),"UTF-8")).close();
-
-            // did the digest match? this is not a part of the signature validation, but if we have a bug in the c14n
-            // (which is more likely than someone tampering with update center), we can tell
-            String computedDigest = new String(Base64.encode(sha1.digest()));
-            String providedDigest = signature.optString("correct_digest");
-            if (providedDigest==null) {
-                return FormValidation.error("No correct_digest parameter in update center '"+id+"'. This metadata appears to be old.");
-            }
-            if (!computedDigest.equalsIgnoreCase(providedDigest)) {
-                return FormValidation.error("Digest mismatch: "+computedDigest+" vs "+providedDigest+" in update center '"+id+"'");
-            }
-
-            String providedSignature = signature.getString("correct_signature");
-            if (!sig.verify(Base64.decode(providedSignature.toCharArray()))) {
-                return FormValidation.error("Signature in the update center doesn't match with the certificate in update center '"+id+"'");
-            }
-
-            if (warning!=null)  return warning;
-            return FormValidation.ok();
-        } catch (GeneralSecurityException e) {
-            return FormValidation.error(e,"Signature verification failed in the update center '"+id+"'");
-        }
+        return new JSONSignatureValidator("update site '"+id+"'").verifySignature(o);
     }
 
     /**
diff --git a/core/src/main/java/hudson/search/Search.java b/core/src/main/java/hudson/search/Search.java
index 55737b8..f439d8a 100644
--- a/core/src/main/java/hudson/search/Search.java
+++ b/core/src/main/java/hudson/search/Search.java
@@ -84,6 +84,7 @@ public class Search {
      * See http://developer.mozilla.org/en/docs/Supporting_search_suggestions_in_search_plugins
      */
     public void doSuggestOpenSearch(StaplerRequest req, StaplerResponse rsp, @QueryParameter String q) throws IOException, ServletException {
+        rsp.setContentType(Flavor.JSON.contentType);
         DataWriter w = Flavor.JSON.createDataWriter(null, rsp);
         w.startArray();
         w.value(q);
diff --git a/core/src/main/java/hudson/tasks/junit/History.java b/core/src/main/java/hudson/tasks/junit/History.java
index 6d5333c..7a46b60 100644
--- a/core/src/main/java/hudson/tasks/junit/History.java
+++ b/core/src/main/java/hudson/tasks/junit/History.java
@@ -293,4 +293,12 @@ public class History {
 
     }
 
+    public static int asInt(String s, int defalutValue) {
+        if (s==null)    return defalutValue;
+        try {
+            return Integer.parseInt(s);
+        } catch (NumberFormatException e) {
+            return defalutValue;
+        }
+    }
 }
diff --git a/core/src/main/java/jenkins/util/JSONSignatureValidator.java b/core/src/main/java/jenkins/util/JSONSignatureValidator.java
new file mode 100644
index 0000000..69c6144
--- /dev/null
+++ b/core/src/main/java/jenkins/util/JSONSignatureValidator.java
@@ -0,0 +1,140 @@
+package jenkins.util;
+
+import com.trilead.ssh2.crypto.Base64;
+import hudson.util.FormValidation;
+import jenkins.model.Jenkins;
+import net.sf.json.JSONObject;
+import org.apache.commons.io.output.NullOutputStream;
+import org.apache.commons.io.output.TeeOutputStream;
+import org.jvnet.hudson.crypto.CertificateUtil;
+import org.jvnet.hudson.crypto.SignatureOutputStream;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.security.DigestOutputStream;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.Signature;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <NAME>
+ */
+public class JSONSignatureValidator {
+    private final String name;
+
+    public JSONSignatureValidator(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Verifies the signature in the update center data file.
+     */
+    public FormValidation verifySignature(JSONObject o) throws IOException {
+        try {
+            FormValidation warning = null;
+
+            JSONObject signature = o.getJSONObject("signature");
+            if (signature.isNullObject()) {
+                return FormValidation.error("No signature block found in "+name);
+            }
+            o.remove("signature");
+
+            List<X509Certificate> certs = new ArrayList<X509Certificate>();
+            {// load and verify certificates
+                CertificateFactory cf = CertificateFactory.getInstance("X509");
+                for (Object cert : signature.getJSONArray("certificates")) {
+                    X509Certificate c = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.toString().toCharArray())));
+                    try {
+                        c.checkValidity();
+                    } catch (CertificateExpiredException e) { // even if the certificate isn't valid yet, we'll proceed it anyway
+                        warning = FormValidation.warning(e,String.format("Certificate %s has expired in %s",cert.toString(),name));
+                    } catch (CertificateNotYetValidException e) {
+                        warning = FormValidation.warning(e,String.format("Certificate %s is not yet valid in %s",cert.toString(),name));
+                    }
+                    certs.add(c);
+                }
+
+                // if we trust default root CAs, we end up trusting anyone who has a valid certificate,
+                // which isn't useful at all
+                Set<TrustAnchor> anchors = new HashSet<TrustAnchor>(); // CertificateUtil.getDefaultRootCAs();
+                Jenkins j = Jenkins.getInstance();
+                for (String cert : (Set<String>) j.servletContext.getResourcePaths("/WEB-INF/update-center-rootCAs")) {
+                    if (cert.endsWith(".txt"))  continue;       // skip text files that are meant to be documentation
+                    anchors.add(new TrustAnchor((X509Certificate)cf.generateCertificate(j.servletContext.getResourceAsStream(cert)),null));
+                }
+                File[] cas = new File(j.root, "update-center-rootCAs").listFiles();
+                if (cas!=null) {
+                    for (File cert : cas) {
+                        if (cert.getName().endsWith(".txt"))  continue;       // skip text files that are meant to be documentation
+                        FileInputStream in = new FileInputStream(cert);
+                        try {
+                            anchors.add(new TrustAnchor((X509Certificate)cf.generateCertificate(in),null));
+                        } finally {
+                            in.close();
+                        }
+                    }
+                }
+                CertificateUtil.validatePath(certs, anchors);
+            }
+
+            // this is for computing a digest to check sanity
+            MessageDigest sha1 = MessageDigest.getInstance("SHA1");
+            DigestOutputStream dos = new DigestOutputStream(new NullOutputStream(),sha1);
+
+            // this is for computing a signature
+            Signature sig = Signature.getInstance("SHA1withRSA");
+            sig.initVerify(certs.get(0));
+            SignatureOutputStream sos = new SignatureOutputStream(sig);
+
+            // until JENKINS-11110 fix, UC used to serve invalid digest (and therefore unverifiable signature)
+            // that only covers the earlier portion of the file. This was caused by the lack of close() call
+            // in the canonical writing, which apparently leave some bytes somewhere that's not flushed to
+            // the digest output stream. This affects Jenkins [1.424,1,431].
+            // Jenkins 1.432 shipped with the "fix" (1eb0c64abb3794edce29cbb1de50c93fa03a8229) that made it
+            // compute the correct digest, but it breaks all the existing UC json metadata out there. We then
+            // quickly discovered ourselves in the catch-22 situation. If we generate UC with the correct signature,
+            // it'll cut off [1.424,1.431] from the UC. But if we don't, we'll cut off [1.432,*).
+            //
+            // In 1.433, we revisited 1eb0c64abb3794edce29cbb1de50c93fa03a8229 so that the original "digest"/"signature"
+            // pair continues to be generated in a buggy form, while "correct_digest"/"correct_signature" are generated
+            // correctly.
+            //
+            // Jenkins should ignore "digest"/"signature" pair. Accepting it creates a vulnerability that allows
+            // the attacker to inject a fragment at the end of the json.
+            o.writeCanonical(new OutputStreamWriter(new TeeOutputStream(dos,sos),"UTF-8")).close();
+
+            // did the digest match? this is not a part of the signature validation, but if we have a bug in the c14n
+            // (which is more likely than someone tampering with update center), we can tell
+            String computedDigest = new String(Base64.encode(sha1.digest()));
+            String providedDigest = signature.optString("correct_digest");
+            if (providedDigest==null) {
+                return FormValidation.error("No correct_digest parameter in "+name+". This metadata appears to be old.");
+            }
+            if (!computedDigest.equalsIgnoreCase(providedDigest)) {
+                return FormValidation.error("Digest mismatch: "+computedDigest+" vs "+providedDigest+" in "+name);
+            }
+
+            String providedSignature = signature.getString("correct_signature");
+            if (!sig.verify(Base64.decode(providedSignature.toCharArray()))) {
+                return FormValidation.error("Signature in the update center doesn't match with the certificate in "+name);
+            }
+
+            if (warning!=null)  return warning;
+            return FormValidation.ok();
+        } catch (GeneralSecurityException e) {
+            return FormValidation.error(e,"Signature verification failed in "+name);
+        }
+    }
+}
diff --git a/core/src/main/resources/hudson/tasks/junit/History/index.jelly b/core/src/main/resources/hudson/tasks/junit/History/index.jelly
index 8ad7ee4..cda7a24 100644
--- a/core/src/main/resources/hudson/tasks/junit/History/index.jelly
+++ b/core/src/main/resources/hudson/tasks/junit/History/index.jelly
@@ -26,8 +26,8 @@ THE SOFTWARE.
 <?jelly escape-by-default='true'?>
 <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
   <l:layout title="${%title(it.testObject.displayName)}">
-		<j:set var="start" value="${request.getParameter('start')?:0}"/>
-		<j:set var="end" value="${request.getParameter('end')?:start+24}"/>
+		<j:set var="start" value="${it.asInt(request.getParameter('start'),0)}"/>
+		<j:set var="end" value="${it.asInt(request.getParameter('end'),start+24)}"/>
 		<j:set var="rangeParameters" value="start=${start}&amp;end=${end+1}"/>
 		<script type="text/javascript">
 			function setCount() {
diff --git a/war/src/main/webapp/scripts/hudson-behavior.js b/war/src/main/webapp/scripts/hudson-behavior.js
index f4abe0b..7b74dda 100644
--- a/war/src/main/webapp/scripts/hudson-behavior.js
+++ b/war/src/main/webapp/scripts/hudson-behavior.js
@@ -2308,12 +2308,69 @@ function loadScript(href,callback) {
     head.insertBefore( script, head.firstChild );
 }
 
+/**
+ * Loads a dynamically created invisible IFRAME.
+ */
+function createIframe(src,callback) {
+    var iframe = document.createElement("iframe");
+    iframe.src = src;
+    iframe.style.display = "none";
+
+    var done = false;
+    iframe.onload = iframe.onreadystatechange = function() {
+        if ( !done && (!this.readyState ||
+                this.readyState === "loaded" || this.readyState === "complete") ) {
+            done = true;
+            callback();
+        }
+    };
+
+    document.body.appendChild(iframe);
+    return iframe;
+}
+
 var downloadService = {
     continuations: {},
 
     download : function(id,url,info, postBack,completionHandler) {
-        this.continuations[id] = {postBack:postBack,completionHandler:completionHandler};
-        loadScript(url+"?"+Hash.toQueryString(info));
+        var tag = {id:id,postBack:postBack,completionHandler:completionHandler,received:false};
+        this.continuations[id] = tag;
+
+        // use JSONP to download the data
+        function fallback() {
+            loadScript(url+"?id="+id+'&'+Hash.toQueryString(info));
+        }
+
+        if (window.postMessage) {
+            // try downloading the postMessage version of the data,
+            // if we don't receive postMessage (which probably means the server isn't ready with these new datasets),
+            // fallback to JSONP
+            tag.iframe = createIframe(url+".html?id="+id+'&'+Hash.toQueryString(info),function() {
+                window.setTimeout(function() {
+                    if (!tag.received)
+                        fallback();
+                },100); // bit of delay in case onload on our side fires first
+            });
+        } else {
+            // this browser doesn't support postMessage
+            fallback();
+        }
+
+        // NOTE:
+        //   the only reason we even try fallback() is in case our server accepts the submission without a signature
+        //   (which it really shouldn't)
+    },
+
+    /**
+     * Call back to postMessage
+     */
+    receiveMessage : function(ev) {
+        var self = this;
+        Object.values(this.continuations).each(function(tag) {
+            if (tag.iframe.contentWindow==ev.source) {
+                self.post(tag.id,JSON.parse(ev.data));
+            }
+        })
     },
 
     post : function(id,data) {
@@ -2322,15 +2379,22 @@ var downloadService = {
             data = id;
             id = data.id;
         }
-        var o = this.continuations[id];
+        var tag = this.continuations[id];
+        if (tag==undefined) {
+            console.log("Submission from update center that we don't know: "+id);
+            console.log("Likely mismatch between the registered ID vs ID in JSON");
+            return;
+        }
+        tag.received = true;
+
         // send the payload back in the body. We used to send this in as a form submission, but that hits the form size check in Jetty.
-        new Ajax.Request(o.postBack, {
+        new Ajax.Request(tag.postBack, {
             contentType:"application/json",
             encoding:"UTF-8",
             postBody:Object.toJSON(data),
             onSuccess: function() {
-                if(o.completionHandler!=null)
-                    o.completionHandler();
+                if(tag.completionHandler!=null)
+                    tag.completionHandler();
                 else if(downloadService.completionHandler!=null)
                     downloadService.completionHandler();
             }
@@ -2341,6 +2405,8 @@ var downloadService = {
 // update center service. to remain compatible with earlier version of Hudson, aliased.
 var updateCenter = downloadService;
 
+YAHOO.util.Event.addListener(window, "message", function(ev) { downloadService.receiveMessage(ev); })
+
 /*
 redirects to a page once the page is ready.
 

{'fraction_non_alphanumeric': 0.12785374982006623, 'fraction_numerical': 0.01954800633366921, 'mean_word_length': 2.5394334624006523, 'pattern_counts': {'":': 0, '<': 34, '<?xml version=': 0, '>': 32, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 14}, 'pii_count': 0, 'substrings_counts': 1, 'word_list_counts': {'cursed_substrings.json': 35, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5625823', 'n_tokens_mistral': 10641, 'n_tokens_neox': 9806, 'n_words': 2355}