1 /* 2 * Copyright (C) 2014 Square, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.squareup.okhttp.curl; 17 18 import com.google.common.base.Joiner; 19 import com.squareup.okhttp.ConnectionPool; 20 import com.squareup.okhttp.Headers; 21 import com.squareup.okhttp.MediaType; 22 import com.squareup.okhttp.OkHttpClient; 23 import com.squareup.okhttp.Protocol; 24 import com.squareup.okhttp.Request; 25 import com.squareup.okhttp.RequestBody; 26 import com.squareup.okhttp.Response; 27 import com.squareup.okhttp.internal.http.StatusLine; 28 import com.squareup.okhttp.internal.framed.Http2; 29 30 import io.airlift.command.Arguments; 31 import io.airlift.command.Command; 32 import io.airlift.command.HelpOption; 33 import io.airlift.command.Option; 34 import io.airlift.command.SingleCommand; 35 import java.io.IOException; 36 import java.io.InputStream; 37 import java.security.cert.CertificateException; 38 import java.security.cert.X509Certificate; 39 import java.util.List; 40 import java.util.Properties; 41 import java.util.logging.ConsoleHandler; 42 import java.util.logging.Level; 43 import java.util.logging.LogRecord; 44 import java.util.logging.Logger; 45 import java.util.logging.SimpleFormatter; 46 import javax.net.ssl.HostnameVerifier; 47 import javax.net.ssl.SSLContext; 48 import javax.net.ssl.SSLSession; 49 import javax.net.ssl.SSLSocketFactory; 50 import javax.net.ssl.TrustManager; 51 import javax.net.ssl.X509TrustManager; 52 import okio.BufferedSource; 53 import okio.Okio; 54 import okio.Sink; 55 56 import static java.util.concurrent.TimeUnit.SECONDS; 57 58 @Command(name = Main.NAME, description = "A curl for the next-generation web.") 59 public class Main extends HelpOption implements Runnable { 60 static final String NAME = "okcurl"; 61 static final int DEFAULT_TIMEOUT = -1; 62 63 static Main fromArgs(String... args) { 64 return SingleCommand.singleCommand(Main.class).parse(args); 65 } 66 67 public static void main(String... args) { 68 fromArgs(args).run(); 69 } 70 71 private static String versionString() { 72 try { 73 Properties prop = new Properties(); 74 InputStream in = Main.class.getResourceAsStream("/okcurl-version.properties"); 75 prop.load(in); 76 in.close(); 77 return prop.getProperty("version"); 78 } catch (IOException e) { 79 throw new AssertionError("Could not load okcurl-version.properties."); 80 } 81 } 82 83 private static String protocols() { 84 return Joiner.on(", ").join(Protocol.values()); 85 } 86 87 @Option(name = { "-X", "--request" }, description = "Specify request command to use") 88 public String method; 89 90 @Option(name = { "-d", "--data" }, description = "HTTP POST data") 91 public String data; 92 93 @Option(name = { "-H", "--header" }, description = "Custom header to pass to server") 94 public List<String> headers; 95 96 @Option(name = { "-A", "--user-agent" }, description = "User-Agent to send to server") 97 public String userAgent = NAME + "/" + versionString(); 98 99 @Option(name = "--connect-timeout", description = "Maximum time allowed for connection (seconds)") 100 public int connectTimeout = DEFAULT_TIMEOUT; 101 102 @Option(name = "--read-timeout", description = "Maximum time allowed for reading data (seconds)") 103 public int readTimeout = DEFAULT_TIMEOUT; 104 105 @Option(name = { "-L", "--location" }, description = "Follow redirects") 106 public boolean followRedirects; 107 108 @Option(name = { "-k", "--insecure" }, 109 description = "Allow connections to SSL sites without certs") 110 public boolean allowInsecure; 111 112 @Option(name = { "-i", "--include" }, description = "Include protocol headers in the output") 113 public boolean showHeaders; 114 115 @Option(name = "--frames", description = "Log HTTP/2 frames to STDERR") 116 public boolean showHttp2Frames; 117 118 @Option(name = { "-e", "--referer" }, description = "Referer URL") 119 public String referer; 120 121 @Option(name = { "-V", "--version" }, description = "Show version number and quit") 122 public boolean version; 123 124 @Arguments(title = "url", description = "Remote resource URL") 125 public String url; 126 127 private OkHttpClient client; 128 129 @Override public void run() { 130 if (showHelpIfRequested()) { 131 return; 132 } 133 if (version) { 134 System.out.println(NAME + " " + versionString()); 135 System.out.println("Protocols: " + protocols()); 136 return; 137 } 138 139 if (showHttp2Frames) { 140 enableHttp2FrameLogging(); 141 } 142 143 client = createClient(); 144 Request request = createRequest(); 145 try { 146 Response response = client.newCall(request).execute(); 147 if (showHeaders) { 148 System.out.println(StatusLine.get(response)); 149 Headers headers = response.headers(); 150 for (int i = 0, size = headers.size(); i < size; i++) { 151 System.out.println(headers.name(i) + ": " + headers.value(i)); 152 } 153 System.out.println(); 154 } 155 156 // Stream the response to the System.out as it is returned from the server. 157 Sink out = Okio.sink(System.out); 158 BufferedSource source = response.body().source(); 159 while (!source.exhausted()) { 160 out.write(source.buffer(), source.buffer().size()); 161 out.flush(); 162 } 163 164 response.body().close(); 165 } catch (IOException e) { 166 e.printStackTrace(); 167 } finally { 168 close(); 169 } 170 } 171 172 private OkHttpClient createClient() { 173 OkHttpClient client = new OkHttpClient(); 174 client.setFollowSslRedirects(followRedirects); 175 if (connectTimeout != DEFAULT_TIMEOUT) { 176 client.setConnectTimeout(connectTimeout, SECONDS); 177 } 178 if (readTimeout != DEFAULT_TIMEOUT) { 179 client.setReadTimeout(readTimeout, SECONDS); 180 } 181 if (allowInsecure) { 182 client.setSslSocketFactory(createInsecureSslSocketFactory()); 183 client.setHostnameVerifier(createInsecureHostnameVerifier()); 184 } 185 // If we don't set this reference, there's no way to clean shutdown persistent connections. 186 client.setConnectionPool(ConnectionPool.getDefault()); 187 return client; 188 } 189 190 private String getRequestMethod() { 191 if (method != null) { 192 return method; 193 } 194 if (data != null) { 195 return "POST"; 196 } 197 return "GET"; 198 } 199 200 private RequestBody getRequestBody() { 201 if (data == null) { 202 return null; 203 } 204 String bodyData = data; 205 206 String mimeType = "application/x-www-form-urlencoded"; 207 if (headers != null) { 208 for (String header : headers) { 209 String[] parts = header.split(":", -1); 210 if ("Content-Type".equalsIgnoreCase(parts[0])) { 211 mimeType = parts[1].trim(); 212 headers.remove(header); 213 break; 214 } 215 } 216 } 217 218 return RequestBody.create(MediaType.parse(mimeType), bodyData); 219 } 220 221 Request createRequest() { 222 Request.Builder request = new Request.Builder(); 223 224 request.url(url); 225 request.method(getRequestMethod(), getRequestBody()); 226 227 if (headers != null) { 228 for (String header : headers) { 229 String[] parts = header.split(":", 2); 230 request.header(parts[0], parts[1]); 231 } 232 } 233 if (referer != null) { 234 request.header("Referer", referer); 235 } 236 request.header("User-Agent", userAgent); 237 238 return request.build(); 239 } 240 241 private void close() { 242 client.getConnectionPool().evictAll(); // Close any persistent connections. 243 } 244 245 private static SSLSocketFactory createInsecureSslSocketFactory() { 246 try { 247 SSLContext context = SSLContext.getInstance("TLS"); 248 TrustManager permissive = new X509TrustManager() { 249 @Override public void checkClientTrusted(X509Certificate[] chain, String authType) 250 throws CertificateException { 251 } 252 253 @Override public void checkServerTrusted(X509Certificate[] chain, String authType) 254 throws CertificateException { 255 } 256 257 @Override public X509Certificate[] getAcceptedIssuers() { 258 return null; 259 } 260 }; 261 context.init(null, new TrustManager[] { permissive }, null); 262 return context.getSocketFactory(); 263 } catch (Exception e) { 264 throw new AssertionError(e); 265 } 266 } 267 268 private static HostnameVerifier createInsecureHostnameVerifier() { 269 return new HostnameVerifier() { 270 @Override public boolean verify(String s, SSLSession sslSession) { 271 return true; 272 } 273 }; 274 } 275 276 private static void enableHttp2FrameLogging() { 277 Logger logger = Logger.getLogger(Http2.class.getName() + "$FrameLogger"); 278 logger.setLevel(Level.FINE); 279 ConsoleHandler handler = new ConsoleHandler(); 280 handler.setLevel(Level.FINE); 281 handler.setFormatter(new SimpleFormatter() { 282 @Override public String format(LogRecord record) { 283 return String.format("%s%n", record.getMessage()); 284 } 285 }); 286 logger.addHandler(handler); 287 } 288 } 289