/*
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
*
* 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.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.URL;
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.ObjectId;
import org.spearce.jgit.lib.Ref;
import org.spearce.jgit.lib.Repository;
import org.spearce.jgit.util.HttpSupport;
/**
* Transport over the non-Git aware HTTP and FTP protocol.
* <p>
* The HTTP transport does not require any specialized Git support on the remote
* (server side) repository. Object files are retrieved directly through
* standard HTTP GET requests, making it easy to serve a Git repository through
* a standard web host provider that does not offer specific support for Git.
*
* @see WalkFetchConnection
*/
public class TransportHttp extends HttpTransport implements WalkTransport {
static boolean canHandle(final URIish uri) {
if (!uri.isRemote())
return false;
final String s = uri.getScheme();
return "http".equals(s) || "https".equals(s) || "ftp".equals(s);
}
private final URL baseUrl;
private final URL objectsUrl;
private final ProxySelector proxySelector;
TransportHttp(final Repository local, final URIish uri)
throws NotSupportedException {
super(local, uri);
try {
String uriString = uri.toString();
if (!uriString.endsWith("/"))
uriString += "/";
baseUrl = new URL(uriString);
objectsUrl = new URL(baseUrl, "objects/");
} catch (MalformedURLException e) {
throw new NotSupportedException("Invalid URL " + uri, e);
}
proxySelector = ProxySelector.getDefault();
}
@Override
public FetchConnection openFetch() throws TransportException {
final HttpObjectDB c = new HttpObjectDB(objectsUrl);
final WalkFetchConnection r = new WalkFetchConnection(this, c);
r.available(c.readAdvertisedRefs());
return r;
}
@Override
public PushConnection openPush() throws NotSupportedException,
TransportException {
final String s = getURI().getScheme();
throw new NotSupportedException("Push not supported over " + s + ".");
}
@Override
public void close() {
// No explicit connections are maintained.
}
class HttpObjectDB extends WalkRemoteObjectDatabase {
private final URL objectsUrl;
HttpObjectDB(final URL b) {
objectsUrl = b;
}
@Override
URIish getURI() {
return new URIish(objectsUrl);
}
@Override
Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException {
try {
return readAlternates(INFO_HTTP_ALTERNATES);
} catch (FileNotFoundException err) {
// Fall through.
}
try {
return readAlternates(INFO_ALTERNATES);
} catch (FileNotFoundException err) {
// Fall through.
}
return null;
}
@Override
WalkRemoteObjectDatabase openAlternate(final String location)
throws IOException {
return new HttpObjectDB(new URL(objectsUrl, location));
}
@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 {
final URL base = objectsUrl;
final URL u = new URL(base, path);
final Proxy proxy = HttpSupport.proxyFor(proxySelector, u);
final HttpURLConnection c;
c = (HttpURLConnection) u.openConnection(proxy);
switch (HttpSupport.response(c)) {
case HttpURLConnection.HTTP_OK:
final InputStream in = c.getInputStream();
final int len = c.getContentLength();
return new FileStream(in, len);
case HttpURLConnection.HTTP_NOT_FOUND:
throw new FileNotFoundException(u.toString());
default:
throw new IOException(u.toString() + ": "
+ HttpSupport.response(c) + " "
+ c.getResponseMessage());
}
}
Map<String, Ref> readAdvertisedRefs() throws TransportException {
try {
final BufferedReader br = openReader(INFO_REFS);
try {
return readAdvertisedImpl(br);
} finally {
br.close();
}
} catch (IOException err) {
try {
throw new TransportException(new URL(objectsUrl, INFO_REFS)
+ ": cannot read available refs", err);
} catch (MalformedURLException mue) {
throw new TransportException(objectsUrl + INFO_REFS
+ ": cannot read available refs", err);
}
}
}
private Map<String, Ref> readAdvertisedImpl(final BufferedReader br)
throws IOException, PackProtocolException {
final TreeMap<String, Ref> avail = new TreeMap<String, Ref>();
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;
}
private PackProtocolException outOfOrderAdvertisement(final String n) {
return new PackProtocolException("advertisement of " + n
+ "^{} came before " + n);
}
private PackProtocolException invalidAdvertisement(final String n) {
return new PackProtocolException("invalid advertisement of " + n);
}
private PackProtocolException duplicateAdvertisement(final String n) {
return new PackProtocolException("duplicate advertisements of " + n);
}
@Override
void close() {
// We do not maintain persistent connections.
}
}
}