[prev in list] [next in list] [prev in thread] [next in thread] 

List:       openjdk-distro-pkg-dev
Subject:    /hg/icedtea6: Add backports from OpenJDK.
From:       andrew () icedtea ! classpath ! org (andrew at icedtea ! classpath ! org)
Date:       2011-12-23 0:59:41
Message-ID: hg.8a24f86753c6.1324601981.2873452341184383832 () icedtea ! classpath ! org
[Download RAW message or body]

changeset 8a24f86753c6 in /hg/icedtea6
details: http://icedtea.classpath.org/hg/icedtea6?cmd=changeset;node=8a24f86753c6
author: Andrew John Hughes <ahughes at redhat.com>
date: Fri Dec 23 00:59:34 2011 +0000

	Add backports from OpenJDK. S6851973: ignore incoming channel
	binding if acceptor does not set one S7091528: javadoc attempts to
	parse .class files S7103725: REGRESSION - 6u29 breaks ssl
	connectivity using TLS_DH_anon_WITH_AES_128_CBC_SHA

	2011-12-20 Andrew John Hughes <ahughes at redhat.com>

	 * Makefile.am: Add new patches.
		* patches/openjdk/6851973-kerberos.patch,
		* patches/openjdk/7091528-javadoc_class_files.patch,
		* patches/openjdk/7103725-ssl_beast_regression.patch: Add
	OpenJDK backports.
		* NEWS: Updated.


diffstat:

 ChangeLog                                          |    9 +
 Makefile.am                                        |    5 +-
 NEWS                                               |    3 +
 patches/openjdk/6851973-kerberos.patch             |  200 +++++++++++++++++++++
 patches/openjdk/7091528-javadoc_class_files.patch  |  187 +++++++++++++++++++
 patches/openjdk/7103725-ssl_beast_regression.patch |   21 ++
 6 files changed, 424 insertions(+), 1 deletions(-)

diffs (471 lines):

diff -r 4d6b4215b78f -r 8a24f86753c6 ChangeLog
--- a/ChangeLog	Thu Dec 22 11:54:40 2011 -0500
+++ b/ChangeLog	Fri Dec 23 00:59:34 2011 +0000
@@ -1,3 +1,12 @@
+2011-12-20  Andrew John Hughes  <ahughes at redhat.com>
+
+	* Makefile.am: Add new patches.
+	* patches/openjdk/6851973-kerberos.patch,
+	* patches/openjdk/7091528-javadoc_class_files.patch,
+	* patches/openjdk/7103725-ssl_beast_regression.patch:
+	Add OpenJDK backports.
+	* NEWS: Updated.
+
 2011-12-22  Andrew Haley  <aph at redhat.com>
 
 	* arm_port/hotspot/src/cpu/zero/vm/thumb2.cpp (Thumb2_Safepoint):
diff -r 4d6b4215b78f -r 8a24f86753c6 Makefile.am
--- a/Makefile.am	Thu Dec 22 11:54:40 2011 -0500
+++ b/Makefile.am	Fri Dec 23 00:59:34 2011 +0000
@@ -398,7 +398,10 @@
 	patches/openjdk/7103224-glibc_name_collision.patch \
 	patches/arm-debug.patch \
 	patches/openjdk/683768-System-tray-icon.patch \
-	patches/openjdk/7102369-7094468-rmiregistry.patch
+	patches/openjdk/7102369-7094468-rmiregistry.patch \
+	patches/openjdk/6851973-kerberos.patch \
+	patches/openjdk/7091528-javadoc_class_files.patch \
+	patches/openjdk/7103725-ssl_beast_regression.patch
 
 if WITH_RHINO
 ICEDTEA_PATCHES += \
diff -r 4d6b4215b78f -r 8a24f86753c6 NEWS
--- a/NEWS	Thu Dec 22 11:54:40 2011 -0500
+++ b/NEWS	Fri Dec 23 00:59:34 2011 +0000
@@ -385,6 +385,9 @@
   - S7103224: collision between __LEAF define in interfaceSupport.hpp and \
                /usr/include/sys/cdefs.h with gcc
   - S7102369: remove java.rmi.server.codebase property parsing from registyimpl
   - S7094468: rmiregistry clean up
