From e93ee5debaaf702457f855dffe1ce3553c6b9345 Mon Sep 17 00:00:00 2001 From: Doug Lea
JarFile class is used to read the contents of a jar file
@@ -178,7 +179,7 @@ class JarFile extends ZipFile {
byte[] b = getBytes(manEntry);
man = new Manifest(new ByteArrayInputStream(b));
if (!jvInitialized) {
- jv = new JarVerifier(b);
+ jv = new JarVerifier(b, man);
}
} else {
man = new Manifest(super.getInputStream(manEntry));
@@ -297,10 +298,7 @@ class JarFile extends ZipFile {
if (names != null) {
for (int i = 0; i < names.length; i++) {
String name = names[i].toUpperCase(Locale.ENGLISH);
- if (name.endsWith(".DSA") ||
- name.endsWith(".RSA") ||
- name.endsWith(".EC") ||
- name.endsWith(".SF")) {
+ if (SignatureFileVerifier.isBlockOrSF(name)) {
// Assume since we found a signature-related file
// that the jar is signed and that we therefore
// need a JarVerifier and Manifest
@@ -329,17 +327,17 @@ class JarFile extends ZipFile {
if (names != null) {
for (int i = 0; i < names.length; i++) {
JarEntry e = getJarEntry(names[i]);
- if (!e.isDirectory()) {
+ if (!e.isDirectory() &&
+ SignatureFileVerifier.isBlock(names[i])) {
if (mev == null) {
mev = new ManifestEntryVerifier
(getManifestFromReference());
}
- byte[] b = getBytes(e);
- if (b != null && b.length > 0) {
- jv.beginEntry(e, mev);
- jv.update(b.length, b, 0, b.length, mev);
- jv.update(-1, null, 0, 0, mev);
- }
+ String key = names[i].substring(
+ 0, names[i].lastIndexOf("."));
+ jv.verifyBlock(names[i],
+ getBytes(e),
+ super.getInputStream(getJarEntry(key + ".SF")));
}
}
}
diff --git a/jdk/src/share/classes/java/util/jar/JarInputStream.java b/jdk/src/share/classes/java/util/jar/JarInputStream.java
index 3c4553304b2..b84219ea87a 100644
--- a/jdk/src/share/classes/java/util/jar/JarInputStream.java
+++ b/jdk/src/share/classes/java/util/jar/JarInputStream.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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
@@ -95,7 +95,7 @@ class JarInputStream extends ZipInputStream {
man.read(new ByteArrayInputStream(bytes));
closeEntry();
if (doVerify) {
- jv = new JarVerifier(bytes);
+ jv = new JarVerifier(bytes, man);
mev = new ManifestEntryVerifier(man);
}
return (JarEntry)super.getNextEntry();
diff --git a/jdk/src/share/classes/java/util/jar/JarVerifier.java b/jdk/src/share/classes/java/util/jar/JarVerifier.java
index 4f84ac28eff..8c69ff5c583 100644
--- a/jdk/src/share/classes/java/util/jar/JarVerifier.java
+++ b/jdk/src/share/classes/java/util/jar/JarVerifier.java
@@ -48,35 +48,18 @@ class JarVerifier {
/* a table mapping names to code signers, for jar entries that have
had their actual hashes verified */
- private Hashtable verifiedSigners;
+ private Map verifiedSigners;
/* a table mapping names to code signers, for jar entries that have
passed the .SF/.DSA/.EC -> MANIFEST check */
- private Hashtable sigFileSigners;
-
- /* a hash table to hold .SF bytes */
- private Hashtable sigFileData;
-
- /** "queue" of pending PKCS7 blocks that we couldn't parse
- * until we parsed the .SF file */
- private ArrayList pendingBlocks;
+ private Map sigFileSigners;
/* cache of CodeSigner objects */
private ArrayList signerCache;
- /* Are we parsing a block? */
- private boolean parsingBlockOrSF = false;
-
- /* Are we done parsing META-INF entries? */
- private boolean parsingMeta = true;
-
/* Are there are files to verify? */
private boolean anyToVerify = true;
- /* The output stream to use when keeping track of files we are interested
- in */
- private ByteArrayOutputStream baos;
-
/** The ManifestDigester object */
private volatile ManifestDigester manDig;
@@ -92,20 +75,20 @@ class JarVerifier {
/** collect -DIGEST-MANIFEST values for blacklist */
private List manifestDigests;
- public JarVerifier(byte rawBytes[]) {
+ /** The manifest object */
+ Manifest man = null;
+
+ public JarVerifier(byte rawBytes[], Manifest man) {
+ this.man = man;
manifestRawBytes = rawBytes;
- sigFileSigners = new Hashtable();
- verifiedSigners = new Hashtable();
- sigFileData = new Hashtable(11);
- pendingBlocks = new ArrayList();
- baos = new ByteArrayOutputStream();
+ sigFileSigners = new HashMap();
+ verifiedSigners = new HashMap();
manifestDigests = new ArrayList();
}
/**
- * This method scans to see which entry we're parsing and
- * keeps various state information depending on what type of
- * file is being parsed.
+ * This method scans to see which entry we're parsing and keeps
+ * various state information depending on the file being parsed.
*/
public void beginEntry(JarEntry je, ManifestEntryVerifier mev)
throws IOException
@@ -129,30 +112,6 @@ class JarVerifier {
* b. digest mismatch between the actual jar entry and the manifest
*/
- if (parsingMeta) {
- String uname = name.toUpperCase(Locale.ENGLISH);
- if ((uname.startsWith("META-INF/") ||
- uname.startsWith("/META-INF/"))) {
-
- if (je.isDirectory()) {
- mev.setEntry(null, je);
- return;
- }
-
- if (SignatureFileVerifier.isBlockOrSF(uname)) {
- /* We parse only DSA, RSA or EC PKCS7 blocks. */
- parsingBlockOrSF = true;
- baos.reset();
- mev.setEntry(null, je);
- }
- return;
- }
- }
-
- if (parsingMeta) {
- doneWithMeta();
- }
-
if (je.isDirectory()) {
mev.setEntry(null, je);
return;
@@ -188,11 +147,7 @@ class JarVerifier {
throws IOException
{
if (b != -1) {
- if (parsingBlockOrSF) {
- baos.write(b);
- } else {
- mev.update((byte)b);
- }
+ mev.update((byte)b);
} else {
processEntry(mev);
}
@@ -207,11 +162,7 @@ class JarVerifier {
throws IOException
{
if (n != -1) {
- if (parsingBlockOrSF) {
- baos.write(b, off, n);
- } else {
- mev.update(b, off, n);
- }
+ mev.update(b, off, n);
} else {
processEntry(mev);
}
@@ -223,101 +174,10 @@ class JarVerifier {
private void processEntry(ManifestEntryVerifier mev)
throws IOException
{
- if (!parsingBlockOrSF) {
- JarEntry je = mev.getEntry();
- if ((je != null) && (je.signers == null)) {
- je.signers = mev.verify(verifiedSigners, sigFileSigners);
- je.certs = mapSignersToCertArray(je.signers);
- }
- } else {
-
- try {
- parsingBlockOrSF = false;
-
- if (debug != null) {
- debug.println("processEntry: processing block");
- }
-
- String uname = mev.getEntry().getName()
- .toUpperCase(Locale.ENGLISH);
-
- if (uname.endsWith(".SF")) {
- String key = uname.substring(0, uname.length()-3);
- byte bytes[] = baos.toByteArray();
- // add to sigFileData in case future blocks need it
- sigFileData.put(key, bytes);
- // check pending blocks, we can now process
- // anyone waiting for this .SF file
- Iterator it = pendingBlocks.iterator();
- while (it.hasNext()) {
- SignatureFileVerifier sfv =
- (SignatureFileVerifier) it.next();
- if (sfv.needSignatureFile(key)) {
- if (debug != null) {
- debug.println(
- "processEntry: processing pending block");
- }
-
- sfv.setSignatureFile(bytes);
- sfv.process(sigFileSigners, manifestDigests);
- }
- }
- return;
- }
-
- // now we are parsing a signature block file
-
- String key = uname.substring(0, uname.lastIndexOf("."));
-
- if (signerCache == null)
- signerCache = new ArrayList();
-
- if (manDig == null) {
- synchronized(manifestRawBytes) {
- if (manDig == null) {
- manDig = new ManifestDigester(manifestRawBytes);
- manifestRawBytes = null;
- }
- }
- }
-
- SignatureFileVerifier sfv =
- new SignatureFileVerifier(signerCache,
- manDig, uname, baos.toByteArray());
-
- if (sfv.needSignatureFileBytes()) {
- // see if we have already parsed an external .SF file
- byte[] bytes = (byte[]) sigFileData.get(key);
-
- if (bytes == null) {
- // put this block on queue for later processing
- // since we don't have the .SF bytes yet
- // (uname, block);
- if (debug != null) {
- debug.println("adding pending block");
- }
- pendingBlocks.add(sfv);
- return;
- } else {
- sfv.setSignatureFile(bytes);
- }
- }
- sfv.process(sigFileSigners, manifestDigests);
-
- } catch (IOException ioe) {
- // e.g. sun.security.pkcs.ParsingException
- if (debug != null) debug.println("processEntry caught: "+ioe);
- // ignore and treat as unsigned
- } catch (SignatureException se) {
- if (debug != null) debug.println("processEntry caught: "+se);
- // ignore and treat as unsigned
- } catch (NoSuchAlgorithmException nsae) {
- if (debug != null) debug.println("processEntry caught: "+nsae);
- // ignore and treat as unsigned
- } catch (CertificateException ce) {
- if (debug != null) debug.println("processEntry caught: "+ce);
- // ignore and treat as unsigned
- }
+ JarEntry je = mev.getEntry();
+ if ((je != null) && (je.signers == null)) {
+ je.signers = mev.verify(verifiedSigners, sigFileSigners);
+ je.certs = mapSignersToCertArray(je.signers);
}
}
@@ -354,15 +214,15 @@ class JarVerifier {
* Force a read of the entry data to generate the
* verification hash.
*/
- try {
- InputStream s = jar.getInputStream(entry);
+ try (InputStream s = jar.getInputStream(entry)) {
byte[] buffer = new byte[1024];
int n = buffer.length;
while (n != -1) {
n = s.read(buffer, 0, buffer.length);
}
- s.close();
} catch (IOException e) {
+ // Ignore. When an exception is thrown, code signer
+ // will not be assigned.
}
}
return getCodeSigners(name);
@@ -408,11 +268,7 @@ class JarVerifier {
*/
void doneWithMeta()
{
- parsingMeta = false;
anyToVerify = !sigFileSigners.isEmpty();
- baos = null;
- sigFileData = null;
- pendingBlocks = null;
signerCache = null;
manDig = null;
// MANIFEST.MF is always treated as signed and verified,
@@ -423,6 +279,41 @@ class JarVerifier {
}
}
+ /**
+ * Verifies a PKCS7 SignedData block
+ * @param key name of block
+ * @param block the pkcs7 file
+ * @param ins the clear data
+ */
+ void verifyBlock(String key, byte[] block, InputStream ins) {
+ try {
+ if (signerCache == null)
+ signerCache = new ArrayList();
+
+ if (manDig == null) {
+ synchronized(manifestRawBytes) {
+ if (manDig == null) {
+ manDig = new ManifestDigester(manifestRawBytes);
+ manifestRawBytes = null;
+ }
+ }
+ }
+ SignatureFileVerifier sfv =
+ new SignatureFileVerifier(signerCache, man,
+ manDig, key, block);
+
+ if (sfv.needSignatureFile()) {
+ // see if we have already parsed an external .SF file
+ sfv.setSignatureFile(ins);
+ }
+ sfv.process(sigFileSigners, manifestDigests);
+ } catch (Exception e) {
+ if (debug != null) {
+ e.printStackTrace();
+ }
+ }
+ }
+
static class VerifierStream extends java.io.InputStream {
private InputStream is;
@@ -553,10 +444,7 @@ class JarVerifier {
* but this handles a CodeSource of any type, just in case.
*/
CodeSource[] sources = mapSignersToCodeSources(cs.getLocation(), getJarCodeSigners(), true);
- List sourceList = new ArrayList();
- for (int i = 0; i < sources.length; i++) {
- sourceList.add(sources[i]);
- }
+ List sourceList = Arrays.asList(sources);
int j = sourceList.indexOf(cs);
if (j != -1) {
CodeSigner[] match;
diff --git a/jdk/src/share/classes/sun/security/pkcs/PKCS7.java b/jdk/src/share/classes/sun/security/pkcs/PKCS7.java
index e80beb7372c..72a2e5f9f3e 100644
--- a/jdk/src/share/classes/sun/security/pkcs/PKCS7.java
+++ b/jdk/src/share/classes/sun/security/pkcs/PKCS7.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 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
@@ -38,6 +38,7 @@ import java.security.*;
import sun.security.util.*;
import sun.security.x509.AlgorithmId;
import sun.security.x509.CertificateIssuerName;
+import sun.security.x509.KeyUsageExtension;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CertInfo;
import sun.security.x509.X509CRLImpl;
@@ -492,7 +493,7 @@ public class PKCS7 {
// CRLs (optional)
if (crls != null && crls.length != 0) {
// cast to X509CRLImpl[] since X509CRLImpl implements DerEncoder
- Set+ * This method must be called in a row, reading chunks from a single + * manifest file by order. After all chunks are read, caller must call + * {@code update(null)} to fully consume the manifest. + *
+ * The entry names and attributes read will be merged in with the current + * manifest entries. The {@link #read} method cannot be called inside a + * row of update calls. + *
+ * Along with the calls, caller can call {@link #getMainAttributes()},
+ * {@link #getAttributes(java.lang.String)} or {@link #getEntries()}
+ * to get already available contents. However, in order not to return
+ * partial result, when the main attributes in the new manifest is not
+ * consumed completely, {@link #getMainAttributes()} throws an
+ * {@code IllegalStateException}. When a certain named entry is not
+ * consumed completely, {@link #getAttributes(java.lang.String)}
+ * returns the old {@code Attributes} for the name (if it exists).
+ *
+ * @param data null for last call, otherwise, feeding chunks
+ * @param offset offset into data to begin read
+ * @param length length of data after offset to read
+ * @exception IOException if an I/O error has occurred
+ * @exception IllegalStateException if {@code update(null)} is called
+ * without any previous {@code update(non-null)} call
+ */
+ public void update(byte[] data, int offset, int length) throws IOException {
+
+ // The last call
+ if (data == null) {
+ if (state == 0) {
+ throw new IllegalStateException("No data to update");
+ }
+ // We accept manifest not ended with \n or \n\n
+ if (hasLastByte()) {
+ consumeCurrent();
+ }
+ // We accept empty lines at the end
+ if (!currentAttr.isEmpty()) {
+ consumeAttr();
+ }
+ state = 0; // back to non-update state
+ current = null;
+ currentAttr = null;
+ return;
+ }
+
+ // The first call
+ if (state == 0) {
+ current = new byte[1024];
+ currentAttr = super.getMainAttributes(); // the main attribute
+ state = 1;
+ }
+
+ int end = offset + length;
+
+ while (offset < end) {
+ switch (data[offset]) {
+ case '\r':
+ break; // always skip
+ case '\n':
+ if (hasLastByte() && lastByte() == '\n') { // new section
+ consumeCurrent();
+ consumeAttr();
+ if (state == 1) {
+ state = 2;
+ }
+ currentAttr = new Attributes(2);
+ } else {
+ if (hasLastByte()) {
+ // save \n into current but do not parse,
+ // there might be a continuation later
+ ensureCapacity();
+ current[currentPos++] = data[offset];
+ } else if (state == 1) {
+ // there can be multiple empty lines between
+ // sections, but cannot be at the beginning
+ throw new IOException("invalid manifest format");
+ }
+ }
+ break;
+ case ' ':
+ if (!hasLastByte()) {
+ throw new IOException("invalid manifest format");
+ } else if (lastByte() == '\n') {
+ currentPos--; // continuation, remove last \n
+ } else { // a very normal ' '
+ ensureCapacity();
+ current[currentPos++] = data[offset];
+ }
+ break;
+ default:
+ if (hasLastByte() && lastByte() == '\n') {
+ // The start of a new pair, not continuation
+ consumeCurrent(); // the last line read
+ }
+ ensureCapacity();
+ current[currentPos++] = data[offset];
+ break;
+ }
+ offset++;
+ }
+ }
+
+ /**
+ * Returns the main Attributes for the Manifest.
+ * @exception IllegalStateException the main attributes is being read
+ * @return the main Attributes for the Manifest
+ */
+ public Attributes getMainAttributes() {
+ if (state == 1) {
+ throw new IllegalStateException();
+ }
+ return super.getMainAttributes();
+ }
+
+ /**
+ * Reads the Manifest from the specified InputStream. The entry
+ * names and attributes read will be merged in with the current
+ * manifest entries.
+ *
+ * @param is the input stream
+ * @exception IOException if an I/O error has occurred
+ * @exception IllegalStateException if called between two {@link #update}
+ * calls
+ */
+ public void read(InputStream is) throws IOException {
+ if (state != 0) {
+ throw new IllegalStateException("Cannot call read between updates");
+ }
+ super.read(is);
+ }
+
+ /*
+ * ---------- Helper methods -----------------
+ */
+
+ private void ensureCapacity() {
+ if (currentPos >= current.length-1) {
+ current = Arrays.copyOf(current, current.length*2);
+ }
+ }
+
+ private boolean hasLastByte() {
+ return currentPos > 0;
+ }
+
+ private byte lastByte() {
+ return current[currentPos-1];
+ }
+
+ // Parse current as key:value and save into currentAttr.
+ // There MUST be something inside current.
+ private void consumeCurrent() throws IOException {
+ // current normally has a \n end, except for the last line
+ if (current[currentPos-1] == '\n') currentPos--;
+ for (int i=0; i
+
+# Case_Ignorable (CI)
+
+CI ; N ; No ; F ; False
+CI ; Y ; Yes ; T ; True
+
+# Cased (Cased)
+
+Cased; N ; No ; F ; False
+Cased; Y ; Yes ; T ; True
+
+# Changes_When_Casefolded (CWCF)
+
+CWCF; N ; No ; F ; False
+CWCF; Y ; Yes ; T ; True
+
+# Changes_When_Casemapped (CWCM)
+
+CWCM; N ; No ; F ; False
+CWCM; Y ; Yes ; T ; True
+
+# Changes_When_Lowercased (CWL)
+
+CWL; N ; No ; F ; False
+CWL; Y ; Yes ; T ; True
+
+# Changes_When_NFKC_Casefolded (CWKCF)
+
+CWKCF; N ; No ; F ; False
+CWKCF; Y ; Yes ; T ; True
+
+# Changes_When_Titlecased (CWT)
+
+CWT; N ; No ; F ; False
+CWT; Y ; Yes ; T ; True
+
+# Changes_When_Uppercased (CWU)
+
+CWU; N ; No ; F ; False
+CWU; Y ; Yes ; T ; True
+
+# Composition_Exclusion (CE)
+
+CE ; N ; No ; F ; False
+CE ; Y ; Yes ; T ; True
+
+# Dash (Dash)
+
+Dash; N ; No ; F ; False
+Dash; Y ; Yes ; T ; True
+
+# Decomposition_Mapping (dm)
+
+# @missing: 0000..10FFFF; Decomposition_Mapping;
+
+# Decomposition_Type (dt)
+
+dt ; Can ; Canonical ; can
+dt ; Com ; Compat ; com
+dt ; Enc ; Circle ; enc
+dt ; Fin ; Final ; fin
+dt ; Font ; font
+dt ; Fra ; Fraction ; fra
+dt ; Init ; Initial ; init
+dt ; Iso ; Isolated ; iso
+dt ; Med ; Medial ; med
+dt ; Nar ; Narrow ; nar
+dt ; Nb ; Nobreak ; nb
+dt ; None ; none
+dt ; Sml ; Small ; sml
+dt ; Sqr ; Square ; sqr
+dt ; Sub ; sub
+dt ; Sup ; Super ; sup
+dt ; Vert ; Vertical ; vert
+dt ; Wide ; wide
+
+# Default_Ignorable_Code_Point (DI)
+
+DI ; N ; No ; F ; False
+DI ; Y ; Yes ; T ; True
+
+# Deprecated (Dep)
+
+Dep; N ; No ; F ; False
+Dep; Y ; Yes ; T ; True
+
+# Diacritic (Dia)
+
+Dia; N ; No ; F ; False
+Dia; Y ; Yes ; T ; True
+
+# East_Asian_Width (ea)
+
+ea ; A ; Ambiguous
+ea ; F ; Fullwidth
+ea ; H ; Halfwidth
+ea ; N ; Neutral
+ea ; Na ; Narrow
+ea ; W ; Wide
+
+# Expands_On_NFC (XO_NFC)
+
+XO_NFC; N ; No ; F ; False
+XO_NFC; Y ; Yes ; T ; True
+
+# Expands_On_NFD (XO_NFD)
+
+XO_NFD; N ; No ; F ; False
+XO_NFD; Y ; Yes ; T ; True
+
+# Expands_On_NFKC (XO_NFKC)
+
+XO_NFKC; N ; No ; F ; False
+XO_NFKC; Y ; Yes ; T ; True
+
+# Expands_On_NFKD (XO_NFKD)
+
+XO_NFKD; N ; No ; F ; False
+XO_NFKD; Y ; Yes ; T ; True
+
+# Extender (Ext)
+
+Ext; N ; No ; F ; False
+Ext; Y ; Yes ; T ; True
+
+# FC_NFKC_Closure (FC_NFKC)
+
+# @missing: 0000..10FFFF; FC_NFKC_Closure;
+
+# Full_Composition_Exclusion (Comp_Ex)
+
+Comp_Ex; N ; No ; F ; False
+Comp_Ex; Y ; Yes ; T ; True
+
+# General_Category (gc)
+
+gc ; C ; Other # Cc | Cf | Cn | Co | Cs
+gc ; Cc ; Control ; cntrl
+gc ; Cf ; Format
+gc ; Cn ; Unassigned
+gc ; Co ; Private_Use
+gc ; Cs ; Surrogate
+gc ; L ; Letter # Ll | Lm | Lo | Lt | Lu
+gc ; LC ; Cased_Letter # Ll | Lt | Lu
+gc ; Ll ; Lowercase_Letter
+gc ; Lm ; Modifier_Letter
+gc ; Lo ; Other_Letter
+gc ; Lt ; Titlecase_Letter
+gc ; Lu ; Uppercase_Letter
+gc ; M ; Mark # Mc | Me | Mn
+gc ; Mc ; Spacing_Mark
+gc ; Me ; Enclosing_Mark
+gc ; Mn ; Nonspacing_Mark
+gc ; N ; Number # Nd | Nl | No
+gc ; Nd ; Decimal_Number ; digit
+gc ; Nl ; Letter_Number
+gc ; No ; Other_Number
+gc ; P ; Punctuation ; punct # Pc | Pd | Pe | Pf | Pi | Po | Ps
+gc ; Pc ; Connector_Punctuation
+gc ; Pd ; Dash_Punctuation
+gc ; Pe ; Close_Punctuation
+gc ; Pf ; Final_Punctuation
+gc ; Pi ; Initial_Punctuation
+gc ; Po ; Other_Punctuation
+gc ; Ps ; Open_Punctuation
+gc ; S ; Symbol # Sc | Sk | Sm | So
+gc ; Sc ; Currency_Symbol
+gc ; Sk ; Modifier_Symbol
+gc ; Sm ; Math_Symbol
+gc ; So ; Other_Symbol
+gc ; Z ; Separator # Zl | Zp | Zs
+gc ; Zl ; Line_Separator
+gc ; Zp ; Paragraph_Separator
+gc ; Zs ; Space_Separator
+
+# Grapheme_Base (Gr_Base)
+
+Gr_Base; N ; No ; F ; False
+Gr_Base; Y ; Yes ; T ; True
+
+# Grapheme_Cluster_Break (GCB)
+
+GCB; CN ; Control
+GCB; CR ; CR
+GCB; EX ; Extend
+GCB; L ; L
+GCB; LF ; LF
+GCB; LV ; LV
+GCB; LVT ; LVT
+GCB; PP ; Prepend
+GCB; SM ; SpacingMark
+GCB; T ; T
+GCB; V ; V
+GCB; XX ; Other
+
+# Grapheme_Extend (Gr_Ext)
+
+Gr_Ext; N ; No ; F ; False
+Gr_Ext; Y ; Yes ; T ; True
+
+# Grapheme_Link (Gr_Link)
+
+Gr_Link; N ; No ; F ; False
+Gr_Link; Y ; Yes ; T ; True
+
+# Hangul_Syllable_Type (hst)
+
+hst; L ; Leading_Jamo
+hst; LV ; LV_Syllable
+hst; LVT ; LVT_Syllable
+hst; NA ; Not_Applicable
+hst; T ; Trailing_Jamo
+hst; V ; Vowel_Jamo
+
+# Hex_Digit (Hex)
+
+Hex; N ; No ; F ; False
+Hex; Y ; Yes ; T ; True
+
+# Hyphen (Hyphen)
+
+Hyphen; N ; No ; F ; False
+Hyphen; Y ; Yes ; T ; True
+
+# IDS_Binary_Operator (IDSB)
+
+IDSB; N ; No ; F ; False
+IDSB; Y ; Yes ; T ; True
+
+# IDS_Trinary_Operator (IDST)
+
+IDST; N ; No ; F ; False
+IDST; Y ; Yes ; T ; True
+
+# ID_Continue (IDC)
+
+IDC; N ; No ; F ; False
+IDC; Y ; Yes ; T ; True
+
+# ID_Start (IDS)
+
+IDS; N ; No ; F ; False
+IDS; Y ; Yes ; T ; True
+
+# ISO_Comment (isc)
+
+# @missing: 0000..10FFFF; ISO_Comment;
+
+# Math (Math)
+
+Math; N ; No ; F ; False
+Math; Y ; Yes ; T ; True
+
+# NFC_Quick_Check (NFC_QC)
+
+NFC_QC; M ; Maybe
+NFC_QC; N ; No
+NFC_QC; Y ; Yes
+
+# NFD_Quick_Check (NFD_QC)
+
+NFD_QC; N ; No
+NFD_QC; Y ; Yes
+
+# NFKC_Casefold (NFKC_CF)
+
+# @missing: 0000..10FFFF; NFKC_Casefold;
+
+# NFKC_Quick_Check (NFKC_QC)
+
+NFKC_QC; M ; Maybe
+NFKC_QC; N ; No
+NFKC_QC; Y ; Yes
+
+# NFKD_Quick_Check (NFKD_QC)
+
+NFKD_QC; N ; No
+NFKD_QC; Y ; Yes
+
+# Name (na)
+
+# @missing: 0000..10FFFF; Name;
+
+# Simple_Lowercase_Mapping (slc)
+
+# @missing: 0000..10FFFF; Simple_Lowercase_Mapping;
+
+# Simple_Titlecase_Mapping (stc)
+
+# @missing: 0000..10FFFF; Simple_Titlecase_Mapping;
+
+# Simple_Uppercase_Mapping (suc)
+
+# @missing: 0000..10FFFF; Simple_Uppercase_Mapping;
+
+# Soft_Dotted (SD)
+
+SD ; N ; No ; F ; False
+SD ; Y ; Yes ; T ; True
+
+# Terminal_Punctuation (Term)
+
+Term; N ; No ; F ; False
+Term; Y ; Yes ; T ; True
+
+# Titlecase_Mapping (tc)
+
+# @missing: 0000..10FFFF; Titlecase_Mapping;
+
+# Unicode_1_Name (na1)
+
+# @missing: 0000..10FFFF; Unicode_1_Name;
+
+# Variation_Selector (VS)
+
+VS ; N ; No ; F ; False
+VS ; Y ; Yes ; T ; True
+
+# White_Space (WSpace)
+
+WSpace; N ; No ; F ; False
+WSpace; Y ; Yes ; T ; True
+
+# Word_Break (WB)
+
+WB ; CR ; CR
+WB ; EX ; ExtendNumLet
+WB ; Extend ; Extend
+WB ; FO ; Format
+WB ; KA ; Katakana
+WB ; LE ; ALetter
+WB ; LF ; LF
+WB ; MB ; MidNumLet
+WB ; ML ; MidLetter
+WB ; MN ; MidNum
+WB ; NL ; Newline
+WB ; NU ; Numeric
+WB ; XX ; Other
+
+# XID_Continue (XIDC)
+
+XIDC; N ; No ; F ; False
+XIDC; Y ; Yes ; T ; True
+
+# XID_Start (XIDS)
+
+XIDS; N ; No ; F ; False
+XIDS; Y ; Yes ; T ; True
+
+# cjkAccountingNumeric (cjkAccountingNumeric)
+
+# @missing: 0000..10FFFF; cjkAccountingNumeric; NaN
+
+# cjkCompatibilityVariant (cjkCompatibilityVariant)
+
+# @missing: 0000..10FFFF; cjkCompatibilityVariant;
+
+# cjkIICore (cjkIICore)
+
+# @missing: 0000..10FFFF; cjkIICore;