s243a

FreenetURI.java

Oct 22nd, 2014
357
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 42.64 KB | None | 0 0
  1. //s243a peartree node: http://www.pearltrees.com/s243a/freeneturi-java/id12827554
  2. /* This code is part of Freenet. It is distributed under the GNU General
  3.  * Public License, version 2 (or at your option any later version). See
  4.  * http://www.gnu.org/ for further details of the GPL. */
  5. package freenet.keys;
  6.  
  7. import java.io.ByteArrayInputStream;
  8. import java.io.ByteArrayOutputStream;
  9. import java.io.DataInputStream;
  10. import java.io.DataOutputStream;
  11. import java.io.IOException;
  12. import java.io.Serializable;
  13. import java.net.MalformedURLException;
  14. import java.net.URI;
  15. import java.net.URISyntaxException;
  16. import java.util.ArrayList;
  17. import java.util.Arrays;
  18. import java.util.Comparator;
  19. import java.util.List;
  20. import java.util.Random;
  21. import java.util.StringTokenizer;
  22. import java.util.regex.Matcher;
  23. import java.util.regex.Pattern;
  24.  
  25. import com.db4o.ObjectContainer;
  26.  
  27. import freenet.client.InsertException;
  28. import freenet.support.Base64;
  29. import freenet.support.Fields;
  30. import freenet.support.HexUtil;
  31. import freenet.support.IllegalBase64Exception;
  32. import freenet.support.LogThresholdCallback;
  33. import freenet.support.Logger;
  34. import freenet.support.Logger.LogLevel;
  35. import freenet.support.URLDecoder;
  36. import freenet.support.URLEncodedFormatException;
  37. import freenet.support.URLEncoder;
  38. import freenet.support.io.FileUtil;
  39.  
  40. /**
  41.  * Note that the metadata pairs below are not presently supported. They are supported
  42.  * by the old (0.5) code however.
  43.  *
  44.  * FreenetURI handles parsing and creation of the Freenet URI format, defined
  45.  * as follows:
  46.  * <p>
  47.  * <code>freenet:[KeyType@]RoutingKey,CryptoKey[,n1=v1,n2=v2,...][/docname][/metastring]</code>
  48.  * </p>
  49.  * <p>
  50.  * where KeyType is the TLA of the key (currently USK, SSK, KSK, or CHK). If
  51.  * omitted, KeyType defaults to KSK.
  52.  * BUT: CHKs don't support or require a docname.
  53.  * KSKs and SSKs do.
  54.  * Therefore CHKs go straight into metastrings.
  55.  * </p>
  56.  * <p>
  57.  * For KSKs, the string keyword (docname) takes the RoutingKey position and the
  58.  * remainder of the fields are inapplicable (except metastring). Examples:
  59.  * <coe>freenet:KSK@foo/bar freenet:[email protected] freenet:test.html</code>.
  60.  * </p>
  61.  * <p>
  62.  * RoutingKey is the modified Base64 encoded key value. CryptoKey is the
  63.  * modified Base64 encoded decryption key.
  64.  * </p>
  65.  * <p>
  66.  * Following the RoutingKey and CryptoKey there may be a series of <code>
  67.  * name=value</code> pairs representing URI meta-information.
  68.  * </p>
  69.  * <p>
  70.  * The docname is only meaningful for SSKs, and is hashed with the PK
  71.  * fingerprint to get the key value.
  72.  * </p>
  73.  * <p>
  74.  * The metastring is meant to be passed to the metadata processing systems that
  75.  * act on the retrieved document.
  76.  * </p>
  77.  * <p>
  78.  * When constructing a FreenetURI with a String argument, it is now legal for CHK keys
  79.  * to have a '.extension' tail, eg '[email protected]'. The constructor will simply
  80.  * chop off at the first dot.
  81.  * </p>
  82.  * REDFLAG: Old code has a FieldSet, and the ability to put arbitrary metadata
  83.  * in through name/value pairs. Do we want this?
  84.  */
  85. // WARNING: THIS CLASS IS STORED IN DB4O -- THINK TWICE BEFORE ADD/REMOVE/RENAME FIELDS
  86. public class FreenetURI implements Cloneable, Comparable<FreenetURI>, Serializable {
  87.  
  88.     /**
  89.      * For Serializable.
  90.      */
  91.     private static transient final long serialVersionUID = 1L;
  92.    
  93.     private static volatile boolean logMINOR;
  94.     private static volatile boolean logDEBUG;
  95.     static {
  96.         Logger.registerLogThresholdCallback(new LogThresholdCallback() {
  97.             @Override
  98.             public void shouldUpdate() {
  99.                 logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
  100.                 logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this);
  101.             }
  102.         });
  103.     }
  104.  
  105.     private final String keyType,  docName;
  106.     /** The meta-strings, in the order they are given. Typically we will
  107.      * construct the base key from the key type, routing key, extra, and
  108.      * document name (SSK@blah,blah,blah/filename, CHK@blah,blah,blah,
  109.      * KSK@filename or USK@blah,blah,blah/filename/20), fetch it, discover
  110.      * that it is a manifest, and look up the first meta-string. If this is
  111.      * the final data, we use that (and complain if there are meta-strings
  112.      * left), else we look up the next meta-string in the manifest, and so
  113.      * on. This is executed by SingleFileFetcher. */
  114.     private final String[] metaStr;
  115.     /* for SSKs, routingKey is actually the pkHash. the actual routing key is
  116.      * calculated in NodeSSK and is a function of pkHash and the docName
  117.      */
  118.     private final byte[] routingKey,  cryptoKey,  extra;
  119.     private final long suggestedEdition; // for USKs
  120.     private boolean hasHashCode;
  121.     private int hashCode;
  122. //  private final int uniqueHashCode;
  123.     static final String[] VALID_KEY_TYPES =
  124.         new String[]{"CHK", "SSK", "KSK", "USK"};
  125.  
  126.     @Override
  127.     public synchronized int hashCode() {
  128.         if(hasHashCode)
  129.             return hashCode;
  130.         int x = keyType.hashCode();
  131.         if(docName != null)
  132.             x ^= docName.hashCode();
  133.         if(metaStr != null)
  134.             for(int i = 0; i < metaStr.length; i++)
  135.                 x ^= metaStr[i].hashCode();
  136.         if(routingKey != null)
  137.             x ^= Fields.hashCode(routingKey);
  138.         if(cryptoKey != null)
  139.             x ^= Fields.hashCode(cryptoKey);
  140.         if(extra != null)
  141.             x ^= Fields.hashCode(extra);
  142.         if(keyType.equals("USK"))
  143.             x ^= suggestedEdition;
  144.         hashCode = x;
  145.         hasHashCode = true;
  146.         return x;
  147.     }
  148.  
  149.     @Override
  150.     public boolean equals(Object o) {
  151.         if(o == this) return true;
  152.         if(!(o instanceof FreenetURI))
  153.             return false;
  154.         else {
  155.             FreenetURI f = (FreenetURI) o;
  156.             if(!keyType.equals(f.keyType))
  157.                 return false;
  158.             if(keyType.equals("USK"))
  159.                 if(!(suggestedEdition == f.suggestedEdition))
  160.                     return false;
  161.             if((docName == null) ^ (f.docName == null))
  162.                 return false;
  163.             if((metaStr == null || metaStr.length == 0) ^ (f.metaStr == null || f.metaStr.length == 0))
  164.                 return false;
  165.             if((routingKey == null) ^ (f.routingKey == null))
  166.                 return false;
  167.             if((cryptoKey == null) ^ (f.cryptoKey == null))
  168.                 return false;
  169.             if((extra == null) ^ (f.extra == null))
  170.                 return false;
  171.             if((docName != null) && !docName.equals(f.docName))
  172.                 return false;
  173.             if((metaStr != null) && !Arrays.equals(metaStr, f.metaStr))
  174.                 return false;
  175.             if((routingKey != null) && !Arrays.equals(routingKey, f.routingKey))
  176.                 return false;
  177.             if((cryptoKey != null) && !Arrays.equals(cryptoKey, f.cryptoKey))
  178.                 return false;
  179.             if((extra != null) && !Arrays.equals(extra, f.extra))
  180.                 return false;
  181.             return true;
  182.         }
  183.     }
  184.  
  185.     /** Is the keypair (the routing key and crypto key) the same as the
  186.      * given key?
  187.      * @return False if there is no routing key or no crypto key (CHKs,
  188.      * SSKs, USKs have them, KSKs don't), or if the keys don't have the
  189.      * same crypto key and routing key.
  190.      */
  191.     public boolean equalsKeypair(FreenetURI u2) {
  192.         if((routingKey != null) && (cryptoKey != null))
  193.             return Arrays.equals(routingKey, u2.routingKey) && Arrays.equals(cryptoKey, u2.cryptoKey);
  194.  
  195.         return false;
  196.     }
  197.  
  198.     @Override
  199.     public final FreenetURI clone() {
  200.         return new FreenetURI(this);
  201.     }
  202.  
  203.     public FreenetURI(FreenetURI uri) {
  204. //      this.uniqueHashCode = super.hashCode();
  205.         if(uri.keyType == null) throw new NullPointerException();
  206.         keyType = uri.keyType;
  207.         docName = uri.docName;
  208.         if(uri.metaStr != null) {
  209.             metaStr = uri.metaStr.clone();
  210.         } else metaStr = null;
  211.         if(uri.routingKey != null) {
  212.             routingKey = uri.routingKey.clone();
  213.         } else
  214.             routingKey = null;
  215.         if(uri.cryptoKey != null) {
  216.             cryptoKey = uri.cryptoKey.clone();
  217.         } else
  218.             cryptoKey = null;
  219.         if(uri.extra != null) {
  220.             extra = uri.extra.clone();
  221.         } else
  222.             extra = null;
  223.         this.suggestedEdition = uri.suggestedEdition;
  224.         if(logDEBUG) Logger.debug(this, "Copied: "+toString()+" from "+uri.toString(), new Exception("debug"));
  225.     }
  226.    
  227.     boolean noCacheURI = false;
  228.    
  229.     /** Optimize for memory. */
  230.     public FreenetURI intern() {
  231.         boolean changedAnything = false;
  232.         byte[] x = extra;
  233.         if(keyType.equals("CHK"))
  234.             x = ClientCHK.internExtra(x);
  235.         else
  236.             x = ClientSSK.internExtra(x);
  237.         if(x != extra) changedAnything = true;
  238.         String[] newMetaStr = null;
  239.         if(metaStr != null) {
  240.             newMetaStr = new String[metaStr.length];
  241.             for(int i=0;i<metaStr.length;i++) {
  242.                 newMetaStr[i] = metaStr[i].intern();
  243.                 if(metaStr[i] != newMetaStr[i]) changedAnything = true;
  244.             }
  245.         }
  246.         String dn = docName == null ? null : docName.intern();
  247.         if(dn != docName) changedAnything = true;
  248.         if(!changedAnything) {
  249.             noCacheURI = true;
  250.             return this;
  251.         }
  252.         FreenetURI u = new FreenetURI(keyType, dn, newMetaStr, routingKey, cryptoKey, extra);
  253.         u.noCacheURI = true;
  254.         return u;
  255.     }
  256.  
  257.     public FreenetURI(String keyType, String docName) {
  258.         this(keyType, docName, (String[]) null, null, null, null);
  259.     }
  260.     public static final FreenetURI EMPTY_CHK_URI = new FreenetURI("CHK", null, null, null, null, null);
  261.  
  262.     public FreenetURI(
  263.         String keyType,
  264.         String docName,
  265.         byte[] routingKey,
  266.         byte[] cryptoKey, byte[] extra2) {
  267.         this(keyType, docName, (String[]) null, routingKey, cryptoKey, extra2);
  268.     }
  269.  
  270.     public FreenetURI(
  271.         String keyType,
  272.         String docName,
  273.         String metaStr,
  274.         byte[] routingKey,
  275.         byte[] cryptoKey) {
  276.         this(
  277.             keyType,
  278.             docName,
  279.             (metaStr == null ? (String[]) null : new String[]{metaStr}),
  280.             routingKey,
  281.             cryptoKey,
  282.             null);
  283.     }
  284.  
  285.     public FreenetURI(
  286.         String keyType,
  287.         String docName,
  288.         String[] metaStr,
  289.         byte[] routingKey,
  290.         byte[] cryptoKey, byte[] extra2) {
  291. //      this.uniqueHashCode = super.hashCode();
  292.         this.keyType = keyType.trim().toUpperCase().intern();
  293.         this.docName = docName;
  294.         this.metaStr = metaStr;
  295.         this.routingKey = routingKey;
  296.         if(routingKey != null && keyType.equals("CHK") && routingKey.length != 32)
  297.             throw new IllegalArgumentException("Bad URI: Routing key should be 32 bytes");
  298.         this.cryptoKey = cryptoKey;
  299.         if(cryptoKey != null && cryptoKey.length != 32)
  300.             throw new IllegalArgumentException("Bad URI: Crypto key should be 32 bytes");
  301.         this.extra = extra2;
  302.         this.suggestedEdition = -1;
  303.         if (logDEBUG) Logger.minor(this, "Created from components: "+toString(), new Exception("debug"));
  304.     }
  305.  
  306.     public FreenetURI(
  307.         String keyType,
  308.         String docName,
  309.         String[] metaStr,
  310.         byte[] routingKey,
  311.         byte[] cryptoKey, byte[] extra2,
  312.         long suggestedEdition) {
  313. //      this.uniqueHashCode = super.hashCode();
  314.         this.keyType = keyType.trim().toUpperCase().intern();
  315.         this.docName = docName;
  316.         this.metaStr = metaStr;
  317.         this.routingKey = routingKey;
  318.         if(routingKey != null && keyType.equals("CHK") && routingKey.length != 32)
  319.             throw new IllegalArgumentException("Bad URI: Routing key should be 32 bytes");
  320.         this.cryptoKey = cryptoKey;
  321.         if(cryptoKey != null && cryptoKey.length != 32)
  322.             throw new IllegalArgumentException("Bad URI: Crypto key should be 32 bytes");
  323.         this.extra = extra2;
  324.         this.suggestedEdition = suggestedEdition;
  325.         if (logDEBUG) Logger.minor(this, "Created from components (B): "+toString(), new Exception("debug"));
  326.     }
  327.  
  328.     // Strip http:// and freenet: prefix
  329.     protected final static Pattern URI_PREFIX = Pattern.compile("^(http://[^/]+/+)?(freenet:)?");
  330.  
  331.     public FreenetURI(String URI) throws MalformedURLException {
  332.         this(URI, false);
  333.     }
  334.    
  335.     /**
  336.      * Create a FreenetURI from its string form. May or may not have a
  337.      * freenet: prefix.
  338.      * @throws MalformedURLException If the string could not be parsed.
  339.      */
  340.     public FreenetURI(String URI, boolean noTrim) throws MalformedURLException {
  341. //      this.uniqueHashCode = super.hashCode();
  342.         if(URI == null)
  343.             throw new MalformedURLException("No URI specified");
  344.  
  345.         if(!noTrim)
  346.             URI = URI.trim();
  347.        
  348.         // Strip ?max-size, ?type etc.
  349.         // Un-encoded ?'s are illegal.
  350.         int x = URI.indexOf('?');
  351.         if(x > -1)
  352.             URI = URI.substring(0, x);
  353.            
  354.         if(URI.indexOf('@') < 0 || URI.indexOf('/') < 0)
  355.             // Encoded URL?
  356.             try {
  357.                 URI = URLDecoder.decode(URI, false);
  358.             } catch(URLEncodedFormatException e) {
  359.                 throw new MalformedURLException("Invalid URI: no @ or /, or @ or / is escaped but there are invalid escapes");
  360.             }
  361.  
  362.         URI = URI_PREFIX.matcher(URI).replaceFirst("");
  363.  
  364.         // decode keyType
  365.         int atchar = URI.indexOf('@');
  366.         if(atchar == -1)
  367.             throw new MalformedURLException("There is no @ in that URI! (" + URI + ')');
  368.  
  369.         String _keyType = URI.substring(0, atchar).toUpperCase();
  370.         URI = URI.substring(atchar + 1);
  371.  
  372.         boolean validKeyType = false;
  373.         for(int i = 0; i < VALID_KEY_TYPES.length; i++) {
  374.             if (_keyType.equals(VALID_KEY_TYPES[i])) {
  375.                 validKeyType = true;
  376.                 _keyType = VALID_KEY_TYPES[i];
  377.                 break;
  378.             }
  379.         }
  380.         keyType = _keyType;
  381.         if(!validKeyType)
  382.             throw new MalformedURLException("Invalid key type: " + keyType);
  383.  
  384.         boolean isSSK = "SSK".equals(keyType);
  385.         boolean isUSK = "USK".equals(keyType);
  386.         boolean isKSK = "KSK".equals(keyType);
  387.  
  388.         // decode metaString
  389.         ArrayList<String> sv = null;
  390.         int slash2;
  391.         sv = new ArrayList<String>();
  392.         if (isKSK) URI = "/" + URI; // ensure that KSK docNames are decoded
  393.         while ((slash2 = URI.lastIndexOf('/')) != -1) {
  394.             String s;
  395.             try {
  396.                 s = URLDecoder.decode(URI.substring(slash2 + 1 /* "/".length() */), true);
  397.             } catch(URLEncodedFormatException e) {
  398.                 throw (MalformedURLException)new MalformedURLException(e.toString()).initCause(e);
  399.             }
  400.             if(s != null)
  401.                 sv.add(s);
  402.             URI = URI.substring(0, slash2);
  403.         }
  404.  
  405.         // sv is *backwards*
  406.         // this makes for more efficient handling
  407.  
  408.         // SSK@ = create a random SSK
  409.         if(sv.isEmpty() && (isUSK || isKSK))
  410.             throw new MalformedURLException("No docname for " + keyType);
  411.        
  412.         if((isSSK || isUSK || isKSK) && !sv.isEmpty()) {
  413.  
  414.             docName = sv.remove(sv.size() - 1);
  415.             if(isUSK) {
  416.                 if(sv.isEmpty())
  417.                     throw new MalformedURLException("No suggested edition number for USK");
  418.                 try {
  419.                     suggestedEdition = Long.parseLong(sv.remove(sv.size() - 1));
  420.                 } catch(NumberFormatException e) {
  421.                     throw (MalformedURLException)new MalformedURLException("Invalid suggested edition: " + e).initCause(e);
  422.                 }
  423.             } else
  424.                 suggestedEdition = -1;
  425.         } else {
  426.             // docName not necessary, nor is it supported, for CHKs.
  427.             docName = null;
  428.             suggestedEdition = -1;
  429.         }
  430.  
  431.         if(!sv.isEmpty()) {
  432.             metaStr = new String[sv.size()];
  433.             for(int i = 0; i < metaStr.length; i++) {
  434.                 metaStr[i] = sv.get(metaStr.length - 1 - i).intern();
  435.                 if(metaStr[i] == null)
  436.                     throw new NullPointerException();
  437.             }
  438.         } else
  439.             metaStr = null;
  440.  
  441.         if(isKSK) {
  442.             routingKey = extra = cryptoKey = null;
  443.             return;
  444.         }
  445.  
  446.         // strip 'file extensions' from CHKs
  447.         // added by aum ([email protected])
  448.         if("CHK".equals(keyType)) {
  449.             int idx = URI.lastIndexOf('.');
  450.             if(idx != -1)
  451.                 URI = URI.substring(0, idx);
  452.         }
  453.  
  454.         // URI now contains: routingKey[,cryptoKey][,metaInfo]
  455.         StringTokenizer st = new StringTokenizer(URI, ",");
  456.         try {
  457.             if(st.hasMoreTokens()) {
  458.                 routingKey = Base64.decode(st.nextToken());
  459.                 if(routingKey.length != 32 && keyType.equals("CHK"))
  460.                     throw new MalformedURLException("Bad URI: Routing key should be 32 bytes long");
  461.             } else {
  462.                 routingKey = cryptoKey = extra = null;
  463.                 return;
  464.             }
  465.             if(!st.hasMoreTokens()) {
  466.                 cryptoKey = extra = null;
  467.                 return;
  468.             }
  469.  
  470.             // Can be cryptokey or name-value pair.
  471.             String t = st.nextToken();
  472.             cryptoKey = Base64.decode(t);
  473.             if(cryptoKey.length != 32)
  474.                 throw new MalformedURLException("Bad URI: Routing key should be 32 bytes long");
  475.             if(!st.hasMoreTokens()) {
  476.                 extra = null;
  477.                 return;
  478.             }
  479.             extra = Base64.decode(st.nextToken());
  480.  
  481.         } catch(IllegalBase64Exception e) {
  482.             throw new MalformedURLException("Invalid Base64 quantity: " + e);
  483.         }
  484.         if (logDEBUG) Logger.debug(this, "Created from parse: "+toString()+" from "+URI, new Exception("debug"));
  485.     }
  486.  
  487.     /** USK constructor from components. */
  488.     public FreenetURI(byte[] pubKeyHash, byte[] cryptoKey, byte[] extra, String siteName, long suggestedEdition2) {
  489. //      this.uniqueHashCode = super.hashCode();
  490.         this.keyType = "USK";
  491.         this.routingKey = pubKeyHash;
  492.         // Don't check routingKey as it could be an insertable USK
  493.         this.cryptoKey = cryptoKey;
  494.         if(cryptoKey != null && cryptoKey.length != 32)
  495.             throw new IllegalArgumentException("Bad URI: Crypto key should be 32 bytes");
  496.         this.extra = extra;
  497.         this.docName = siteName;
  498.         this.suggestedEdition = suggestedEdition2;
  499.         metaStr = null;
  500.         if (logDEBUG) Logger.minor(this, "Created from components (USK): "+toString(), new Exception("debug"));
  501.     }
  502.  
  503.     /** Dump the individual components of the key to System.out. */
  504.     public void decompose() {
  505.         String r = routingKey == null ? "none" : HexUtil.bytesToHex(routingKey);
  506.         String k = cryptoKey == null ? "none" : HexUtil.bytesToHex(cryptoKey);
  507.         String e = extra == null ? "none" : HexUtil.bytesToHex(extra);
  508.         System.out.println("FreenetURI" + this);
  509.         System.out.println("Key type   : " + keyType);
  510.         System.out.println("Routing key: " + r);
  511.         System.out.println("Crypto key : " + k);
  512.         System.out.println("Extra      : " + e);
  513.         System.out.println(
  514.             "Doc name   : " + (docName == null ? "none" : docName));
  515.         System.out.print("Meta strings: ");
  516.         if(metaStr == null)
  517.             System.out.println("none");
  518.         else
  519.             System.out.println(Arrays.asList(metaStr).toString());
  520.     }
  521.  
  522.     public String getGuessableKey() {
  523.         return getDocName();
  524.     }
  525.  
  526.     /** Get the document name. For a KSK this is everything from the @ to
  527.      * the first slash or the end of the key. For an SSK this is everything
  528.      * from the slash to the next slash or the end of the key. CHKs don't
  529.      * have a doc name, they only have meta strings. */
  530.     public String getDocName() {
  531.         return docName;
  532.     }
  533.  
  534.     /** Get the first meta-string. This is just after the main part of the
  535.      * key and the doc name. Meta-strings are directory (manifest) lookups
  536.      * delimited by /'es after the main key and the doc name if any.
  537.      */
  538.     public String getMetaString() {
  539.         return ((metaStr == null) || (metaStr.length == 0)) ? null : metaStr[0];
  540.     }
  541.  
  542.     /** Get the last meta string. Meta-strings are directory (manifest)
  543.      * lookups after the main key and the doc name if any. So the last meta
  544.      * string, if there is one, is from the last / to the end of the uri
  545.      * i.e. usually the filename. */
  546.     public String lastMetaString() {
  547.         return ((metaStr == null) || (metaStr.length == 0)) ? null : metaStr[metaStr.length - 1];
  548.     }
  549.  
  550.     /** Get all the meta strings. Meta strings are directory (manifest)
  551.      * lookups after the main key and the doc name if any. Examples:
  552.      *
  553.      * CHK@blah,blah,blah/filename
  554.      *
  555.      * This has a routing key, a crypto key, extra bytes, no document name,
  556.      * and one meta string "filename"
  557.      *
  558.      * SSK@blah,blah,blah/docname/dir/subdir/filename
  559.      *
  560.      * This has a routing key, a crypto key, extra bytes, a document name,
  561.      * and three meta strings "dir", "subdir" and "filename". The SSK
  562.      * including the docname is turned into a low level Freenet key, which
  563.      * we fetch. This will produce a metadata document containing a
  564.      * manifest, within which we look up "dir". This either gives us
  565.      * another metadata document directly, or a redirect if the dir is
  566.      * inserted separately. And so on. If it's a container, the files will
  567.      * be stored, with the metadata, in the container (tar.bz2 or
  568.      * whatever); the metadata fetched by SSK@blah,blah,blah/docname will
  569.      * say that there is a container and explain how to fetch it.
  570.      *
  571.      *
  572.      * This has no routing key, no crypto key, and no meta strings (but
  573.      * KSKs *can* have meta strings), but it has a document name.
  574.      */
  575.     public String[] getAllMetaStrings() {
  576.         return metaStr;
  577.     }
  578.  
  579.     /** Are there any meta-strings? */
  580.     public boolean hasMetaStrings() {
  581.         return !(metaStr == null || metaStr.length == 0);
  582.     }
  583.  
  584.     /** Get the routing key. This is the first part of the key after the @
  585.      * for CHKs, SSKs and USKs. For purposes of FreenetURI, KSKs do not
  586.      * have a routing key. For CHKs, this is ultimately derived from the
  587.      * hash of the encrypted data; for SSKs it is the hash of the public
  588.      * key.
  589.      */
  590.     public byte[] getRoutingKey() {
  591.         return routingKey;
  592.     }
  593.  
  594.     /** Get the crypto key. This is the second part of the key after the @
  595.      * for CHKs, SSKs and USKs. For purposes of FreenetURI, KSKs do not
  596.      * have a crypto key. For CHKs, this is derived from the hash of the
  597.      * *original* plaintext data; for SSKs it is a separate key for
  598.      * decryption. The crypto key is kept on the requesting node and is not
  599.      * sent over the network - but of course many freesites and other
  600.      * documents on the network include URIs which do include crypto keys.
  601.      */
  602.     public byte[] getCryptoKey() {
  603.         return cryptoKey;
  604.     }
  605.  
  606.     /** Get the key type. CHK, SSK, KSK or USK. Upper case, we normally
  607.      * use the constants. */
  608.     public String getKeyType() {
  609.         return keyType;
  610.     }
  611.  
  612.     /**
  613.      * Returns a copy of this URI with the first meta string removed.
  614.      */
  615.     public FreenetURI popMetaString() {
  616.         String[] newMetaStr = null;
  617.         if (metaStr != null) {
  618.             final int metaStrLength = metaStr.length;
  619.             if (metaStrLength > 1) {
  620.                 newMetaStr = Arrays.copyOf(metaStr, metaStr.length-1);
  621.             }
  622.         }
  623.         return setMetaString(newMetaStr);
  624.     }
  625.  
  626.     /** Create a new URI with the last few meta-strings dropped.
  627.      * @param i The number of meta-strings to drop.
  628.      * @return A new FreenetURI with the specified number of meta-strings
  629.      * removed from the end.
  630.      */
  631.     public FreenetURI dropLastMetaStrings(int i) {
  632.         String[] newMetaStr = null;
  633.         if((metaStr != null) && (metaStr.length > i)) {
  634.             newMetaStr = Arrays.copyOf(metaStr, metaStr.length - i);
  635.         }
  636.         return setMetaString(newMetaStr);
  637.     }
  638.  
  639.     /**
  640.      * Returns a copy of this URI with the given string appended as a
  641.      * meta-string.
  642.      */
  643.     public FreenetURI pushMetaString(String name) {
  644.         String[] newMetaStr;
  645.         if(name == null)
  646.             throw new NullPointerException();
  647.         if(metaStr == null)
  648.             newMetaStr = new String[]{name};
  649.         else {
  650.             newMetaStr = Arrays.copyOf(metaStr, metaStr.length + 1);
  651.             newMetaStr[metaStr.length] = name.intern();
  652.         }
  653.         return setMetaString(newMetaStr);
  654.     }
  655.  
  656.     /**
  657.      * Returns a copy of this URI with these meta strings appended.
  658.      */
  659.     public FreenetURI addMetaStrings(String[] strs) {
  660.         if(strs == null)
  661.             return this; // legal noop, since getMetaStrings can return null
  662.         for(int i = 0; i < strs.length; i++)
  663.             if(strs[i] == null)
  664.                 throw new NullPointerException("element " + i + " of " + strs.length + " is null");
  665.         String[] newMetaStr;
  666.         if(metaStr == null)
  667.             return setMetaString(strs);
  668.         else {
  669.             newMetaStr = Arrays.copyOf(metaStr, metaStr.length + strs.length);
  670.             System.arraycopy(strs, 0, newMetaStr, metaStr.length, strs.length);
  671.             return setMetaString(newMetaStr);
  672.         }
  673.     }
  674.  
  675.     /**
  676.      * Returns a copy of this URI with these meta strings appended.
  677.      */
  678.     public FreenetURI addMetaStrings(List<String> metaStrings) {
  679.         return addMetaStrings(metaStrings.toArray(new String[metaStrings.size()]));
  680.     }
  681.  
  682.     /**
  683.      * Returns a copy of this URI with a new Document name set.
  684.      */
  685.     public FreenetURI setDocName(String name) {
  686.         return new FreenetURI(
  687.             keyType,
  688.             name,
  689.             metaStr,
  690.             routingKey,
  691.             cryptoKey,
  692.             extra,
  693.             suggestedEdition);
  694.  
  695.     }
  696.  
  697.     /** Returns a copy of this URI with new meta-strings. */
  698.     public FreenetURI setMetaString(String[] newMetaStr) {
  699.         return new FreenetURI(
  700.             keyType,
  701.             docName,
  702.             newMetaStr,
  703.             routingKey,
  704.             cryptoKey,
  705.             extra,
  706.             suggestedEdition);
  707.     }
  708.  
  709.     protected String toStringCache;
  710.  
  711.     /** toString() is equivalent to toString(false, false) but is cached. */
  712.     @Override
  713.     public String toString() {
  714.         if (toStringCache == null)
  715.             toStringCache = toString(false, false)/* + "#"+super.toString()+"#"+uniqueHashCode*/;
  716.         return toStringCache;
  717.     }
  718.  
  719.     /**
  720.      * @deprecated Use {@link #toASCIIString()} instead
  721.      */
  722.     @Deprecated
  723.     public String toACIIString() {
  724.         return toASCIIString();
  725.     }
  726.  
  727.     /**
  728.      * Get the FreenetURI as a pure ASCII string, any non-english
  729.      * characters as well as any dangerous characters are encoded.
  730.      * @return
  731.      */
  732.     public String toASCIIString() {
  733.         return toString(true, true);
  734.     }
  735.  
  736.     /**
  737.      * Get the FreenetURI as a string.
  738.      * @param prefix Whether to include the freenet: prefix.
  739.      * @param pureAscii If true, encode any non-english characters. If
  740.      * false, only encode dangerous characters (slashes e.g.).
  741.      */
  742.     public String toString(boolean prefix, boolean pureAscii) {
  743.         if(keyType == null) {
  744.             // Not activated or something...
  745.             if(logMINOR) Logger.minor(this, "Not activated?? in toString("+prefix+","+pureAscii+")");
  746.             return null;
  747.         }
  748.         StringBuilder b;
  749.         if(prefix)
  750.             b = new StringBuilder("freenet:");
  751.         else
  752.             b = new StringBuilder();
  753.  
  754.         b.append(keyType).append('@');
  755.  
  756.         if(!"KSK".equals(keyType)) {
  757.             if(routingKey != null)
  758.                 b.append(Base64.encode(routingKey));
  759.             if(cryptoKey != null)
  760.                 b.append(',').append(Base64.encode(cryptoKey));
  761.             if(extra != null)
  762.                 b.append(',').append(Base64.encode(extra));
  763.             if(docName != null)
  764.                 b.append('/');
  765.         }
  766.  
  767.         if(docName != null)
  768.             b.append(URLEncoder.encode(docName, "/", pureAscii));
  769.         if(keyType.equals("USK")) {
  770.             b.append('/');
  771.             b.append(suggestedEdition);
  772.         }
  773.         if(metaStr != null)
  774.             for(int i = 0; i < metaStr.length; i++) {
  775.                 b.append('/').append(URLEncoder.encode(metaStr[i], "/", pureAscii));
  776.             }
  777.         return b.toString();
  778.     }
  779.  
  780.     /** Encode to a user-friendly, incomplete string with ... replacing some of
  781.      * the base64. Allow spaces, foreign chars etc. */
  782.     public String toShortString() {
  783.         StringBuilder b = new StringBuilder();
  784.  
  785.         b.append(keyType).append('@');
  786.  
  787.         if(!"KSK".equals(keyType)) {
  788.             b.append("...");
  789.             if(docName != null)
  790.                 b.append('/');
  791.         }
  792.  
  793.         if(docName != null)
  794.             b.append(URLEncoder.encode(docName, "/", false, " "));
  795.         if(keyType.equals("USK")) {
  796.             b.append('/');
  797.             b.append(suggestedEdition);
  798.         }
  799.         if(metaStr != null)
  800.             for(int i = 0; i < metaStr.length; i++) {
  801.                 b.append('/').append(URLEncoder.encode(metaStr[i], "/", false, " "));
  802.             }
  803.         return b.toString();
  804.     }
  805.  
  806.     /** Run this class to decompose the argument. */
  807.     public static void main(String[] args) throws Exception {
  808.         (new FreenetURI(args[0])).decompose();
  809.     }
  810.  
  811.     /** Get the extra bytes. SSKs and CHKs have extra bytes, these come
  812.      * after the second comma, and specify encryption and hashing
  813.      * algorithms etc.
  814.      */
  815.     public byte[] getExtra() {
  816.         return extra;
  817.     }
  818.  
  819.     /** Get the meta strings as an ArrayList. */
  820.     public ArrayList<String> listMetaStrings() {
  821.         if(metaStr != null) {
  822.             ArrayList<String> l = new ArrayList<String>(metaStr.length);
  823.             for(int i = 0; i < metaStr.length; i++)
  824.                 l.add(metaStr[i]);
  825.             return l;
  826.         } else return new ArrayList<String>(0);
  827.     }
  828.     static final byte CHK = 1;
  829.     static final byte SSK = 2;
  830.     static final byte KSK = 3;
  831.     static final byte USK = 4;
  832.  
  833.     /** Read the binary form of a key, preceded by a short for its length. */
  834.     public static FreenetURI readFullBinaryKeyWithLength(DataInputStream dis) throws IOException {
  835.         int len = dis.readShort();
  836.         byte[] buf = new byte[len];
  837.         dis.readFully(buf);
  838.         if(logMINOR) Logger.minor(FreenetURI.class, "Read " + len + " bytes for key");
  839.         return fromFullBinaryKey(buf);
  840.     }
  841.  
  842.     /** Create a FreenetURI from the binary form of the key. */
  843.     public static FreenetURI fromFullBinaryKey(byte[] buf) throws IOException {
  844.         ByteArrayInputStream bais = new ByteArrayInputStream(buf);
  845.         DataInputStream dis = new DataInputStream(bais);
  846.         return readFullBinaryKey(dis);
  847.     }
  848.  
  849.     /** Create a FreenetURI from the binary form of the key, read from a
  850.      * stream, with no length.
  851.      * @throws MalformedURLException If there was a format error in the data.
  852.      * @throws IOException If a read error occurred */
  853.     public static FreenetURI readFullBinaryKey(DataInputStream dis) throws IOException {
  854.         byte type = dis.readByte();
  855.         String keyType;
  856.         if(type == CHK)
  857.             keyType = "CHK";
  858.         else if(type == SSK)
  859.             keyType = "SSK";
  860.         else if(type == KSK)
  861.             keyType = "KSK";
  862.         else
  863.             throw new MalformedURLException("Unrecognized type " + type);
  864.         byte[] routingKey = null;
  865.         byte[] cryptoKey = null;
  866.         byte[] extra = null;
  867.         if((type == CHK) || (type == SSK)) {
  868.             // routingKey is a hash, so is exactly 32 bytes
  869.             routingKey = new byte[32];
  870.             dis.readFully(routingKey);
  871.             // cryptoKey is a 256 bit AES key, so likewise
  872.             cryptoKey = new byte[32];
  873.             dis.readFully(cryptoKey);
  874.             // Number of bytes of extra depends on key type
  875.             int extraLen;
  876.             extraLen = (type == CHK ? ClientCHK.EXTRA_LENGTH : ClientSSK.EXTRA_LENGTH);
  877.             extra = new byte[extraLen];
  878.             dis.readFully(extra);
  879.         }
  880.         String docName = null;
  881.         if(type != CHK)
  882.             docName = dis.readUTF();
  883.         int count = dis.readInt();
  884.         String[] metaStrings = new String[count];
  885.         for(int i = 0; i < metaStrings.length; i++)
  886.             metaStrings[i] = dis.readUTF();
  887.         return new FreenetURI(keyType, docName, metaStrings, routingKey, cryptoKey, extra);
  888.     }
  889.  
  890.     /**
  891.      * Write a binary representation of this URI, with a short length, so it can be passed over if necessary.
  892.      * @param dos The stream to write to.
  893.      * @throws MalformedURLException If the key could not be written because of inconsistencies or other
  894.      * problems in the key itself.
  895.      * @throws IOException If an error occurred while writing the key.
  896.      */
  897.     public void writeFullBinaryKeyWithLength(DataOutputStream dos) throws IOException {
  898.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  899.         DataOutputStream ndos = new DataOutputStream(baos);
  900.         writeFullBinaryKey(ndos);
  901.         ndos.close();
  902.         byte[] data = baos.toByteArray();
  903.         if(data.length > Short.MAX_VALUE)
  904.             throw new MalformedURLException("Full key too long: " + data.length + " - " + this);
  905.         dos.writeShort((short) data.length);
  906.         if(logMINOR)
  907.             Logger.minor(this, "Written " + data.length + " bytes");
  908.         dos.write(data);
  909.     }
  910.  
  911.     /**
  912.      * Write a binary representation of this URI.
  913.      * @param dos The stream to write to.
  914.      * @throws MalformedURLException If the key could not be written because of inconsistencies or other
  915.      * problems in the key itself.
  916.      * @throws IOException If an error occurred while writing the key.
  917.      */
  918.     private void writeFullBinaryKey(DataOutputStream dos) throws IOException {
  919.         if(keyType.equals("CHK"))
  920.             dos.writeByte(CHK);
  921.         else if(keyType.equals("SSK"))
  922.             dos.writeByte(SSK);
  923.         else if(keyType.equals("KSK"))
  924.             dos.writeByte(KSK);
  925.         else if(keyType.equals("USK"))
  926.             throw new MalformedURLException("Cannot write USKs as binary keys");
  927.         else
  928.             throw new MalformedURLException("Cannot write key of type " + keyType + " - do not know how");
  929.         if(!keyType.equals("KSK")) {
  930.             if(routingKey.length != 32)
  931.                 throw new MalformedURLException("Routing key must be of length 32");
  932.             dos.write(routingKey);
  933.             if(cryptoKey.length != 32)
  934.                 throw new MalformedURLException("Crypto key must be of length 32");
  935.             dos.write(cryptoKey);
  936.             if(keyType.equals("CHK") && (extra.length != ClientCHK.EXTRA_LENGTH))
  937.                 throw new MalformedURLException("Wrong number of extra bytes for CHK");
  938.             if(keyType.equals("SSK") && (extra.length != ClientSSK.EXTRA_LENGTH))
  939.                 throw new MalformedURLException("Wrong number of extra bytes for SSK");
  940.             dos.write(extra);
  941.         }
  942.         if(!keyType.equals("CHK"))
  943.             dos.writeUTF(docName);
  944.         if(metaStr != null) {
  945.             dos.writeInt(metaStr.length);
  946.             for(int i = 0; i < metaStr.length; i++)
  947.                 dos.writeUTF(metaStr[i]);
  948.         } else
  949.             dos.writeInt(0);
  950.     }
  951.  
  952.     /** Get suggested edition. Only valid for USKs. */
  953.     public long getSuggestedEdition() {
  954.         if(keyType.equals("USK"))
  955.             return suggestedEdition;
  956.         else
  957.             throw new IllegalArgumentException("Not a USK requesting suggested edition");
  958.     }
  959.  
  960.     /** Generate a suggested filename for the URI. This may be constructed
  961.      * from more than one part of the URI e.g. SSK@blah,blah,blah/sitename/
  962.      * might return sitename. The returned string will already have been
  963.      * through FileUtil.sanitize(). */
  964.     public String getPreferredFilename() {
  965.         if (logMINOR)
  966.             Logger.minor(this, "Getting preferred filename for " + this);
  967.         ArrayList<String> names = new ArrayList<String>();
  968.         if(keyType != null && (keyType.equals("KSK") || keyType.equals("SSK") || keyType.equals("USK"))) {
  969.             if(logMINOR)
  970.                 Logger.minor(this, "Adding docName: " + docName);
  971.             if(docName != null) {
  972.                 names.add(docName);
  973.                 if(keyType.equals("USK"))
  974.                     names.add(Long.toString(suggestedEdition));
  975.             } else if(!keyType.equals("SSK")) {
  976.                 // "SSK@" is legal for an upload.
  977.                 throw new IllegalStateException("No docName for key of type "+keyType);
  978.             }
  979.         }
  980.         if(metaStr != null)
  981.             for(String s : metaStr) {
  982.                 if(s == null || s.equals("")) {
  983.                     if(logMINOR)
  984.                         Logger.minor(this, "metaString \"" + s + "\": was null or empty");
  985.                     continue;
  986.                 }
  987.                 if(logMINOR)
  988.                     Logger.minor(this, "Adding metaString \"" + s + "\"");
  989.                 names.add(s);
  990.             }
  991.         StringBuilder out = new StringBuilder();
  992.         for(int i = 0; i < names.size(); i++) {
  993.             String s = names.get(i);
  994.             if(logMINOR)
  995.                 Logger.minor(this, "name " + i + " = " + s);
  996.             s = FileUtil.sanitize(s);
  997.             if(logMINOR)
  998.                 Logger.minor(this, "Sanitized name " + i + " = " + s);
  999.             if(s.length() > 0) {
  1000.                 if(out.length() > 0)
  1001.                     out.append('-');
  1002.                 out.append(s);
  1003.             }
  1004.         }
  1005.         if(logMINOR)
  1006.             Logger.minor(this, "out = " + out.toString());
  1007.         if(out.length() == 0) {
  1008.             if(routingKey != null) {
  1009.                 if(logMINOR)
  1010.                     Logger.minor(this, "Returning base64 encoded routing key");
  1011.                 return Base64.encode(routingKey);
  1012.             }
  1013.             // FIXME return null in this case, localise in a wrapper.
  1014.             return "unknown";
  1015.         }
  1016.         assert out.toString().equals(FileUtil.sanitize(out.toString())) : ("Not sanitized? \""+out.toString()+"\" -> \""+FileUtil.sanitize(out.toString()))+"\"";
  1017.         return out.toString();
  1018.     }
  1019.  
  1020.     /** Returns a <b>new</b> FreenetURI with a new suggested edition number.
  1021.      * Note that the suggested edition number is only valid for USKs. */
  1022.     public FreenetURI setSuggestedEdition(long newEdition) {
  1023.         return new FreenetURI(
  1024.             keyType,
  1025.             docName,
  1026.             metaStr,
  1027.             routingKey,
  1028.             cryptoKey,
  1029.             extra,
  1030.             newEdition);
  1031.     }
  1032.  
  1033.     /** Returns a <b>new</b> FreenetURI with a new key type. Usually this
  1034.      * will be invalid!
  1035.      */
  1036.     public FreenetURI setKeyType(String newKeyType) {
  1037.         return new FreenetURI(
  1038.             newKeyType,
  1039.             docName,
  1040.             metaStr,
  1041.             routingKey,
  1042.             cryptoKey,
  1043.             extra,
  1044.             suggestedEdition);
  1045.     }
  1046.  
  1047.     /** Returns a <b>new</b> FreenetURI with a new routing key. KSKs do not
  1048.      * have a routing key. */
  1049.     public FreenetURI setRoutingKey(byte[] newRoutingKey) {
  1050.         return new FreenetURI(
  1051.             keyType,
  1052.             docName,
  1053.             metaStr,
  1054.             newRoutingKey,
  1055.             cryptoKey,
  1056.             extra,
  1057.             suggestedEdition);
  1058.     }
  1059.  
  1060.     /** Throw an InsertException if we have any meta-strings. They are not
  1061.      * valid for inserts, you must insert a directory to create a directory
  1062.      * structure. */
  1063.     public void checkInsertURI() throws InsertException {
  1064.         if(metaStr != null && metaStr.length > 0)
  1065.             throw new InsertException(InsertException.META_STRINGS_NOT_SUPPORTED, this);
  1066.     }
  1067.  
  1068.     /** Throw an InsertException if the argument has any meta-strings. They
  1069.      * are not valid for inserts, you must insert a directory to create a
  1070.      * directory structure. */
  1071.     public static void checkInsertURI(FreenetURI uri) throws InsertException {
  1072.         uri.checkInsertURI();
  1073.     }
  1074.  
  1075.     /** Convert to a relative URI in the form of a URI (/[email protected] etc). */
  1076.     public URI toRelativeURI() throws URISyntaxException {
  1077.         // Single-argument constructor used because it preserves encoded /'es in path.
  1078.         // Hence we can have slashes, question marks etc in the path, but they are encoded.
  1079.         return new URI('/' + toString(false, false));
  1080.     }
  1081.  
  1082.     /** Convert to a relative URI in the form of a URI, with the base path
  1083.      * not necessarily /. */
  1084.     public URI toURI(String basePath) throws URISyntaxException {
  1085.         return new URI(basePath + toString(false, false));
  1086.     }
  1087.  
  1088.     /** Is this key an SSK? */
  1089.     public boolean isSSK() {
  1090.         return "SSK".equals(keyType);
  1091.     }
  1092.  
  1093.     /** Remove from the database. */
  1094.     public void removeFrom(ObjectContainer container) {
  1095.         // All members are inline (arrays, ints etc), treated as values, so we can happily just call delete(this).
  1096.         container.delete(this);
  1097.     }
  1098.  
  1099.     public boolean objectCanNew(ObjectContainer container) {
  1100.         if(this == FreenetURI.EMPTY_CHK_URI) {
  1101.             throw new RuntimeException("Storing static CHK@ to database - can't remove it!");
  1102.         }
  1103.         return true;
  1104.     }
  1105.  
  1106.     public boolean objectCanUpdate(ObjectContainer container) {
  1107.         if(!container.ext().isActive(this)) {
  1108.             Logger.error(this, "Updating but not active!", new Exception("error"));
  1109.             return false;
  1110.         }
  1111.         return true;
  1112.     }
  1113.  
  1114.     public void objectOnDelete(ObjectContainer container) {
  1115.         if(logDEBUG) Logger.debug(this, "Deleting URI", new Exception("debug"));
  1116.     }
  1117.  
  1118.     /** Is this key a USK? */
  1119.     public boolean isUSK() {
  1120.         return "USK".equals(keyType);
  1121.     }
  1122.  
  1123.     /** Is this key a CHK? */
  1124.     public boolean isCHK() {
  1125.         return "CHK".equals(keyType);
  1126.     }
  1127.  
  1128.     /** Is this key a KSK? */
  1129.     public boolean isKSK() {
  1130.         return "KSK".equals(keyType);
  1131.     }
  1132.  
  1133.     /** Convert a USK into an SSK by appending "-" and the suggested edition
  1134.      * to the document name and changing the key type. */
  1135.     public FreenetURI sskForUSK() {
  1136.         if(!keyType.equalsIgnoreCase("USK")) throw new IllegalStateException();
  1137.         return new FreenetURI("SSK", docName+"-"+suggestedEdition, metaStr, routingKey, cryptoKey, extra, 0);
  1138.     }
  1139.  
  1140.     private static final Pattern docNameWithEditionPattern;
  1141.     static {
  1142.         docNameWithEditionPattern = Pattern.compile(".*\\-([0-9]+)");
  1143.     }
  1144.  
  1145.     /** Could this SSK be the result of sskForUSK()? */
  1146.     public boolean isSSKForUSK() {
  1147.         return keyType.equalsIgnoreCase("SSK") && docName != null && docNameWithEditionPattern.matcher(docName).matches();
  1148.     }
  1149.  
  1150.     /** Convert an SSK into a USK, if possible. */
  1151.     public FreenetURI uskForSSK() {
  1152.         if(!keyType.equalsIgnoreCase("SSK")) throw new IllegalStateException();
  1153.         Matcher matcher = docNameWithEditionPattern.matcher(docName);
  1154.         if (!matcher.matches())
  1155.             throw new IllegalStateException();
  1156.  
  1157.         int offset = matcher.start(1) - 1;
  1158.         String siteName = docName.substring(0, offset);
  1159.         long edition = Long.valueOf(docName.substring(offset + 1, docName.length()));
  1160.  
  1161.         return new FreenetURI("USK", siteName, metaStr, routingKey, cryptoKey, extra, edition);
  1162.     }
  1163.  
  1164.     /**
  1165.      * Get the edition number, if the key is a USK or a USK converted to an
  1166.      * SSK.
  1167.      */
  1168.     public long getEdition() {
  1169.         if(keyType.equalsIgnoreCase("USK"))
  1170.             return suggestedEdition;
  1171.         else if(keyType.equalsIgnoreCase("SSK")) {
  1172.             if(docName == null)
  1173.                 throw new IllegalStateException();
  1174.            
  1175.             Matcher matcher = docNameWithEditionPattern.matcher(docName);
  1176.             if (!matcher.matches()) /* Taken from uskForSSK, also modify there if necessary; TODO just use isSSKForUSK() here?! */
  1177.                 throw new IllegalStateException();
  1178.  
  1179.             return Long.valueOf(docName.substring(matcher.start(1), docName.length()));
  1180.         } else
  1181.             throw new IllegalStateException();
  1182.     }
  1183.  
  1184.     @Override
  1185.     /** This looks expensive, but 99% of the time it will quit out pretty
  1186.      * early on: Either a different key type or a different routing key. The
  1187.      * worst case cost is relatively bad though. Unfortunately we can't use
  1188.      * a HashMap if an attacker might be able to influence the keys and
  1189.      * create a hash collision DoS, so we *do* need this. */
  1190.     public int compareTo(FreenetURI o) {
  1191.         if(this == o) return 0;
  1192.         int cmp = keyType.compareTo(o.keyType);
  1193.         if(cmp != 0) return cmp;
  1194.         if(routingKey != null) {
  1195.             // Same type will have same routingKey != null
  1196.             cmp = Fields.compareBytes(routingKey, o.routingKey);
  1197.             if(cmp != 0) return cmp;
  1198.         }
  1199.         if(cryptoKey != null) {
  1200.             // Same type will have same cryptoKey != null
  1201.             cmp = Fields.compareBytes(cryptoKey, o.cryptoKey);
  1202.             if(cmp != 0) return cmp;
  1203.         }
  1204.         if(docName == null && o.docName != null) return -1;
  1205.         if(docName != null && o.docName == null) return 1;
  1206.         if(docName != null && o.docName != null) {
  1207.             cmp = docName.compareTo(o.docName);
  1208.             if(cmp != 0) return cmp;
  1209.         }
  1210.         if(extra != null) {
  1211.             // Same type will have same cryptoKey != null
  1212.             cmp = Fields.compareBytes(extra, o.extra);
  1213.             if(cmp != 0) return cmp;
  1214.         }
  1215.         if(metaStr != null && o.metaStr == null) return 1;
  1216.         if(metaStr == null && o.metaStr != null) return -1;
  1217.         if(metaStr != null && o.metaStr != null) {
  1218.             if(metaStr.length > o.metaStr.length) return 1;
  1219.             if(metaStr.length < o.metaStr.length) return -1;
  1220.             for(int i=0;i<metaStr.length;i++) {
  1221.                 cmp = metaStr[i].compareTo(o.metaStr[i]);
  1222.                 if(cmp != 0) return cmp;
  1223.             }
  1224.         }
  1225.         if(suggestedEdition > o.suggestedEdition) return 1;
  1226.         if(suggestedEdition < o.suggestedEdition) return -1;
  1227.         return 0;
  1228.     }
  1229.    
  1230.     /**
  1231.      * If this object is a USK/SSK insert URI, this function computes the request URI which belongs to it.
  1232.      * If it is a CHK/KSK, the original URI is returned as CHK/KSK do not have a private insert URI, they are their own "insert URI".
  1233.      *
  1234.      * If you want to give people access to content at an URI, you should always publish only the request URI.
  1235.      * Never give away the insert URI, this allows anyone to insert under your URI!
  1236.      *  
  1237.      * @return The request URI which belongs to this insert URI.
  1238.      * @throws MalformedURLException If this object is a USK/SSK request URI already. NOT thrown for CHK/KSK URIs!
  1239.      */
  1240.     public FreenetURI deriveRequestURIFromInsertURI() throws MalformedURLException {
  1241.         final FreenetURI originalURI = this;
  1242.        
  1243.         if(originalURI.isCHK()) {
  1244.             return originalURI;
  1245.         } else if(originalURI.isSSK() || originalURI.isUSK()) {
  1246.             FreenetURI newURI = originalURI;
  1247.             if(originalURI.isUSK())
  1248.                 newURI = newURI.sskForUSK();
  1249.             InsertableClientSSK issk = InsertableClientSSK.create(newURI);
  1250.             newURI = issk.getURI();
  1251.             if(originalURI.isUSK()) {
  1252.                 newURI = newURI.uskForSSK();
  1253.                 newURI = newURI.setSuggestedEdition(originalURI.getSuggestedEdition());
  1254.             }
  1255.             // docName will be preserved.
  1256.             // Any meta strings *should not* be preserved.
  1257.             return newURI;
  1258.         } else if(originalURI.isKSK()) {
  1259.             return originalURI;
  1260.         } else {
  1261.             throw new IllegalArgumentException("Not implemented yet for this key type: " + getKeyType());
  1262.         }
  1263.     }
  1264.  
  1265.     public static final Comparator<FreenetURI> FAST_COMPARATOR = new Comparator<FreenetURI>() {
  1266.  
  1267.         @Override
  1268.         public int compare(FreenetURI uri0, FreenetURI uri1) {
  1269.             // Unfortunately the hashCode's may not have been computed yet.
  1270.             // But it's still cheaper to recompute them in the long run.
  1271.             int hash0 = uri0.hashCode();
  1272.             int hash1 = uri1.hashCode();
  1273.             if(hash0 > hash1) return 1;
  1274.             else if(hash1 > hash0) return -1;
  1275.             return uri0.compareTo(uri1);
  1276.         }
  1277.        
  1278.     };
  1279.  
  1280.     public static FreenetURI generateRandomCHK(Random rand) {
  1281.         byte[] rkey = new byte[32];
  1282.         rand.nextBytes(rkey);
  1283.         byte[] ckey = new byte[32];
  1284.         rand.nextBytes(ckey);
  1285.         byte[] extra = ClientCHK.getExtra(Key.ALGO_AES_CTR_256_SHA256, (short)-1, false);
  1286.         return new FreenetURI("CHK", null, rkey, ckey, extra);
  1287.     }
  1288.  
  1289.     // TODO add something like the following?
  1290.     // public boolean isUpdatable() { return isUSK() || isSSKForUSK() }
  1291. }
Add Comment
Please, Sign In to add comment