+  - S6851973: ignore incoming channel binding if acceptor does not set one
+  - S7091528: javadoc attempts to parse .class files
+  - S7103725: REGRESSION - 6u29 breaks ssl connectivity using \
                TLS_DH_anon_WITH_AES_128_CBC_SHA
 * Allow selection of test suites using the jtreg_checks argument e.g. \
                jtreg_checks="langtools"
 * CACAO
   - CA149: Used wrong class loader.
diff -r 4d6b4215b78f -r 8a24f86753c6 patches/openjdk/6851973-kerberos.patch
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/patches/openjdk/6851973-kerberos.patch	Fri Dec 23 00:59:34 2011 +0000
@@ -0,0 +1,202 @@
+# HG changeset patch
+# User weijun
+# Date 1245405807 -28800
+# Node ID 37ed72fe7561c143a882d03944a7d2240cbafe89
+# Parent  81c176909720561ec1c44fa3d94b18845e350ee9
+6851973: ignore incoming channel binding if acceptor does not set one
+Reviewed-by: valeriep
+
+diff -r 81c176909720 -r 37ed72fe7561 \
src/share/classes/sun/security/jgss/krb5/InitialToken.java +--- \
openjdk/jdk/src/share/classes/sun/security/jgss/krb5/InitialToken.java	Thu Jun 18 \
10:38:21 2009 -0400 ++++ \
openjdk/jdk/src/share/classes/sun/security/jgss/krb5/InitialToken.java	Fri Jun 19 \
18:03:27 2009 +0800 +@@ -33,6 +33,7 @@
+ import java.net.Inet6Address;
+ import java.security.MessageDigest;
+ import java.security.NoSuchAlgorithmException;
++import java.util.Arrays;
+ import sun.security.krb5.*;
+ import sun.security.jgss.HttpCaller;
+ import sun.security.krb5.internal.Krb5;
+@@ -219,43 +220,35 @@
+                         "Incorrect checksum");
+             }
+ 
+-            byte[] remoteBindingBytes = new byte[CHECKSUM_BINDINGS_SIZE];
+-            System.arraycopy(checksumBytes, 4, remoteBindingBytes, 0,
+-                             CHECKSUM_BINDINGS_SIZE);
+-
+-            byte[] noBindings = new byte[CHECKSUM_BINDINGS_SIZE];
+-            boolean tokenContainsBindings =
+-                (!java.util.Arrays.equals(noBindings, remoteBindingBytes));
+-
+             ChannelBinding localBindings = context.getChannelBinding();
+ 
+-            if (tokenContainsBindings ||
+-                localBindings != null) {
++            // Ignore remote channel binding info when not requested at
++            // local side (RFC 4121 4.1.1.2: the acceptor MAY ignore...).
++            //
++            // All major krb5 implementors implement this "MAY",
++            // and some applications depend on it as a workaround
++            // for not having a way to negotiate the use of channel
++            // binding -- the initiator application always uses CB
++            // and hopes the acceptor will ignore the CB if the
++            // acceptor doesn't support CB.
++            if (localBindings != null) {
++                byte[] remoteBindingBytes = new byte[CHECKSUM_BINDINGS_SIZE];
++                System.arraycopy(checksumBytes, 4, remoteBindingBytes, 0,
++                                 CHECKSUM_BINDINGS_SIZE);
+ 
+-                boolean badBindings = false;
+-                String errorMessage = null;
+-
+-                if (tokenContainsBindings &&
+-                    localBindings != null) {
++                byte[] noBindings = new byte[CHECKSUM_BINDINGS_SIZE];
++                if (!Arrays.equals(noBindings, remoteBindingBytes)) {
+                     byte[] localBindingsBytes =
+                         computeChannelBinding(localBindings);
+-                    //              System.out.println("ChannelBinding hash: "
+-                    //         + getHexBytes(localBindingsBytes));
+-                    badBindings =
+-                        (!java.util.Arrays.equals(localBindingsBytes,
+-                                                remoteBindingBytes));
+-                    errorMessage = "Bytes mismatch!";
+-                } else if (localBindings == null) {
+-                    errorMessage = "ChannelBinding not provided!";
+-                    badBindings = true;
++                    if (!Arrays.equals(localBindingsBytes,
++                                                remoteBindingBytes)) {
++                        throw new GSSException(GSSException.BAD_BINDINGS, -1,
++                                               "Bytes mismatch!");
++                    }
+                 } else {
+-                    errorMessage = "Token missing ChannelBinding!";
+-                    badBindings = true;
++                    throw new GSSException(GSSException.BAD_BINDINGS, -1,
++                                           "Token missing ChannelBinding!");
+                 }
+-
+-                if (badBindings)
+-                    throw new GSSException(GSSException.BAD_BINDINGS, -1,
+-                                           errorMessage);
+             }
+ 
+             flags = readLittleEndian(checksumBytes, 20, 4);
+diff -r 81c176909720 -r 37ed72fe7561 \
test/sun/security/krb5/auto/IgnoreChannelBinding.java +--- /dev/null	Thu Jan 01 \
00:00:00 1970 +0000 ++++ \
openjdk/jdk/test/sun/security/krb5/auto/IgnoreChannelBinding.java	Fri Jun 19 18:03:27 \
2009 +0800 +@@ -0,0 +1,113 @@
++/*
++ * Copyright 2009 Sun Microsystems, Inc.  All Rights Reserved.
++ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
++ *
++ * This code is free software; you can redistribute it and/or modify it
++ * under the terms of the GNU General Public License version 2 only, as
++ * published by the Free Software Foundation.
++ *
++ * This code is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
++ * version 2 for more details (a copy is included in the LICENSE file that
++ * accompanied this code).
++ *
++ * You should have received a copy of the GNU General Public License version
++ * 2 along with this work; if not, write to the Free Software Foundation,
++ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
++ *
++ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
++ * CA 95054 USA or visit www.sun.com if you need additional information or
++ * have any questions.
++ */
++
++/*
++ * @test
++ * @bug 6851973
++ * @summary ignore incoming channel binding if acceptor does not set one
++ */
++
++import java.net.InetAddress;
++import org.ietf.jgss.ChannelBinding;
++import org.ietf.jgss.GSSException;
++import sun.security.jgss.GSSUtil;
++
++public class IgnoreChannelBinding {
++
++    public static void main(String[] args)
++            throws Exception {
++
++        new OneKDC(null).writeJAASConf();
++
++        Context c = Context.fromJAAS("client");
++        Context s = Context.fromJAAS("server");
++
++        // All silent
++        c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
++        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
++        Context.handshake(c, s);
++
++        // Initiator req, acceptor ignore
++        c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
++        c.x().setChannelBinding(new ChannelBinding(
++                InetAddress.getByName("client.rabbit.hole"),
++                InetAddress.getByName("host.rabbit.hole"),
++                new byte[0]
++                ));
++        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
++        Context.handshake(c, s);
++
++        // Both req, and match
++        c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
++        c.x().setChannelBinding(new ChannelBinding(
++                InetAddress.getByName("client.rabbit.hole"),
++                InetAddress.getByName("host.rabbit.hole"),
++                new byte[0]
++                ));
++        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
++        s.x().setChannelBinding(new ChannelBinding(
++                InetAddress.getByName("client.rabbit.hole"),
++                InetAddress.getByName("host.rabbit.hole"),
++                new byte[0]
++                ));
++        Context.handshake(c, s);
++
++        // Both req, NOT match
++        c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
++        c.x().setChannelBinding(new ChannelBinding(
++                InetAddress.getByName("client.rabbit.hole"),
++                InetAddress.getByName("host.rabbit.hole"),
++                new byte[0]
++                ));
++        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
++        s.x().setChannelBinding(new ChannelBinding(
++                InetAddress.getByName("client.rabbit.hole"),
++                InetAddress.getByName("host.rabbit.hole"),
++                new byte[1]     // 0 -> 1
++                ));
++        try {
++            Context.handshake(c, s);
++            throw new Exception("Acceptor should reject initiator");
++        } catch (GSSException ge) {
++            // Expected bahavior
++        }
++
++        // Acceptor req, reject
++        c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
++        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
++        s.x().setChannelBinding(new ChannelBinding(
++                InetAddress.getByName("client.rabbit.hole"),
++                InetAddress.getByName("host.rabbit.hole"),
++                new byte[0]
++                ));
++        try {
++            Context.handshake(c, s);
++            throw new Exception("Acceptor should reject initiator");
++        } catch (GSSException ge) {
++            // Expected bahavior
++            if (ge.getMajor() != GSSException.BAD_BINDINGS) {
++                throw ge;
++            }
++        }
++    }
++}
diff -r 4d6b4215b78f -r 8a24f86753c6 \
                patches/openjdk/7091528-javadoc_class_files.patch
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/patches/openjdk/7091528-javadoc_class_files.patch	Fri Dec 23 00:59:34 2011 \
+0000 @@ -0,0 +1,191 @@
+# HG changeset patch
+# User jjg
+# Date 1316215126 25200
+# Node ID dea82aa3ca4fc15712cc8cfce72caf0d3fb210fc
+# Parent  c0835c8489b0fc6c5c20095ed0f0d6c1b01b69e0
+7091528: javadoc attempts to parse .class files
+Reviewed-by: darcy
+
+diff -r c0835c8489b0 -r dea82aa3ca4f \
src/share/classes/com/sun/tools/javadoc/JavadocTool.java +--- \
openjdk/langtools/src/share/classes/com/sun/tools/javadoc/JavadocTool.java	Fri Sep 16 \
14:16:11 2011 +0100 ++++ \
openjdk/langtools/src/share/classes/com/sun/tools/javadoc/JavadocTool.java	Fri Sep 16 \
16:18:46 2011 -0700 +@@ -1,5 +1,5 @@
+ /*
+- * Copyright (c) 2001, 2006, Oracle and/or its affiliates. All rights reserved.
++ * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved.
+  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+  *
+  * This code is free software; you can redistribute it and/or modify it
+@@ -257,24 +257,15 @@
+         for (String p: excludedPackages)
+             includedPackages.put(p, false);
+ 
+-        if (docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH)) {
+-            searchSubPackages(subPackages,
+-                    includedPackages,
+-                    packages, packageFiles,
+-                    StandardLocation.SOURCE_PATH,
+-                    EnumSet.of(JavaFileObject.Kind.SOURCE));
+-            searchSubPackages(subPackages,
+-                    includedPackages,
+-                    packages, packageFiles,
+-                    StandardLocation.CLASS_PATH,
+-                    EnumSet.of(JavaFileObject.Kind.CLASS));
+-        } else {
+-            searchSubPackages(subPackages,
+-                    includedPackages,
+-                    packages, packageFiles,
+-                    StandardLocation.CLASS_PATH,
+-                    EnumSet.of(JavaFileObject.Kind.SOURCE, \
JavaFileObject.Kind.CLASS)); +-        }
++        StandardLocation path = \
docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH) ++                ? \
StandardLocation.SOURCE_PATH : StandardLocation.CLASS_PATH; ++
++        searchSubPackages(subPackages,
++                includedPackages,
++                packages, packageFiles,
++                path,
++                EnumSet.of(JavaFileObject.Kind.SOURCE));
++
+         return packageFiles;
+     }
+ 
+diff -r c0835c8489b0 -r dea82aa3ca4f test/tools/javadoc/parser/7091528/T7091528.java
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ openjdk/langtools/test/tools/javadoc/parser/7091528/T7091528.java	Fri Sep 16 \
16:18:46 2011 -0700 +@@ -0,0 +1,71 @@
++/*
++ * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
++ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
++ *
++ * This code is free software; you can redistribute it and/or modify it
++ * under the terms of the GNU General Public License version 2 only, as
++ * published by the Free Software Foundation.
++ *
++ * This code is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
++ * version 2 for more details (a copy is included in the LICENSE file that
++ * accompanied this code).
++ *
++ * You should have received a copy of the GNU General Public License version
++ * 2 along with this work; if not, write to the Free Software Foundation,
++ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
++ *
++ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
++ * or visit www.oracle.com if you need additional information or have any
++ * questions.
++ */
++
++/**
++ * @test
++ * @bug     7091528
++ * @summary javadoc attempts to parse .class files
++ * @compile p/C1.java p/q/C2.java
++ * @run main T7091528
++ */
++
++import java.io.File;
++import java.io.PrintWriter;
++import java.io.StringWriter;
++
++public class T7091528 {
++    public static void main(String... args) {
++        new T7091528().run();
++    }
++
++    void run() {
++        File testSrc = new File(System.getProperty("test.src"));
++        File testClasses = new File(System.getProperty("test.classes"));
++        String[] args = {
++            "-d", ".",
++            "-sourcepath", testClasses + File.pathSeparator + testSrc,
++            "-subpackages",
++            "p"
++        };
++
++        StringWriter sw = new StringWriter();
++        PrintWriter pw = new PrintWriter(sw);
++        String doclet = com.sun.tools.doclets.standard.Standard.class.getName();
++        int rc = com.sun.tools.javadoc.Main.execute("javadoc", pw, pw, pw, doclet, \
args); ++        pw.close();
++
++        String out = sw.toString();
++        if (!out.isEmpty()) {
++            System.err.println(out);
++        }
++
++        if (rc != 0)
++            System.err.println("javadoc failed: exit code = " + rc);
++
++        if (out.matches("(?s).*p/[^ ]+\\.class.*"))
++            throw new Error("reading .class files");
++
++        if (!new File("index.html").exists())
++            throw new Error("index.html not found");
++    }
++}
+diff -r c0835c8489b0 -r dea82aa3ca4f test/tools/javadoc/parser/7091528/p/C1.java
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ openjdk/langtools/test/tools/javadoc/parser/7091528/p/C1.java	Fri Sep 16 \
16:18:46 2011 -0700 +@@ -0,0 +1,28 @@
++/*
++ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
++ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
++ *
++ * This code is free software; you can redistribute it and/or modify it
++ * under the terms of the GNU General Public License version 2 only, as
++ * published by the Free Software Foundation.
++ *
++ * This code is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
++ * version 2 for more details (a copy is included in the LICENSE file that
++ * accompanied this code).
++ *
++ * You should have received a copy of the GNU General Public License version
++ * 2 along with this work; if not, write to the Free Software Foundation,
++ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
++ *
++ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
++ * or visit www.oracle.com if you need additional information or have any
++ * questions.
++ */
++
++package p1;
++
++/** This is class C1. */
++public class C1 { }
++
+diff -r c0835c8489b0 -r dea82aa3ca4f test/tools/javadoc/parser/7091528/p/q/C2.java
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ openjdk/langtools/test/tools/javadoc/parser/7091528/p/q/C2.java	Fri Sep 16 \
16:18:46 2011 -0700 +@@ -0,0 +1,28 @@
++/*
++ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
++ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
++ *
++ * This code is free software; you can redistribute it and/or modify it
++ * under the terms of the GNU General Public License version 2 only, as
++ * published by the Free Software Foundation.
++ *
++ * This code is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
++ * version 2 for more details (a copy is included in the LICENSE file that
++ * accompanied this code).
++ *
++ * You should have received a copy of the GNU General Public License version
++ * 2 along with this work; if not, write to the Free Software Foundation,
++ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
++ *
++ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
++ * or visit www.oracle.com if you need additional information or have any
++ * questions.
++ */
++
++package p.q;
++
++/** This is class p.q.C2. */
++public class C2 { }
++
diff -r 4d6b4215b78f -r 8a24f86753c6 \
                patches/openjdk/7103725-ssl_beast_regression.patch
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/patches/openjdk/7103725-ssl_beast_regression.patch	Fri Dec 23 00:59:34 2011 \
+0000 @@ -0,0 +1,22 @@
+# HG changeset patch
+# User robm
+# Date 1324518632 0
+# Node ID cb20ed4b953add8f2443831a0552640efca53ab7
+# Parent  6b46f3c7c97cb060f88b196171b95d33bff80b7c
+7103725: REGRESSION - 6u29 breaks ssl connectivity using \
TLS_DH_anon_WITH_AES_128_CBC_SHA +Summary: resolving an issue with 7064341.
+Reviewed-by: wetmore, coffeys
+
+diff -r 6b46f3c7c97c -r cb20ed4b953a \
src/share/classes/sun/security/ssl/AppOutputStream.java +--- \
openjdk/jdk/src/share/classes/sun/security/ssl/AppOutputStream.java	Wed Nov 16 \
13:14:57 2011 +0000 ++++ \
openjdk/jdk/src/share/classes/sun/security/ssl/AppOutputStream.java	Thu Dec 22 \
01:50:32 2011 +0000 +@@ -90,7 +90,8 @@
+             do {
+                 int howmuch;
+                 if (isFirstRecordOfThePayload && c.needToSplitPayload()) {
+-                    howmuch = Math.min(0x01, r.availableDataBytes());
++                    howmuch = (len == 0) ? 0 : Math.min(
++                        0x01, r.availableDataBytes());
+                 } else {
+                     howmuch = Math.min(len, r.availableDataBytes());
+                 }


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic