Signed-off-by: Daniel Cheng (aka SDiZ) <j16sdiz+freenet@xxxxxxxxx> --- .../src/org/spearce/jgit/transport/Transport.java | 3 + .../org/spearce/jgit/transport/TransportFcp2.java | 436 ++++++++++++++++++++ 2 files changed, 439 insertions(+), 0 deletions(-) create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/TransportFcp2.java diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java index a0a2575..3fc1735 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java +++ b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java @@ -206,6 +206,9 @@ else if (TransportGitAnon.canHandle(remote)) else if (TransportAmazonS3.canHandle(remote)) return new TransportAmazonS3(local, remote); + else if (TransportFcp2.canHandle(remote)) + return new TransportFcp2(local, remote); + else if (TransportBundleFile.canHandle(remote)) return new TransportBundleFile(local, remote); diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportFcp2.java b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportFcp2.java new file mode 100644 index 0000000..ed12995 --- /dev/null +++ b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportFcp2.java @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2009, CHENG Yuk-Pong, Daniel <j16sdiz+freenet@xxxxxxxxx> + * Copyright (C) 2008, Shawn O. Pearce <spearce@xxxxxxxxxxx> + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Git Development Community nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spearce.jgit.transport; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.TreeMap; + +import org.spearce.jgit.errors.NotSupportedException; +import org.spearce.jgit.errors.PackProtocolException; +import org.spearce.jgit.errors.TransportException; +import org.spearce.jgit.lib.Constants; +import org.spearce.jgit.lib.NullProgressMonitor; +import org.spearce.jgit.lib.ObjectId; +import org.spearce.jgit.lib.ProgressMonitor; +import org.spearce.jgit.lib.Ref; +import org.spearce.jgit.lib.Repository; +import org.spearce.jgit.lib.Ref.Storage; +import org.spearce.jgit.transport.fcpv2.FCPConnection; + +/** + * Transport over Freenet Client Protocol 2.0 + * <p> + * The Freenet transport is similar to HTTP Transport, but more restrictive. To + * workaround the freenet restrictions, path separator ('/') are replaced with + * dash ('-'). + *<p> + * URI are in forms of <code>fcp://USK@PUBLIC_KEY/identifier</code> (read-only) + * or <code>fcp://USK@PUBLIC_KEY^PRIVATE_KEY/identifier</code>. The identifier + * must not contain any slash. The Freenet URI should begin with + * <code>SSK@</code>, all updatable component are converted to <code>USK@</code> + * internally. + * + * @see WalkFetchConnection + */ +class TransportFcp2 extends WalkTransport { + static boolean canHandle(final URIish uri) { + if (!uri.isRemote()) + return false; + final String s = uri.getScheme(); + return "fcp".equals(s); + } + + private final String publicKey; + + private final String privateKey; + + private final String identifier; + + private FCPConnection fcp; + + TransportFcp2(final Repository local, final URIish uri) + throws NotSupportedException { + super(local, uri); + + String uriString = uri.toString(); + if (!uriString.startsWith("fcp://SSK@")) + throw new NotSupportedException("URL not SSK@ " + uri); + + String[] uriSegment = uriString.split("\\/"); + if (uriSegment.length != 4) + throw new NotSupportedException("Invalid URL " + uri); + + String[] keys = uriSegment[2].split("\\^"); + if (keys.length > 2) + throw new NotSupportedException("Invalid URL " + uri); + publicKey = keys[0].substring(4); // Remove 'SSK@' prefix + privateKey = (keys.length == 2) ? keys[1] : null; + System.out.println(privateKey); + identifier = uriSegment[3]; + } + + @Override + public FetchConnection openFetch() throws TransportException { + try { + fcp = new FCPConnection(); + fcp.connect(); + fcp.hello("JGit-" + toString()); + + final Fcp2ObjectDB c = new Fcp2ObjectDB("/objects", false); + final WalkFetchConnection r = new WalkFetchConnection(this, c); + r.available(c.readAdvertisedRefs()); + return r; + } catch (IOException e) { + throw new TransportException("IO Error" + e, e); + } + } + + @Override + public PushConnection openPush() throws TransportException { + try { + fcp = new FCPConnection(); + fcp.connect(); + fcp.hello("JGit-" + toString()); + + final Fcp2ObjectDB c = new Fcp2ObjectDB("/objects", false); + final WalkPushConnection r = new WalkPushConnection(this, c); + r.available(c.readAdvertisedRefs()); + return r; + } catch (IOException e) { + throw new TransportException("IO Error" + e, e); + } + } + + @Override + public void close() { + try { + if (fcp != null) + fcp.close(); + } catch (IOException e) { + // Fall through. + } + fcp = null; + } + + class Fcp2ObjectDB extends WalkRemoteObjectDatabase { + private final String path; + private boolean useArchive; + + @Override + void deleteFile(String path) throws IOException { + // TODO Auto-generated method stub + super.deleteFile(path); + } + + @Override + OutputStream writeFile(String path, ProgressMonitor monitor, + String monitorTask) throws IOException { + System.err.println(" write -> " + path); + return new FcpOutputStream(path, monitor, monitorTask); + } + + class FcpOutputStream extends OutputStream { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + private String path; + + private ProgressMonitor monitor; + + private String monitorTask; + + public FcpOutputStream(String path, ProgressMonitor monitor, + String monitorTask) { + if (monitor == null) + monitor = NullProgressMonitor.INSTANCE; + if (monitorTask == null) + monitorTask = "Uploading " + path; + + this.path = path; + this.monitor = monitor; + this.monitorTask = monitorTask; + } + + @Override + public void write(int b) throws IOException { + os.write(b); + } + + @Override + public void close() throws IOException { + byte[] b = os.toByteArray(); + os = null; + + fcp.simplePut(getFreenetURI(path, true), b, monitor, + monitorTask); + } + } + + Fcp2ObjectDB(String path, boolean useArchive) { + if (path.endsWith("/")) + path = path.substring(0, path.length() - 1); + if (!path.startsWith("/")) + path = "/" + path; + this.path = path; + this.useArchive = useArchive; + } + + private String resolveKey(String subpath) { + if (subpath.endsWith("/")) + subpath = subpath.substring(0, subpath.length() - 1); + String k = path; + while (subpath.startsWith(ROOT_DIR)) { + k = k.substring(0, k.lastIndexOf('/')); + subpath = subpath.substring(3); + } + return k + "/" + subpath; + } + + @Override + URIish getURI() { + try { + // just a pseudo-URIish for logging, not really supported + return new URIish("fcp://SSK@" + publicKey + "/" + identifier + + path); + } catch (URISyntaxException e) { + return null; + } + } + + @Override + Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException { + if (!useArchive) // try to use archive + return java.util.Arrays.asList( + new WalkRemoteObjectDatabase[] { new Fcp2ObjectDB(path, true) } ); + return null; + } + + @Override + WalkRemoteObjectDatabase openAlternate(final String location) + throws IOException { + return null; + } + + @Override + Collection<String> getPackNames() throws IOException { + final Collection<String> packs = new ArrayList<String>(); + try { + final BufferedReader br = openReader(INFO_PACKS); + try { + for (;;) { + final String s = br.readLine(); + if (s == null || s.length() == 0) + break; + if (!s.startsWith("P pack-") || !s.endsWith(".pack")) + throw invalidAdvertisement(s); + packs.add(s.substring(2)); + } + return packs; + } finally { + br.close(); + } + } catch (FileNotFoundException err) { + return packs; + } + } + + @Override + FileStream open(final String path) throws IOException { + System.err.println(" read -> " + path); + byte[] b = fcp.simpleGet(getFreenetURI(path, false)); + if (b == null) + throw new FileNotFoundException(); + + ByteArrayInputStream is = new ByteArrayInputStream(b); + return new FileStream(is); + } + + private String getFreenetURI(final String path, boolean push) { + String key = push ? privateKey : publicKey; + String fPath = resolveKey(path); + String ret; + + if (useArchive) + ret = "USK@" + key + "/" + identifier + "/1/" + fPath; + else + ret = "USK@" + key + "/" + identifier + fPath.replaceAll("\\/", "-") + "/1/"; + + System.err.println(ret); + return ret; + } + + Map<String, Ref> readAdvertisedRefs() throws TransportException { + final TreeMap<String, Ref> avail = new TreeMap<String, Ref>(); + try { + readPackedRefs(avail); + } catch (TransportException t) {} + try { + readInfoRefs(avail); + } catch (TransportException t) {} + try { + readRef(avail, Constants.HEAD); + } catch (TransportException t) {} + + if (avail.isEmpty() && !useArchive) { + return new Fcp2ObjectDB(path, true).readAdvertisedRefs(); + } + + return avail; + } + + private Map<String, Ref> readInfoRefs(TreeMap<String, Ref> avail) + throws TransportException { + try { + final BufferedReader br = openReader(INFO_REFS); + for (;;) { + String line = br.readLine(); + if (line == null) + break; + + final int tab = line.indexOf('\t'); + if (tab < 0) + throw invalidAdvertisement(line); + + String name; + final ObjectId id; + + name = line.substring(tab + 1); + id = ObjectId.fromString(line.substring(0, tab)); + if (name.endsWith("^{}")) { + name = name.substring(0, name.length() - 3); + final Ref prior = avail.get(name); + if (prior == null) + throw outOfOrderAdvertisement(name); + + if (prior.getPeeledObjectId() != null) + throw duplicateAdvertisement(name + "^{}"); + + avail.put(name, new Ref(Ref.Storage.NETWORK, name, + prior.getObjectId(), id, true)); + } else { + final Ref prior = avail.put(name, new Ref( + Ref.Storage.NETWORK, name, id)); + if (prior != null) + throw duplicateAdvertisement(name); + } + } + return avail; + } catch (IOException err) { + throw new TransportException(INFO_REFS + + ": cannot read available refs", err); + } + } + + private PackProtocolException outOfOrderAdvertisement(final String n) { + return new PackProtocolException("advertisement of " + n + + "^{} came before " + n); + } + + private PackProtocolException duplicateAdvertisement(final String n) { + return new PackProtocolException("duplicate advertisements of " + n); + } + + private Ref readRef(final TreeMap<String, Ref> avail, final String rn) + throws TransportException { + final String s; + String ref = ROOT_DIR + rn; + try { + final BufferedReader br = openReader(ref); + try { + s = br.readLine(); + } finally { + br.close(); + } + } catch (FileNotFoundException noRef) { + return null; + } catch (IOException err) { + throw new TransportException(getURI(), "read " + ref, err); + } + + if (s == null) + throw new TransportException(getURI(), "Empty ref: " + rn); + + if (s.startsWith("ref: ")) { + final String target = s.substring("ref: ".length()); + Ref r = avail.get(target); + if (r == null) + r = readRef(avail, target); + if (r == null) + return null; + r = new Ref(r.getStorage(), rn, r.getObjectId(), r + .getPeeledObjectId(), r.isPeeled()); + avail.put(r.getName(), r); + return r; + } + + if (ObjectId.isId(s)) { + // FIXME this does not really works + final Ref r = new Ref(loose(avail.get(rn)), rn, ObjectId + .fromString(s)); + avail.put(r.getName(), r); + return r; + } + + throw new TransportException(getURI(), "Bad ref: " + rn + ": " + s); + } + + private Storage loose(final Ref r) { + if (r != null && r.getStorage() == Storage.PACKED) + return Storage.LOOSE_PACKED; + return Storage.LOOSE; + } + + private PackProtocolException invalidAdvertisement(final String n) { + return new PackProtocolException("invalid advertisement of " + n); + } + + @Override + void close() { + // We do not maintain persistent connections. + } + } +} -- 1.6.2.rc2 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html