/* * Universal Password Manager * Copyright (c) 2010-2011 Adrian Smith * * This file is part of Universal Password Manager. * * Universal Password Manager is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Universal Password Manager is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Universal Password Manager; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package com.u17od.upm.transport; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; import com.u17od.upm.util.Base64; import com.u17od.upm.util.Util; public class HTTPTransport extends Transport { private static final String BOUNDRY = "=================================="; private File certFile; private SSLSocketFactory sslFactory; private String trustedHost; private File tmpDir; public HTTPTransport(File certFile, String trustedHost, File tmpDir) { this.certFile = certFile; this.trustedHost = trustedHost; this.tmpDir = tmpDir; } public void put(String targetLocation, File file) throws TransportException { put(targetLocation, file, null, null); } public void put(String targetLocation, File file, String username, String password) throws TransportException { HttpURLConnection conn = null; try { targetLocation = addTrailingSlash(targetLocation) + "upload.php"; // These strings are sent in the request body. They provide information about the file being uploaded String contentDisposition = "Content-Disposition: form-data; name=\"userfile\"; filename=\"" + file.getName() + "\""; String contentType = "Content-Type: application/octet-stream"; // This is the standard format for a multipart request ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write("--".getBytes()); baos.write(BOUNDRY.getBytes()); baos.write("\n".getBytes()); baos.write(contentDisposition.getBytes()); baos.write("\n".getBytes()); baos.write(contentType.getBytes()); baos.write("\n".getBytes()); baos.write("\n".getBytes()); baos.write(Util.getBytesFromFile(file)); baos.write("\n".getBytes()); baos.write("--".getBytes()); baos.write(BOUNDRY.getBytes()); baos.write("--".getBytes()); // Make a connect to the server URL url = new URL(targetLocation); conn = getConnection(url); // Put the authentication details in the request if (username != null) { conn.setRequestProperty ("Authorization", createAuthenticationString(username, password)); } conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDRY); // Send the body DataOutputStream dataOS = new DataOutputStream(conn.getOutputStream()); baos.writeTo(dataOS); dataOS.flush(); dataOS.close(); // Ensure we got the HTTP 200 response code int responseCode = conn.getResponseCode(); if (responseCode != 200) { throw new TransportException(String.format("Received the response code %d from the URL %s", responseCode, url)); } // Read the response InputStream is = conn.getInputStream(); byte[] bytesReceived = readFromResponseStream(is); is.close(); String response = new String(bytesReceived); if (!response.toString().equals("OK")) { throw new TransportException(String.format("Received the response code %s from the URL %s", response, url)); } } catch (Exception e) { throw new TransportException(e); } finally { if (conn != null) { conn.disconnect(); } } } public byte[] get(String urlToGet) throws TransportException { return get(urlToGet, null, null); } public byte[] get(String urlToGet, String username, String password) throws TransportException { byte[] bytesReceived = null; HttpURLConnection conn = null; try { // Make a connect to the server URL url = new URL(urlToGet); conn = getConnection(url); // Put the authentication details in the request if (username != null && !username.trim().equals("")) { conn.setRequestProperty ("Authorization", createAuthenticationString(username, password)); } conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); conn.setRequestMethod("GET"); // Ensure we get the either 200 or 404 // 200 is OK // 404 means file doesn't exist. This is a valid result so we just return null int responseCode = conn.getResponseCode(); if (responseCode == 200) { // Read the response InputStream is = conn.getInputStream(); bytesReceived = readFromResponseStream(is); is.close(); conn.getInputStream().close(); } else if (responseCode != 404) { throw new TransportException(String.format("Received the response code %d from the URL %s", responseCode, url)); } } catch (MalformedURLException e) { throw new TransportException(e); } catch (IOException e) { throw new TransportException(e); } finally { if (conn != null) { conn.disconnect(); } } return bytesReceived; } private HttpURLConnection getConnection(URL url) throws TransportException { HttpURLConnection conn; try { conn = (HttpURLConnection) url.openConnection(); // This is for testing purposes. Setting http.proxyHost and http.proxyPort // doesn't seem to work but this does. // Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", 8888)); // HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy); if(conn instanceof HttpsURLConnection) { if (certFile.exists() && sslFactory == null) { buildSSLFactory(); } if (sslFactory != null) { ((HttpsURLConnection) conn).setSSLSocketFactory(sslFactory); // If we've been provided with a hostname we should always // trust then add a HostnameVerifier for that hostname if (trustedHost != null) { ((HttpsURLConnection) conn).setHostnameVerifier( new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return hostname.equals(trustedHost); } } ); } } } } catch (IOException e) { throw new TransportException(e); } catch (KeyManagementException e) { throw new TransportException(e); } catch (CertificateException e) { throw new TransportException(e); } catch (KeyStoreException e) { throw new TransportException(e); } catch (NoSuchAlgorithmException e) { throw new TransportException(e); } return conn; } private void buildSSLFactory() throws CertificateException, KeyStoreException, NoSuchAlgorithmException, IOException, KeyManagementException { FileInputStream fileStream = new FileInputStream(certFile); CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); Certificate cert = certFactory.generateCertificate(fileStream); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null); keyStore.setCertificateEntry("cert0", cert); TrustManagerFactory trustManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManager.init(keyStore); SSLContext context = SSLContext.getInstance("TLS"); context.init(null, trustManager.getTrustManagers(), null); sslFactory = context.getSocketFactory(); } private byte[] readFromResponseStream(InputStream is) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] bytes = new byte[1024]; int bytesRead; while((bytesRead = is.read(bytes)) != -1) { baos.write(bytes, 0, bytesRead); } byte[] bytesToReturn = baos.toByteArray(); baos.close(); return bytesToReturn; } public File getRemoteFile(String remoteLocation) throws TransportException { return getRemoteFile(remoteLocation, null, null); } public File getRemoteFile(String remoteLocation, String fileName, String httpUsername, String httpPassword) throws TransportException { remoteLocation = addTrailingSlash(remoteLocation); return getRemoteFile(remoteLocation + fileName, httpUsername, httpPassword); } public File getRemoteFile(String remoteLocation, String httpUsername, String httpPassword) throws TransportException { try { File downloadedFile = null; byte[] remoteFile = get(remoteLocation, httpUsername, httpPassword); if (remoteFile != null) { downloadedFile = File.createTempFile("upm", null, tmpDir); FileOutputStream fos = new FileOutputStream(downloadedFile); fos.write(remoteFile); fos.close(); } return downloadedFile; } catch (IOException e) { throw new TransportException(e); } } public void delete(String sharedDbURL, String fileToDelete, String username, String password) throws TransportException { HttpURLConnection conn = null; String targetURL = addTrailingSlash(sharedDbURL) + "deletefile.php"; String requestBody = String.format("fileToDelete=%s&Delete=Submit+Query", fileToDelete); try { // Make a connect to the server URL url = new URL(targetURL); conn = getConnection(url); // Put the authentication details in the request if (username != null) { conn.setRequestProperty ("Authorization", createAuthenticationString(username, password)); } int contentLength = requestBody.length(); conn.setRequestProperty("Content-Length", String.valueOf(contentLength)); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); conn.setRequestMethod("POST"); conn.setFixedLengthStreamingMode(contentLength); // Send the body OutputStreamWriter dataOS = new OutputStreamWriter(conn.getOutputStream()); dataOS.write(requestBody); dataOS.flush(); dataOS.close(); // Ensure we got the HTTP 200 response code int responseCode = conn.getResponseCode(); if (responseCode != 200) { throw new TransportException(String.format("Received the response code %d from the URL %s", responseCode, url)); } // Read the response InputStream is = conn.getInputStream(); byte[] bytesReceived = readFromResponseStream(is); is.close(); String response = new String(bytesReceived); // If we don't get OK or FILE_DOESNT_EXIST then thrown an error if (!response.toString().equals("OK") && !response.toString().equals("FILE_DOESNT_EXIST")) { throw new TransportException(String.format("Received the response code %s from the URL %s", response, url)); } } catch (Exception e) { throw new TransportException(e); } finally { if (conn != null) { conn.disconnect(); } } } public void delete(String targetLocation, String fileToDelete) throws TransportException { delete(targetLocation, fileToDelete, null, null); } private String addTrailingSlash(String url) { if (url.charAt(url.length() - 1) != '/') { url = url + '/'; } return url; } private String createAuthenticationString(String username, String password) { String usernamePassword = username + ":" + password; String encodedUsernamePassword = Base64.encodeBytes(usernamePassword.getBytes()); return "Basic " + encodedUsernamePassword; } }