package hudson.cli;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.codec.binary.Base64;
/**
* Creates a capacity-unlimited bi-directional {@link InputStream}/{@link OutputStream} pair over
* HTTP, which is a request/response protocol.
*
* @author Kohsuke Kawaguchi
*/
public class FullDuplexHttpStream {
private final URL target;
/**
* Authorization header value needed to get through the HTTP layer.
*/
private final String authorization;
private final OutputStream output;
private final InputStream input;
public InputStream getInputStream() {
return input;
}
public OutputStream getOutputStream() {
return output;
}
@Deprecated
public FullDuplexHttpStream(URL target) throws IOException {
this(target,basicAuth(target.getUserInfo()));
}
private static String basicAuth(String userInfo) {
if (userInfo != null)
return "Basic "+new String(Base64.encodeBase64(userInfo.getBytes()));
return null;
}
/**
* @param target
* The endpoint that we are making requests to.
* @param authorization
* The value of the authorization header, if non-null.
*/
public FullDuplexHttpStream(URL target, String authorization) throws IOException {
this.target = target;
this.authorization = authorization;
CrumbData crumbData = new CrumbData();
UUID uuid = UUID.randomUUID(); // so that the server can correlate those two connections
// server->client
HttpURLConnection con = (HttpURLConnection) target.openConnection();
con.setDoOutput(true); // request POST to avoid caching
con.setRequestMethod("POST");
con.addRequestProperty("Session", uuid.toString());
con.addRequestProperty("Side","download");
if (authorization != null) {
con.addRequestProperty("Authorization", authorization);
}
if(crumbData.isValid) {
con.addRequestProperty(crumbData.crumbName, crumbData.crumb);
}
con.getOutputStream().close();
input = con.getInputStream();
// make sure we hit the right URL
if(con.getHeaderField("Hudson-Duplex")==null)
throw new IOException(target+" doesn't look like Jenkins");
// client->server uses chunked encoded POST for unlimited capacity.
con = (HttpURLConnection) target.openConnection();
con.setDoOutput(true); // request POST
con.setRequestMethod("POST");
con.setChunkedStreamingMode(0);
con.setRequestProperty("Content-type","application/octet-stream");
con.addRequestProperty("Session", uuid.toString());
con.addRequestProperty("Side","upload");
if (authorization != null) {
con.addRequestProperty ("Authorization", authorization);
}
if(crumbData.isValid) {
con.addRequestProperty(crumbData.crumbName, crumbData.crumb);
}
output = con.getOutputStream();
}
static final int BLOCK_SIZE = 1024;
static final Logger LOGGER = Logger.getLogger(FullDuplexHttpStream.class.getName());
private final class CrumbData {
String crumbName;
String crumb;
boolean isValid;
private CrumbData() {
this.crumbName = "";
this.crumb = "";
this.isValid = false;
getData();
}
private void getData() {
try {
String base = createCrumbUrlBase();
String[] pair = readData(base + "?xpath=concat(//crumbRequestField,\":\",//crumb)").split(":", 2);
crumbName = pair[0];
crumb = pair[1];
isValid = true;
LOGGER.fine("Crumb data: "+crumbName+"="+crumb);
} catch (IOException e) {
// presumably this Hudson doesn't use crumb
LOGGER.log(Level.FINE,"Failed to get crumb data",e);
}
}
private String createCrumbUrlBase() {
String url = target.toExternalForm();
return new StringBuilder(url.substring(0, url.lastIndexOf("/cli"))).append("/crumbIssuer/api/xml/").toString();
}
private String readData(String dest) throws IOException {
HttpURLConnection con = (HttpURLConnection) new URL(dest).openConnection();
if (authorization != null) {
con.addRequestProperty("Authorization", authorization);
}
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
String line = reader.readLine();
String nextLine = reader.readLine();
if (nextLine != null) {
System.err.println("Warning: received junk from " + dest);
System.err.println(line);
System.err.println(nextLine);
while ((nextLine = reader.readLine()) != null) {
System.err.println(nextLine);
}
}
return line;
}
finally {
con.disconnect();
}
}
}
}