diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index a234b2ea7..000000000 --- a/.gitmodules +++ /dev/null @@ -1,12 +0,0 @@ -[submodule "yorozuya"] - path = yorozuya - url = https://github.com/seven332/Yorozuya -[submodule "easyrecyclerview"] - path = easyrecyclerview - url = https://github.com/seven332/EasyRecyclerView -[submodule "beerbelly"] - path = beerbelly - url = https://github.com/seven332/BeerBelly -[submodule "conaco"] - path = conaco - url = https://github.com/seven332/Conaco diff --git a/app/build.gradle b/app/build.gradle index c35c547c9..42ff3f26e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,24 +55,27 @@ clean { } dependencies { - compile project(':beerbelly') - compile project(':conaco') - compile project(':easyrecyclerview') - compile project(':okhttp3') - compile project(':yorozuya') compile fileTree(include: ['*.jar'], dir: 'libs') - testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.3.0' compile 'com.android.support:design:23.3.0' compile 'com.github.amlcurran.showcaseview:library:5.4.3' compile 'com.github.anrwatchdog:anrwatchdog:1.2.0' + compile 'com.github.seven332:beerbelly:0.1.1' + compile 'com.github.seven332:conaco:0.1.2-eh' + compile 'com.github.seven332:easyrecyclerview:0.1.0' compile 'com.github.seven332:drawerLayout:0.1.0' - compile 'com.github.seven332:image:0.1.0' + compile 'com.github.seven332:image:0.1.2' + compile 'com.github.seven332:okhttp:3.2.0-eh' compile 'com.github.seven332:refreshlayout:0.1.0' compile 'com.github.seven332:ripple:0.1.1' + compile 'com.github.seven332:streampipe:0.1.0' compile 'com.github.seven332:unifile:0.1.3' + compile 'com.github.seven332:yorozuya-collect:0.1.2' + compile 'com.github.seven332:yorozuya-thread:0.1.0' + compile 'com.github.seven332:yorozuya:0.1.0' compile 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.8.7' compile 'org.ccil.cowan.tagsoup:tagsoup:1.2.1' compile 'org.greenrobot:greendao:2.2.0' compile 'org.jsoup:jsoup:1.9.1' + testCompile 'junit:junit:4.12' } diff --git a/app/src/main/java/com/hippo/drawable/PreciselyClipDrawable.java b/app/src/main/java/com/hippo/drawable/PreciselyClipDrawable.java index e3ed1d3a6..043b69f7c 100644 --- a/app/src/main/java/com/hippo/drawable/PreciselyClipDrawable.java +++ b/app/src/main/java/com/hippo/drawable/PreciselyClipDrawable.java @@ -24,6 +24,9 @@ import android.support.v7.graphics.drawable.DrawableWrapper; import com.hippo.yorozuya.MathUtils; +/** + * Show a part of the original drawable + */ public class PreciselyClipDrawable extends DrawableWrapper { private final boolean mClip; diff --git a/app/src/main/java/com/hippo/drawable/UnikeryDrawable.java b/app/src/main/java/com/hippo/drawable/UnikeryDrawable.java index 0b735aa9a..d576be2bc 100644 --- a/app/src/main/java/com/hippo/drawable/UnikeryDrawable.java +++ b/app/src/main/java/com/hippo/drawable/UnikeryDrawable.java @@ -16,26 +16,59 @@ package com.hippo.drawable; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; -import android.widget.TextView; +import android.util.Log; import com.hippo.conaco.Conaco; +import com.hippo.conaco.ConacoTask; import com.hippo.conaco.Unikery; -import com.hippo.conaco.ValueHolder; +import com.hippo.image.ImageBitmap; +import com.hippo.image.ImageDrawable; +import com.hippo.image.RecycledException; +import com.hippo.widget.ObservedTextView; -public class UnikeryDrawable extends WrapDrawable implements Unikery { +public class UnikeryDrawable extends WrapDrawable implements Unikery, + ObservedTextView.OnWindowAttachListener { + + private static final String TAG = UnikeryDrawable.class.getSimpleName(); private int mTaskId = Unikery.INVALID_ID; - private final TextView mTextView; + private final ObservedTextView mTextView; + private final Conaco mConaco; + private String mUrl; - private ValueHolder mHolder; - - public UnikeryDrawable(TextView textView) { + public UnikeryDrawable(ObservedTextView textView, Conaco conaco) { mTextView = textView; + mTextView.setOnWindowAttachListener(this); + mConaco = conaco; + } + + @Override + public void onAttachedToWindow() { + load(mUrl); + } + + @Override + public void onDetachedFromWindow() { + mConaco.cancel(this); + clearDrawable(); + } + + public void load(String url) { + if (url != null) { + mUrl = url; + mConaco.load(new ConacoTask.Builder().setUnikery(this).setUrl(url).setKey(url)); + } + } + + private void clearDrawable() { + Drawable drawable = getDrawable(); + if (drawable instanceof ImageDrawable) { + ((ImageDrawable) drawable).recycle(); + } + setDrawable(null); } @Override @@ -54,9 +87,7 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery { updateBounds(); if (drawable != null) { - // mTextView.requestLayout() not work! - CharSequence cs = mTextView.getText(); - mTextView.setText(cs); + invalidateSelf(); } } @@ -71,49 +102,41 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery { } @Override - public void onMiss(Conaco.Source source) { + public void invalidateSelf() { + CharSequence cs = mTextView.getText(); + mTextView.setText(cs); } @Override - public void onRequest() { - } + public void onMiss(int source) {} @Override - public void onProgress(long singleReceivedSize, long receivedSize, long totalSize) { - } + public void onRequest() {} - private void removeDrawableAndHolder() { - // Remove drawable - setDrawable(null); + @Override + public void onProgress(long singleReceivedSize, long receivedSize, long totalSize) {} - // Remove holder - if (mHolder != null) { - mHolder.release(this); - mHolder = null; + @Override + public void onWait() {} + + @Override + public boolean onGetValue(@NonNull ImageBitmap value, int source) { + ImageDrawable drawable; + try { + drawable = new ImageDrawable(value); + } catch (RecycledException e) { + Log.d(TAG, "The ImageBitmap is recycled", e); + return false; } - } - @Override - public boolean onGetObject(@NonNull ValueHolder holder, Conaco.Source source) { - holder.obtain(this); - - removeDrawableAndHolder(); - - mHolder = holder; - Drawable drawable = new BitmapDrawable(mTextView.getResources(), holder.getValue()); + clearDrawable(); setDrawable(drawable); + drawable.start(); return true; } - @Override - public void onSetDrawable(Drawable drawable) { - removeDrawableAndHolder(); - - setDrawable(drawable); - } - @Override public void onFailure() { // Empty diff --git a/app/src/main/java/com/hippo/ehviewer/EhApplication.java b/app/src/main/java/com/hippo/ehviewer/EhApplication.java index 0ff3fad3b..f820be50e 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhApplication.java +++ b/app/src/main/java/com/hippo/ehviewer/EhApplication.java @@ -22,7 +22,6 @@ import android.content.ComponentCallbacks2; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.graphics.Bitmap; import android.os.Debug; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -41,6 +40,7 @@ import com.hippo.ehviewer.client.data.GalleryDetail; import com.hippo.ehviewer.download.DownloadManager; import com.hippo.ehviewer.spider.SpiderDen; import com.hippo.ehviewer.ui.CommonOperations; +import com.hippo.image.ImageBitmap; import com.hippo.network.StatusCodeException; import com.hippo.okhttp.CookieDB; import com.hippo.scene.SceneApplication; @@ -79,8 +79,8 @@ public class EhApplication extends SceneApplication implements Thread.UncaughtEx private EhCookieStore mEhCookieStore; private EhClient mEhClient; private OkHttpClient mOkHttpClient; - private BitmapHelper mBitmapHelper; - private Conaco mConaco; + private ImageBitmapHelper mImageBitmapHelper; + private Conaco mConaco; private LruCache mGalleryDetailCache; private SimpleDiskCache mSpiderInfoCache; private DownloadManager mDownloadManager; @@ -194,7 +194,7 @@ public class EhApplication extends SceneApplication implements Thread.UncaughtEx public void clearMemoryCache() { if (null != mConaco) { - mConaco.clearMemoryCache(); + mConaco.getBeerBelly().clearMemory(); } if (null != mGalleryDetailCache) { mGalleryDetailCache.evictAll(); @@ -275,12 +275,12 @@ public class EhApplication extends SceneApplication implements Thread.UncaughtEx } @NonNull - public static BitmapHelper getBitmapHelper(@NonNull Context context) { + public static ImageBitmapHelper getImageBitmapHelper(@NonNull Context context) { EhApplication application = ((EhApplication) context.getApplicationContext()); - if (application.mBitmapHelper == null) { - application.mBitmapHelper = new BitmapHelper(); + if (application.mImageBitmapHelper == null) { + application.mImageBitmapHelper = new ImageBitmapHelper(); } - return application.mBitmapHelper; + return application.mImageBitmapHelper; } private static int getMemoryCacheMaxSize(Context context) { @@ -291,17 +291,17 @@ public class EhApplication extends SceneApplication implements Thread.UncaughtEx } @NonNull - public static Conaco getConaco(@NonNull Context context) { + public static Conaco getConaco(@NonNull Context context) { EhApplication application = ((EhApplication) context.getApplicationContext()); if (application.mConaco == null) { - Conaco.Builder builder = new Conaco.Builder<>(); + Conaco.Builder builder = new Conaco.Builder<>(); builder.hasMemoryCache = true; builder.memoryCacheMaxSize = getMemoryCacheMaxSize(context); builder.hasDiskCache = true; builder.diskCacheDir = new File(context.getCacheDir(), "thumb"); builder.diskCacheMaxSize = 80 * 1024 * 1024; // 80MB builder.okHttpClient = getOkHttpClient(context); - builder.objectHelper = getBitmapHelper(context); + builder.objectHelper = getImageBitmapHelper(context); builder.debug = DEBUG_CONACO; application.mConaco = builder.build(); } diff --git a/app/src/main/java/com/hippo/ehviewer/EhDB.java b/app/src/main/java/com/hippo/ehviewer/EhDB.java index 05ead6e76..07a3f4040 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhDB.java +++ b/app/src/main/java/com/hippo/ehviewer/EhDB.java @@ -47,7 +47,7 @@ import com.hippo.util.SqlUtils; import com.hippo.yorozuya.FileUtils; import com.hippo.yorozuya.IOUtils; import com.hippo.yorozuya.ObjectUtils; -import com.hippo.yorozuya.sparse.SparseJLArray; +import com.hippo.yorozuya.collect.SparseJLArray; import java.io.File; import java.io.FileInputStream; diff --git a/app/src/main/java/com/hippo/ehviewer/BitmapHelper.java b/app/src/main/java/com/hippo/ehviewer/ImageBitmapHelper.java similarity index 50% rename from app/src/main/java/com/hippo/ehviewer/BitmapHelper.java rename to app/src/main/java/com/hippo/ehviewer/ImageBitmapHelper.java index 001dc0867..4b6d21c4f 100644 --- a/app/src/main/java/com/hippo/ehviewer/BitmapHelper.java +++ b/app/src/main/java/com/hippo/ehviewer/ImageBitmapHelper.java @@ -16,25 +16,31 @@ package com.hippo.ehviewer; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.hippo.conaco.ValueHelper; -import com.hippo.conaco.ValueHolder; -import com.hippo.yorozuya.io.InputStreamPipe; +import com.hippo.image.Image; +import com.hippo.image.ImageBitmap; +import com.hippo.streampipe.InputStreamPipe; import java.io.IOException; -public class BitmapHelper implements ValueHelper { +public class ImageBitmapHelper implements ValueHelper { + + private static final int MAX_CACHE_SIZE = 512 * 512; @Nullable @Override - public Bitmap decode(@NonNull InputStreamPipe isPipe) { + public ImageBitmap decode(@NonNull InputStreamPipe isPipe) { try { isPipe.obtain(); - return BitmapFactory.decodeStream(isPipe.open()); + Image image = Image.decode(isPipe.open(), false); + if (image != null) { + return new ImageBitmap(image); + } else { + return null; + } } catch (OutOfMemoryError e) { return null; } catch (IOException e) { @@ -46,15 +52,26 @@ public class BitmapHelper implements ValueHelper { } @Override - public int sizeOf(@NonNull String key, @NonNull Bitmap value) { - return value.getByteCount(); + public int sizeOf(@NonNull String key, @NonNull ImageBitmap value) { + return value.getWidth() * value.getHeight() * 4; } @Override - public void onRemove(@NonNull String key, @NonNull ValueHolder oldValue) {} + public void onAddToMemoryCache(@NonNull ImageBitmap oldValue) { + oldValue.obtain(); + } @Override - public boolean useMemoryCache(@NonNull String key, ValueHolder holder) { - return true; + public void onRemoveFromMemoryCache(@NonNull String key, @NonNull ImageBitmap oldValue) { + oldValue.release(); + } + + @Override + public boolean useMemoryCache(@NonNull String key, ImageBitmap value) { + if (value != null) { + return value.getWidth() * value.getHeight() <= MAX_CACHE_SIZE; + } else { + return true; + } } } diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java index e2521d19d..dd331fbf2 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java @@ -22,8 +22,8 @@ import android.os.AsyncTask; import com.hippo.ehviewer.EhApplication; import com.hippo.ehviewer.client.data.GalleryInfo; import com.hippo.ehviewer.client.exception.CancelledException; -import com.hippo.yorozuya.PriorityThreadFactory; import com.hippo.yorozuya.SimpleHandler; +import com.hippo.yorozuya.thread.PriorityThreadFactory; import java.io.File; import java.util.List; diff --git a/app/src/main/java/com/hippo/ehviewer/client/data/LargePreviewSet.java b/app/src/main/java/com/hippo/ehviewer/client/data/LargePreviewSet.java index 032a62c54..b91ae6b3a 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/data/LargePreviewSet.java +++ b/app/src/main/java/com/hippo/ehviewer/client/data/LargePreviewSet.java @@ -21,7 +21,7 @@ import android.os.Parcelable; import com.hippo.ehviewer.client.EhCacheKeyFactory; import com.hippo.widget.LoadImageView; -import com.hippo.yorozuya.IntList; +import com.hippo.yorozuya.collect.IntList; import java.util.ArrayList; diff --git a/app/src/main/java/com/hippo/ehviewer/client/data/NormalPreviewSet.java b/app/src/main/java/com/hippo/ehviewer/client/data/NormalPreviewSet.java index cb4a380a2..d2945f72c 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/data/NormalPreviewSet.java +++ b/app/src/main/java/com/hippo/ehviewer/client/data/NormalPreviewSet.java @@ -19,7 +19,7 @@ package com.hippo.ehviewer.client.data; import android.os.Parcel; import com.hippo.widget.LoadImageView; -import com.hippo.yorozuya.IntList; +import com.hippo.yorozuya.collect.IntList; import java.util.ArrayList; diff --git a/app/src/main/java/com/hippo/ehviewer/download/DownloadManager.java b/app/src/main/java/com/hippo/ehviewer/download/DownloadManager.java index ad8735d90..55655e689 100644 --- a/app/src/main/java/com/hippo/ehviewer/download/DownloadManager.java +++ b/app/src/main/java/com/hippo/ehviewer/download/DownloadManager.java @@ -28,12 +28,12 @@ import com.hippo.ehviewer.dao.DownloadLabel; import com.hippo.ehviewer.spider.SpiderQueen; import com.hippo.image.Image; import com.hippo.yorozuya.ConcurrentPool; -import com.hippo.yorozuya.LongList; import com.hippo.yorozuya.MathUtils; import com.hippo.yorozuya.ObjectUtils; import com.hippo.yorozuya.SimpleHandler; -import com.hippo.yorozuya.sparse.SparseIJArray; -import com.hippo.yorozuya.sparse.SparseJLArray; +import com.hippo.yorozuya.collect.LongList; +import com.hippo.yorozuya.collect.SparseIJArray; +import com.hippo.yorozuya.collect.SparseJLArray; import java.util.ArrayList; import java.util.Collections; diff --git a/app/src/main/java/com/hippo/ehviewer/download/DownloadService.java b/app/src/main/java/com/hippo/ehviewer/download/DownloadService.java index a96df27e2..64c31b52f 100644 --- a/app/src/main/java/com/hippo/ehviewer/download/DownloadService.java +++ b/app/src/main/java/com/hippo/ehviewer/download/DownloadService.java @@ -40,10 +40,10 @@ import com.hippo.ehviewer.ui.scene.DownloadsScene; import com.hippo.scene.StageActivity; import com.hippo.util.ReadableTime; import com.hippo.yorozuya.FileUtils; -import com.hippo.yorozuya.LongList; import com.hippo.yorozuya.SimpleHandler; -import com.hippo.yorozuya.sparse.SparseJBArray; -import com.hippo.yorozuya.sparse.SparseJLArray; +import com.hippo.yorozuya.collect.LongList; +import com.hippo.yorozuya.collect.SparseJBArray; +import com.hippo.yorozuya.collect.SparseJLArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/DirGalleryProvider.java b/app/src/main/java/com/hippo/ehviewer/gallery/DirGalleryProvider.java index 718331891..10a89f492 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/DirGalleryProvider.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/DirGalleryProvider.java @@ -28,8 +28,8 @@ import com.hippo.image.Image; import com.hippo.unifile.UniFile; import com.hippo.yorozuya.FileUtils; import com.hippo.yorozuya.IOUtils; -import com.hippo.yorozuya.PriorityThread; import com.hippo.yorozuya.StringUtils; +import com.hippo.yorozuya.thread.PriorityThread; import java.io.File; import java.io.FilenameFilter; diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ZipGalleryProvider.java b/app/src/main/java/com/hippo/ehviewer/gallery/ZipGalleryProvider.java index 97f2f4a37..a15b0fd74 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ZipGalleryProvider.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ZipGalleryProvider.java @@ -26,8 +26,8 @@ import com.hippo.ehviewer.R; import com.hippo.ehviewer.gallery.gl.GalleryPageView; import com.hippo.image.Image; import com.hippo.unifile.UniFile; -import com.hippo.yorozuya.PriorityThread; import com.hippo.yorozuya.StringUtils; +import com.hippo.yorozuya.thread.PriorityThread; import java.io.File; import java.io.FileNotFoundException; diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/gl/PagerLayoutManager.java b/app/src/main/java/com/hippo/ehviewer/gallery/gl/PagerLayoutManager.java index c1bbdac0f..d870bf016 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/gl/PagerLayoutManager.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/gl/PagerLayoutManager.java @@ -184,8 +184,8 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { @Override public void onAttach(GalleryView.Adapter adapter) { - AssertUtils.assertEquals("The PagerLayoutManager is attached", mAdapter, null); - AssertUtils.assertNotEquals("The adapter is null", adapter, null); + AssertUtils.assertNull("The PagerLayoutManager is attached", mAdapter); + AssertUtils.assertNotNull("The adapter is null", adapter); mAdapter = adapter; // Reset parameters resetParameters(); @@ -232,7 +232,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { @Override public GalleryView.Adapter onDetach() { - AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null); + AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter); // Cancel all animations cancelAllAnimations(); @@ -293,7 +293,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { public void onFill() { GalleryView.Adapter adapter = mAdapter; GalleryView galleryView = mGalleryView; - AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", adapter, null); + AssertUtils.assertNotNull("The PagerLayoutManager is not attached", adapter); int width = galleryView.getWidth(); int height = galleryView.getHeight(); @@ -703,7 +703,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager { @Override public void onDataChanged() { - AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null); + AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter); // Cancel all animations cancelAllAnimations(); diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/gl/ScrollLayoutManager.java b/app/src/main/java/com/hippo/ehviewer/gallery/gl/ScrollLayoutManager.java index 418d370ca..c844f2e15 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/gl/ScrollLayoutManager.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/gl/ScrollLayoutManager.java @@ -112,8 +112,8 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { @Override public void onAttach(GalleryView.Adapter adapter) { - AssertUtils.assertEquals("The ScrollLayoutManager is attached", mAdapter, null); - AssertUtils.assertNotEquals("The iterator is null", adapter, null); + AssertUtils.assertNull("The ScrollLayoutManager is attached", mAdapter); + AssertUtils.assertNotNull("The iterator is null", adapter); mAdapter = adapter; // Reset parameters resetParameters(); @@ -151,7 +151,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { @Override public GalleryView.Adapter onDetach() { - AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null); + AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter); // Cancel all animations cancelAllAnimations(); @@ -396,7 +396,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { public void onFill() { GalleryView.Adapter adapter = mAdapter; GalleryView galleryView = mGalleryView; - AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", adapter, null); + AssertUtils.assertNotNull("The PagerLayoutManager is not attached", adapter); int size = adapter.size(); String errorStr = adapter.getError(); @@ -831,7 +831,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager { @Override public void onDataChanged() { - AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null); + AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter); // Cancel all animations cancelAllAnimations(); diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.java b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.java index d5b81c61c..deee17a71 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.java +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.java @@ -30,14 +30,14 @@ import com.hippo.ehviewer.client.data.GalleryInfo; import com.hippo.ehviewer.gallery.GalleryProvider; import com.hippo.io.UniFileInputStreamPipe; import com.hippo.io.UniFileOutputStreamPipe; +import com.hippo.streampipe.InputStreamPipe; +import com.hippo.streampipe.OutputStreamPipe; import com.hippo.unifile.FilenameFilter; import com.hippo.unifile.UniFile; import com.hippo.yorozuya.FileUtils; import com.hippo.yorozuya.IOUtils; import com.hippo.yorozuya.MathUtils; import com.hippo.yorozuya.Utilities; -import com.hippo.yorozuya.io.InputStreamPipe; -import com.hippo.yorozuya.io.OutputStreamPipe; import java.io.File; import java.io.IOException; diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java index a8eafab74..5a49cb02a 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java @@ -48,18 +48,18 @@ import com.hippo.ehviewer.client.parser.GalleryPageUrlParser; import com.hippo.ehviewer.gallery.GalleryProvider; import com.hippo.ehviewer.gallery.gl.GalleryPageView; import com.hippo.image.Image; +import com.hippo.streampipe.InputStreamPipe; +import com.hippo.streampipe.OutputStreamPipe; import com.hippo.unifile.UniFile; import com.hippo.util.ExceptionUtils; import com.hippo.yorozuya.IOUtils; import com.hippo.yorozuya.MathUtils; import com.hippo.yorozuya.OSUtils; -import com.hippo.yorozuya.PriorityThread; -import com.hippo.yorozuya.PriorityThreadFactory; import com.hippo.yorozuya.StringUtils; import com.hippo.yorozuya.Utilities; -import com.hippo.yorozuya.io.InputStreamPipe; -import com.hippo.yorozuya.io.OutputStreamPipe; -import com.hippo.yorozuya.sparse.SparseJLArray; +import com.hippo.yorozuya.collect.SparseJLArray; +import com.hippo.yorozuya.thread.PriorityThread; +import com.hippo.yorozuya.thread.PriorityThreadFactory; import java.io.IOException; import java.io.InputStream; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.java index 4ba2273c3..2b35421a5 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.java @@ -86,10 +86,10 @@ import com.hippo.widget.LoadImageView; import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager; import com.hippo.yorozuya.AssertUtils; import com.hippo.yorozuya.FileUtils; -import com.hippo.yorozuya.LongList; import com.hippo.yorozuya.ObjectUtils; import com.hippo.yorozuya.ResourcesUtils; import com.hippo.yorozuya.ViewUtils; +import com.hippo.yorozuya.collect.LongList; import java.util.ArrayList; import java.util.LinkedList; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java index 86ec4b0ea..ce123716a 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java @@ -74,14 +74,15 @@ import com.hippo.util.TextUrl; import com.hippo.view.ViewTransition; import com.hippo.widget.FabLayout; import com.hippo.widget.LinkifyTextView; +import com.hippo.widget.ObservedTextView; import com.hippo.yorozuya.AnimationUtils; import com.hippo.yorozuya.AssertUtils; -import com.hippo.yorozuya.IntList; import com.hippo.yorozuya.LayoutUtils; import com.hippo.yorozuya.ResourcesUtils; import com.hippo.yorozuya.SimpleAnimatorListener; import com.hippo.yorozuya.StringUtils; import com.hippo.yorozuya.ViewUtils; +import com.hippo.yorozuya.collect.IntList; import java.util.ArrayList; import java.util.List; @@ -616,7 +617,7 @@ public final class GalleryCommentsScene extends ToolbarScene return new CommentHolder(mInflater.inflate(R.layout.item_gallery_comment, parent, false)); } - public CharSequence generateComment(Context context, TextView textView, GalleryComment comment) { + public CharSequence generateComment(Context context, ObservedTextView textView, GalleryComment comment) { SpannableStringBuilder ssb = Html.fromHtml(comment.comment, new URLImageGetter(textView, EhApplication.getConaco(context)), null); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java index 298cdb670..44d0db68f 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java @@ -96,6 +96,7 @@ import com.hippo.util.ReadableTime; import com.hippo.view.ViewTransition; import com.hippo.widget.AutoWrapLayout; import com.hippo.widget.LoadImageView; +import com.hippo.widget.ObservedTextView; import com.hippo.widget.ProgressView; import com.hippo.widget.ProgressiveRatingBar; import com.hippo.widget.SimpleGridAutoSpanLayout; @@ -869,7 +870,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene user.setText(comment.user); TextView time = (TextView) v.findViewById(R.id.time); time.setText(ReadableTime.getTimeAgo(comment.time)); - TextView c = (TextView) v.findViewById(R.id.comment); + ObservedTextView c = (ObservedTextView) v.findViewById(R.id.comment); c.setMaxLines(5); c.setText(Html.fromHtml(comment.comment, new URLImageGetter(c, EhApplication.getConaco(context)), null)); diff --git a/app/src/main/java/com/hippo/ehviewer/widget/SearchDatabase.java b/app/src/main/java/com/hippo/ehviewer/widget/SearchDatabase.java index b133b1ee3..4200dfb62 100644 --- a/app/src/main/java/com/hippo/ehviewer/widget/SearchDatabase.java +++ b/app/src/main/java/com/hippo/ehviewer/widget/SearchDatabase.java @@ -22,14 +22,14 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.text.TextUtils; +import android.util.Log; import com.hippo.util.SqlUtils; -import com.hippo.yorozuya.Say; import java.util.LinkedList; import java.util.List; -public class SearchDatabase { +public final class SearchDatabase { private static final String TAG = SearchDatabase.class.getSimpleName(); @@ -41,7 +41,7 @@ public class SearchDatabase { private static final int MAX_HISTORY = 100; - private SQLiteDatabase mDatabase; + private final SQLiteDatabase mDatabase; private static SearchDatabase sInstance; @@ -127,11 +127,10 @@ public class SearchDatabase { } mDatabase.delete(TABLE_SUGGESTIONS, selection, null); } catch (RuntimeException e) { - Say.e(TAG, "truncateHistory", e); + Log.e(TAG, "truncateHistory", e); } } - /** * Builds the database. This version has extra support for using the version field * as a mode flags field, and configures the database columns depending on the mode bits diff --git a/app/src/main/java/com/hippo/gl/glrenderer/GLES20Canvas.java b/app/src/main/java/com/hippo/gl/glrenderer/GLES20Canvas.java index d358a5a46..75dd96328 100644 --- a/app/src/main/java/com/hippo/gl/glrenderer/GLES20Canvas.java +++ b/app/src/main/java/com/hippo/gl/glrenderer/GLES20Canvas.java @@ -24,8 +24,8 @@ import android.opengl.GLUtils; import android.opengl.Matrix; import android.util.Log; -import com.hippo.yorozuya.IntArray; import com.hippo.yorozuya.MathUtils; +import com.hippo.yorozuya.collect.IntList; import java.nio.Buffer; import java.nio.ByteBuffer; @@ -166,7 +166,7 @@ public class GLES20Canvas implements GLCanvas { // Keep track of restore state private float[] mMatrices = new float[INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE]; private float[] mAlphas = new float[INITIAL_RESTORE_STATE_SIZE]; - private IntArray mSaveFlags = new IntArray(); + private final IntList mSaveFlags = new IntList(); private int mCurrentAlphaIndex = 0; private int mCurrentMatrixIndex = 0; @@ -176,20 +176,20 @@ public class GLES20Canvas implements GLCanvas { private int mHeight; // Projection matrix - private float[] mProjectionMatrix = new float[MATRIX_SIZE]; + private final float[] mProjectionMatrix = new float[MATRIX_SIZE]; // Screen size for when we aren't bound to a texture private int mScreenWidth; private int mScreenHeight; // GL programs - private int mDrawProgram; - private int mTextureProgram; - private int mOesTextureProgram; - private int mMeshProgram; + private final int mDrawProgram; + private final int mTextureProgram; + private final int mOesTextureProgram; + private final int mMeshProgram; // GL buffer containing BOX_COORDINATES - private int mBoxCoordinates; + private final int mBoxCoordinates; // Handle indices -- common private static final int INDEX_POSITION = 0; @@ -268,8 +268,8 @@ public class GLES20Canvas implements GLCanvas { new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA }; - private final IntArray mUnboundTextures = new IntArray(); - private final IntArray mDeleteBuffers = new IntArray(); + private final IntList mUnboundTextures = new IntList(); + private final IntList mDeleteBuffers = new IntList(); // Keep track of statistics for debugging private int mCountDrawMesh = 0; @@ -279,10 +279,10 @@ public class GLES20Canvas implements GLCanvas { // Buffer for framebuffer IDs -- we keep track so we can switch the attached // texture. - private int[] mFrameBuffer = new int[1]; + private final int[] mFrameBuffer = new int[1]; // Bound textures. - private ArrayList mTargetTextures = new ArrayList(); + private final ArrayList mTargetTextures = new ArrayList<>(); // Temporary variables used within calculations private final float[] mTempMatrix = new float[32]; @@ -492,7 +492,7 @@ public class GLES20Canvas implements GLCanvas { @Override public void restore() { - int restoreFlags = mSaveFlags.removeLast(); + int restoreFlags = mSaveFlags.removeAt(mSaveFlags.size() - 1); boolean restoreAlpha = (restoreFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA; if (restoreAlpha) { mCurrentAlphaIndex--; @@ -869,7 +869,7 @@ public class GLES20Canvas implements GLCanvas { @Override public void deleteRecycledResources() { synchronized (mUnboundTextures) { - IntArray ids = mUnboundTextures; + IntList ids = mUnboundTextures; if (mUnboundTextures.size() > 0) { mGLId.glDeleteTextures(null, ids.size(), ids.getInternalArray(), 0); ids.clear(); diff --git a/app/src/main/java/com/hippo/gl/glrenderer/ImageTexture.java b/app/src/main/java/com/hippo/gl/glrenderer/ImageTexture.java index b858488c4..6d2b4e7a3 100644 --- a/app/src/main/java/com/hippo/gl/glrenderer/ImageTexture.java +++ b/app/src/main/java/com/hippo/gl/glrenderer/ImageTexture.java @@ -25,8 +25,8 @@ import android.support.annotation.NonNull; import com.hippo.gl.annotation.RenderThread; import com.hippo.gl.view.GLRoot; import com.hippo.image.Image; -import com.hippo.yorozuya.PVLock; -import com.hippo.yorozuya.PriorityThreadFactory; +import com.hippo.yorozuya.thread.PVLock; +import com.hippo.yorozuya.thread.PriorityThreadFactory; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/app/src/main/java/com/hippo/gl/util/GalleryUtils.java b/app/src/main/java/com/hippo/gl/util/GalleryUtils.java index fb6c011e8..a55d88d0c 100644 --- a/app/src/main/java/com/hippo/gl/util/GalleryUtils.java +++ b/app/src/main/java/com/hippo/gl/util/GalleryUtils.java @@ -18,7 +18,7 @@ package com.hippo.gl.util; import android.graphics.Color; -import com.hippo.yorozuya.AssertUtils; +import com.hippo.yorozuya.AssertError; public class GalleryUtils { @@ -51,6 +51,8 @@ public class GalleryUtils { } public static void assertInRenderThread() { - AssertUtils.assertEquals("Should not do this in render thread", sCurrentThread, Thread.currentThread()); + if (sCurrentThread != Thread.currentThread()) { + throw new AssertError("Should not do this in render thread"); + } } } diff --git a/app/src/main/java/com/hippo/gl/widget/GLMovableTextView.java b/app/src/main/java/com/hippo/gl/widget/GLMovableTextView.java index fd3004975..ffe11e93a 100644 --- a/app/src/main/java/com/hippo/gl/widget/GLMovableTextView.java +++ b/app/src/main/java/com/hippo/gl/widget/GLMovableTextView.java @@ -23,20 +23,20 @@ import com.hippo.gl.glrenderer.GLCanvas; import com.hippo.gl.glrenderer.MovableTextTexture; import com.hippo.gl.view.GLView; import com.hippo.gl.view.Gravity; -import com.hippo.yorozuya.ArrayUtils; +import com.hippo.yorozuya.collect.CollectionUtils; public class GLMovableTextView extends GLView { MovableTextTexture mTextTexture; private String mText = ""; - private int[] mIndexes = ArrayUtils.EMPTY_INT_ARRAY; + private int[] mIndexes = CollectionUtils.EMPTY_INT_ARRAY; private int mGravity = Gravity.NO_GRAVITY; private void generateIndexes() { if (mTextTexture == null || TextUtils.isEmpty(mText)) { - mIndexes = ArrayUtils.EMPTY_INT_ARRAY; + mIndexes = CollectionUtils.EMPTY_INT_ARRAY; } else { mIndexes = mTextTexture.getTextIndexes(mText); } diff --git a/app/src/main/java/com/hippo/io/FileInputStreamPipe.java b/app/src/main/java/com/hippo/io/FileInputStreamPipe.java index 140b133ba..62b5f4c9f 100644 --- a/app/src/main/java/com/hippo/io/FileInputStreamPipe.java +++ b/app/src/main/java/com/hippo/io/FileInputStreamPipe.java @@ -18,8 +18,8 @@ package com.hippo.io; import android.support.annotation.NonNull; +import com.hippo.streampipe.InputStreamPipe; import com.hippo.yorozuya.IOUtils; -import com.hippo.yorozuya.io.InputStreamPipe; import java.io.File; import java.io.FileInputStream; @@ -28,7 +28,7 @@ import java.io.InputStream; public class FileInputStreamPipe implements InputStreamPipe { - private File mFile; + private final File mFile; private InputStream mIs; public FileInputStreamPipe(File file) { diff --git a/app/src/main/java/com/hippo/io/FileOutputStreamPipe.java b/app/src/main/java/com/hippo/io/FileOutputStreamPipe.java index 498bcb20e..1c7830e91 100644 --- a/app/src/main/java/com/hippo/io/FileOutputStreamPipe.java +++ b/app/src/main/java/com/hippo/io/FileOutputStreamPipe.java @@ -18,8 +18,8 @@ package com.hippo.io; import android.support.annotation.NonNull; +import com.hippo.streampipe.OutputStreamPipe; import com.hippo.yorozuya.IOUtils; -import com.hippo.yorozuya.io.OutputStreamPipe; import java.io.File; import java.io.FileOutputStream; @@ -28,7 +28,7 @@ import java.io.OutputStream; public class FileOutputStreamPipe implements OutputStreamPipe { - private File mFile; + private final File mFile; private OutputStream mOs; public FileOutputStreamPipe(File file) { diff --git a/app/src/main/java/com/hippo/io/UniFileInputStreamPipe.java b/app/src/main/java/com/hippo/io/UniFileInputStreamPipe.java index b43616c71..fbb68796e 100644 --- a/app/src/main/java/com/hippo/io/UniFileInputStreamPipe.java +++ b/app/src/main/java/com/hippo/io/UniFileInputStreamPipe.java @@ -18,16 +18,16 @@ package com.hippo.io; import android.support.annotation.NonNull; +import com.hippo.streampipe.InputStreamPipe; import com.hippo.unifile.UniFile; import com.hippo.yorozuya.IOUtils; -import com.hippo.yorozuya.io.InputStreamPipe; import java.io.IOException; import java.io.InputStream; public class UniFileInputStreamPipe implements InputStreamPipe { - private UniFile mFile; + private final UniFile mFile; private InputStream mIs; public UniFileInputStreamPipe(UniFile file) { diff --git a/app/src/main/java/com/hippo/io/UniFileOutputStreamPipe.java b/app/src/main/java/com/hippo/io/UniFileOutputStreamPipe.java index 3487b215e..36387b140 100644 --- a/app/src/main/java/com/hippo/io/UniFileOutputStreamPipe.java +++ b/app/src/main/java/com/hippo/io/UniFileOutputStreamPipe.java @@ -18,17 +18,16 @@ package com.hippo.io; import android.support.annotation.NonNull; +import com.hippo.streampipe.OutputStreamPipe; import com.hippo.unifile.UniFile; import com.hippo.yorozuya.IOUtils; -import com.hippo.yorozuya.io.OutputStreamPipe; import java.io.IOException; import java.io.OutputStream; public class UniFileOutputStreamPipe implements OutputStreamPipe { - private UniFile mUniFile; - + private final UniFile mUniFile; private OutputStream mOs; public UniFileOutputStreamPipe(UniFile uniFile) { diff --git a/app/src/main/java/com/hippo/scene/SceneFragment.java b/app/src/main/java/com/hippo/scene/SceneFragment.java index 3075c2527..40ef5a5c5 100644 --- a/app/src/main/java/com/hippo/scene/SceneFragment.java +++ b/app/src/main/java/com/hippo/scene/SceneFragment.java @@ -25,7 +25,7 @@ import android.support.v4.app.FragmentActivity; import android.view.View; import com.hippo.ehviewer.R; -import com.hippo.yorozuya.IntList; +import com.hippo.yorozuya.collect.IntList; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/app/src/main/java/com/hippo/text/URLImageGetter.java b/app/src/main/java/com/hippo/text/URLImageGetter.java index 7d34fc0df..9c0541292 100644 --- a/app/src/main/java/com/hippo/text/URLImageGetter.java +++ b/app/src/main/java/com/hippo/text/URLImageGetter.java @@ -16,28 +16,27 @@ package com.hippo.text; -import android.graphics.Bitmap; import android.graphics.drawable.Drawable; -import android.widget.TextView; import com.hippo.conaco.Conaco; -import com.hippo.conaco.ConacoTask; import com.hippo.drawable.UnikeryDrawable; +import com.hippo.image.ImageBitmap; +import com.hippo.widget.ObservedTextView; public class URLImageGetter implements Html.ImageGetter { - private final TextView mTextView; - private final Conaco mConaco; + private final ObservedTextView mTextView; + private final Conaco mConaco; - public URLImageGetter(TextView textView, Conaco conaco) { + public URLImageGetter(ObservedTextView textView, Conaco conaco) { mTextView = textView; mConaco = conaco; } @Override public Drawable getDrawable(String source) { - UnikeryDrawable drawable = new UnikeryDrawable(mTextView); - mConaco.load(new ConacoTask.Builder().setUnikery(drawable).setUrl(source).setKey(source)); + UnikeryDrawable drawable = new UnikeryDrawable(mTextView, mConaco); + drawable.load(source); return drawable; } } diff --git a/app/src/main/java/com/hippo/util/BitmapUtils.java b/app/src/main/java/com/hippo/util/BitmapUtils.java index 43d45e3aa..7544e79a5 100644 --- a/app/src/main/java/com/hippo/util/BitmapUtils.java +++ b/app/src/main/java/com/hippo/util/BitmapUtils.java @@ -22,12 +22,13 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.support.annotation.NonNull; +import com.hippo.streampipe.InputStreamPipe; import com.hippo.yorozuya.MathUtils; -import com.hippo.yorozuya.io.InputStreamPipe; import java.io.IOException; -public class BitmapUtils { +public final class BitmapUtils { + private BitmapUtils() {} public static Context sContext; diff --git a/app/src/main/java/com/hippo/widget/ContentLayout.java b/app/src/main/java/com/hippo/widget/ContentLayout.java index df4169435..023279bf8 100644 --- a/app/src/main/java/com/hippo/widget/ContentLayout.java +++ b/app/src/main/java/com/hippo/widget/ContentLayout.java @@ -41,9 +41,9 @@ import com.hippo.util.DrawableManager; import com.hippo.util.ExceptionUtils; import com.hippo.view.ViewTransition; import com.hippo.yorozuya.IntIdGenerator; -import com.hippo.yorozuya.IntList; import com.hippo.yorozuya.LayoutUtils; import com.hippo.yorozuya.ResourcesUtils; +import com.hippo.yorozuya.collect.IntList; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/com/hippo/widget/LinkifyTextView.java b/app/src/main/java/com/hippo/widget/LinkifyTextView.java index db721c695..deab26aa2 100644 --- a/app/src/main/java/com/hippo/widget/LinkifyTextView.java +++ b/app/src/main/java/com/hippo/widget/LinkifyTextView.java @@ -23,9 +23,8 @@ import android.text.Spanned; import android.text.style.ClickableSpan; import android.util.AttributeSet; import android.view.MotionEvent; -import android.widget.TextView; -public class LinkifyTextView extends TextView { +public class LinkifyTextView extends ObservedTextView { private ClickableSpan mCurrentSpan; diff --git a/app/src/main/java/com/hippo/widget/LoadImageView.java b/app/src/main/java/com/hippo/widget/LoadImageView.java index 8069637f8..d5cbe26b4 100644 --- a/app/src/main/java/com/hippo/widget/LoadImageView.java +++ b/app/src/main/java/com/hippo/widget/LoadImageView.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Hippo Seven + * Copyright 2015-2016 Hippo Seven * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,32 +18,48 @@ package com.hippo.widget; import android.content.Context; import android.content.res.TypedArray; -import android.graphics.Bitmap; import android.graphics.Color; -import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Animatable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; import android.support.annotation.DrawableRes; +import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import com.hippo.conaco.Conaco; import com.hippo.conaco.ConacoTask; import com.hippo.conaco.DataContainer; import com.hippo.conaco.Unikery; -import com.hippo.conaco.ValueHolder; import com.hippo.drawable.PreciselyClipDrawable; import com.hippo.ehviewer.EhApplication; import com.hippo.ehviewer.R; +import com.hippo.image.ImageBitmap; +import com.hippo.image.ImageDrawable; +import com.hippo.image.RecycledException; -public class LoadImageView extends FixedAspectImageView implements Unikery, - View.OnClickListener, View.OnLongClickListener { +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +public class LoadImageView extends FixedAspectImageView implements Unikery, + View.OnClickListener, View.OnLongClickListener, Animatable { + + private static final String TAG = LoadImageView.class.getSimpleName(); + + @IntDef({RETRY_TYPE_NONE, RETRY_TYPE_CLICK, RETRY_TYPE_LONG_CLICK}) + @Retention(RetentionPolicy.SOURCE) + private @interface RetryType {} + + public static final int RETRY_TYPE_NONE = 0; + public static final int RETRY_TYPE_CLICK = 1; + public static final int RETRY_TYPE_LONG_CLICK = 2; private int mTaskId = Unikery.INVALID_ID; - private Conaco mConaco; + private Conaco mConaco; private String mKey; private String mUrl; @@ -55,49 +71,29 @@ public class LoadImageView extends FixedAspectImageView implements Unikery mHolder; + private int mRetryType; private boolean mFailed; - private RetryType mRetryType = RetryType.NONE; - - private static final RetryType[] sRetryTypeArray = { - RetryType.NONE, - RetryType.CLICK, - RetryType.LONG_CLICK - }; - - public enum RetryType { - NONE (0), - CLICK (1), - LONG_CLICK (2); - - RetryType(int ni) { - nativeInt = ni; - } - final int nativeInt; - } - public LoadImageView(Context context) { super(context); + init(context, null, 0, 0); } public LoadImageView(Context context, AttributeSet attrs) { super(context, attrs); - init(context, attrs); + init(context, attrs, 0, 0); } - public LoadImageView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs); + public LoadImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs, defStyle, 0); } - private void init(Context context, AttributeSet attrs) { - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LoadImageView); - final int index = a.getInt(R.styleable.LoadImageView_retryType, -1); - if (index >= 0) { - setRetryType(sRetryTypeArray[index]); - } + @SuppressWarnings("WrongConstant") + private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LoadImageView, defStyleAttr, defStyleRes); + setRetryType(a.getInt(R.styleable.LoadImageView_retryType, 0)); a.recycle(); if (!isInEditMode()) { @@ -105,11 +101,23 @@ public class LoadImageView extends FixedAspectImageView implements Unikery builder = new ConacoTask.Builder() + ConacoTask.Builder builder = new ConacoTask.Builder() .setUnikery(this) .setKey(key) .setUrl(url) @@ -217,66 +253,45 @@ public class LoadImageView extends FixedAspectImageView implements Unikery holder, Conaco.Source source) { - // Release - mKey = null; - mUrl = null; - mContainer = null; - - holder.obtain(this); - - removeDrawableAndHolder(); - - mHolder = holder; - Bitmap bitmap = holder.getValue(); - Drawable drawable = new BitmapDrawable(getResources(), bitmap); + clearDrawable(); if (Integer.MIN_VALUE != mOffsetX) { drawable = new PreciselyClipDrawable(drawable, mOffsetX, mOffsetY, mClipWidth, mClipHeight); } - if ((source == Conaco.Source.DISK || source == Conaco.Source.NETWORK)) { + if ((source == Conaco.SOURCE_DISK || source == Conaco.SOURCE_NETWORK)) { Drawable[] layers = new Drawable[2]; layers[0] = new ColorDrawable(Color.TRANSPARENT); layers[1] = drawable; @@ -290,23 +305,14 @@ public class LoadImageView extends FixedAspectImageView implements Unikery + app:retryType="click"/> + + + + + + + + + diff --git a/beerbelly b/beerbelly deleted file mode 160000 index 99020f63a..000000000 --- a/beerbelly +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 99020f63acc89303e0283d1d0a0f0dc702d7e994 diff --git a/conaco b/conaco deleted file mode 160000 index 27ba4001e..000000000 --- a/conaco +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 27ba4001e49788e561ffae9c8392262e43759d67 diff --git a/easyrecyclerview b/easyrecyclerview deleted file mode 160000 index c0ee4edea..000000000 --- a/easyrecyclerview +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c0ee4edeaf95d9228e1767578030cb0a924a137a diff --git a/okhttp3/.gitignore b/okhttp3/.gitignore deleted file mode 100644 index a0cc187d4..000000000 --- a/okhttp3/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/build - -# Intellij -*.iml diff --git a/okhttp3/build.gradle b/okhttp3/build.gradle deleted file mode 100644 index 6660b80a7..000000000 --- a/okhttp3/build.gradle +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2016 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -apply plugin: 'com.android.library' - -android { - compileSdkVersion 23 - buildToolsVersion "23.0.3" - - defaultConfig { - minSdkVersion 1 - targetSdkVersion 23 - versionCode 1 - versionName "1.0" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } -} - -dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile 'com.squareup.okio:okio:1.6.0' -} diff --git a/okhttp3/proguard-rules.pro b/okhttp3/proguard-rules.pro deleted file mode 100644 index 468280303..000000000 --- a/okhttp3/proguard-rules.pro +++ /dev/null @@ -1,17 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in E:\Android\android-sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/okhttp3/src/androidTest/java/com/squareup/okhttp3/ApplicationTest.java b/okhttp3/src/androidTest/java/com/squareup/okhttp3/ApplicationTest.java deleted file mode 100644 index eac90949c..000000000 --- a/okhttp3/src/androidTest/java/com/squareup/okhttp3/ApplicationTest.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2016 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.squareup.okhttp3; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/okhttp3/src/main/AndroidManifest.xml b/okhttp3/src/main/AndroidManifest.xml deleted file mode 100644 index f0fc0347c..000000000 --- a/okhttp3/src/main/AndroidManifest.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/okhttp3/src/main/java/okhttp3/Address.java b/okhttp3/src/main/java/okhttp3/Address.java deleted file mode 100644 index cec154262..000000000 --- a/okhttp3/src/main/java/okhttp3/Address.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.net.Proxy; -import java.net.ProxySelector; -import java.util.List; -import javax.net.SocketFactory; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSocketFactory; -import okhttp3.internal.Util; - -import static okhttp3.internal.Util.equal; - -/** - * A specification for a connection to an origin server. For simple connections, this is the - * server's hostname and port. If an explicit proxy is requested (or {@linkplain Proxy#NO_PROXY no - * proxy} is explicitly requested), this also includes that proxy information. For secure - * connections the address also includes the SSL socket factory, hostname verifier, and certificate - * pinner. - * - *

HTTP requests that share the same {@code Address} may also share the same {@link Connection}. - */ -public final class Address { - final HttpUrl url; - final Dns dns; - final SocketFactory socketFactory; - final Authenticator proxyAuthenticator; - final List protocols; - final List connectionSpecs; - final ProxySelector proxySelector; - final Proxy proxy; - final SSLSocketFactory sslSocketFactory; - final HostnameVerifier hostnameVerifier; - final CertificatePinner certificatePinner; - - public Address(String uriHost, int uriPort, Dns dns, SocketFactory socketFactory, - SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier, - CertificatePinner certificatePinner, Authenticator proxyAuthenticator, Proxy proxy, - List protocols, List connectionSpecs, ProxySelector proxySelector) { - this.url = new HttpUrl.Builder() - .scheme(sslSocketFactory != null ? "https" : "http") - .host(uriHost) - .port(uriPort) - .build(); - - if (dns == null) throw new IllegalArgumentException("dns == null"); - this.dns = dns; - - if (socketFactory == null) throw new IllegalArgumentException("socketFactory == null"); - this.socketFactory = socketFactory; - - if (proxyAuthenticator == null) { - throw new IllegalArgumentException("proxyAuthenticator == null"); - } - this.proxyAuthenticator = proxyAuthenticator; - - if (protocols == null) throw new IllegalArgumentException("protocols == null"); - this.protocols = Util.immutableList(protocols); - - if (connectionSpecs == null) throw new IllegalArgumentException("connectionSpecs == null"); - this.connectionSpecs = Util.immutableList(connectionSpecs); - - if (proxySelector == null) throw new IllegalArgumentException("proxySelector == null"); - this.proxySelector = proxySelector; - - this.proxy = proxy; - this.sslSocketFactory = sslSocketFactory; - this.hostnameVerifier = hostnameVerifier; - this.certificatePinner = certificatePinner; - } - - /** - * Returns a URL with the hostname and port of the origin server. The path, query, and fragment of - * this URL are always empty, since they are not significant for planning a route. - */ - public HttpUrl url() { - return url; - } - - /** Returns the service that will be used to resolve IP addresses for hostnames. */ - public Dns dns() { - return dns; - } - - /** Returns the socket factory for new connections. */ - public SocketFactory socketFactory() { - return socketFactory; - } - - /** Returns the client's proxy authenticator. */ - public Authenticator proxyAuthenticator() { - return proxyAuthenticator; - } - - /** - * Returns the protocols the client supports. This method always returns a non-null list that - * contains minimally {@link Protocol#HTTP_1_1}. - */ - public List protocols() { - return protocols; - } - - public List connectionSpecs() { - return connectionSpecs; - } - - /** - * Returns this address's proxy selector. Only used if the proxy is null. If none of this - * selector's proxies are reachable, a direct connection will be attempted. - */ - public ProxySelector proxySelector() { - return proxySelector; - } - - /** - * Returns this address's explicitly-specified HTTP proxy, or null to delegate to the {@linkplain - * #proxySelector proxy selector}. - */ - public Proxy proxy() { - return proxy; - } - - /** Returns the SSL socket factory, or null if this is not an HTTPS address. */ - public SSLSocketFactory sslSocketFactory() { - return sslSocketFactory; - } - - /** Returns the hostname verifier, or null if this is not an HTTPS address. */ - public HostnameVerifier hostnameVerifier() { - return hostnameVerifier; - } - - /** Returns this address's certificate pinner, or null if this is not an HTTPS address. */ - public CertificatePinner certificatePinner() { - return certificatePinner; - } - - @Override public boolean equals(Object other) { - if (other instanceof Address) { - Address that = (Address) other; - return this.url.equals(that.url) - && this.dns.equals(that.dns) - && this.proxyAuthenticator.equals(that.proxyAuthenticator) - && this.protocols.equals(that.protocols) - && this.connectionSpecs.equals(that.connectionSpecs) - && this.proxySelector.equals(that.proxySelector) - && equal(this.proxy, that.proxy) - && equal(this.sslSocketFactory, that.sslSocketFactory) - && equal(this.hostnameVerifier, that.hostnameVerifier) - && equal(this.certificatePinner, that.certificatePinner); - } - return false; - } - - @Override public int hashCode() { - int result = 17; - result = 31 * result + url.hashCode(); - result = 31 * result + dns.hashCode(); - result = 31 * result + proxyAuthenticator.hashCode(); - result = 31 * result + protocols.hashCode(); - result = 31 * result + connectionSpecs.hashCode(); - result = 31 * result + proxySelector.hashCode(); - result = 31 * result + (proxy != null ? proxy.hashCode() : 0); - result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0); - result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0); - result = 31 * result + (certificatePinner != null ? certificatePinner.hashCode() : 0); - return result; - } -} diff --git a/okhttp3/src/main/java/okhttp3/Authenticator.java b/okhttp3/src/main/java/okhttp3/Authenticator.java deleted file mode 100644 index ede539805..000000000 --- a/okhttp3/src/main/java/okhttp3/Authenticator.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2015 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.io.IOException; - -/** - * Responds to an authentication challenge from either a remote web server or a proxy server. - * Implementations may either attempt to satisfy the challenge by returning a request that includes - * an authorization header, or they may refuse the challenge by returning null. In this case the - * unauthenticated response will be returned to the caller that triggered it. - * - *

When authentication is requested by an origin server, the response code is 401 and the - * implementation should respond with a new request that sets the "Authorization" header. - *

   {@code
- *
- *    String credential = Credentials.basic(...)
- *    return response.request().newBuilder()
- *        .header("Authorization", credential)
- *        .build();
- * }
- * - *

Whn authentication is requested by a proxy server, the response code is 407 and the - * implementation should respond with a new request that sets the "Proxy-Authorization" header. - *

   {@code
- *
- *    String credential = Credentials.basic(...)
- *    return response.request().newBuilder()
- *        .header("Proxy-Authorization", credential)
- *        .build();
- * }
- * - *

Applications may configure OkHttp with an authenticator for origin servers, or proxy servers, - * or both. - */ -public interface Authenticator { - /** An authenticator that knows no credentials and makes no attempt to authenticate. */ - Authenticator NONE = new Authenticator() { - @Override public Request authenticate(Route route, Response response) { - return null; - } - }; - - /** - * Returns a request that includes a credential to satisfy an authentication challenge in {@code - * response}. Returns null if the challenge cannot be satisfied. - */ - Request authenticate(Route route, Response response) throws IOException; -} diff --git a/okhttp3/src/main/java/okhttp3/Cache.java b/okhttp3/src/main/java/okhttp3/Cache.java deleted file mode 100644 index 7e0b2431a..000000000 --- a/okhttp3/src/main/java/okhttp3/Cache.java +++ /dev/null @@ -1,742 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.io.Closeable; -import java.io.File; -import java.io.Flushable; -import java.io.IOException; -import java.security.cert.Certificate; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; -import okhttp3.internal.DiskLruCache; -import okhttp3.internal.InternalCache; -import okhttp3.internal.Util; -import okhttp3.internal.http.CacheRequest; -import okhttp3.internal.http.CacheStrategy; -import okhttp3.internal.http.HttpMethod; -import okhttp3.internal.http.OkHeaders; -import okhttp3.internal.http.StatusLine; -import okhttp3.internal.io.FileSystem; -import okio.Buffer; -import okio.BufferedSink; -import okio.BufferedSource; -import okio.ByteString; -import okio.ForwardingSink; -import okio.ForwardingSource; -import okio.Okio; -import okio.Sink; -import okio.Source; - -/** - * Caches HTTP and HTTPS responses to the filesystem so they may be reused, saving time and - * bandwidth. - * - *

Cache Optimization

- * - *

To measure cache effectiveness, this class tracks three statistics: - *

    - *
  • {@linkplain #requestCount() Request Count:} the number of HTTP - * requests issued since this cache was created. - *
  • {@linkplain #networkCount() Network Count:} the number of those - * requests that required network use. - *
  • {@linkplain #hitCount() Hit Count:} the number of those requests - * whose responses were served by the cache. - *
- * - * Sometimes a request will result in a conditional cache hit. If the cache contains a stale copy of - * the response, the client will issue a conditional {@code GET}. The server will then send either - * the updated response if it has changed, or a short 'not modified' response if the client's copy - * is still valid. Such responses increment both the network count and hit count. - * - *

The best way to improve the cache hit rate is by configuring the web server to return - * cacheable responses. Although this client honors all HTTP/1.1 (RFC 7234) cache headers, it doesn't cache - * partial responses. - * - *

Force a Network Response

- * - *

In some situations, such as after a user clicks a 'refresh' button, it may be necessary to - * skip the cache, and fetch data directly from the server. To force a full refresh, add the {@code - * no-cache} directive:

   {@code
- *
- *   Request request = new Request.Builder()
- *       .cacheControl(new CacheControl.Builder().noCache().build())
- *       .url("http://publicobject.com/helloworld.txt")
- *       .build();
- * }
- * - * If it is only necessary to force a cached response to be validated by the server, use the more - * efficient {@code max-age=0} directive instead:
   {@code
- *
- *   Request request = new Request.Builder()
- *       .cacheControl(new CacheControl.Builder()
- *           .maxAge(0, TimeUnit.SECONDS)
- *           .build())
- *       .url("http://publicobject.com/helloworld.txt")
- *       .build();
- * }
- * - *

Force a Cache Response

- * - *

Sometimes you'll want to show resources if they are available immediately, but not otherwise. - * This can be used so your application can show something while waiting for the latest data - * to be downloaded. To restrict a request to locally-cached resources, add the {@code - * only-if-cached} directive:

   {@code
- *
- *     Request request = new Request.Builder()
- *         .cacheControl(new CacheControl.Builder()
- *             .onlyIfCached()
- *             .build())
- *         .url("http://publicobject.com/helloworld.txt")
- *         .build();
- *     Response forceCacheResponse = client.newCall(request).execute();
- *     if (forceCacheResponse.code() != 504) {
- *       // The resource was cached! Show it.
- *     } else {
- *       // The resource was not cached.
- *     }
- * }
- * This technique works even better in situations where a stale response is better than no response. - * To permit stale cached responses, use the {@code max-stale} directive with the maximum staleness - * in seconds:
   {@code
- *
- *   Request request = new Request.Builder()
- *       .cacheControl(new CacheControl.Builder()
- *           .maxStale(365, TimeUnit.DAYS)
- *           .build())
- *       .url("http://publicobject.com/helloworld.txt")
- *       .build();
- * }
- * - *

The {@link CacheControl} class can configure request caching directives and parse response - * caching directives. It even offers convenient constants {@link CacheControl#FORCE_NETWORK} and - * {@link CacheControl#FORCE_CACHE} that address the use cases above. - */ -public final class Cache implements Closeable, Flushable { - private static final int VERSION = 201105; - private static final int ENTRY_METADATA = 0; - private static final int ENTRY_BODY = 1; - private static final int ENTRY_COUNT = 2; - - final InternalCache internalCache = new InternalCache() { - @Override public Response get(Request request) throws IOException { - return Cache.this.get(request); - } - - @Override public CacheRequest put(Response response) throws IOException { - return Cache.this.put(response); - } - - @Override public void remove(Request request) throws IOException { - Cache.this.remove(request); - } - - @Override public void update(Response cached, Response network) throws IOException { - Cache.this.update(cached, network); - } - - @Override public void trackConditionalCacheHit() { - Cache.this.trackConditionalCacheHit(); - } - - @Override public void trackResponse(CacheStrategy cacheStrategy) { - Cache.this.trackResponse(cacheStrategy); - } - }; - - private final DiskLruCache cache; - - /* read and write statistics, all guarded by 'this' */ - private int writeSuccessCount; - private int writeAbortCount; - private int networkCount; - private int hitCount; - private int requestCount; - - public Cache(File directory, long maxSize) { - this(directory, maxSize, FileSystem.SYSTEM); - } - - Cache(File directory, long maxSize, FileSystem fileSystem) { - this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize); - } - - private static String urlToKey(Request request) { - return Util.md5Hex(request.url().toString()); - } - - Response get(Request request) { - String key = urlToKey(request); - DiskLruCache.Snapshot snapshot; - Entry entry; - try { - snapshot = cache.get(key); - if (snapshot == null) { - return null; - } - } catch (IOException e) { - // Give up because the cache cannot be read. - return null; - } - - try { - entry = new Entry(snapshot.getSource(ENTRY_METADATA)); - } catch (IOException e) { - Util.closeQuietly(snapshot); - return null; - } - - Response response = entry.response(snapshot); - - if (!entry.matches(request, response)) { - Util.closeQuietly(response.body()); - return null; - } - - return response; - } - - private CacheRequest put(Response response) throws IOException { - String requestMethod = response.request().method(); - - if (HttpMethod.invalidatesCache(response.request().method())) { - try { - remove(response.request()); - } catch (IOException ignored) { - // The cache cannot be written. - } - return null; - } - if (!requestMethod.equals("GET")) { - // Don't cache non-GET responses. We're technically allowed to cache - // HEAD requests and some POST requests, but the complexity of doing - // so is high and the benefit is low. - return null; - } - - if (OkHeaders.hasVaryAll(response)) { - return null; - } - - Entry entry = new Entry(response); - DiskLruCache.Editor editor = null; - try { - editor = cache.edit(urlToKey(response.request())); - if (editor == null) { - return null; - } - entry.writeTo(editor); - return new CacheRequestImpl(editor); - } catch (IOException e) { - abortQuietly(editor); - return null; - } - } - - private void remove(Request request) throws IOException { - cache.remove(urlToKey(request)); - } - - private void update(Response cached, Response network) { - Entry entry = new Entry(network); - DiskLruCache.Snapshot snapshot = ((CacheResponseBody) cached.body()).snapshot; - DiskLruCache.Editor editor = null; - try { - editor = snapshot.edit(); // Returns null if snapshot is not current. - if (editor != null) { - entry.writeTo(editor); - editor.commit(); - } - } catch (IOException e) { - abortQuietly(editor); - } - } - - private void abortQuietly(DiskLruCache.Editor editor) { - // Give up because the cache cannot be written. - try { - if (editor != null) { - editor.abort(); - } - } catch (IOException ignored) { - } - } - - /** - * Initialize the cache. This will include reading the journal files from the storage and building - * up the necessary in-memory cache information. - * - *

The initialization time may vary depending on the journal file size and the current actual - * cache size. The application needs to be aware of calling this function during the - * initialization phase and preferably in a background worker thread. - * - *

Note that if the application chooses to not call this method to initialize the cache. By - * default, the okhttp will perform lazy initialization upon the first usage of the cache. - */ - public void initialize() throws IOException { - cache.initialize(); - } - - /** - * Closes the cache and deletes all of its stored values. This will delete all files in the cache - * directory including files that weren't created by the cache. - */ - public void delete() throws IOException { - cache.delete(); - } - - /** - * Deletes all values stored in the cache. In-flight writes to the cache will complete normally, - * but the corresponding responses will not be stored. - */ - public void evictAll() throws IOException { - cache.evictAll(); - } - - /** - * Returns an iterator over the URLs in this cache. This iterator doesn't throw {@code - * ConcurrentModificationException}, but if new responses are added while iterating, their URLs - * will not be returned. If existing responses are evicted during iteration, they will be absent - * (unless they were already returned). - * - *

The iterator supports {@linkplain Iterator#remove}. Removing a URL from the iterator evicts - * the corresponding response from the cache. Use this to evict selected responses. - */ - public Iterator urls() throws IOException { - return new Iterator() { - final Iterator delegate = cache.snapshots(); - - String nextUrl; - boolean canRemove; - - @Override public boolean hasNext() { - if (nextUrl != null) return true; - - canRemove = false; // Prevent delegate.remove() on the wrong item! - while (delegate.hasNext()) { - DiskLruCache.Snapshot snapshot = delegate.next(); - try { - BufferedSource metadata = Okio.buffer(snapshot.getSource(ENTRY_METADATA)); - nextUrl = metadata.readUtf8LineStrict(); - return true; - } catch (IOException ignored) { - // We couldn't read the metadata for this snapshot; possibly because the host filesystem - // has disappeared! Skip it. - } finally { - snapshot.close(); - } - } - - return false; - } - - @Override public String next() { - if (!hasNext()) throw new NoSuchElementException(); - String result = nextUrl; - nextUrl = null; - canRemove = true; - return result; - } - - @Override public void remove() { - if (!canRemove) throw new IllegalStateException("remove() before next()"); - delegate.remove(); - } - }; - } - - public synchronized int writeAbortCount() { - return writeAbortCount; - } - - public synchronized int writeSuccessCount() { - return writeSuccessCount; - } - - public long size() throws IOException { - return cache.size(); - } - - public long maxSize() { - return cache.getMaxSize(); - } - - @Override public void flush() throws IOException { - cache.flush(); - } - - @Override public void close() throws IOException { - cache.close(); - } - - public File directory() { - return cache.getDirectory(); - } - - public boolean isClosed() { - return cache.isClosed(); - } - - private synchronized void trackResponse(CacheStrategy cacheStrategy) { - requestCount++; - - if (cacheStrategy.networkRequest != null) { - // If this is a conditional request, we'll increment hitCount if/when it hits. - networkCount++; - } else if (cacheStrategy.cacheResponse != null) { - // This response uses the cache and not the network. That's a cache hit. - hitCount++; - } - } - - private synchronized void trackConditionalCacheHit() { - hitCount++; - } - - public synchronized int networkCount() { - return networkCount; - } - - public synchronized int hitCount() { - return hitCount; - } - - public synchronized int requestCount() { - return requestCount; - } - - private final class CacheRequestImpl implements CacheRequest { - private final DiskLruCache.Editor editor; - private Sink cacheOut; - private boolean done; - private Sink body; - - public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException { - this.editor = editor; - this.cacheOut = editor.newSink(ENTRY_BODY); - this.body = new ForwardingSink(cacheOut) { - @Override public void close() throws IOException { - synchronized (Cache.this) { - if (done) { - return; - } - done = true; - writeSuccessCount++; - } - super.close(); - editor.commit(); - } - }; - } - - @Override public void abort() { - synchronized (Cache.this) { - if (done) { - return; - } - done = true; - writeAbortCount++; - } - Util.closeQuietly(cacheOut); - try { - editor.abort(); - } catch (IOException ignored) { - } - } - - @Override public Sink body() { - return body; - } - } - - private static final class Entry { - private final String url; - private final Headers varyHeaders; - private final String requestMethod; - private final Protocol protocol; - private final int code; - private final String message; - private final Headers responseHeaders; - private final Handshake handshake; - - /** - * Reads an entry from an input stream. A typical entry looks like this: - *

{@code
-     *   http://google.com/foo
-     *   GET
-     *   2
-     *   Accept-Language: fr-CA
-     *   Accept-Charset: UTF-8
-     *   HTTP/1.1 200 OK
-     *   3
-     *   Content-Type: image/png
-     *   Content-Length: 100
-     *   Cache-Control: max-age=600
-     * }
- * - *

A typical HTTPS file looks like this: - *

{@code
-     *   https://google.com/foo
-     *   GET
-     *   2
-     *   Accept-Language: fr-CA
-     *   Accept-Charset: UTF-8
-     *   HTTP/1.1 200 OK
-     *   3
-     *   Content-Type: image/png
-     *   Content-Length: 100
-     *   Cache-Control: max-age=600
-     *
-     *   AES_256_WITH_MD5
-     *   2
-     *   base64-encoded peerCertificate[0]
-     *   base64-encoded peerCertificate[1]
-     *   -1
-     *   TLSv1.2
-     * }
- * The file is newline separated. The first two lines are the URL and the request method. Next - * is the number of HTTP Vary request header lines, followed by those lines. - * - *

Next is the response status line, followed by the number of HTTP response header lines, - * followed by those lines. - * - *

HTTPS responses also contain SSL session information. This begins with a blank line, and - * then a line containing the cipher suite. Next is the length of the peer certificate chain. - * These certificates are base64-encoded and appear each on their own line. The next line - * contains the length of the local certificate chain. These certificates are also - * base64-encoded and appear each on their own line. A length of -1 is used to encode a null - * array. The last line is optional. If present, it contains the TLS version. - */ - public Entry(Source in) throws IOException { - try { - BufferedSource source = Okio.buffer(in); - url = source.readUtf8LineStrict(); - requestMethod = source.readUtf8LineStrict(); - Headers.Builder varyHeadersBuilder = new Headers.Builder(); - int varyRequestHeaderLineCount = readInt(source); - for (int i = 0; i < varyRequestHeaderLineCount; i++) { - varyHeadersBuilder.addLenient(source.readUtf8LineStrict()); - } - varyHeaders = varyHeadersBuilder.build(); - - StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict()); - protocol = statusLine.protocol; - code = statusLine.code; - message = statusLine.message; - Headers.Builder responseHeadersBuilder = new Headers.Builder(); - int responseHeaderLineCount = readInt(source); - for (int i = 0; i < responseHeaderLineCount; i++) { - responseHeadersBuilder.addLenient(source.readUtf8LineStrict()); - } - responseHeaders = responseHeadersBuilder.build(); - - if (isHttps()) { - String blank = source.readUtf8LineStrict(); - if (blank.length() > 0) { - throw new IOException("expected \"\" but was \"" + blank + "\""); - } - String cipherSuiteString = source.readUtf8LineStrict(); - CipherSuite cipherSuite = CipherSuite.forJavaName(cipherSuiteString); - List peerCertificates = readCertificateList(source); - List localCertificates = readCertificateList(source); - TlsVersion tlsVersion = !source.exhausted() - ? TlsVersion.forJavaName(source.readUtf8LineStrict()) - : null; - handshake = Handshake.get(tlsVersion, cipherSuite, peerCertificates, localCertificates); - } else { - handshake = null; - } - } finally { - in.close(); - } - } - - public Entry(Response response) { - this.url = response.request().url().toString(); - this.varyHeaders = OkHeaders.varyHeaders(response); - this.requestMethod = response.request().method(); - this.protocol = response.protocol(); - this.code = response.code(); - this.message = response.message(); - this.responseHeaders = response.headers(); - this.handshake = response.handshake(); - } - - public void writeTo(DiskLruCache.Editor editor) throws IOException { - BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA)); - - sink.writeUtf8(url); - sink.writeByte('\n'); - sink.writeUtf8(requestMethod); - sink.writeByte('\n'); - sink.writeDecimalLong(varyHeaders.size()); - sink.writeByte('\n'); - for (int i = 0, size = varyHeaders.size(); i < size; i++) { - sink.writeUtf8(varyHeaders.name(i)); - sink.writeUtf8(": "); - sink.writeUtf8(varyHeaders.value(i)); - sink.writeByte('\n'); - } - - sink.writeUtf8(new StatusLine(protocol, code, message).toString()); - sink.writeByte('\n'); - sink.writeDecimalLong(responseHeaders.size()); - sink.writeByte('\n'); - for (int i = 0, size = responseHeaders.size(); i < size; i++) { - sink.writeUtf8(responseHeaders.name(i)); - sink.writeUtf8(": "); - sink.writeUtf8(responseHeaders.value(i)); - sink.writeByte('\n'); - } - - if (isHttps()) { - sink.writeByte('\n'); - sink.writeUtf8(handshake.cipherSuite().javaName()); - sink.writeByte('\n'); - writeCertList(sink, handshake.peerCertificates()); - writeCertList(sink, handshake.localCertificates()); - // The handshake’s TLS version is null on HttpsURLConnection and on older cached responses. - if (handshake.tlsVersion() != null) { - sink.writeUtf8(handshake.tlsVersion().javaName()); - sink.writeByte('\n'); - } - } - sink.close(); - } - - private boolean isHttps() { - return url.startsWith("https://"); - } - - private List readCertificateList(BufferedSource source) throws IOException { - int length = readInt(source); - if (length == -1) return Collections.emptyList(); // OkHttp v1.2 used -1 to indicate null. - - try { - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - List result = new ArrayList<>(length); - for (int i = 0; i < length; i++) { - String line = source.readUtf8LineStrict(); - Buffer bytes = new Buffer(); - bytes.write(ByteString.decodeBase64(line)); - result.add(certificateFactory.generateCertificate(bytes.inputStream())); - } - return result; - } catch (CertificateException e) { - throw new IOException(e.getMessage()); - } - } - - private void writeCertList(BufferedSink sink, List certificates) - throws IOException { - try { - sink.writeDecimalLong(certificates.size()); - sink.writeByte('\n'); - for (int i = 0, size = certificates.size(); i < size; i++) { - byte[] bytes = certificates.get(i).getEncoded(); - String line = ByteString.of(bytes).base64(); - sink.writeUtf8(line); - sink.writeByte('\n'); - } - } catch (CertificateEncodingException e) { - throw new IOException(e.getMessage()); - } - } - - public boolean matches(Request request, Response response) { - return url.equals(request.url().toString()) - && requestMethod.equals(request.method()) - && OkHeaders.varyMatches(response, varyHeaders, request); - } - - public Response response(DiskLruCache.Snapshot snapshot) { - String contentType = responseHeaders.get("Content-Type"); - String contentLength = responseHeaders.get("Content-Length"); - Request cacheRequest = new Request.Builder() - .url(url) - .method(requestMethod, null) - .headers(varyHeaders) - .build(); - return new Response.Builder() - .request(cacheRequest) - .protocol(protocol) - .code(code) - .message(message) - .headers(responseHeaders) - .body(new CacheResponseBody(snapshot, contentType, contentLength)) - .handshake(handshake) - .build(); - } - } - - private static int readInt(BufferedSource source) throws IOException { - try { - long result = source.readDecimalLong(); - String line = source.readUtf8LineStrict(); - if (result < 0 || result > Integer.MAX_VALUE || !line.isEmpty()) { - throw new IOException("expected an int but was \"" + result + line + "\""); - } - return (int) result; - } catch (NumberFormatException e) { - throw new IOException(e.getMessage()); - } - } - - private static class CacheResponseBody extends ResponseBody { - private final DiskLruCache.Snapshot snapshot; - private final BufferedSource bodySource; - private final String contentType; - private final String contentLength; - - public CacheResponseBody(final DiskLruCache.Snapshot snapshot, - String contentType, String contentLength) { - this.snapshot = snapshot; - this.contentType = contentType; - this.contentLength = contentLength; - - Source source = snapshot.getSource(ENTRY_BODY); - bodySource = Okio.buffer(new ForwardingSource(source) { - @Override public void close() throws IOException { - snapshot.close(); - super.close(); - } - }); - } - - @Override public MediaType contentType() { - return contentType != null ? MediaType.parse(contentType) : null; - } - - @Override public long contentLength() { - try { - return contentLength != null ? Long.parseLong(contentLength) : -1; - } catch (NumberFormatException e) { - return -1; - } - } - - @Override public BufferedSource source() { - return bodySource; - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/CacheControl.java b/okhttp3/src/main/java/okhttp3/CacheControl.java deleted file mode 100644 index d8eb44614..000000000 --- a/okhttp3/src/main/java/okhttp3/CacheControl.java +++ /dev/null @@ -1,354 +0,0 @@ -package okhttp3; - -import java.util.concurrent.TimeUnit; -import okhttp3.internal.http.HeaderParser; - -/** - * A Cache-Control header with cache directives from a server or client. These directives set policy - * on what responses can be stored, and which requests can be satisfied by those stored responses. - * - *

See RFC 2616, - * 14.9. - */ -public final class CacheControl { - /** - * Cache control request directives that require network validation of responses. Note that such - * requests may be assisted by the cache via conditional GET requests. - */ - public static final CacheControl FORCE_NETWORK = new Builder().noCache().build(); - - /** - * Cache control request directives that uses the cache only, even if the cached response is - * stale. If the response isn't available in the cache or requires server validation, the call - * will fail with a {@code 504 Unsatisfiable Request}. - */ - public static final CacheControl FORCE_CACHE = new Builder() - .onlyIfCached() - .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS) - .build(); - - private final boolean noCache; - private final boolean noStore; - private final int maxAgeSeconds; - private final int sMaxAgeSeconds; - private final boolean isPrivate; - private final boolean isPublic; - private final boolean mustRevalidate; - private final int maxStaleSeconds; - private final int minFreshSeconds; - private final boolean onlyIfCached; - private final boolean noTransform; - - String headerValue; // Lazily computed, null if absent. - - private CacheControl(boolean noCache, boolean noStore, int maxAgeSeconds, int sMaxAgeSeconds, - boolean isPrivate, boolean isPublic, boolean mustRevalidate, int maxStaleSeconds, - int minFreshSeconds, boolean onlyIfCached, boolean noTransform, String headerValue) { - this.noCache = noCache; - this.noStore = noStore; - this.maxAgeSeconds = maxAgeSeconds; - this.sMaxAgeSeconds = sMaxAgeSeconds; - this.isPrivate = isPrivate; - this.isPublic = isPublic; - this.mustRevalidate = mustRevalidate; - this.maxStaleSeconds = maxStaleSeconds; - this.minFreshSeconds = minFreshSeconds; - this.onlyIfCached = onlyIfCached; - this.noTransform = noTransform; - this.headerValue = headerValue; - } - - private CacheControl(Builder builder) { - this.noCache = builder.noCache; - this.noStore = builder.noStore; - this.maxAgeSeconds = builder.maxAgeSeconds; - this.sMaxAgeSeconds = -1; - this.isPrivate = false; - this.isPublic = false; - this.mustRevalidate = false; - this.maxStaleSeconds = builder.maxStaleSeconds; - this.minFreshSeconds = builder.minFreshSeconds; - this.onlyIfCached = builder.onlyIfCached; - this.noTransform = builder.noTransform; - } - - /** - * In a response, this field's name "no-cache" is misleading. It doesn't prevent us from caching - * the response; it only means we have to validate the response with the origin server before - * returning it. We can do this with a conditional GET. - * - *

In a request, it means do not use a cache to satisfy the request. - */ - public boolean noCache() { - return noCache; - } - - /** If true, this response should not be cached. */ - public boolean noStore() { - return noStore; - } - - /** - * The duration past the response's served date that it can be served without validation. - */ - public int maxAgeSeconds() { - return maxAgeSeconds; - } - - /** - * The "s-maxage" directive is the max age for shared caches. Not to be confused with "max-age" - * for non-shared caches, As in Firefox and Chrome, this directive is not honored by this cache. - */ - public int sMaxAgeSeconds() { - return sMaxAgeSeconds; - } - - public boolean isPrivate() { - return isPrivate; - } - - public boolean isPublic() { - return isPublic; - } - - public boolean mustRevalidate() { - return mustRevalidate; - } - - public int maxStaleSeconds() { - return maxStaleSeconds; - } - - public int minFreshSeconds() { - return minFreshSeconds; - } - - /** - * This field's name "only-if-cached" is misleading. It actually means "do not use the network". - * It is set by a client who only wants to make a request if it can be fully satisfied by the - * cache. Cached responses that would require validation (ie. conditional gets) are not permitted - * if this header is set. - */ - public boolean onlyIfCached() { - return onlyIfCached; - } - - public boolean noTransform() { - return noTransform; - } - - /** - * Returns the cache directives of {@code headers}. This honors both Cache-Control and Pragma - * headers if they are present. - */ - public static CacheControl parse(Headers headers) { - boolean noCache = false; - boolean noStore = false; - int maxAgeSeconds = -1; - int sMaxAgeSeconds = -1; - boolean isPrivate = false; - boolean isPublic = false; - boolean mustRevalidate = false; - int maxStaleSeconds = -1; - int minFreshSeconds = -1; - boolean onlyIfCached = false; - boolean noTransform = false; - - boolean canUseHeaderValue = true; - String headerValue = null; - - for (int i = 0, size = headers.size(); i < size; i++) { - String name = headers.name(i); - String value = headers.value(i); - - if (name.equalsIgnoreCase("Cache-Control")) { - if (headerValue != null) { - // Multiple cache-control headers means we can't use the raw value. - canUseHeaderValue = false; - } else { - headerValue = value; - } - } else if (name.equalsIgnoreCase("Pragma")) { - // Might specify additional cache-control params. We invalidate just in case. - canUseHeaderValue = false; - } else { - continue; - } - - int pos = 0; - while (pos < value.length()) { - int tokenStart = pos; - pos = HeaderParser.skipUntil(value, pos, "=,;"); - String directive = value.substring(tokenStart, pos).trim(); - String parameter; - - if (pos == value.length() || value.charAt(pos) == ',' || value.charAt(pos) == ';') { - pos++; // consume ',' or ';' (if necessary) - parameter = null; - } else { - pos++; // consume '=' - pos = HeaderParser.skipWhitespace(value, pos); - - // quoted string - if (pos < value.length() && value.charAt(pos) == '\"') { - pos++; // consume '"' open quote - int parameterStart = pos; - pos = HeaderParser.skipUntil(value, pos, "\""); - parameter = value.substring(parameterStart, pos); - pos++; // consume '"' close quote (if necessary) - - // unquoted string - } else { - int parameterStart = pos; - pos = HeaderParser.skipUntil(value, pos, ",;"); - parameter = value.substring(parameterStart, pos).trim(); - } - } - - if ("no-cache".equalsIgnoreCase(directive)) { - noCache = true; - } else if ("no-store".equalsIgnoreCase(directive)) { - noStore = true; - } else if ("max-age".equalsIgnoreCase(directive)) { - maxAgeSeconds = HeaderParser.parseSeconds(parameter, -1); - } else if ("s-maxage".equalsIgnoreCase(directive)) { - sMaxAgeSeconds = HeaderParser.parseSeconds(parameter, -1); - } else if ("private".equalsIgnoreCase(directive)) { - isPrivate = true; - } else if ("public".equalsIgnoreCase(directive)) { - isPublic = true; - } else if ("must-revalidate".equalsIgnoreCase(directive)) { - mustRevalidate = true; - } else if ("max-stale".equalsIgnoreCase(directive)) { - maxStaleSeconds = HeaderParser.parseSeconds(parameter, Integer.MAX_VALUE); - } else if ("min-fresh".equalsIgnoreCase(directive)) { - minFreshSeconds = HeaderParser.parseSeconds(parameter, -1); - } else if ("only-if-cached".equalsIgnoreCase(directive)) { - onlyIfCached = true; - } else if ("no-transform".equalsIgnoreCase(directive)) { - noTransform = true; - } - } - } - - if (!canUseHeaderValue) { - headerValue = null; - } - return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPrivate, isPublic, - mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached, noTransform, headerValue); - } - - @Override public String toString() { - String result = headerValue; - return result != null ? result : (headerValue = headerValue()); - } - - private String headerValue() { - StringBuilder result = new StringBuilder(); - if (noCache) result.append("no-cache, "); - if (noStore) result.append("no-store, "); - if (maxAgeSeconds != -1) result.append("max-age=").append(maxAgeSeconds).append(", "); - if (sMaxAgeSeconds != -1) result.append("s-maxage=").append(sMaxAgeSeconds).append(", "); - if (isPrivate) result.append("private, "); - if (isPublic) result.append("public, "); - if (mustRevalidate) result.append("must-revalidate, "); - if (maxStaleSeconds != -1) result.append("max-stale=").append(maxStaleSeconds).append(", "); - if (minFreshSeconds != -1) result.append("min-fresh=").append(minFreshSeconds).append(", "); - if (onlyIfCached) result.append("only-if-cached, "); - if (noTransform) result.append("no-transform, "); - if (result.length() == 0) return ""; - result.delete(result.length() - 2, result.length()); - return result.toString(); - } - - /** Builds a {@code Cache-Control} request header. */ - public static final class Builder { - boolean noCache; - boolean noStore; - int maxAgeSeconds = -1; - int maxStaleSeconds = -1; - int minFreshSeconds = -1; - boolean onlyIfCached; - boolean noTransform; - - /** Don't accept an unvalidated cached response. */ - public Builder noCache() { - this.noCache = true; - return this; - } - - /** Don't store the server's response in any cache. */ - public Builder noStore() { - this.noStore = true; - return this; - } - - /** - * Sets the maximum age of a cached response. If the cache response's age exceeds {@code - * maxAge}, it will not be used and a network request will be made. - * - * @param maxAge a non-negative integer. This is stored and transmitted with {@link - * TimeUnit#SECONDS} precision; finer precision will be lost. - */ - public Builder maxAge(int maxAge, TimeUnit timeUnit) { - if (maxAge < 0) throw new IllegalArgumentException("maxAge < 0: " + maxAge); - long maxAgeSecondsLong = timeUnit.toSeconds(maxAge); - this.maxAgeSeconds = maxAgeSecondsLong > Integer.MAX_VALUE - ? Integer.MAX_VALUE - : (int) maxAgeSecondsLong; - return this; - } - - /** - * Accept cached responses that have exceeded their freshness lifetime by up to {@code - * maxStale}. If unspecified, stale cache responses will not be used. - * - * @param maxStale a non-negative integer. This is stored and transmitted with {@link - * TimeUnit#SECONDS} precision; finer precision will be lost. - */ - public Builder maxStale(int maxStale, TimeUnit timeUnit) { - if (maxStale < 0) throw new IllegalArgumentException("maxStale < 0: " + maxStale); - long maxStaleSecondsLong = timeUnit.toSeconds(maxStale); - this.maxStaleSeconds = maxStaleSecondsLong > Integer.MAX_VALUE - ? Integer.MAX_VALUE - : (int) maxStaleSecondsLong; - return this; - } - - /** - * Sets the minimum number of seconds that a response will continue to be fresh for. If the - * response will be stale when {@code minFresh} have elapsed, the cached response will not be - * used and a network request will be made. - * - * @param minFresh a non-negative integer. This is stored and transmitted with {@link - * TimeUnit#SECONDS} precision; finer precision will be lost. - */ - public Builder minFresh(int minFresh, TimeUnit timeUnit) { - if (minFresh < 0) throw new IllegalArgumentException("minFresh < 0: " + minFresh); - long minFreshSecondsLong = timeUnit.toSeconds(minFresh); - this.minFreshSeconds = minFreshSecondsLong > Integer.MAX_VALUE - ? Integer.MAX_VALUE - : (int) minFreshSecondsLong; - return this; - } - - /** - * Only accept the response if it is in the cache. If the response isn't cached, a {@code 504 - * Unsatisfiable Request} response will be returned. - */ - public Builder onlyIfCached() { - this.onlyIfCached = true; - return this; - } - - /** Don't accept a transformed response. */ - public Builder noTransform() { - this.noTransform = true; - return this; - } - - public CacheControl build() { - return new CacheControl(this); - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/Call.java b/okhttp3/src/main/java/okhttp3/Call.java deleted file mode 100644 index 58506d137..000000000 --- a/okhttp3/src/main/java/okhttp3/Call.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.io.IOException; - -/** - * A call is a request that has been prepared for execution. A call can be canceled. As this object - * represents a single request/response pair (stream), it cannot be executed twice. - */ -public interface Call { - /** Returns the original request that initiated this call. */ - Request request(); - - /** - * Invokes the request immediately, and blocks until the response can be processed or is in - * error. - * - *

The caller may read the response body with the response's {@link Response#body} method. To - * facilitate connection recycling, callers should always {@link ResponseBody#close() close the - * response body}. - * - *

Note that transport-layer success (receiving a HTTP response code, headers and body) does - * not necessarily indicate application-layer success: {@code response} may still indicate an - * unhappy HTTP response code like 404 or 500. - * - * @throws IOException if the request could not be executed due to cancellation, a connectivity - * problem or timeout. Because networks can fail during an exchange, it is possible that the - * remote server accepted the request before the failure. - * @throws IllegalStateException when the call has already been executed. - */ - Response execute() throws IOException; - - /** - * Schedules the request to be executed at some point in the future. - * - *

The {@link OkHttpClient#dispatcher dispatcher} defines when the request will run: usually - * immediately unless there are several other requests currently being executed. - * - *

This client will later call back {@code responseCallback} with either an HTTP response or a - * failure exception. - * - * @throws IllegalStateException when the call has already been executed. - */ - void enqueue(Callback responseCallback); - - /** Cancels the request, if possible. Requests that are already complete cannot be canceled. */ - void cancel(); - - /** - * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain - * #enqueue(Callback) enqueued}. It is an error to execute a call more than once. - */ - boolean isExecuted(); - - boolean isCanceled(); - - interface Factory { - Call newCall(Request request); - } -} diff --git a/okhttp3/src/main/java/okhttp3/Callback.java b/okhttp3/src/main/java/okhttp3/Callback.java deleted file mode 100644 index 42a8a109b..000000000 --- a/okhttp3/src/main/java/okhttp3/Callback.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.io.IOException; - -public interface Callback { - /** - * Called when the request could not be executed due to cancellation, a connectivity problem or - * timeout. Because networks can fail during an exchange, it is possible that the remote server - * accepted the request before the failure. - */ - void onFailure(Call call, IOException e); - - /** - * Called when the HTTP response was successfully returned by the remote server. The callback may - * proceed to read the response body with {@link Response#body}. The response is still live until - * its response body is closed with {@code response.body().close()}. The recipient of the callback - * may even consume the response body on another thread. - * - *

Note that transport-layer success (receiving a HTTP response code, headers and body) does - * not necessarily indicate application-layer success: {@code response} may still indicate an - * unhappy HTTP response code like 404 or 500. - */ - void onResponse(Call call, Response response) throws IOException; -} diff --git a/okhttp3/src/main/java/okhttp3/CertificatePinner.java b/okhttp3/src/main/java/okhttp3/CertificatePinner.java deleted file mode 100644 index f7a136764..000000000 --- a/okhttp3/src/main/java/okhttp3/CertificatePinner.java +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import javax.net.ssl.SSLPeerUnverifiedException; -import okhttp3.internal.Util; -import okhttp3.internal.tls.CertificateChainCleaner; -import okhttp3.internal.tls.TrustRootIndex; -import okio.ByteString; - -/** - * Constrains which certificates are trusted. Pinning certificates defends against attacks on - * certificate authorities. It also prevents connections through man-in-the-middle certificate - * authorities either known or unknown to the application's user. - * - *

This class currently pins a certificate's Subject Public Key Info as described on Adam Langley's Weblog. Pins are either base64 SHA-256 hashes as - * in HTTP Public Key Pinning (HPKP) or SHA-1 - * base64 hashes as in Chromium's static certificates. - * - *

Setting up Certificate Pinning

- * - *

The easiest way to pin a host is turn on pinning with a broken configuration and read the - * expected configuration when the connection fails. Be sure to do this on a trusted network, and - * without man-in-the-middle tools like Charles or Fiddler. - * - *

For example, to pin {@code https://publicobject.com}, start with a broken - * configuration:

   {@code
- *
- *     String hostname = "publicobject.com";
- *     CertificatePinner certificatePinner = new CertificatePinner.Builder()
- *         .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
- *         .build();
- *     OkHttpClient client = new OkHttpClient();
- *     client.setCertificatePinner(certificatePinner);
- *
- *     Request request = new Request.Builder()
- *         .url("https://" + hostname)
- *         .build();
- *     client.newCall(request).execute();
- * }
- * - * As expected, this fails with a certificate pinning exception:
   {@code
- *
- * javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
- *   Peer certificate chain:
- *     sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=: CN=publicobject.com, OU=PositiveSSL
- *     sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=: CN=COMODO RSA Secure Server CA
- *     sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=: CN=COMODO RSA Certification Authority
- *     sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=: CN=AddTrust External CA Root
- *   Pinned certificates for publicobject.com:
- *     sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
- *   at okhttp3.CertificatePinner.check(CertificatePinner.java)
- *   at okhttp3.Connection.upgradeToTls(Connection.java)
- *   at okhttp3.Connection.connect(Connection.java)
- *   at okhttp3.Connection.connectAndSetOwner(Connection.java)
- * }
- * - * Follow up by pasting the public key hashes from the exception into the - * certificate pinner's configuration:
   {@code
- *
- *     CertificatePinner certificatePinner = new CertificatePinner.Builder()
- *       .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")
- *       .add("publicobject.com", "sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=")
- *       .add("publicobject.com", "sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=")
- *       .add("publicobject.com", "sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=")
- *       .build();
- * }
- * - * Pinning is per-hostname and/or per-wildcard pattern. To pin both {@code publicobject.com} and - * {@code www.publicobject.com}, you must configure both hostnames. - * - *

Wildcard pattern rules: - *

    - *
  1. Asterisk {@code *} is only permitted in the left-most domain name label and must be the - * only character in that label (i.e., must match the whole left-most label). For example, - * {@code *.example.com} is permitted, while {@code *a.example.com}, {@code a*.example.com}, - * {@code a*b.example.com}, {@code a.*.example.com} are not permitted. - *
  2. Asterisk {@code *} cannot match across domain name labels. For example, - * {@code *.example.com} matches {@code test.example.com} but does not match - * {@code sub.test.example.com}. - *
  3. Wildcard patterns for single-label domain names are not permitted. - *
- * - * If hostname pinned directly and via wildcard pattern, both direct and wildcard pins will be used. - * For example: {@code *.example.com} pinned with {@code pin1} and {@code a.example.com} pinned with - * {@code pin2}, to check {@code a.example.com} both {@code pin1} and {@code pin2} will be used. - * - *

Warning: Certificate Pinning is Dangerous!

- * - *

Pinning certificates limits your server team's abilities to update their TLS certificates. By - * pinning certificates, you take on additional operational complexity and limit your ability to - * migrate between certificate authorities. Do not use certificate pinning without the blessing of - * your server's TLS administrator! - * - *

Note about self-signed certificates

- * - *

{@link CertificatePinner} can not be used to pin self-signed certificate if such certificate - * is not accepted by {@link javax.net.ssl.TrustManager}. - * - * @see OWASP: - * Certificate and Public Key Pinning - */ -public final class CertificatePinner { - public static final CertificatePinner DEFAULT = new Builder().build(); - - private final List pins; - private final TrustRootIndex trustRootIndex; - - private CertificatePinner(Builder builder) { - this.pins = Util.immutableList(builder.pins); - this.trustRootIndex = builder.trustRootIndex; - } - - /** - * Confirms that at least one of the certificates pinned for {@code hostname} is in {@code - * peerCertificates}. Does nothing if there are no certificates pinned for {@code hostname}. - * OkHttp calls this after a successful TLS handshake, but before the connection is used. - * - * @throws SSLPeerUnverifiedException if {@code peerCertificates} don't match the certificates - * pinned for {@code hostname}. - */ - public void check(String hostname, List peerCertificates) - throws SSLPeerUnverifiedException { - List pins = findMatchingPins(hostname); - if (pins.isEmpty()) return; - - if (trustRootIndex != null) { - peerCertificates = new CertificateChainCleaner(trustRootIndex).clean(peerCertificates); - } - - for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) { - X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c); - - // Lazily compute the hashes for each certificate. - ByteString sha1 = null; - ByteString sha256 = null; - - for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) { - Pin pin = pins.get(p); - if (pin.hashAlgorithm.equals("sha256/")) { - if (sha256 == null) sha256 = sha256(x509Certificate); - if (pin.hash.equals(sha256)) return; // Success! - } else if (pin.hashAlgorithm.equals("sha1/")) { - if (sha1 == null) sha1 = sha1(x509Certificate); - if (pin.hash.equals(sha1)) return; // Success! - } else { - throw new AssertionError(); - } - } - } - - // If we couldn't find a matching pin, format a nice exception. - StringBuilder message = new StringBuilder() - .append("Certificate pinning failure!") - .append("\n Peer certificate chain:"); - for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) { - X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c); - message.append("\n ").append(pin(x509Certificate)) - .append(": ").append(x509Certificate.getSubjectDN().getName()); - } - message.append("\n Pinned certificates for ").append(hostname).append(":"); - for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) { - Pin pin = pins.get(p); - message.append("\n ").append(pin); - } - throw new SSLPeerUnverifiedException(message.toString()); - } - - /** @deprecated replaced with {@link #check(String, List)}. */ - public void check(String hostname, Certificate... peerCertificates) - throws SSLPeerUnverifiedException { - check(hostname, Arrays.asList(peerCertificates)); - } - - /** - * Returns list of matching certificates' pins for the hostname. Returns an empty list if the - * hostname does not have pinned certificates. - */ - List findMatchingPins(String hostname) { - List result = Collections.emptyList(); - for (Pin pin : pins) { - if (pin.matches(hostname)) { - if (result.isEmpty()) result = new ArrayList<>(); - result.add(pin); - } - } - return result; - } - - Builder newBuilder() { - return new Builder(this); - } - - /** - * Returns the SHA-256 of {@code certificate}'s public key. - * - *

In OkHttp 3.1.2 and earlier, this returned a SHA-1 hash of the public key. Both types are - * supported, but SHA-256 is preferred. - */ - public static String pin(Certificate certificate) { - if (!(certificate instanceof X509Certificate)) { - throw new IllegalArgumentException("Certificate pinning requires X509 certificates"); - } - return "sha256/" + sha256((X509Certificate) certificate).base64(); - } - - static ByteString sha1(X509Certificate x509Certificate) { - return Util.sha1(ByteString.of(x509Certificate.getPublicKey().getEncoded())); - } - - static ByteString sha256(X509Certificate x509Certificate) { - return Util.sha256(ByteString.of(x509Certificate.getPublicKey().getEncoded())); - } - - static final class Pin { - /** A hostname like {@code example.com} or a pattern like {@code *.example.com}. */ - final String pattern; - /** Either {@code sha1/} or {@code sha256/}. */ - final String hashAlgorithm; - /** The hash of the pinned certificate using {@link #hashAlgorithm}. */ - final ByteString hash; - - Pin(String pattern, String pin) { - this.pattern = pattern; - if (pin.startsWith("sha1/")) { - this.hashAlgorithm = "sha1/"; - this.hash = ByteString.decodeBase64(pin.substring("sha1/".length())); - } else if (pin.startsWith("sha256/")) { - this.hashAlgorithm = "sha256/"; - this.hash = ByteString.decodeBase64(pin.substring("sha256/".length())); - } else { - throw new IllegalArgumentException("pins must start with 'sha256/' or 'sha1/': " + pin); - } - - if (this.hash == null) { - throw new IllegalArgumentException("pins must be base64: " + pin); - } - } - - boolean matches(String hostname) { - if (pattern.equals(hostname)) return true; - - int firstDot = hostname.indexOf('.'); - return pattern.startsWith("*.") - && hostname.regionMatches(false, firstDot + 1, pattern, 2, pattern.length() - 2); - } - - @Override public boolean equals(Object other) { - return other instanceof Pin - && pattern.equals(((Pin) other).pattern) - && hashAlgorithm.equals(((Pin) other).hashAlgorithm) - && hash.equals(((Pin) other).hash); - } - - @Override public int hashCode() { - int result = 17; - result = 31 * result + pattern.hashCode(); - result = 31 * result + hashAlgorithm.hashCode(); - result = 31 * result + hash.hashCode(); - return result; - } - - @Override public String toString() { - return hashAlgorithm + hash.base64(); - } - } - - /** Builds a configured certificate pinner. */ - public static final class Builder { - private final List pins = new ArrayList<>(); - private TrustRootIndex trustRootIndex; - - public Builder() { - } - - Builder(CertificatePinner certificatePinner) { - this.pins.addAll(certificatePinner.pins); - this.trustRootIndex = certificatePinner.trustRootIndex; - } - - public Builder trustRootIndex(TrustRootIndex trustRootIndex) { - this.trustRootIndex = trustRootIndex; - return this; - } - - /** - * Pins certificates for {@code pattern}. - * - * @param pattern lower-case host name or wildcard pattern such as {@code *.example.com}. - * @param pins SHA-256 or SHA-1 hashes. Each pin is a hash of a certificate's Subject Public Key - * Info, base64-encoded and prefixed with either {@code sha256/} or {@code sha1/}. - */ - public Builder add(String pattern, String... pins) { - if (pattern == null) throw new IllegalArgumentException("pattern == null"); - - for (String pin : pins) { - this.pins.add(new Pin(pattern, pin)); - } - - return this; - } - - public CertificatePinner build() { - return new CertificatePinner(this); - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/Challenge.java b/okhttp3/src/main/java/okhttp3/Challenge.java deleted file mode 100644 index f4bb369c4..000000000 --- a/okhttp3/src/main/java/okhttp3/Challenge.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import okhttp3.internal.Util; - -/** An RFC 2617 challenge. */ -public final class Challenge { - private final String scheme; - private final String realm; - - public Challenge(String scheme, String realm) { - this.scheme = scheme; - this.realm = realm; - } - - /** Returns the authentication scheme, like {@code Basic}. */ - public String scheme() { - return scheme; - } - - /** Returns the protection space. */ - public String realm() { - return realm; - } - - @Override public boolean equals(Object o) { - return o instanceof Challenge - && Util.equal(scheme, ((Challenge) o).scheme) - && Util.equal(realm, ((Challenge) o).realm); - } - - @Override public int hashCode() { - int result = 29; - result = 31 * result + (realm != null ? realm.hashCode() : 0); - result = 31 * result + (scheme != null ? scheme.hashCode() : 0); - return result; - } - - @Override public String toString() { - return scheme + " realm=\"" + realm + "\""; - } -} diff --git a/okhttp3/src/main/java/okhttp3/CipherSuite.java b/okhttp3/src/main/java/okhttp3/CipherSuite.java deleted file mode 100644 index 6697cad84..000000000 --- a/okhttp3/src/main/java/okhttp3/CipherSuite.java +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import static java.lang.Integer.MAX_VALUE; - -/** - * TLS cipher - * suites. - * - *

Not all cipher suites are supported on all platforms. As newer cipher suites - * are created (for stronger privacy, better performance, etc.) they will be adopted by the platform - * and then exposed here. Cipher suites that are not available on either Android (through API level - * 20) or Java (through JDK 8) are omitted for brevity. - * - *

See also NativeCrypto.java - * from conscrypt, which lists the cipher suites supported by Android. - */ -public enum CipherSuite { - // Last updated 2014-11-11 using cipher suites from Android 21 and Java 8. - - // TLS_NULL_WITH_NULL_NULL("TLS_NULL_WITH_NULL_NULL", 0x0000, 5246, MAX_VALUE, MAX_VALUE), - TLS_RSA_WITH_NULL_MD5("SSL_RSA_WITH_NULL_MD5", 0x0001, 5246, 6, 10), - TLS_RSA_WITH_NULL_SHA("SSL_RSA_WITH_NULL_SHA", 0x0002, 5246, 6, 10), - TLS_RSA_EXPORT_WITH_RC4_40_MD5("SSL_RSA_EXPORT_WITH_RC4_40_MD5", 0x0003, 4346, 6, 10), - TLS_RSA_WITH_RC4_128_MD5("SSL_RSA_WITH_RC4_128_MD5", 0x0004, 5246, 6, 10), - TLS_RSA_WITH_RC4_128_SHA("SSL_RSA_WITH_RC4_128_SHA", 0x0005, 5246, 6, 10), - // TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5("SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5", 0x0006, 4346, MAX_VALUE, MAX_VALUE), - // TLS_RSA_WITH_IDEA_CBC_SHA("TLS_RSA_WITH_IDEA_CBC_SHA", 0x0007, 5469, MAX_VALUE, MAX_VALUE), - TLS_RSA_EXPORT_WITH_DES40_CBC_SHA("SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0008, 4346, 6, 10), - TLS_RSA_WITH_DES_CBC_SHA("SSL_RSA_WITH_DES_CBC_SHA", 0x0009, 5469, 6, 10), - TLS_RSA_WITH_3DES_EDE_CBC_SHA("SSL_RSA_WITH_3DES_EDE_CBC_SHA", 0x000a, 5246, 6, 10), - // TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA("SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", 0x000b, 4346, MAX_VALUE, MAX_VALUE), - // TLS_DH_DSS_WITH_DES_CBC_SHA("TLS_DH_DSS_WITH_DES_CBC_SHA", 0x000c, 5469, MAX_VALUE, MAX_VALUE), - // TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA("TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", 0x000d, 5246, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA("SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x000e, 4346, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_DES_CBC_SHA("TLS_DH_RSA_WITH_DES_CBC_SHA", 0x000f, 5469, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA("TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", 0x0010, 5246, MAX_VALUE, MAX_VALUE), - TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA("SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", 0x0011, 4346, 6, 10), - TLS_DHE_DSS_WITH_DES_CBC_SHA("SSL_DHE_DSS_WITH_DES_CBC_SHA", 0x0012, 5469, 6, 10), - TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA("SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", 0x0013, 5246, 6, 10), - TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA("SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0014, 4346, 6, 10), - TLS_DHE_RSA_WITH_DES_CBC_SHA("SSL_DHE_RSA_WITH_DES_CBC_SHA", 0x0015, 5469, 6, 10), - TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA("SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", 0x0016, 5246, 6, 10), - TLS_DH_anon_EXPORT_WITH_RC4_40_MD5("SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", 0x0017, 4346, 6, 10), - TLS_DH_anon_WITH_RC4_128_MD5("SSL_DH_anon_WITH_RC4_128_MD5", 0x0018, 5246, 6, 10), - TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA("SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA", 0x0019, 4346, 6, 10), - TLS_DH_anon_WITH_DES_CBC_SHA("SSL_DH_anon_WITH_DES_CBC_SHA", 0x001a, 5469, 6, 10), - TLS_DH_anon_WITH_3DES_EDE_CBC_SHA("SSL_DH_anon_WITH_3DES_EDE_CBC_SHA", 0x001b, 5246, 6, 10), - TLS_KRB5_WITH_DES_CBC_SHA("TLS_KRB5_WITH_DES_CBC_SHA", 0x001e, 2712, 6, MAX_VALUE), - TLS_KRB5_WITH_3DES_EDE_CBC_SHA("TLS_KRB5_WITH_3DES_EDE_CBC_SHA", 0x001f, 2712, 6, MAX_VALUE), - TLS_KRB5_WITH_RC4_128_SHA("TLS_KRB5_WITH_RC4_128_SHA", 0x0020, 2712, 6, MAX_VALUE), - // TLS_KRB5_WITH_IDEA_CBC_SHA("TLS_KRB5_WITH_IDEA_CBC_SHA", 0x0021, 2712, MAX_VALUE, MAX_VALUE), - TLS_KRB5_WITH_DES_CBC_MD5("TLS_KRB5_WITH_DES_CBC_MD5", 0x0022, 2712, 6, MAX_VALUE), - TLS_KRB5_WITH_3DES_EDE_CBC_MD5("TLS_KRB5_WITH_3DES_EDE_CBC_MD5", 0x0023, 2712, 6, MAX_VALUE), - TLS_KRB5_WITH_RC4_128_MD5("TLS_KRB5_WITH_RC4_128_MD5", 0x0024, 2712, 6, MAX_VALUE), - // TLS_KRB5_WITH_IDEA_CBC_MD5("TLS_KRB5_WITH_IDEA_CBC_MD5", 0x0025, 2712, MAX_VALUE, MAX_VALUE), - TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA("TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", 0x0026, 2712, 6, MAX_VALUE), - // TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", 0x0027, 2712, MAX_VALUE, MAX_VALUE), - TLS_KRB5_EXPORT_WITH_RC4_40_SHA("TLS_KRB5_EXPORT_WITH_RC4_40_SHA", 0x0028, 2712, 6, MAX_VALUE), - TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5("TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", 0x0029, 2712, 6, MAX_VALUE), - // TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", 0x002a, 2712, MAX_VALUE, MAX_VALUE), - TLS_KRB5_EXPORT_WITH_RC4_40_MD5("TLS_KRB5_EXPORT_WITH_RC4_40_MD5", 0x002b, 2712, 6, MAX_VALUE), - // TLS_PSK_WITH_NULL_SHA("TLS_PSK_WITH_NULL_SHA", 0x002c, 4785, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_NULL_SHA("TLS_DHE_PSK_WITH_NULL_SHA", 0x002d, 4785, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_NULL_SHA("TLS_RSA_PSK_WITH_NULL_SHA", 0x002e, 4785, MAX_VALUE, MAX_VALUE), - TLS_RSA_WITH_AES_128_CBC_SHA("TLS_RSA_WITH_AES_128_CBC_SHA", 0x002f, 5246, 6, 10), - // TLS_DH_DSS_WITH_AES_128_CBC_SHA("TLS_DH_DSS_WITH_AES_128_CBC_SHA", 0x0030, 5246, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_AES_128_CBC_SHA("TLS_DH_RSA_WITH_AES_128_CBC_SHA", 0x0031, 5246, MAX_VALUE, MAX_VALUE), - TLS_DHE_DSS_WITH_AES_128_CBC_SHA("TLS_DHE_DSS_WITH_AES_128_CBC_SHA", 0x0032, 5246, 6, 10), - TLS_DHE_RSA_WITH_AES_128_CBC_SHA("TLS_DHE_RSA_WITH_AES_128_CBC_SHA", 0x0033, 5246, 6, 10), - TLS_DH_anon_WITH_AES_128_CBC_SHA("TLS_DH_anon_WITH_AES_128_CBC_SHA", 0x0034, 5246, 6, 10), - TLS_RSA_WITH_AES_256_CBC_SHA("TLS_RSA_WITH_AES_256_CBC_SHA", 0x0035, 5246, 6, 10), - // TLS_DH_DSS_WITH_AES_256_CBC_SHA("TLS_DH_DSS_WITH_AES_256_CBC_SHA", 0x0036, 5246, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_AES_256_CBC_SHA("TLS_DH_RSA_WITH_AES_256_CBC_SHA", 0x0037, 5246, MAX_VALUE, MAX_VALUE), - TLS_DHE_DSS_WITH_AES_256_CBC_SHA("TLS_DHE_DSS_WITH_AES_256_CBC_SHA", 0x0038, 5246, 6, 10), - TLS_DHE_RSA_WITH_AES_256_CBC_SHA("TLS_DHE_RSA_WITH_AES_256_CBC_SHA", 0x0039, 5246, 6, 10), - TLS_DH_anon_WITH_AES_256_CBC_SHA("TLS_DH_anon_WITH_AES_256_CBC_SHA", 0x003a, 5246, 6, 10), - TLS_RSA_WITH_NULL_SHA256("TLS_RSA_WITH_NULL_SHA256", 0x003b, 5246, 7, 21), - TLS_RSA_WITH_AES_128_CBC_SHA256("TLS_RSA_WITH_AES_128_CBC_SHA256", 0x003c, 5246, 7, 21), - TLS_RSA_WITH_AES_256_CBC_SHA256("TLS_RSA_WITH_AES_256_CBC_SHA256", 0x003d, 5246, 7, 21), - // TLS_DH_DSS_WITH_AES_128_CBC_SHA256("TLS_DH_DSS_WITH_AES_128_CBC_SHA256", 0x003e, 5246, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_AES_128_CBC_SHA256("TLS_DH_RSA_WITH_AES_128_CBC_SHA256", 0x003f, 5246, MAX_VALUE, MAX_VALUE), - TLS_DHE_DSS_WITH_AES_128_CBC_SHA256("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", 0x0040, 5246, 7, 21), - // TLS_RSA_WITH_CAMELLIA_128_CBC_SHA("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0041, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", 0x0042, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0043, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", 0x0044, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0045, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", 0x0046, 5932, MAX_VALUE, MAX_VALUE), - TLS_DHE_RSA_WITH_AES_128_CBC_SHA256("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", 0x0067, 5246, 7, 21), - // TLS_DH_DSS_WITH_AES_256_CBC_SHA256("TLS_DH_DSS_WITH_AES_256_CBC_SHA256", 0x0068, 5246, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_AES_256_CBC_SHA256("TLS_DH_RSA_WITH_AES_256_CBC_SHA256", 0x0069, 5246, MAX_VALUE, MAX_VALUE), - TLS_DHE_DSS_WITH_AES_256_CBC_SHA256("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", 0x006a, 5246, 7, 21), - TLS_DHE_RSA_WITH_AES_256_CBC_SHA256("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", 0x006b, 5246, 7, 21), - TLS_DH_anon_WITH_AES_128_CBC_SHA256("TLS_DH_anon_WITH_AES_128_CBC_SHA256", 0x006c, 5246, 7, 21), - TLS_DH_anon_WITH_AES_256_CBC_SHA256("TLS_DH_anon_WITH_AES_256_CBC_SHA256", 0x006d, 5246, 7, 21), - // TLS_RSA_WITH_CAMELLIA_256_CBC_SHA("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0084, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", 0x0085, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0086, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", 0x0087, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0088, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", 0x0089, 5932, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_RC4_128_SHA("TLS_PSK_WITH_RC4_128_SHA", 0x008a, 4279, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_3DES_EDE_CBC_SHA("TLS_PSK_WITH_3DES_EDE_CBC_SHA", 0x008b, 4279, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_AES_128_CBC_SHA("TLS_PSK_WITH_AES_128_CBC_SHA", 0x008c, 4279, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_AES_256_CBC_SHA("TLS_PSK_WITH_AES_256_CBC_SHA", 0x008d, 4279, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_RC4_128_SHA("TLS_DHE_PSK_WITH_RC4_128_SHA", 0x008e, 4279, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA("TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", 0x008f, 4279, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_AES_128_CBC_SHA("TLS_DHE_PSK_WITH_AES_128_CBC_SHA", 0x0090, 4279, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_AES_256_CBC_SHA("TLS_DHE_PSK_WITH_AES_256_CBC_SHA", 0x0091, 4279, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_RC4_128_SHA("TLS_RSA_PSK_WITH_RC4_128_SHA", 0x0092, 4279, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA("TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", 0x0093, 4279, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_AES_128_CBC_SHA("TLS_RSA_PSK_WITH_AES_128_CBC_SHA", 0x0094, 4279, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_AES_256_CBC_SHA("TLS_RSA_PSK_WITH_AES_256_CBC_SHA", 0x0095, 4279, MAX_VALUE, MAX_VALUE), - // TLS_RSA_WITH_SEED_CBC_SHA("TLS_RSA_WITH_SEED_CBC_SHA", 0x0096, 4162, MAX_VALUE, MAX_VALUE), - // TLS_DH_DSS_WITH_SEED_CBC_SHA("TLS_DH_DSS_WITH_SEED_CBC_SHA", 0x0097, 4162, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_SEED_CBC_SHA("TLS_DH_RSA_WITH_SEED_CBC_SHA", 0x0098, 4162, MAX_VALUE, MAX_VALUE), - // TLS_DHE_DSS_WITH_SEED_CBC_SHA("TLS_DHE_DSS_WITH_SEED_CBC_SHA", 0x0099, 4162, MAX_VALUE, MAX_VALUE), - // TLS_DHE_RSA_WITH_SEED_CBC_SHA("TLS_DHE_RSA_WITH_SEED_CBC_SHA", 0x009a, 4162, MAX_VALUE, MAX_VALUE), - // TLS_DH_anon_WITH_SEED_CBC_SHA("TLS_DH_anon_WITH_SEED_CBC_SHA", 0x009b, 4162, MAX_VALUE, MAX_VALUE), - TLS_RSA_WITH_AES_128_GCM_SHA256("TLS_RSA_WITH_AES_128_GCM_SHA256", 0x009c, 5288, 8, 21), - TLS_RSA_WITH_AES_256_GCM_SHA384("TLS_RSA_WITH_AES_256_GCM_SHA384", 0x009d, 5288, 8, 21), - TLS_DHE_RSA_WITH_AES_128_GCM_SHA256("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", 0x009e, 5288, 8, 21), - TLS_DHE_RSA_WITH_AES_256_GCM_SHA384("TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", 0x009f, 5288, 8, 21), - // TLS_DH_RSA_WITH_AES_128_GCM_SHA256("TLS_DH_RSA_WITH_AES_128_GCM_SHA256", 0x00a0, 5288, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_AES_256_GCM_SHA384("TLS_DH_RSA_WITH_AES_256_GCM_SHA384", 0x00a1, 5288, MAX_VALUE, MAX_VALUE), - TLS_DHE_DSS_WITH_AES_128_GCM_SHA256("TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", 0x00a2, 5288, 8, 21), - TLS_DHE_DSS_WITH_AES_256_GCM_SHA384("TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", 0x00a3, 5288, 8, 21), - // TLS_DH_DSS_WITH_AES_128_GCM_SHA256("TLS_DH_DSS_WITH_AES_128_GCM_SHA256", 0x00a4, 5288, MAX_VALUE, MAX_VALUE), - // TLS_DH_DSS_WITH_AES_256_GCM_SHA384("TLS_DH_DSS_WITH_AES_256_GCM_SHA384", 0x00a5, 5288, MAX_VALUE, MAX_VALUE), - TLS_DH_anon_WITH_AES_128_GCM_SHA256("TLS_DH_anon_WITH_AES_128_GCM_SHA256", 0x00a6, 5288, 8, 21), - TLS_DH_anon_WITH_AES_256_GCM_SHA384("TLS_DH_anon_WITH_AES_256_GCM_SHA384", 0x00a7, 5288, 8, 21), - // TLS_PSK_WITH_AES_128_GCM_SHA256("TLS_PSK_WITH_AES_128_GCM_SHA256", 0x00a8, 5487, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_AES_256_GCM_SHA384("TLS_PSK_WITH_AES_256_GCM_SHA384", 0x00a9, 5487, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_AES_128_GCM_SHA256("TLS_DHE_PSK_WITH_AES_128_GCM_SHA256", 0x00aa, 5487, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_AES_256_GCM_SHA384("TLS_DHE_PSK_WITH_AES_256_GCM_SHA384", 0x00ab, 5487, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_AES_128_GCM_SHA256("TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", 0x00ac, 5487, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_AES_256_GCM_SHA384("TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", 0x00ad, 5487, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_AES_128_CBC_SHA256("TLS_PSK_WITH_AES_128_CBC_SHA256", 0x00ae, 5487, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_AES_256_CBC_SHA384("TLS_PSK_WITH_AES_256_CBC_SHA384", 0x00af, 5487, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_NULL_SHA256("TLS_PSK_WITH_NULL_SHA256", 0x00b0, 5487, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_NULL_SHA384("TLS_PSK_WITH_NULL_SHA384", 0x00b1, 5487, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_AES_128_CBC_SHA256("TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", 0x00b2, 5487, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_AES_256_CBC_SHA384("TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", 0x00b3, 5487, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_NULL_SHA256("TLS_DHE_PSK_WITH_NULL_SHA256", 0x00b4, 5487, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_NULL_SHA384("TLS_DHE_PSK_WITH_NULL_SHA384", 0x00b5, 5487, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_AES_128_CBC_SHA256("TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", 0x00b6, 5487, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_AES_256_CBC_SHA384("TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", 0x00b7, 5487, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_NULL_SHA256("TLS_RSA_PSK_WITH_NULL_SHA256", 0x00b8, 5487, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_NULL_SHA384("TLS_RSA_PSK_WITH_NULL_SHA384", 0x00b9, 5487, MAX_VALUE, MAX_VALUE), - // TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00ba, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", 0x00bb, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00bc, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", 0x00bd, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00be, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", 0x00bf, 5932, MAX_VALUE, MAX_VALUE), - // TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c0, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", 0x00c1, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c2, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", 0x00c3, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c4, 5932, MAX_VALUE, MAX_VALUE), - // TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", 0x00c5, 5932, MAX_VALUE, MAX_VALUE), - TLS_EMPTY_RENEGOTIATION_INFO_SCSV("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", 0x00ff, 5746, 6, 14), - TLS_ECDH_ECDSA_WITH_NULL_SHA("TLS_ECDH_ECDSA_WITH_NULL_SHA", 0xc001, 4492, 7, 14), - TLS_ECDH_ECDSA_WITH_RC4_128_SHA("TLS_ECDH_ECDSA_WITH_RC4_128_SHA", 0xc002, 4492, 7, 14), - TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", 0xc003, 4492, 7, 14), - TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", 0xc004, 4492, 7, 14), - TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", 0xc005, 4492, 7, 14), - TLS_ECDHE_ECDSA_WITH_NULL_SHA("TLS_ECDHE_ECDSA_WITH_NULL_SHA", 0xc006, 4492, 7, 14), - TLS_ECDHE_ECDSA_WITH_RC4_128_SHA("TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", 0xc007, 4492, 7, 14), - TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", 0xc008, 4492, 7, 14), - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", 0xc009, 4492, 7, 14), - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", 0xc00a, 4492, 7, 14), - TLS_ECDH_RSA_WITH_NULL_SHA("TLS_ECDH_RSA_WITH_NULL_SHA", 0xc00b, 4492, 7, 14), - TLS_ECDH_RSA_WITH_RC4_128_SHA("TLS_ECDH_RSA_WITH_RC4_128_SHA", 0xc00c, 4492, 7, 14), - TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", 0xc00d, 4492, 7, 14), - TLS_ECDH_RSA_WITH_AES_128_CBC_SHA("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", 0xc00e, 4492, 7, 14), - TLS_ECDH_RSA_WITH_AES_256_CBC_SHA("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", 0xc00f, 4492, 7, 14), - TLS_ECDHE_RSA_WITH_NULL_SHA("TLS_ECDHE_RSA_WITH_NULL_SHA", 0xc010, 4492, 7, 14), - TLS_ECDHE_RSA_WITH_RC4_128_SHA("TLS_ECDHE_RSA_WITH_RC4_128_SHA", 0xc011, 4492, 7, 14), - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", 0xc012, 4492, 7, 14), - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", 0xc013, 4492, 7, 14), - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", 0xc014, 4492, 7, 14), - TLS_ECDH_anon_WITH_NULL_SHA("TLS_ECDH_anon_WITH_NULL_SHA", 0xc015, 4492, 7, 14), - TLS_ECDH_anon_WITH_RC4_128_SHA("TLS_ECDH_anon_WITH_RC4_128_SHA", 0xc016, 4492, 7, 14), - TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA("TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", 0xc017, 4492, 7, 14), - TLS_ECDH_anon_WITH_AES_128_CBC_SHA("TLS_ECDH_anon_WITH_AES_128_CBC_SHA", 0xc018, 4492, 7, 14), - TLS_ECDH_anon_WITH_AES_256_CBC_SHA("TLS_ECDH_anon_WITH_AES_256_CBC_SHA", 0xc019, 4492, 7, 14), - // TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA("TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", 0xc01a, 5054, MAX_VALUE, MAX_VALUE), - // TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA("TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", 0xc01b, 5054, MAX_VALUE, MAX_VALUE), - // TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", 0xc01c, 5054, MAX_VALUE, MAX_VALUE), - // TLS_SRP_SHA_WITH_AES_128_CBC_SHA("TLS_SRP_SHA_WITH_AES_128_CBC_SHA", 0xc01d, 5054, MAX_VALUE, MAX_VALUE), - // TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA("TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", 0xc01e, 5054, MAX_VALUE, MAX_VALUE), - // TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA("TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", 0xc01f, 5054, MAX_VALUE, MAX_VALUE), - // TLS_SRP_SHA_WITH_AES_256_CBC_SHA("TLS_SRP_SHA_WITH_AES_256_CBC_SHA", 0xc020, 5054, MAX_VALUE, MAX_VALUE), - // TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA("TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", 0xc021, 5054, MAX_VALUE, MAX_VALUE), - // TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA("TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", 0xc022, 5054, MAX_VALUE, MAX_VALUE), - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", 0xc023, 5289, 7, 21), - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", 0xc024, 5289, 7, 21), - TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", 0xc025, 5289, 7, 21), - TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", 0xc026, 5289, 7, 21), - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", 0xc027, 5289, 7, 21), - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", 0xc028, 5289, 7, 21), - TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", 0xc029, 5289, 7, 21), - TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", 0xc02a, 5289, 7, 21), - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 0xc02b, 5289, 8, 21), - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", 0xc02c, 5289, 8, 21), - TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256("TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", 0xc02d, 5289, 8, 21), - TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384("TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", 0xc02e, 5289, 8, 21), - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 0xc02f, 5289, 8, 21), - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", 0xc030, 5289, 8, 21), - TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256("TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", 0xc031, 5289, 8, 21), - TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384("TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", 0xc032, 5289, 8, 21), - // TLS_ECDHE_PSK_WITH_RC4_128_SHA("TLS_ECDHE_PSK_WITH_RC4_128_SHA", 0xc033, 5489, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA("TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", 0xc034, 5489, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", 0xc035, 5489, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", 0xc036, 5489, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", 0xc037, 5489, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", 0xc038, 5489, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_PSK_WITH_NULL_SHA("TLS_ECDHE_PSK_WITH_NULL_SHA", 0xc039, 5489, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_PSK_WITH_NULL_SHA256("TLS_ECDHE_PSK_WITH_NULL_SHA256", 0xc03a, 5489, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_PSK_WITH_NULL_SHA384("TLS_ECDHE_PSK_WITH_NULL_SHA384", 0xc03b, 5489, MAX_VALUE, MAX_VALUE), - // TLS_RSA_WITH_ARIA_128_CBC_SHA256("TLS_RSA_WITH_ARIA_128_CBC_SHA256", 0xc03c, 6209, MAX_VALUE, MAX_VALUE), - // TLS_RSA_WITH_ARIA_256_CBC_SHA384("TLS_RSA_WITH_ARIA_256_CBC_SHA384", 0xc03d, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256("TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", 0xc03e, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384("TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", 0xc03f, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256("TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", 0xc040, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384("TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", 0xc041, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256("TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", 0xc042, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384("TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", 0xc043, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256("TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", 0xc044, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384("TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", 0xc045, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DH_anon_WITH_ARIA_128_CBC_SHA256("TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", 0xc046, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DH_anon_WITH_ARIA_256_CBC_SHA384("TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", 0xc047, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", 0xc048, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", 0xc049, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", 0xc04a, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", 0xc04b, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", 0xc04c, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", 0xc04d, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", 0xc04e, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", 0xc04f, 6209, MAX_VALUE, MAX_VALUE), - // TLS_RSA_WITH_ARIA_128_GCM_SHA256("TLS_RSA_WITH_ARIA_128_GCM_SHA256", 0xc050, 6209, MAX_VALUE, MAX_VALUE), - // TLS_RSA_WITH_ARIA_256_GCM_SHA384("TLS_RSA_WITH_ARIA_256_GCM_SHA384", 0xc051, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256("TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", 0xc052, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384("TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", 0xc053, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256("TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", 0xc054, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384("TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", 0xc055, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256("TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", 0xc056, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384("TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", 0xc057, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256("TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", 0xc058, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384("TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", 0xc059, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DH_anon_WITH_ARIA_128_GCM_SHA256("TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", 0xc05a, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DH_anon_WITH_ARIA_256_GCM_SHA384("TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", 0xc05b, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", 0xc05c, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", 0xc05d, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", 0xc05e, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", 0xc05f, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", 0xc060, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", 0xc061, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", 0xc062, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", 0xc063, 6209, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_ARIA_128_CBC_SHA256("TLS_PSK_WITH_ARIA_128_CBC_SHA256", 0xc064, 6209, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_ARIA_256_CBC_SHA384("TLS_PSK_WITH_ARIA_256_CBC_SHA384", 0xc065, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256("TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", 0xc066, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384("TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", 0xc067, 6209, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256("TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", 0xc068, 6209, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384("TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", 0xc069, 6209, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_ARIA_128_GCM_SHA256("TLS_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06a, 6209, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_ARIA_256_GCM_SHA384("TLS_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06b, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256("TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06c, 6209, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384("TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06d, 6209, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256("TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06e, 6209, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384("TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06f, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256("TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", 0xc070, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384("TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", 0xc071, 6209, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc072, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc073, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc074, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc075, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc076, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc077, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc078, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc079, 6367, MAX_VALUE, MAX_VALUE), - // TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07a, 6367, MAX_VALUE, MAX_VALUE), - // TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07b, 6367, MAX_VALUE, MAX_VALUE), - // TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07c, 6367, MAX_VALUE, MAX_VALUE), - // TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07d, 6367, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07e, 6367, MAX_VALUE, MAX_VALUE), - // TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07f, 6367, MAX_VALUE, MAX_VALUE), - // TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256("TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256", 0xc080, 6367, MAX_VALUE, MAX_VALUE), - // TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384("TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384", 0xc081, 6367, MAX_VALUE, MAX_VALUE), - // TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256("TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", 0xc082, 6367, MAX_VALUE, MAX_VALUE), - // TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384("TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", 0xc083, 6367, MAX_VALUE, MAX_VALUE), - // TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256("TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", 0xc084, 6367, MAX_VALUE, MAX_VALUE), - // TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384("TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", 0xc085, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc086, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc087, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc088, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc089, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc08a, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc08b, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc08c, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc08d, 6367, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256("TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc08e, 6367, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384("TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc08f, 6367, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256("TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc090, 6367, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384("TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc091, 6367, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256("TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc092, 6367, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384("TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc093, 6367, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc094, 6367, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc095, 6367, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc096, 6367, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc097, 6367, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc098, 6367, MAX_VALUE, MAX_VALUE), - // TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc099, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc09a, 6367, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc09b, 6367, MAX_VALUE, MAX_VALUE), - // TLS_RSA_WITH_AES_128_CCM("TLS_RSA_WITH_AES_128_CCM", 0xc09c, 6655, MAX_VALUE, MAX_VALUE), - // TLS_RSA_WITH_AES_256_CCM("TLS_RSA_WITH_AES_256_CCM", 0xc09d, 6655, MAX_VALUE, MAX_VALUE), - // TLS_DHE_RSA_WITH_AES_128_CCM("TLS_DHE_RSA_WITH_AES_128_CCM", 0xc09e, 6655, MAX_VALUE, MAX_VALUE), - // TLS_DHE_RSA_WITH_AES_256_CCM("TLS_DHE_RSA_WITH_AES_256_CCM", 0xc09f, 6655, MAX_VALUE, MAX_VALUE), - // TLS_RSA_WITH_AES_128_CCM_8("TLS_RSA_WITH_AES_128_CCM_8", 0xc0a0, 6655, MAX_VALUE, MAX_VALUE), - // TLS_RSA_WITH_AES_256_CCM_8("TLS_RSA_WITH_AES_256_CCM_8", 0xc0a1, 6655, MAX_VALUE, MAX_VALUE), - // TLS_DHE_RSA_WITH_AES_128_CCM_8("TLS_DHE_RSA_WITH_AES_128_CCM_8", 0xc0a2, 6655, MAX_VALUE, MAX_VALUE), - // TLS_DHE_RSA_WITH_AES_256_CCM_8("TLS_DHE_RSA_WITH_AES_256_CCM_8", 0xc0a3, 6655, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_AES_128_CCM("TLS_PSK_WITH_AES_128_CCM", 0xc0a4, 6655, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_AES_256_CCM("TLS_PSK_WITH_AES_256_CCM", 0xc0a5, 6655, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_AES_128_CCM("TLS_DHE_PSK_WITH_AES_128_CCM", 0xc0a6, 6655, MAX_VALUE, MAX_VALUE), - // TLS_DHE_PSK_WITH_AES_256_CCM("TLS_DHE_PSK_WITH_AES_256_CCM", 0xc0a7, 6655, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_AES_128_CCM_8("TLS_PSK_WITH_AES_128_CCM_8", 0xc0a8, 6655, MAX_VALUE, MAX_VALUE), - // TLS_PSK_WITH_AES_256_CCM_8("TLS_PSK_WITH_AES_256_CCM_8", 0xc0a9, 6655, MAX_VALUE, MAX_VALUE), - // TLS_PSK_DHE_WITH_AES_128_CCM_8("TLS_PSK_DHE_WITH_AES_128_CCM_8", 0xc0aa, 6655, MAX_VALUE, MAX_VALUE), - // TLS_PSK_DHE_WITH_AES_256_CCM_8("TLS_PSK_DHE_WITH_AES_256_CCM_8", 0xc0ab, 6655, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_ECDSA_WITH_AES_128_CCM("TLS_ECDHE_ECDSA_WITH_AES_128_CCM", 0xc0ac, 7251, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_ECDSA_WITH_AES_256_CCM("TLS_ECDHE_ECDSA_WITH_AES_256_CCM", 0xc0ad, 7251, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8("TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", 0xc0ae, 7251, MAX_VALUE, MAX_VALUE), - // TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8("TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", 0xc0af, 7251, MAX_VALUE, MAX_VALUE), - ; - - final String javaName; - - /** - * @param javaName the name used by Java APIs for this cipher suite. Different than the IANA name - * for older cipher suites because the prefix is {@code SSL_} instead of {@code TLS_}. - * @param value the integer identifier for this cipher suite. (Documentation only.) - * @param rfc the RFC describing this cipher suite. (Documentation only.) - * @param sinceJavaVersion the first major Java release supporting this cipher suite. - * @param sinceAndroidVersion the first Android SDK version supporting this cipher suite. - */ - private CipherSuite( - String javaName, int value, int rfc, int sinceJavaVersion, int sinceAndroidVersion) { - this.javaName = javaName; - } - - /** - * Returns the Java name of this cipher suite. For some older cipher suites the Java name has the - * prefix {@code SSL_}, causing the Java name to be different from the {@linkplain #name() enum - * name} which is always prefixed {@code TLS_}. - */ - public String javaName() { - return javaName; - } - - public static CipherSuite forJavaName(String javaName) { - return javaName.startsWith("SSL_") - ? valueOf("TLS_" + javaName.substring(4)) - : valueOf(javaName); - } -} diff --git a/okhttp3/src/main/java/okhttp3/Connection.java b/okhttp3/src/main/java/okhttp3/Connection.java deleted file mode 100644 index de3c22d98..000000000 --- a/okhttp3/src/main/java/okhttp3/Connection.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.net.Socket; - -/** - * The sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection. May be used for multiple - * HTTP request/response exchanges. Connections may be direct to the origin server or via a proxy. - * - *

Typically instances of this class are created, connected and exercised automatically by the - * HTTP client. Applications may use this class to monitor HTTP connections as members of a - * {@linkplain ConnectionPool connection pool}. - * - *

Do not confuse this class with the misnamed {@code HttpURLConnection}, which isn't so much a - * connection as a single request/response exchange. - * - *

Modern TLS

- * - *

There are tradeoffs when selecting which options to include when negotiating a secure - * connection to a remote host. Newer TLS options are quite useful: - * - *

    - *
  • Server Name Indication (SNI) enables one IP address to negotiate secure connections for - * multiple domain names. - *
  • Application Layer Protocol Negotiation (ALPN) enables the HTTPS port (443) to be used for - * different HTTP and SPDY protocols. - *
- * - *

Unfortunately, older HTTPS servers refuse to connect when such options are presented. Rather - * than avoiding these options entirely, this class allows a connection to be attempted with modern - * options and then retried without them should the attempt fail. - * - *

Connection Reuse

- * - *

Each connection can carry a varying number streams, depending on the underlying protocol being - * used. HTTP/1.x connections can carry either zero or one streams. HTTP/2 connections can carry any - * number of streams, dynamically configured with {@code SETTINGS_MAX_CONCURRENT_STREAMS}. A - * connection currently carrying zero streams is an idle stream. We keep it alive because reusing an - * existing connection is typically faster than establishing a new one. - * - *

When a single logical call requires multiple streams due to redirects or authorization - * challenges, we prefer to use the same physical connection for all streams in the sequence. There - * are potential performance and behavior consequences to this preference. To support this feature, - * this class separates allocations from streams. An allocation is created by a call, - * used for one or more streams, and then released. An allocated connection won't be stolen by other - * calls while a redirect or authorization challenge is being handled. - * - *

When the maximum concurrent streams limit is reduced, some allocations will be rescinded. - * Attempting to create new streams on these allocations will fail. - * - *

Note that an allocation may be released before its stream is completed. This is intended to - * make bookkeeping easier for the caller: releasing the allocation as soon as the terminal stream - * has been found. But only complete the stream once its data stream has been exhausted. - */ -public interface Connection { - /** Returns the route used by this connection. */ - Route route(); - - /** - * Returns the socket that this connection is using. Returns an {@linkplain - * javax.net.ssl.SSLSocket SSL socket} if this connection is HTTPS. If this is an HTTP/2 or SPDY - * connection the socket may be shared by multiple concurrent calls. - */ - Socket socket(); - - /** - * Returns the TLS handshake used to establish this connection, or null if the connection is not - * HTTPS. - */ - Handshake handshake(); - - /** - * Returns the protocol negotiated by this connection, or {@link Protocol#HTTP_1_1} if no protocol - * has been negotiated. This method returns {@link Protocol#HTTP_1_1} even if the remote peer is - * using {@link Protocol#HTTP_1_0}. - */ - Protocol protocol(); -} diff --git a/okhttp3/src/main/java/okhttp3/ConnectionPool.java b/okhttp3/src/main/java/okhttp3/ConnectionPool.java deleted file mode 100644 index 701a98813..000000000 --- a/okhttp3/src/main/java/okhttp3/ConnectionPool.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.lang.ref.Reference; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.Executor; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import okhttp3.internal.Internal; -import okhttp3.internal.RouteDatabase; -import okhttp3.internal.Util; -import okhttp3.internal.http.StreamAllocation; -import okhttp3.internal.io.RealConnection; - -import static okhttp3.internal.Util.closeQuietly; - -/** - * Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP requests that share - * the same {@link Address} may share a {@link Connection}. This class implements the policy of - * which connections to keep open for future use. - */ -public final class ConnectionPool { - /** - * Background threads are used to cleanup expired connections. There will be at most a single - * thread running per connection pool. The thread pool executor permits the pool itself to be - * garbage collected. - */ - private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */, - Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS, - new SynchronousQueue(), Util.threadFactory("OkHttp ConnectionPool", true)); - - /** The maximum number of idle connections for each address. */ - private final int maxIdleConnections; - private final long keepAliveDurationNs; - private final Runnable cleanupRunnable = new Runnable() { - @Override public void run() { - while (true) { - long waitNanos = cleanup(System.nanoTime()); - if (waitNanos == -1) return; - if (waitNanos > 0) { - long waitMillis = waitNanos / 1000000L; - waitNanos -= (waitMillis * 1000000L); - synchronized (ConnectionPool.this) { - try { - ConnectionPool.this.wait(waitMillis, (int) waitNanos); - } catch (InterruptedException ignored) { - } - } - } - } - } - }; - - private final Deque connections = new ArrayDeque<>(); - final RouteDatabase routeDatabase = new RouteDatabase(); - boolean cleanupRunning; - - /** - * Create a new connection pool with tuning parameters appropriate for a single-user application. - * The tuning parameters in this pool are subject to change in future OkHttp releases. Currently - * this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity. - */ - public ConnectionPool() { - this(5, 5, TimeUnit.MINUTES); - } - - public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) { - this.maxIdleConnections = maxIdleConnections; - this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration); - - // Put a floor on the keep alive duration, otherwise cleanup will spin loop. - if (keepAliveDuration <= 0) { - throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration); - } - } - - /** Returns the number of idle connections in the pool. */ - public synchronized int idleConnectionCount() { - int total = 0; - for (RealConnection connection : connections) { - if (connection.allocations.isEmpty()) total++; - } - return total; - } - - /** - * Returns total number of connections in the pool. Note that prior to OkHttp 2.7 this included - * only idle connections and SPDY connections. Since OkHttp 2.7 this includes all connections, - * both active and inactive. Use {@link #idleConnectionCount()} to count connections not currently - * in use. - */ - public synchronized int connectionCount() { - return connections.size(); - } - - /** Returns a recycled connection to {@code address}, or null if no such connection exists. */ - RealConnection get(Address address, StreamAllocation streamAllocation) { - assert (Thread.holdsLock(this)); - for (RealConnection connection : connections) { - if (connection.allocations.size() < connection.allocationLimit - && address.equals(connection.route().address) - && !connection.noNewStreams) { - streamAllocation.acquire(connection); - return connection; - } - } - return null; - } - - void put(RealConnection connection) { - assert (Thread.holdsLock(this)); - if (!cleanupRunning) { - cleanupRunning = true; - executor.execute(cleanupRunnable); - } - connections.add(connection); - } - - /** - * Notify this pool that {@code connection} has become idle. Returns true if the connection has - * been removed from the pool and should be closed. - */ - boolean connectionBecameIdle(RealConnection connection) { - assert (Thread.holdsLock(this)); - if (connection.noNewStreams || maxIdleConnections == 0) { - connections.remove(connection); - return true; - } else { - notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit. - return false; - } - } - - /** Close and remove all idle connections in the pool. */ - public void evictAll() { - List evictedConnections = new ArrayList<>(); - synchronized (this) { - for (Iterator i = connections.iterator(); i.hasNext(); ) { - RealConnection connection = i.next(); - if (connection.allocations.isEmpty()) { - connection.noNewStreams = true; - evictedConnections.add(connection); - i.remove(); - } - } - } - - for (RealConnection connection : evictedConnections) { - closeQuietly(connection.socket()); - } - } - - /** - * Performs maintenance on this pool, evicting the connection that has been idle the longest if - * either it has exceeded the keep alive limit or the idle connections limit. - * - *

Returns the duration in nanos to sleep until the next scheduled call to this method. Returns - * -1 if no further cleanups are required. - */ - long cleanup(long now) { - int inUseConnectionCount = 0; - int idleConnectionCount = 0; - RealConnection longestIdleConnection = null; - long longestIdleDurationNs = Long.MIN_VALUE; - - // Find either a connection to evict, or the time that the next eviction is due. - synchronized (this) { - for (Iterator i = connections.iterator(); i.hasNext(); ) { - RealConnection connection = i.next(); - - // If the connection is in use, keep searching. - if (pruneAndGetAllocationCount(connection, now) > 0) { - inUseConnectionCount++; - continue; - } - - idleConnectionCount++; - - // If the connection is ready to be evicted, we're done. - long idleDurationNs = now - connection.idleAtNanos; - if (idleDurationNs > longestIdleDurationNs) { - longestIdleDurationNs = idleDurationNs; - longestIdleConnection = connection; - } - } - - if (longestIdleDurationNs >= this.keepAliveDurationNs - || idleConnectionCount > this.maxIdleConnections) { - // We've found a connection to evict. Remove it from the list, then close it below (outside - // of the synchronized block). - connections.remove(longestIdleConnection); - } else if (idleConnectionCount > 0) { - // A connection will be ready to evict soon. - return keepAliveDurationNs - longestIdleDurationNs; - } else if (inUseConnectionCount > 0) { - // All connections are in use. It'll be at least the keep alive duration 'til we run again. - return keepAliveDurationNs; - } else { - // No connections, idle or in use. - cleanupRunning = false; - return -1; - } - } - - closeQuietly(longestIdleConnection.socket()); - - // Cleanup again immediately. - return 0; - } - - /** - * Prunes any leaked allocations and then returns the number of remaining live allocations on - * {@code connection}. Allocations are leaked if the connection is tracking them but the - * application code has abandoned them. Leak detection is imprecise and relies on garbage - * collection. - */ - private int pruneAndGetAllocationCount(RealConnection connection, long now) { - List> references = connection.allocations; - for (int i = 0; i < references.size(); ) { - Reference reference = references.get(i); - - if (reference.get() != null) { - i++; - continue; - } - - // We've discovered a leaked allocation. This is an application bug. - Internal.logger.warning("A connection to " + connection.route().address().url() - + " was leaked. Did you forget to close a response body?"); - references.remove(i); - connection.noNewStreams = true; - - // If this was the last allocation, the connection is eligible for immediate eviction. - if (references.isEmpty()) { - connection.idleAtNanos = now - keepAliveDurationNs; - return 0; - } - } - - return references.size(); - } -} diff --git a/okhttp3/src/main/java/okhttp3/ConnectionSpec.java b/okhttp3/src/main/java/okhttp3/ConnectionSpec.java deleted file mode 100644 index 8c99932f9..000000000 --- a/okhttp3/src/main/java/okhttp3/ConnectionSpec.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.util.Arrays; -import java.util.List; -import javax.net.ssl.SSLSocket; - -import static okhttp3.internal.Util.concat; -import static okhttp3.internal.Util.contains; -import static okhttp3.internal.Util.immutableList; -import static okhttp3.internal.Util.intersect; - -/** - * Specifies configuration for the socket connection that HTTP traffic travels through. For {@code - * https:} URLs, this includes the TLS version and cipher suites to use when negotiating a secure - * connection. - * - *

The TLS versions configured in a connection spec are only be used if they are also enabled in - * the SSL socket. For example, if an SSL socket does not have TLS 1.2 enabled, it will not be used - * even if it is present on the connection spec. The same policy also applies to cipher suites. - * - *

Use {@link Builder#allEnabledTlsVersions()} and {@link Builder#allEnabledCipherSuites} to - * defer all feature selection to the underlying SSL socket. - */ -public final class ConnectionSpec { - - // This is a subset of the cipher suites supported in Chrome 46, current as of 2015-11-05. - // All of these suites are available on Android 5.0; earlier releases support a subset of - // these suites. https://github.com/square/okhttp/issues/330 - private static final CipherSuite[] APPROVED_CIPHER_SUITES = new CipherSuite[] { - CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - - // Note that the following cipher suites are all on HTTP/2's bad cipher suites list. We'll - // continue to include them until better suites are commonly available. For example, none - // of the better cipher suites listed above shipped with Android 4.4 or Java 7. - CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256, - CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, - CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, - CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, - }; - - /** A modern TLS connection with extensions like SNI and ALPN available. */ - public static final ConnectionSpec MODERN_TLS = new Builder(true) - .cipherSuites(APPROVED_CIPHER_SUITES) - .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0) - .supportsTlsExtensions(true) - .build(); - - /** A backwards-compatible fallback connection for interop with obsolete servers. */ - public static final ConnectionSpec COMPATIBLE_TLS = new Builder(MODERN_TLS) - .tlsVersions(TlsVersion.TLS_1_0) - .supportsTlsExtensions(true) - .build(); - - /** Unencrypted, unauthenticated connections for {@code http:} URLs. */ - public static final ConnectionSpec CLEARTEXT = new Builder(false).build(); - - private final boolean tls; - private final boolean supportsTlsExtensions; - private final String[] cipherSuites; - private final String[] tlsVersions; - - private ConnectionSpec(Builder builder) { - this.tls = builder.tls; - this.cipherSuites = builder.cipherSuites; - this.tlsVersions = builder.tlsVersions; - this.supportsTlsExtensions = builder.supportsTlsExtensions; - } - - public boolean isTls() { - return tls; - } - - /** - * Returns the cipher suites to use for a connection. Returns {@code null} if all of the SSL - * socket's enabled cipher suites should be used. - */ - public List cipherSuites() { - if (cipherSuites == null) return null; - - CipherSuite[] result = new CipherSuite[cipherSuites.length]; - for (int i = 0; i < cipherSuites.length; i++) { - result[i] = CipherSuite.forJavaName(cipherSuites[i]); - } - return immutableList(result); - } - - /** - * Returns the TLS versions to use when negotiating a connection. Returns {@code null} if all of - * the SSL socket's enabled TLS versions should be used. - */ - public List tlsVersions() { - if (tlsVersions == null) return null; - - TlsVersion[] result = new TlsVersion[tlsVersions.length]; - for (int i = 0; i < tlsVersions.length; i++) { - result[i] = TlsVersion.forJavaName(tlsVersions[i]); - } - return immutableList(result); - } - - public boolean supportsTlsExtensions() { - return supportsTlsExtensions; - } - - /** Applies this spec to {@code sslSocket}. */ - void apply(SSLSocket sslSocket, boolean isFallback) { - ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback); - - if (specToApply.tlsVersions != null) { - sslSocket.setEnabledProtocols(specToApply.tlsVersions); - } - if (specToApply.cipherSuites != null) { - sslSocket.setEnabledCipherSuites(specToApply.cipherSuites); - } - } - - /** - * Returns a copy of this that omits cipher suites and TLS versions not enabled by {@code - * sslSocket}. - */ - private ConnectionSpec supportedSpec(SSLSocket sslSocket, boolean isFallback) { - String[] cipherSuitesIntersection = cipherSuites != null - ? intersect(String.class, cipherSuites, sslSocket.getEnabledCipherSuites()) - : sslSocket.getEnabledCipherSuites(); - String[] tlsVersionsIntersection = tlsVersions != null - ? intersect(String.class, tlsVersions, sslSocket.getEnabledProtocols()) - : sslSocket.getEnabledProtocols(); - - // In accordance with https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00 - // the SCSV cipher is added to signal that a protocol fallback has taken place. - if (isFallback && contains(sslSocket.getSupportedCipherSuites(), "TLS_FALLBACK_SCSV")) { - cipherSuitesIntersection = concat(cipherSuitesIntersection, "TLS_FALLBACK_SCSV"); - } - - return new Builder(this) - .cipherSuites(cipherSuitesIntersection) - .tlsVersions(tlsVersionsIntersection) - .build(); - } - - /** - * Returns {@code true} if the socket, as currently configured, supports this connection spec. In - * order for a socket to be compatible the enabled cipher suites and protocols must intersect. - * - *

For cipher suites, at least one of the {@link #cipherSuites() required cipher suites} must - * match the socket's enabled cipher suites. If there are no required cipher suites the socket - * must have at least one cipher suite enabled. - * - *

For protocols, at least one of the {@link #tlsVersions() required protocols} must match the - * socket's enabled protocols. - */ - public boolean isCompatible(SSLSocket socket) { - if (!tls) { - return false; - } - - if (tlsVersions != null - && !nonEmptyIntersection(tlsVersions, socket.getEnabledProtocols())) { - return false; - } - - if (cipherSuites != null - && !nonEmptyIntersection(cipherSuites, socket.getEnabledCipherSuites())) { - return false; - } - - return true; - } - - /** - * An N*M intersection that terminates if any intersection is found. The sizes of both arguments - * are assumed to be so small, and the likelihood of an intersection so great, that it is not - * worth the CPU cost of sorting or the memory cost of hashing. - */ - private static boolean nonEmptyIntersection(String[] a, String[] b) { - if (a == null || b == null || a.length == 0 || b.length == 0) { - return false; - } - for (String toFind : a) { - if (contains(b, toFind)) { - return true; - } - } - return false; - } - - @Override public boolean equals(Object other) { - if (!(other instanceof ConnectionSpec)) return false; - if (other == this) return true; - - ConnectionSpec that = (ConnectionSpec) other; - if (this.tls != that.tls) return false; - - if (tls) { - if (!Arrays.equals(this.cipherSuites, that.cipherSuites)) return false; - if (!Arrays.equals(this.tlsVersions, that.tlsVersions)) return false; - if (this.supportsTlsExtensions != that.supportsTlsExtensions) return false; - } - - return true; - } - - @Override public int hashCode() { - int result = 17; - if (tls) { - result = 31 * result + Arrays.hashCode(cipherSuites); - result = 31 * result + Arrays.hashCode(tlsVersions); - result = 31 * result + (supportsTlsExtensions ? 0 : 1); - } - return result; - } - - @Override public String toString() { - if (!tls) { - return "ConnectionSpec()"; - } - - String cipherSuitesString = cipherSuites != null ? cipherSuites().toString() : "[all enabled]"; - String tlsVersionsString = tlsVersions != null ? tlsVersions().toString() : "[all enabled]"; - return "ConnectionSpec(" - + "cipherSuites=" + cipherSuitesString - + ", tlsVersions=" + tlsVersionsString - + ", supportsTlsExtensions=" + supportsTlsExtensions - + ")"; - } - - public static final class Builder { - private boolean tls; - private String[] cipherSuites; - private String[] tlsVersions; - private boolean supportsTlsExtensions; - - Builder(boolean tls) { - this.tls = tls; - } - - public Builder(ConnectionSpec connectionSpec) { - this.tls = connectionSpec.tls; - this.cipherSuites = connectionSpec.cipherSuites; - this.tlsVersions = connectionSpec.tlsVersions; - this.supportsTlsExtensions = connectionSpec.supportsTlsExtensions; - } - - public Builder allEnabledCipherSuites() { - if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections"); - this.cipherSuites = null; - return this; - } - - public Builder cipherSuites(CipherSuite... cipherSuites) { - if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections"); - - String[] strings = new String[cipherSuites.length]; - for (int i = 0; i < cipherSuites.length; i++) { - strings[i] = cipherSuites[i].javaName; - } - return cipherSuites(strings); - } - - public Builder cipherSuites(String... cipherSuites) { - if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections"); - - if (cipherSuites.length == 0) { - throw new IllegalArgumentException("At least one cipher suite is required"); - } - - this.cipherSuites = cipherSuites.clone(); // Defensive copy. - return this; - } - - public Builder allEnabledTlsVersions() { - if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections"); - this.tlsVersions = null; - return this; - } - - public Builder tlsVersions(TlsVersion... tlsVersions) { - if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections"); - - String[] strings = new String[tlsVersions.length]; - for (int i = 0; i < tlsVersions.length; i++) { - strings[i] = tlsVersions[i].javaName; - } - - return tlsVersions(strings); - } - - public Builder tlsVersions(String... tlsVersions) { - if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections"); - - if (tlsVersions.length == 0) { - throw new IllegalArgumentException("At least one TLS version is required"); - } - - this.tlsVersions = tlsVersions.clone(); // Defensive copy. - return this; - } - - public Builder supportsTlsExtensions(boolean supportsTlsExtensions) { - if (!tls) throw new IllegalStateException("no TLS extensions for cleartext connections"); - this.supportsTlsExtensions = supportsTlsExtensions; - return this; - } - - public ConnectionSpec build() { - return new ConnectionSpec(this); - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/Cookie.java b/okhttp3/src/main/java/okhttp3/Cookie.java deleted file mode 100644 index 74e341de4..000000000 --- a/okhttp3/src/main/java/okhttp3/Cookie.java +++ /dev/null @@ -1,590 +0,0 @@ -/* - * Copyright (C) 2015 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import okhttp3.internal.Util; -import okhttp3.internal.http.HttpDate; - -import static okhttp3.internal.Util.UTC; -import static okhttp3.internal.Util.delimiterOffset; -import static okhttp3.internal.Util.domainToAscii; -import static okhttp3.internal.Util.trimSubstring; -import static okhttp3.internal.Util.verifyAsIpAddress; - -/** - * An RFC 6265 Cookie. - * - *

This class doesn't support additional attributes on cookies, like Chromium's Priority=HIGH - * extension. - */ -public final class Cookie { - private static final Pattern YEAR_PATTERN - = Pattern.compile("(\\d{2,4})[^\\d]*"); - private static final Pattern MONTH_PATTERN - = Pattern.compile("(?i)(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec).*"); - private static final Pattern DAY_OF_MONTH_PATTERN - = Pattern.compile("(\\d{1,2})[^\\d]*"); - private static final Pattern TIME_PATTERN - = Pattern.compile("(\\d{1,2}):(\\d{1,2}):(\\d{1,2})[^\\d]*"); - - private final String name; - private final String value; - private final long expiresAt; - private final String domain; - private final String path; - private final boolean secure; - private final boolean httpOnly; - - private final boolean persistent; // True if 'expires' or 'max-age' is present. - private final boolean hostOnly; // True unless 'domain' is present. - - private Cookie(String name, String value, long expiresAt, String domain, String path, - boolean secure, boolean httpOnly, boolean hostOnly, boolean persistent) { - this.name = name; - this.value = value; - this.expiresAt = expiresAt; - this.domain = domain; - this.path = path; - this.secure = secure; - this.httpOnly = httpOnly; - this.hostOnly = hostOnly; - this.persistent = persistent; - } - - private Cookie(Builder builder) { - if (builder.name == null) throw new IllegalArgumentException("builder.name == null"); - if (builder.value == null) throw new IllegalArgumentException("builder.value == null"); - if (builder.domain == null) throw new IllegalArgumentException("builder.domain == null"); - - this.name = builder.name; - this.value = builder.value; - this.expiresAt = builder.expiresAt; - this.domain = builder.domain; - this.path = builder.path; - this.secure = builder.secure; - this.httpOnly = builder.httpOnly; - this.persistent = builder.persistent; - this.hostOnly = builder.hostOnly; - } - - /** Returns a non-empty string with this cookie's name. */ - public String name() { - return name; - } - - /** Returns a possibly-empty string with this cookie's value. */ - public String value() { - return value; - } - - /** Returns true if this cookie expires at the end of the current session. */ - public boolean persistent() { - return persistent; - } - - /** - * Returns the time that this cookie expires, in the same format as {@link - * System#currentTimeMillis()}. This is December 31, 9999 if the cookie is {@linkplain - * #persistent() not persistent}, in which case it will expire at the end of the current session. - * - *

This may return a value less than the current time, in which case the cookie is already - * expired. Webservers may return expired cookies as a mechanism to delete previously set cookies - * that may or may not themselves be expired. - */ - public long expiresAt() { - return expiresAt; - } - - /** - * Returns true if this cookie's domain should be interpreted as a single host name, or false if - * it should be interpreted as a pattern. This flag will be false if its {@code Set-Cookie} header - * included a {@code domain} attribute. - * - *

For example, suppose the cookie's domain is {@code example.com}. If this flag is true it - * matches only {@code example.com}. If this flag is false it matches {@code - * example.com} and all subdomains including {@code api.example.com}, {@code www.example.com}, and - * {@code beta.api.example.com}. - */ - public boolean hostOnly() { - return hostOnly; - } - - /** - * Returns the cookie's domain. If {@link #hostOnly()} returns true this is the only domain that - * matches this cookie; otherwise it matches this domain and all subdomains. - */ - public String domain() { - return domain; - } - - /** - * Returns this cookie's path. This cookie matches URLs prefixed with path segments that match - * this path's segments. For example, if this path is {@code /foo} this cookie matches requests to - * {@code /foo} and {@code /foo/bar}, but not {@code /} or {@code /football}. - */ - public String path() { - return path; - } - - /** - * Returns true if this cookie should be limited to only HTTP APIs. In web browsers this prevents - * the cookie from being accessible to scripts. - */ - public boolean httpOnly() { - return httpOnly; - } - - /** Returns true if this cookie should be limited to only HTTPS requests. */ - public boolean secure() { - return secure; - } - - /** - * Returns true if this cookie should be included on a request to {@code url}. In addition to this - * check callers should also confirm that this cookie has not expired. - */ - public boolean matches(HttpUrl url) { - boolean domainMatch = hostOnly - ? url.host().equals(domain) - : domainMatch(url, domain); - if (!domainMatch) return false; - - if (!pathMatch(url, path)) return false; - - if (secure && !url.isHttps()) return false; - - return true; - } - - private static boolean domainMatch(HttpUrl url, String domain) { - String urlHost = url.host(); - - if (urlHost.equals(domain)) { - return true; // As in 'example.com' matching 'example.com'. - } - - if (urlHost.endsWith(domain) - && urlHost.charAt(urlHost.length() - domain.length() - 1) == '.' - && !verifyAsIpAddress(urlHost)) { - return true; // As in 'example.com' matching 'www.example.com'. - } - - return false; - } - - private static boolean pathMatch(HttpUrl url, String path) { - String urlPath = url.encodedPath(); - - if (urlPath.equals(path)) { - return true; // As in '/foo' matching '/foo'. - } - - if (urlPath.startsWith(path)) { - if (path.endsWith("/")) return true; // As in '/' matching '/foo'. - if (urlPath.charAt(path.length()) == '/') return true; // As in '/foo' matching '/foo/bar'. - } - - return false; - } - - /** - * Attempt to parse a {@code Set-Cookie} HTTP header value {@code setCookie} as a cookie. Returns - * null if {@code setCookie} is not a well-formed cookie. - */ - public static Cookie parse(HttpUrl url, String setCookie) { - return parse(System.currentTimeMillis(), url, setCookie); - } - - static Cookie parse(long currentTimeMillis, HttpUrl url, String setCookie) { - int pos = 0; - int limit = setCookie.length(); - int cookiePairEnd = delimiterOffset(setCookie, pos, limit, ';'); - - int pairEqualsSign = delimiterOffset(setCookie, pos, cookiePairEnd, '='); - if (pairEqualsSign == cookiePairEnd) return null; - - String cookieName = trimSubstring(setCookie, pos, pairEqualsSign); - if (cookieName.isEmpty()) return null; - - String cookieValue = trimSubstring(setCookie, pairEqualsSign + 1, cookiePairEnd); - - long expiresAt = HttpDate.MAX_DATE; - long deltaSeconds = -1L; - String domain = null; - String path = null; - boolean secureOnly = false; - boolean httpOnly = false; - boolean hostOnly = true; - boolean persistent = false; - - pos = cookiePairEnd + 1; - while (pos < limit) { - int attributePairEnd = delimiterOffset(setCookie, pos, limit, ';'); - - int attributeEqualsSign = delimiterOffset(setCookie, pos, attributePairEnd, '='); - String attributeName = trimSubstring(setCookie, pos, attributeEqualsSign); - String attributeValue = attributeEqualsSign < attributePairEnd - ? trimSubstring(setCookie, attributeEqualsSign + 1, attributePairEnd) - : ""; - - if (attributeName.equalsIgnoreCase("expires")) { - try { - expiresAt = parseExpires(attributeValue, 0, attributeValue.length()); - persistent = true; - } catch (IllegalArgumentException e) { - // Ignore this attribute, it isn't recognizable as a date. - } - } else if (attributeName.equalsIgnoreCase("max-age")) { - try { - deltaSeconds = parseMaxAge(attributeValue); - persistent = true; - } catch (NumberFormatException e) { - // Ignore this attribute, it isn't recognizable as a max age. - } - } else if (attributeName.equalsIgnoreCase("domain")) { - try { - domain = parseDomain(attributeValue); - hostOnly = false; - } catch (IllegalArgumentException e) { - // Ignore this attribute, it isn't recognizable as a domain. - } - } else if (attributeName.equalsIgnoreCase("path")) { - path = attributeValue; - } else if (attributeName.equalsIgnoreCase("secure")) { - secureOnly = true; - } else if (attributeName.equalsIgnoreCase("httponly")) { - httpOnly = true; - } - - pos = attributePairEnd + 1; - } - - // If 'Max-Age' is present, it takes precedence over 'Expires', regardless of the order the two - // attributes are declared in the cookie string. - if (deltaSeconds == Long.MIN_VALUE) { - expiresAt = Long.MIN_VALUE; - } else if (deltaSeconds != -1L) { - long deltaMilliseconds = deltaSeconds <= (Long.MAX_VALUE / 1000) - ? deltaSeconds * 1000 - : Long.MAX_VALUE; - expiresAt = currentTimeMillis + deltaMilliseconds; - if (expiresAt < currentTimeMillis || expiresAt > HttpDate.MAX_DATE) { - expiresAt = HttpDate.MAX_DATE; // Handle overflow & limit the date range. - } - } - - // If the domain is present, it must domain match. Otherwise we have a host-only cookie. - if (domain == null) { - domain = url.host(); - } else if (!domainMatch(url, domain)) { - return null; // No domain match? This is either incompetence or malice! - } - - // If the path is absent or didn't start with '/', use the default path. It's a string like - // '/foo/bar' for a URL like 'http://example.com/foo/bar/baz'. It always starts with '/'. - if (path == null || !path.startsWith("/")) { - String encodedPath = url.encodedPath(); - int lastSlash = encodedPath.lastIndexOf('/'); - path = lastSlash != 0 ? encodedPath.substring(0, lastSlash) : "/"; - } - - return new Cookie(cookieName, cookieValue, expiresAt, domain, path, secureOnly, httpOnly, - hostOnly, persistent); - } - - /** Parse a date as specified in RFC 6265, section 5.1.1. */ - private static long parseExpires(String s, int pos, int limit) { - pos = dateCharacterOffset(s, pos, limit, false); - - int hour = -1; - int minute = -1; - int second = -1; - int dayOfMonth = -1; - int month = -1; - int year = -1; - Matcher matcher = TIME_PATTERN.matcher(s); - - while (pos < limit) { - int end = dateCharacterOffset(s, pos + 1, limit, true); - matcher.region(pos, end); - - if (hour == -1 && matcher.usePattern(TIME_PATTERN).matches()) { - hour = Integer.parseInt(matcher.group(1)); - minute = Integer.parseInt(matcher.group(2)); - second = Integer.parseInt(matcher.group(3)); - } else if (dayOfMonth == -1 && matcher.usePattern(DAY_OF_MONTH_PATTERN).matches()) { - dayOfMonth = Integer.parseInt(matcher.group(1)); - } else if (month == -1 && matcher.usePattern(MONTH_PATTERN).matches()) { - String monthString = matcher.group(1).toLowerCase(Locale.US); - month = MONTH_PATTERN.pattern().indexOf(monthString) / 4; // Sneaky! jan=1, dec=12. - } else if (year == -1 && matcher.usePattern(YEAR_PATTERN).matches()) { - year = Integer.parseInt(matcher.group(1)); - } - - pos = dateCharacterOffset(s, end + 1, limit, false); - } - - // Convert two-digit years into four-digit years. 99 becomes 1999, 15 becomes 2015. - if (year >= 70 && year <= 99) year += 1900; - if (year >= 0 && year <= 69) year += 2000; - - // If any partial is omitted or out of range, return -1. The date is impossible. Note that leap - // seconds are not supported by this syntax. - if (year < 1601) throw new IllegalArgumentException(); - if (month == -1) throw new IllegalArgumentException(); - if (dayOfMonth < 1 || dayOfMonth > 31) throw new IllegalArgumentException(); - if (hour < 0 || hour > 23) throw new IllegalArgumentException(); - if (minute < 0 || minute > 59) throw new IllegalArgumentException(); - if (second < 0 || second > 59) throw new IllegalArgumentException(); - - Calendar calendar = new GregorianCalendar(UTC); - calendar.setLenient(false); - calendar.set(Calendar.YEAR, year); - calendar.set(Calendar.MONTH, month - 1); - calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth); - calendar.set(Calendar.HOUR_OF_DAY, hour); - calendar.set(Calendar.MINUTE, minute); - calendar.set(Calendar.SECOND, second); - calendar.set(Calendar.MILLISECOND, 0); - return calendar.getTimeInMillis(); - } - - /** - * Returns the index of the next date character in {@code input}, or if {@code invert} the index - * of the next non-date character in {@code input}. - */ - private static int dateCharacterOffset(String input, int pos, int limit, boolean invert) { - for (int i = pos; i < limit; i++) { - int c = input.charAt(i); - boolean dateCharacter = (c < ' ' && c != '\t') || (c >= '\u007f') - || (c >= '0' && c <= '9') - || (c >= 'a' && c <= 'z') - || (c >= 'A' && c <= 'Z') - || (c == ':'); - if (dateCharacter == !invert) return i; - } - return limit; - } - - /** - * Returns the positive value if {@code attributeValue} is positive, or {@link Long#MIN_VALUE} if - * it is either 0 or negative. If the value is positive but out of range, this returns {@link - * Long#MAX_VALUE}. - * - * @throws NumberFormatException if {@code s} is not an integer of any precision. - */ - private static long parseMaxAge(String s) { - try { - long parsed = Long.parseLong(s); - return parsed <= 0L ? Long.MIN_VALUE : parsed; - } catch (NumberFormatException e) { - // Check if the value is an integer (positive or negative) that's too big for a long. - if (s.matches("-?\\d+")) { - return s.startsWith("-") ? Long.MIN_VALUE : Long.MAX_VALUE; - } - throw e; - } - } - - /** - * Returns a domain string like {@code example.com} for an input domain like {@code EXAMPLE.COM} - * or {@code .example.com}. - */ - private static String parseDomain(String s) { - if (s.endsWith(".")) { - throw new IllegalArgumentException(); - } - if (s.startsWith(".")) { - s = s.substring(1); - } - String canonicalDomain = domainToAscii(s); - if (canonicalDomain == null) { - throw new IllegalArgumentException(); - } - return canonicalDomain; - } - - /** Returns all of the cookies from a set of HTTP response headers. */ - public static List parseAll(HttpUrl url, Headers headers) { - List cookieStrings = headers.values("Set-Cookie"); - List cookies = null; - - for (int i = 0, size = cookieStrings.size(); i < size; i++) { - Cookie cookie = Cookie.parse(url, cookieStrings.get(i)); - if (cookie == null) continue; - if (cookies == null) cookies = new ArrayList<>(); - cookies.add(cookie); - } - - return cookies != null - ? Collections.unmodifiableList(cookies) - : Collections.emptyList(); - } - - /** - * Builds a cookie. The {@linkplain #name() name}, {@linkplain #value() value}, and {@linkplain - * #domain() domain} values must all be set before calling {@link #build}. - */ - public static final class Builder { - String name; - String value; - long expiresAt = HttpDate.MAX_DATE; - String domain; - String path = "/"; - boolean secure; - boolean httpOnly; - boolean persistent; - boolean hostOnly; - - public Builder name(String name) { - if (name == null) throw new NullPointerException("name == null"); - if (!name.trim().equals(name)) throw new IllegalArgumentException("name is not trimmed"); - this.name = name; - return this; - } - - public Builder value(String value) { - if (value == null) throw new NullPointerException("value == null"); - if (!value.trim().equals(value)) throw new IllegalArgumentException("value is not trimmed"); - this.value = value; - return this; - } - - public Builder expiresAt(long expiresAt) { - if (expiresAt <= 0) expiresAt = Long.MIN_VALUE; - if (expiresAt > HttpDate.MAX_DATE) expiresAt = HttpDate.MAX_DATE; - this.expiresAt = expiresAt; - this.persistent = true; - return this; - } - - /** - * Set the domain pattern for this cookie. The cookie will match {@code domain} and all of its - * subdomains. - */ - public Builder domain(String domain) { - return domain(domain, false); - } - - /** - * Set the host-only domain for this cookie. The cookie will match {@code domain} but none of - * its subdomains. - */ - public Builder hostOnlyDomain(String domain) { - return domain(domain, true); - } - - private Builder domain(String domain, boolean hostOnly) { - if (domain == null) throw new IllegalArgumentException("domain == null"); - String canonicalDomain = Util.domainToAscii(domain); - if (canonicalDomain == null) { - throw new IllegalArgumentException("unexpected domain: " + domain); - } - this.domain = canonicalDomain; - this.hostOnly = hostOnly; - return this; - } - - public Builder path(String path) { - if (!path.startsWith("/")) throw new IllegalArgumentException("path must start with '/'"); - this.path = path; - return this; - } - - public Builder secure() { - this.secure = true; - return this; - } - - public Builder httpOnly() { - this.httpOnly = true; - return this; - } - - public Cookie build() { - return new Cookie(this); - } - } - - @Override public String toString() { - StringBuilder result = new StringBuilder(); - result.append(name); - result.append('='); - result.append(value); - - if (persistent) { - if (expiresAt == Long.MIN_VALUE) { - result.append("; max-age=0"); - } else { - result.append("; expires=").append(HttpDate.format(new Date(expiresAt))); - } - } - - if (!hostOnly) { - result.append("; domain=").append(domain); - } - - result.append("; path=").append(path); - - if (secure) { - result.append("; secure"); - } - - if (httpOnly) { - result.append("; httponly"); - } - - return result.toString(); - } - - @Override public boolean equals(Object other) { - if (!(other instanceof Cookie)) return false; - Cookie that = (Cookie) other; - return that.name.equals(name) - && that.value.equals(value) - && that.domain.equals(domain) - && that.path.equals(path) - && that.expiresAt == expiresAt - && that.secure == secure - && that.httpOnly == httpOnly - && that.persistent == persistent - && that.hostOnly == hostOnly; - } - - @Override public int hashCode() { - int hash = 17; - hash = 31 * hash + name.hashCode(); - hash = 31 * hash + value.hashCode(); - hash = 31 * hash + domain.hashCode(); - hash = 31 * hash + path.hashCode(); - hash = 31 * hash + (int) (expiresAt ^ (expiresAt >>> 32)); - hash = 31 * hash + (secure ? 0 : 1); - hash = 31 * hash + (httpOnly ? 0 : 1); - hash = 31 * hash + (persistent ? 0 : 1); - hash = 31 * hash + (hostOnly ? 0 : 1); - return hash; - } -} diff --git a/okhttp3/src/main/java/okhttp3/CookieJar.java b/okhttp3/src/main/java/okhttp3/CookieJar.java deleted file mode 100644 index 7f6239219..000000000 --- a/okhttp3/src/main/java/okhttp3/CookieJar.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2015 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.util.Collections; -import java.util.List; - -/** - * Provides policy and persistence for HTTP cookies. - * - *

As policy, implementations of this interface are responsible for selecting which cookies to - * accept and which to reject. A reasonable policy is to reject all cookies, though that may be - * interfere with session-based authentication schemes that require cookies. - * - *

As persistence, implementations of this interface must also provide storage of cookies. Simple - * implementations may store cookies in memory; sophisticated ones may use the file system or - * database to hold accepted cookies. The cookie storage model specifies - * policies for updating and expiring cookies. - */ -public interface CookieJar { - /** A cookie jar that never accepts any cookies. */ - CookieJar NO_COOKIES = new CookieJar() { - @Override public void saveFromResponse(HttpUrl url, List cookies) { - } - - @Override public List loadForRequest(HttpUrl url, Request request) { - return Collections.emptyList(); - } - }; - - /** - * Saves {@code cookies} from an HTTP response to this store according to this jar's policy. - * - *

Note that this method may be called a second time for a single HTTP response if the response - * includes a trailer. For this obscure HTTP feature, {@code cookies} contains only the trailer's - * cookies. - */ - void saveFromResponse(HttpUrl url, List cookies); - - /** - * Load cookies from the jar for an HTTP request to {@code url}. This method returns a possibly - * empty list of cookies for the network request. - * - *

Simple implementations will return the accepted cookies that have not yet expired and that - * {@linkplain Cookie#matches match} {@code url}. - */ - List loadForRequest(HttpUrl url, Request request); -} diff --git a/okhttp3/src/main/java/okhttp3/Credentials.java b/okhttp3/src/main/java/okhttp3/Credentials.java deleted file mode 100644 index 9be4e6a22..000000000 --- a/okhttp3/src/main/java/okhttp3/Credentials.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.io.UnsupportedEncodingException; -import okio.ByteString; - -/** Factory for HTTP authorization credentials. */ -public final class Credentials { - private Credentials() { - } - - /** Returns an auth credential for the Basic scheme. */ - public static String basic(String userName, String password) { - try { - String usernameAndPassword = userName + ":" + password; - byte[] bytes = usernameAndPassword.getBytes("ISO-8859-1"); - String encoded = ByteString.of(bytes).base64(); - return "Basic " + encoded; - } catch (UnsupportedEncodingException e) { - throw new AssertionError(); - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/Dispatcher.java b/okhttp3/src/main/java/okhttp3/Dispatcher.java deleted file mode 100644 index 5a135b325..000000000 --- a/okhttp3/src/main/java/okhttp3/Dispatcher.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Deque; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import okhttp3.RealCall.AsyncCall; -import okhttp3.internal.Util; - -/** - * Policy on when async requests are executed. - * - *

Each dispatcher uses an {@link ExecutorService} to run calls internally. If you supply your - * own executor, it should be able to run {@linkplain #getMaxRequests the configured maximum} number - * of calls concurrently. - */ -public final class Dispatcher { - private int maxRequests = 64; - private int maxRequestsPerHost = 5; - - /** Executes calls. Created lazily. */ - private ExecutorService executorService; - - /** Ready async calls in the order they'll be run. */ - private final Deque readyAsyncCalls = new ArrayDeque<>(); - - /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */ - private final Deque runningAsyncCalls = new ArrayDeque<>(); - - /** Running synchronous calls. Includes canceled calls that haven't finished yet. */ - private final Deque runningSyncCalls = new ArrayDeque<>(); - - public Dispatcher(ExecutorService executorService) { - this.executorService = executorService; - } - - public Dispatcher() { - } - - public synchronized ExecutorService executorService() { - if (executorService == null) { - executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, - new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false)); - } - return executorService; - } - - /** - * Set the maximum number of requests to execute concurrently. Above this requests queue in - * memory, waiting for the running calls to complete. - * - *

If more than {@code maxRequests} requests are in flight when this is invoked, those requests - * will remain in flight. - */ - public synchronized void setMaxRequests(int maxRequests) { - if (maxRequests < 1) { - throw new IllegalArgumentException("max < 1: " + maxRequests); - } - this.maxRequests = maxRequests; - promoteCalls(); - } - - public synchronized int getMaxRequests() { - return maxRequests; - } - - /** - * Set the maximum number of requests for each host to execute concurrently. This limits requests - * by the URL's host name. Note that concurrent requests to a single IP address may still exceed - * this limit: multiple hostnames may share an IP address or be routed through the same HTTP - * proxy. - * - *

If more than {@code maxRequestsPerHost} requests are in flight when this is invoked, those - * requests will remain in flight. - */ - public synchronized void setMaxRequestsPerHost(int maxRequestsPerHost) { - if (maxRequestsPerHost < 1) { - throw new IllegalArgumentException("max < 1: " + maxRequestsPerHost); - } - this.maxRequestsPerHost = maxRequestsPerHost; - promoteCalls(); - } - - public synchronized int getMaxRequestsPerHost() { - return maxRequestsPerHost; - } - - synchronized void enqueue(AsyncCall call) { - if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { - runningAsyncCalls.add(call); - executorService().execute(call); - } else { - readyAsyncCalls.add(call); - } - } - - /** - * Cancel all calls currently enqueued or executing. Includes calls executed both {@linkplain - * Call#execute() synchronously} and {@linkplain Call#enqueue asynchronously}. - */ - public synchronized void cancelAll() { - for (AsyncCall call : readyAsyncCalls) { - call.cancel(); - } - - for (AsyncCall call : runningAsyncCalls) { - call.cancel(); - } - - for (RealCall call : runningSyncCalls) { - call.cancel(); - } - } - - /** Used by {@code AsyncCall#run} to signal completion. */ - synchronized void finished(AsyncCall call) { - if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!"); - promoteCalls(); - } - - private void promoteCalls() { - if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. - if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. - - for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) { - AsyncCall call = i.next(); - - if (runningCallsForHost(call) < maxRequestsPerHost) { - i.remove(); - runningAsyncCalls.add(call); - executorService().execute(call); - } - - if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. - } - } - - /** Returns the number of running calls that share a host with {@code call}. */ - private int runningCallsForHost(AsyncCall call) { - int result = 0; - for (AsyncCall c : runningAsyncCalls) { - if (c.host().equals(call.host())) result++; - } - return result; - } - - /** Used by {@code Call#execute} to signal it is in-flight. */ - synchronized void executed(RealCall call) { - runningSyncCalls.add(call); - } - - /** Used by {@code Call#execute} to signal completion. */ - synchronized void finished(Call call) { - if (!runningSyncCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); - } - - /** Returns a snapshot of the calls currently awaiting execution. */ - public synchronized List queuedCalls() { - List result = new ArrayList<>(); - for (AsyncCall asyncCall : readyAsyncCalls) { - result.add(asyncCall.get()); - } - return Collections.unmodifiableList(result); - } - - /** Returns a snapshot of the calls currently being executed. */ - public synchronized List runningCalls() { - List result = new ArrayList<>(); - result.addAll(runningSyncCalls); - for (AsyncCall asyncCall : runningAsyncCalls) { - result.add(asyncCall.get()); - } - return Collections.unmodifiableList(result); - } - - public synchronized int queuedCallsCount() { - return readyAsyncCalls.size(); - } - - public synchronized int runningCallsCount() { - return runningAsyncCalls.size() + runningSyncCalls.size(); - } -} diff --git a/okhttp3/src/main/java/okhttp3/Dns.java b/okhttp3/src/main/java/okhttp3/Dns.java deleted file mode 100644 index a2e6db591..000000000 --- a/okhttp3/src/main/java/okhttp3/Dns.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2012 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.List; - -/** - * A domain name service that resolves IP addresses for host names. Most applications will use the - * {@linkplain #SYSTEM system DNS service}, which is the default. Some applications may provide - * their own implementation to use a different DNS server, to prefer IPv6 addresses, to prefer IPv4 - * addresses, or to force a specific known IP address. - * - *

Implementations of this interface must be safe for concurrent use. - */ -public interface Dns { - /** - * A DNS that uses {@link InetAddress#getAllByName} to ask the underlying operating system to - * lookup IP addresses. Most custom {@link Dns} implementations should delegate to this instance. - */ - Dns SYSTEM = new Dns() { - @Override public List lookup(String hostname) throws UnknownHostException { - if (hostname == null) throw new UnknownHostException("hostname == null"); - return Arrays.asList(InetAddress.getAllByName(hostname)); - } - }; - - /** - * Returns the IP addresses of {@code hostname}, in the order they will be attempted by OkHttp. If - * a connection to an address fails, OkHttp will retry the connection with the next address until - * either a connection is made, the set of IP addresses is exhausted, or a limit is exceeded. - */ - List lookup(String hostname) throws UnknownHostException; -} diff --git a/okhttp3/src/main/java/okhttp3/FormBody.java b/okhttp3/src/main/java/okhttp3/FormBody.java deleted file mode 100644 index 95181b1f7..000000000 --- a/okhttp3/src/main/java/okhttp3/FormBody.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import okhttp3.internal.Util; -import okio.Buffer; -import okio.BufferedSink; - -import static okhttp3.HttpUrl.FORM_ENCODE_SET; -import static okhttp3.HttpUrl.percentDecode; - -public final class FormBody extends RequestBody { - private static final MediaType CONTENT_TYPE = - MediaType.parse("application/x-www-form-urlencoded"); - - private final List encodedNames; - private final List encodedValues; - - private FormBody(List encodedNames, List encodedValues) { - this.encodedNames = Util.immutableList(encodedNames); - this.encodedValues = Util.immutableList(encodedValues); - } - - /** The number of key-value pairs in this form-encoded body. */ - public int size() { - return encodedNames.size(); - } - - public String encodedName(int index) { - return encodedNames.get(index); - } - - public String name(int index) { - return percentDecode(encodedName(index), true); - } - - public String encodedValue(int index) { - return encodedValues.get(index); - } - - public String value(int index) { - return percentDecode(encodedValue(index), true); - } - - @Override public MediaType contentType() { - return CONTENT_TYPE; - } - - @Override public long contentLength() { - return writeOrCountBytes(null, true); - } - - @Override public void writeTo(BufferedSink sink) throws IOException { - writeOrCountBytes(sink, false); - } - - /** - * Either writes this request to {@code sink} or measures its content length. We have one method - * do double-duty to make sure the counting and content are consistent, particularly when it comes - * to awkward operations like measuring the encoded length of header strings, or the - * length-in-digits of an encoded integer. - */ - private long writeOrCountBytes(BufferedSink sink, boolean countBytes) { - long byteCount = 0L; - - Buffer buffer; - if (countBytes) { - buffer = new Buffer(); - } else { - buffer = sink.buffer(); - } - - for (int i = 0, size = encodedNames.size(); i < size; i++) { - if (i > 0) buffer.writeByte('&'); - buffer.writeUtf8(encodedNames.get(i)); - buffer.writeByte('='); - buffer.writeUtf8(encodedValues.get(i)); - } - - if (countBytes) { - byteCount = buffer.size(); - buffer.clear(); - } - - return byteCount; - } - - public static final class Builder { - private final List names = new ArrayList<>(); - private final List values = new ArrayList<>(); - - public Builder add(String name, String value) { - names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, false, false, true, true)); - values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, false, false, true, true)); - return this; - } - - public Builder addEncoded(String name, String value) { - names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, true, false, true, true)); - values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, true, false, true, true)); - return this; - } - - public FormBody build() { - return new FormBody(names, values); - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/Handshake.java b/okhttp3/src/main/java/okhttp3/Handshake.java deleted file mode 100644 index 56e1fd4a2..000000000 --- a/okhttp3/src/main/java/okhttp3/Handshake.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.security.Principal; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.Collections; -import java.util.List; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import okhttp3.internal.Util; - -/** - * A record of a TLS handshake. For HTTPS clients, the client is local and the remote server - * is its peer. - * - *

This value object describes a completed handshake. Use {@link ConnectionSpec} to set policy - * for new handshakes. - */ -public final class Handshake { - private final TlsVersion tlsVersion; - private final CipherSuite cipherSuite; - private final List peerCertificates; - private final List localCertificates; - - private Handshake(TlsVersion tlsVersion, CipherSuite cipherSuite, - List peerCertificates, List localCertificates) { - this.tlsVersion = tlsVersion; - this.cipherSuite = cipherSuite; - this.peerCertificates = peerCertificates; - this.localCertificates = localCertificates; - } - - public static Handshake get(SSLSession session) { - String cipherSuiteString = session.getCipherSuite(); - if (cipherSuiteString == null) throw new IllegalStateException("cipherSuite == null"); - CipherSuite cipherSuite = CipherSuite.forJavaName(cipherSuiteString); - - String tlsVersionString = session.getProtocol(); - if (tlsVersionString == null) throw new IllegalStateException("tlsVersion == null"); - TlsVersion tlsVersion = TlsVersion.forJavaName(tlsVersionString); - - Certificate[] peerCertificates; - try { - peerCertificates = session.getPeerCertificates(); - } catch (SSLPeerUnverifiedException ignored) { - peerCertificates = null; - } - List peerCertificatesList = peerCertificates != null - ? Util.immutableList(peerCertificates) - : Collections.emptyList(); - - Certificate[] localCertificates = session.getLocalCertificates(); - List localCertificatesList = localCertificates != null - ? Util.immutableList(localCertificates) - : Collections.emptyList(); - - return new Handshake(tlsVersion, cipherSuite, peerCertificatesList, localCertificatesList); - } - - public static Handshake get(TlsVersion tlsVersion, CipherSuite cipherSuite, - List peerCertificates, List localCertificates) { - if (cipherSuite == null) throw new IllegalArgumentException("cipherSuite == null"); - return new Handshake(tlsVersion, cipherSuite, Util.immutableList(peerCertificates), - Util.immutableList(localCertificates)); - } - - /** - * Returns the TLS version used for this connection. May return null if the response was cached - * with a version of OkHttp prior to 3.0. - */ - public TlsVersion tlsVersion() { - return tlsVersion; - } - - /** Returns the cipher suite used for the connection. */ - public CipherSuite cipherSuite() { - return cipherSuite; - } - - /** Returns a possibly-empty list of certificates that identify the remote peer. */ - public List peerCertificates() { - return peerCertificates; - } - - /** Returns the remote peer's principle, or null if that peer is anonymous. */ - public Principal peerPrincipal() { - return !peerCertificates.isEmpty() - ? ((X509Certificate) peerCertificates.get(0)).getSubjectX500Principal() - : null; - } - - /** Returns a possibly-empty list of certificates that identify this peer. */ - public List localCertificates() { - return localCertificates; - } - - /** Returns the local principle, or null if this peer is anonymous. */ - public Principal localPrincipal() { - return !localCertificates.isEmpty() - ? ((X509Certificate) localCertificates.get(0)).getSubjectX500Principal() - : null; - } - - @Override public boolean equals(Object other) { - if (!(other instanceof Handshake)) return false; - Handshake that = (Handshake) other; - return Util.equal(cipherSuite, that.cipherSuite) - && cipherSuite.equals(that.cipherSuite) - && peerCertificates.equals(that.peerCertificates) - && localCertificates.equals(that.localCertificates); - } - - @Override public int hashCode() { - int result = 17; - result = 31 * result + (tlsVersion != null ? tlsVersion.hashCode() : 0); - result = 31 * result + cipherSuite.hashCode(); - result = 31 * result + peerCertificates.hashCode(); - result = 31 * result + localCertificates.hashCode(); - return result; - } -} diff --git a/okhttp3/src/main/java/okhttp3/Headers.java b/okhttp3/src/main/java/okhttp3/Headers.java deleted file mode 100644 index 6f72d0cc4..000000000 --- a/okhttp3/src/main/java/okhttp3/Headers.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package okhttp3; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import okhttp3.internal.http.HttpDate; - -/** - * The header fields of a single HTTP message. Values are uninterpreted strings; use {@code Request} - * and {@code Response} for interpreted headers. This class maintains the order of the header fields - * within the HTTP message. - * - *

This class tracks header values line-by-line. A field with multiple comma- separated values on - * the same line will be treated as a field with a single value by this class. It is the caller's - * responsibility to detect and split on commas if their field permits multiple values. This - * simplifies use of single-valued fields whose values routinely contain commas, such as cookies or - * dates. - * - *

This class trims whitespace from values. It never returns values with leading or trailing - * whitespace. - * - *

Instances of this class are immutable. Use {@link Builder} to create instances. - */ -public final class Headers { - private final String[] namesAndValues; - - private Headers(Builder builder) { - this.namesAndValues = builder.namesAndValues.toArray(new String[builder.namesAndValues.size()]); - } - - private Headers(String[] namesAndValues) { - this.namesAndValues = namesAndValues; - } - - /** Returns the last value corresponding to the specified field, or null. */ - public String get(String name) { - return get(namesAndValues, name); - } - - /** - * Returns the last value corresponding to the specified field parsed as an HTTP date, or null if - * either the field is absent or cannot be parsed as a date. - */ - public Date getDate(String name) { - String value = get(name); - return value != null ? HttpDate.parse(value) : null; - } - - /** Returns the number of field values. */ - public int size() { - return namesAndValues.length / 2; - } - - /** Returns the field at {@code position}. */ - public String name(int index) { - return namesAndValues[index * 2]; - } - - /** Returns the value at {@code index}. */ - public String value(int index) { - return namesAndValues[index * 2 + 1]; - } - - /** Returns an immutable case-insensitive set of header names. */ - public Set names() { - TreeSet result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - for (int i = 0, size = size(); i < size; i++) { - result.add(name(i)); - } - return Collections.unmodifiableSet(result); - } - - /** Returns an immutable list of the header values for {@code name}. */ - public List values(String name) { - List result = null; - for (int i = 0, size = size(); i < size; i++) { - if (name.equalsIgnoreCase(name(i))) { - if (result == null) result = new ArrayList<>(2); - result.add(value(i)); - } - } - return result != null - ? Collections.unmodifiableList(result) - : Collections.emptyList(); - } - - public Builder newBuilder() { - Builder result = new Builder(); - Collections.addAll(result.namesAndValues, namesAndValues); - return result; - } - - @Override public String toString() { - StringBuilder result = new StringBuilder(); - for (int i = 0, size = size(); i < size; i++) { - result.append(name(i)).append(": ").append(value(i)).append("\n"); - } - return result.toString(); - } - - public Map> toMultimap() { - Map> result = new LinkedHashMap>(); - for (int i = 0, size = size(); i < size; i++) { - String name = name(i); - List values = result.get(name); - if (values == null) { - values = new ArrayList<>(2); - result.put(name, values); - } - values.add(value(i)); - } - return result; - } - - private static String get(String[] namesAndValues, String name) { - for (int i = namesAndValues.length - 2; i >= 0; i -= 2) { - if (name.equalsIgnoreCase(namesAndValues[i])) { - return namesAndValues[i + 1]; - } - } - return null; - } - - /** - * Returns headers for the alternating header names and values. There must be an even number of - * arguments, and they must alternate between header names and values. - */ - public static Headers of(String... namesAndValues) { - if (namesAndValues == null || namesAndValues.length % 2 != 0) { - throw new IllegalArgumentException("Expected alternating header names and values"); - } - - // Make a defensive copy and clean it up. - namesAndValues = namesAndValues.clone(); - for (int i = 0; i < namesAndValues.length; i++) { - if (namesAndValues[i] == null) throw new IllegalArgumentException("Headers cannot be null"); - namesAndValues[i] = namesAndValues[i].trim(); - } - - // Check for malformed headers. - for (int i = 0; i < namesAndValues.length; i += 2) { - String name = namesAndValues[i]; - String value = namesAndValues[i + 1]; - if (name.length() == 0 || name.indexOf('\0') != -1 || value.indexOf('\0') != -1) { - throw new IllegalArgumentException("Unexpected header: " + name + ": " + value); - } - } - - return new Headers(namesAndValues); - } - - /** - * Returns headers for the header names and values in the {@link Map}. - */ - public static Headers of(Map headers) { - if (headers == null) { - throw new IllegalArgumentException("Expected map with header names and values"); - } - - // Make a defensive copy and clean it up. - String[] namesAndValues = new String[headers.size() * 2]; - int i = 0; - for (Map.Entry header : headers.entrySet()) { - if (header.getKey() == null || header.getValue() == null) { - throw new IllegalArgumentException("Headers cannot be null"); - } - String name = header.getKey().trim(); - String value = header.getValue().trim(); - if (name.length() == 0 || name.indexOf('\0') != -1 || value.indexOf('\0') != -1) { - throw new IllegalArgumentException("Unexpected header: " + name + ": " + value); - } - namesAndValues[i] = name; - namesAndValues[i + 1] = value; - i += 2; - } - - return new Headers(namesAndValues); - } - - public static final class Builder { - private final List namesAndValues = new ArrayList<>(20); - - /** - * Add a header line without any validation. Only appropriate for headers from the remote peer - * or cache. - */ - Builder addLenient(String line) { - int index = line.indexOf(":", 1); - if (index != -1) { - return addLenient(line.substring(0, index), line.substring(index + 1)); - } else if (line.startsWith(":")) { - // Work around empty header names and header names that start with a - // colon (created by old broken SPDY versions of the response cache). - return addLenient("", line.substring(1)); // Empty header name. - } else { - return addLenient("", line); // No header name. - } - } - - /** Add an header line containing a field name, a literal colon, and a value. */ - public Builder add(String line) { - int index = line.indexOf(":"); - if (index == -1) { - throw new IllegalArgumentException("Unexpected header: " + line); - } - return add(line.substring(0, index).trim(), line.substring(index + 1)); - } - - /** Add a field with the specified value. */ - public Builder add(String name, String value) { - checkNameAndValue(name, value); - return addLenient(name, value); - } - - /** - * Add a field with the specified value without any validation. Only appropriate for headers - * from the remote peer or cache. - */ - Builder addLenient(String name, String value) { - namesAndValues.add(name); - namesAndValues.add(value.trim()); - return this; - } - - public Builder removeAll(String name) { - for (int i = 0; i < namesAndValues.size(); i += 2) { - if (name.equalsIgnoreCase(namesAndValues.get(i))) { - namesAndValues.remove(i); // name - namesAndValues.remove(i); // value - i -= 2; - } - } - return this; - } - - /** - * Set a field with the specified value. If the field is not found, it is added. If the field is - * found, the existing values are replaced. - */ - public Builder set(String name, String value) { - checkNameAndValue(name, value); - removeAll(name); - addLenient(name, value); - return this; - } - - private void checkNameAndValue(String name, String value) { - if (name == null) throw new IllegalArgumentException("name == null"); - if (name.isEmpty()) throw new IllegalArgumentException("name is empty"); - for (int i = 0, length = name.length(); i < length; i++) { - char c = name.charAt(i); - if (c <= '\u001f' || c >= '\u007f') { - throw new IllegalArgumentException(String.format( - "Unexpected char %#04x at %d in header name: %s", (int) c, i, name)); - } - } - if (value == null) throw new IllegalArgumentException("value == null"); - for (int i = 0, length = value.length(); i < length; i++) { - char c = value.charAt(i); - if (c <= '\u001f' || c >= '\u007f') { - throw new IllegalArgumentException(String.format( - "Unexpected char %#04x at %d in %s value: %s", (int) c, i, name, value)); - } - } - } - - /** Equivalent to {@code build().get(name)}, but potentially faster. */ - public String get(String name) { - for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) { - if (name.equalsIgnoreCase(namesAndValues.get(i))) { - return namesAndValues.get(i + 1); - } - } - return null; - } - - public Headers build() { - return new Headers(this); - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/HttpUrl.java b/okhttp3/src/main/java/okhttp3/HttpUrl.java deleted file mode 100644 index d7c0c35c3..000000000 --- a/okhttp3/src/main/java/okhttp3/HttpUrl.java +++ /dev/null @@ -1,1614 +0,0 @@ -/* - * Copyright (C) 2015 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.net.InetAddress; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import okio.Buffer; - -import static okhttp3.internal.Util.delimiterOffset; -import static okhttp3.internal.Util.domainToAscii; -import static okhttp3.internal.Util.skipLeadingAsciiWhitespace; -import static okhttp3.internal.Util.skipTrailingAsciiWhitespace; - -/** - * A uniform resource locator (URL) with a scheme of either {@code http} or {@code https}. Use this - * class to compose and decompose Internet addresses. For example, this code will compose and print - * a URL for Google search:

   {@code
- *
- *   HttpUrl url = new HttpUrl.Builder()
- *       .scheme("https")
- *       .host("www.google.com")
- *       .addPathSegment("search")
- *       .addQueryParameter("q", "polar bears")
- *       .build();
- *   System.out.println(url);
- * }
- * - * which prints:
   {@code
- *
- *     https://www.google.com/search?q=polar%20bears
- * }
- * - * As another example, this code prints the human-readable query parameters of a Twitter search: - *
   {@code
- *
- *   HttpUrl url = HttpUrl.parse("https://twitter.com/search?q=cute%20%23puppies&f=images");
- *   for (int i = 0, size = url.querySize(); i < size; i++) {
- *     System.out.println(url.queryParameterName(i) + ": " + url.queryParameterValue(i));
- *   }
- * }
- * - * which prints:
   {@code
- *
- *   q: cute #puppies
- *   f: images
- * }
- * - * In addition to composing URLs from their component parts and decomposing URLs into their - * component parts, this class implements relative URL resolution: what address you'd reach by - * clicking a relative link on a specified page. For example:
   {@code
- *
- *   HttpUrl base = HttpUrl.parse("https://www.youtube.com/user/WatchTheDaily/videos");
- *   HttpUrl link = base.resolve("../../watch?v=cbP2N1BQdYc");
- *   System.out.println(link);
- * }
- * - * which prints:
   {@code
- *
- *   https://www.youtube.com/watch?v=cbP2N1BQdYc
- * }
- * - *

What's in a URL?

- * - * A URL has several components. - * - *

Scheme

- * - *

Sometimes referred to as protocol, A URL's scheme describes what mechanism should be - * used to retrieve the resource. Although URLs have many schemes ({@code mailto}, {@code file}, - * {@code ftp}), this class only supports {@code http} and {@code https}. Use {@link URI - * java.net.URI} for URLs with arbitrary schemes. - * - *

Username and Password

- * - *

Username and password are either present, or the empty string {@code ""} if absent. This class - * offers no mechanism to differentiate empty from absent. Neither of these components are popular - * in practice. Typically HTTP applications use other mechanisms for user identification and - * authentication. - * - *

Host

- * - *

The host identifies the webserver that serves the URL's resource. It is either a hostname like - * {@code square.com} or {@code localhost}, an IPv4 address like {@code 192.168.0.1}, or an IPv6 - * address like {@code ::1}. - * - *

Usually a webserver is reachable with multiple identifiers: its IP addresses, registered - * domain names, and even {@code localhost} when connecting from the server itself. Each of a - * webserver's names is a distinct URL and they are not interchangeable. For example, even if {@code - * http://square.github.io/dagger} and {@code http://google.github.io/dagger} are served by the same - * IP address, the two URLs identify different resources. - * - *

Port

- * - *

The port used to connect to the webserver. By default this is 80 for HTTP and 443 for HTTPS. - * This class never returns -1 for the port: if no port is explicitly specified in the URL then the - * scheme's default is used. - * - *

Path

- * - *

The path identifies a specific resource on the host. Paths have a hierarchical structure like - * "/square/okhttp/issues/1486". Each path segment is prefixed with "/". This class offers methods - * to compose and decompose paths by segment. If a path's last segment is the empty string, then the - * path ends with "/". This class always builds non-empty paths: if the path is omitted it defaults - * to "/", which is a path whose only segment is the empty string. - * - *

Query

- * - *

The query is optional: it can be null, empty, or non-empty. For many HTTP URLs the query - * string is subdivided into a collection of name-value parameters. This class offers methods to set - * the query as the single string, or as individual name-value parameters. With name-value - * parameters the values are optional and names may be repeated. - * - *

Fragment

- * - *

The fragment is optional: it can be null, empty, or non-empty. Unlike host, port, path, and - * query the fragment is not sent to the webserver: it's private to the client. - * - *

Encoding

- * - *

Each component must be encoded before it is embedded in the complete URL. As we saw above, the - * string {@code cute #puppies} is encoded as {@code cute%20%23puppies} when used as a query - * parameter value. - * - *

Percent encoding

- * - *

Percent encoding replaces a character (like {@code \ud83c\udf69}) with its UTF-8 hex bytes - * (like {@code %F0%9F%8D%A9}). This approach works for whitespace characters, control characters, - * non-ASCII characters, and characters that already have another meaning in a particular context. - * - *

Percent encoding is used in every URL component except for the hostname. But the set of - * characters that need to be encoded is different for each component. For example, the path - * component must escape all of its {@code ?} characters, otherwise it could be interpreted as the - * start of the URL's query. But within the query and fragment components, the {@code ?} character - * doesn't delimit anything and doesn't need to be escaped.

   {@code
- *
- *   HttpUrl url = HttpUrl.parse("http://who-let-the-dogs.out").newBuilder()
- *       .addPathSegment("_Who?_")
- *       .query("_Who?_")
- *       .fragment("_Who?_")
- *       .build();
- *   System.out.println(url);
- * }
- * - * This prints:
   {@code
- *
- *   http://who-let-the-dogs.out/_Who%3F_?_Who?_#_Who?_
- * }
- * - * When parsing URLs that lack percent encoding where it is required, this class will percent encode - * the offending characters. - * - *

IDNA Mapping and Punycode encoding

- * - *

Hostnames have different requirements and use a different encoding scheme. It consists of IDNA - * mapping and Punycode encoding. - * - *

In order to avoid confusion and discourage phishing attacks, IDNA Mapping transforms names to avoid - * confusing characters. This includes basic case folding: transforming shouting {@code SQUARE.COM} - * into cool and casual {@code square.com}. It also handles more exotic characters. For example, the - * Unicode trademark sign (â„¢) could be confused for the letters "TM" in {@code http://hoâ„¢mail.com}. - * To mitigate this, the single character (â„¢) maps to the string (tm). There is similar policy for - * all of the 1.1 million Unicode code points. Note that some code points such as "\ud83c\udf69" are - * not mapped and cannot be used in a hostname. - * - *

Punycode converts a Unicode string to an ASCII - * string to make international domain names work everywhere. For example, "σ" encodes as "xn--4xa". - * The encoded string is not human readable, but can be used with classes like {@link InetAddress} - * to establish connections. - * - *

Why another URL model?

- * - *

Java includes both {@link URL java.net.URL} and {@link URI java.net.URI}. We offer a new URL - * model to address problems that the others don't. - * - *

Different URLs should be different

- * - *

Although they have different content, {@code java.net.URL} considers the following two URLs - * equal, and the {@link Object#equals equals()} method between them returns true: - * - *

    - *
  • http://square.github.io/ - *
  • http://google.github.io/ - *
- * - * This is because those two hosts share the same IP address. This is an old, bad design decision - * that makes {@code java.net.URL} unusable for many things. It shouldn't be used as a {@link - * java.util.Map Map} key or in a {@link Set}. Doing so is both inefficient because equality may - * require a DNS lookup, and incorrect because unequal URLs may be equal because of how they are - * hosted. - * - *

Equal URLs should be equal

- * - *

These two URLs are semantically identical, but {@code java.net.URI} disagrees: - * - *

    - *
  • http://host:80/ - *
  • http://host - *
- * - * Both the unnecessary port specification ({@code :80}) and the absent trailing slash ({@code /}) - * cause URI to bucket the two URLs separately. This harms URI's usefulness in collections. Any - * application that stores information-per-URL will need to either canonicalize manually, or suffer - * unnecessary redundancy for such URLs. - * - *

Because they don't attempt canonical form, these classes are surprisingly difficult to use - * securely. Suppose you're building a webservice that checks that incoming paths are prefixed - * "/static/images/" before serving the corresponding assets from the filesystem.

   {@code
- *
- *   String attack = "http://example.com/static/images/../../../../../etc/passwd";
- *   System.out.println(new URL(attack).getPath());
- *   System.out.println(new URI(attack).getPath());
- *   System.out.println(HttpUrl.parse(attack).path());
- * }
- * - * By canonicalizing the input paths, they are complicit in directory traversal attacks. Code that - * checks only the path prefix may suffer! - *
   {@code
- *
- *    /static/images/../../../../../etc/passwd
- *    /static/images/../../../../../etc/passwd
- *    /etc/passwd
- * }
- * - *

If it works on the web, it should work in your application

- * - *

The {@code java.net.URI} class is strict around what URLs it accepts. It rejects URLs like - * "http://example.com/abc|def" because the '|' character is unsupported. This class is more - * forgiving: it will automatically percent-encode the '|', yielding "http://example.com/abc%7Cdef". - * This kind behavior is consistent with web browsers. {@code HttpUrl} prefers consistency with - * major web browsers over consistency with obsolete specifications. - * - *

Paths and Queries should decompose

- * - *

Neither of the built-in URL models offer direct access to path segments or query parameters. - * Manually using {@code StringBuilder} to assemble these components is cumbersome: do '+' - * characters get silently replaced with spaces? If a query parameter contains a '&', does that - * get escaped? By offering methods to read and write individual query parameters directly, - * application developers are saved from the hassles of encoding and decoding. - * - *

Plus a modern API

- * - *

The URL (JDK1.0) and URI (Java 1.4) classes predate builders and instead use telescoping - * constructors. For example, there's no API to compose a URI with a custom port without also - * providing a query and fragment. - * - *

Instances of {@link HttpUrl} are well-formed and always have a scheme, host, and path. With - * {@code java.net.URL} it's possible to create an awkward URL like {@code http:/} with scheme and - * path but no hostname. Building APIs that consume such malformed values is difficult! - * - *

This class has a modern API. It avoids punitive checked exceptions: {@link #parse parse()} - * returns null if the input is an invalid URL. You can even be explicit about whether each - * component has been encoded already. - */ -public final class HttpUrl { - private static final char[] HEX_DIGITS = - {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - static final String USERNAME_ENCODE_SET = " \"':;<=>@[]^`{}|/\\?#"; - static final String PASSWORD_ENCODE_SET = " \"':;<=>@[]^`{}|/\\?#"; - static final String PATH_SEGMENT_ENCODE_SET = " \"<>^`{}|/\\?#"; - static final String PATH_SEGMENT_ENCODE_SET_URI = "[]"; - static final String QUERY_ENCODE_SET = " \"'<>#"; - static final String QUERY_COMPONENT_ENCODE_SET = " \"'<>#&="; - static final String QUERY_COMPONENT_ENCODE_SET_URI = "\\^`{|}"; - static final String FORM_ENCODE_SET = " \"':;<=>@[]^`{}|/\\?#&!$(),~"; - static final String FRAGMENT_ENCODE_SET = ""; - static final String FRAGMENT_ENCODE_SET_URI = " \"#<>\\^`{|}"; - - /** Either "http" or "https". */ - private final String scheme; - - /** Decoded username. */ - private final String username; - - /** Decoded password. */ - private final String password; - - /** Canonical hostname. */ - private final String host; - - /** Either 80, 443 or a user-specified port. In range [1..65535]. */ - private final int port; - - /** - * A list of canonical path segments. This list always contains at least one element, which may be - * the empty string. Each segment is formatted with a leading '/', so if path segments were ["a", - * "b", ""], then the encoded path would be "/a/b/". - */ - private final List pathSegments; - - /** - * Alternating, decoded query names and values, or null for no query. Names may be empty or - * non-empty, but never null. Values are null if the name has no corresponding '=' separator, or - * empty, or non-empty. - */ - private final List queryNamesAndValues; - - /** Decoded fragment. */ - private final String fragment; - - /** Canonical URL. */ - private final String url; - - private HttpUrl(Builder builder) { - this.scheme = builder.scheme; - this.username = percentDecode(builder.encodedUsername, false); - this.password = percentDecode(builder.encodedPassword, false); - this.host = builder.host; - this.port = builder.effectivePort(); - this.pathSegments = percentDecode(builder.encodedPathSegments, false); - this.queryNamesAndValues = builder.encodedQueryNamesAndValues != null - ? percentDecode(builder.encodedQueryNamesAndValues, true) - : null; - this.fragment = builder.encodedFragment != null - ? percentDecode(builder.encodedFragment, false) - : null; - this.url = builder.toString(); - } - - /** Returns this URL as a {@link URL java.net.URL}. */ - public URL url() { - try { - return new URL(url); - } catch (MalformedURLException e) { - throw new RuntimeException(e); // Unexpected! - } - } - - /** - * Returns this URL as a {@link URI java.net.URI}. Because {@code URI} is more strict than this - * class, the returned URI may be semantically different from this URL: - * - *

    - *
  • Characters forbidden by URI like {@code [} and {@code |} will be escaped. - *
  • Invalid percent-encoded sequences like {@code %xx} will be encoded like {@code %25xx}. - *
  • Whitespace and control characters in the fragment will be stripped. - *
- * - *

These differences may have a significant consequence when the URI is interpretted by a - * webserver. For this reason the {@linkplain URI URI class} and this method should be avoided. - */ - public URI uri() { - String uri = newBuilder().reencodeForUri().toString(); - try { - return new URI(uri); - } catch (URISyntaxException e) { - // Unlikely edge case: the URI has a forbidden character in the fragment. Strip it & retry. - try { - String stripped = uri.replaceAll("[\\u0000-\\u001F\\u007F-\\u009F\\p{javaWhitespace}]", ""); - return URI.create(stripped); - } catch (Exception e1) { - throw new RuntimeException(e); // Unexpected! - } - } - } - - /** Returns either "http" or "https". */ - public String scheme() { - return scheme; - } - - public boolean isHttps() { - return scheme.equals("https"); - } - - /** Returns the username, or an empty string if none is set. */ - public String encodedUsername() { - if (username.isEmpty()) return ""; - int usernameStart = scheme.length() + 3; // "://".length() == 3. - int usernameEnd = delimiterOffset(url, usernameStart, url.length(), ":@"); - return url.substring(usernameStart, usernameEnd); - } - - public String username() { - return username; - } - - /** Returns the password, or an empty string if none is set. */ - public String encodedPassword() { - if (password.isEmpty()) return ""; - int passwordStart = url.indexOf(':', scheme.length() + 3) + 1; - int passwordEnd = url.indexOf('@'); - return url.substring(passwordStart, passwordEnd); - } - - /** Returns the decoded password, or an empty string if none is present. */ - public String password() { - return password; - } - - /** - * Returns the host address suitable for use with {@link InetAddress#getAllByName(String)}. May - * be: - * - *

    - *
  • A regular host name, like {@code android.com}. - *
  • An IPv4 address, like {@code 127.0.0.1}. - *
  • An IPv6 address, like {@code ::1}. Note that there are no square braces. - *
  • An encoded IDN, like {@code xn--n3h.net}. - *
- */ - public String host() { - return host; - } - - /** - * Returns the explicitly-specified port if one was provided, or the default port for this URL's - * scheme. For example, this returns 8443 for {@code https://square.com:8443/} and 443 for {@code - * https://square.com/}. The result is in {@code [1..65535]}. - */ - public int port() { - return port; - } - - /** - * Returns 80 if {@code scheme.equals("http")}, 443 if {@code scheme.equals("https")} and -1 - * otherwise. - */ - public static int defaultPort(String scheme) { - if (scheme.equals("http")) { - return 80; - } else if (scheme.equals("https")) { - return 443; - } else { - return -1; - } - } - - public int pathSize() { - return pathSegments.size(); - } - - /** - * Returns the entire path of this URL, encoded for use in HTTP resource resolution. The returned - * path is always nonempty and is prefixed with {@code /}. - */ - public String encodedPath() { - int pathStart = url.indexOf('/', scheme.length() + 3); // "://".length() == 3. - int pathEnd = delimiterOffset(url, pathStart, url.length(), "?#"); - return url.substring(pathStart, pathEnd); - } - - static void pathSegmentsToString(StringBuilder out, List pathSegments) { - for (int i = 0, size = pathSegments.size(); i < size; i++) { - out.append('/'); - out.append(pathSegments.get(i)); - } - } - - public List encodedPathSegments() { - int pathStart = url.indexOf('/', scheme.length() + 3); - int pathEnd = delimiterOffset(url, pathStart, url.length(), "?#"); - List result = new ArrayList<>(); - for (int i = pathStart; i < pathEnd; ) { - i++; // Skip the '/'. - int segmentEnd = delimiterOffset(url, i, pathEnd, '/'); - result.add(url.substring(i, segmentEnd)); - i = segmentEnd; - } - return result; - } - - public List pathSegments() { - return pathSegments; - } - - /** - * Returns the query of this URL, encoded for use in HTTP resource resolution. The returned string - * may be null (for URLs with no query), empty (for URLs with an empty query) or non-empty (all - * other URLs). - */ - public String encodedQuery() { - if (queryNamesAndValues == null) return null; // No query. - int queryStart = url.indexOf('?') + 1; - int queryEnd = delimiterOffset(url, queryStart + 1, url.length(), '#'); - return url.substring(queryStart, queryEnd); - } - - static void namesAndValuesToQueryString(StringBuilder out, List namesAndValues) { - for (int i = 0, size = namesAndValues.size(); i < size; i += 2) { - String name = namesAndValues.get(i); - String value = namesAndValues.get(i + 1); - if (i > 0) out.append('&'); - out.append(name); - if (value != null) { - out.append('='); - out.append(value); - } - } - } - - /** - * Cuts {@code encodedQuery} up into alternating parameter names and values. This divides a query - * string like {@code subject=math&easy&problem=5-2=3} into the list {@code ["subject", "math", - * "easy", null, "problem", "5-2=3"]}. Note that values may be null and may contain '=' - * characters. - */ - static List queryStringToNamesAndValues(String encodedQuery) { - List result = new ArrayList<>(); - for (int pos = 0; pos <= encodedQuery.length(); ) { - int ampersandOffset = encodedQuery.indexOf('&', pos); - if (ampersandOffset == -1) ampersandOffset = encodedQuery.length(); - - int equalsOffset = encodedQuery.indexOf('=', pos); - if (equalsOffset == -1 || equalsOffset > ampersandOffset) { - result.add(encodedQuery.substring(pos, ampersandOffset)); - result.add(null); // No value for this name. - } else { - result.add(encodedQuery.substring(pos, equalsOffset)); - result.add(encodedQuery.substring(equalsOffset + 1, ampersandOffset)); - } - pos = ampersandOffset + 1; - } - return result; - } - - public String query() { - if (queryNamesAndValues == null) return null; // No query. - StringBuilder result = new StringBuilder(); - namesAndValuesToQueryString(result, queryNamesAndValues); - return result.toString(); - } - - public int querySize() { - return queryNamesAndValues != null ? queryNamesAndValues.size() / 2 : 0; - } - - /** - * Returns the first query parameter named {@code name} decoded using UTF-8, or null if there is - * no such query parameter. - */ - public String queryParameter(String name) { - if (queryNamesAndValues == null) return null; - for (int i = 0, size = queryNamesAndValues.size(); i < size; i += 2) { - if (name.equals(queryNamesAndValues.get(i))) { - return queryNamesAndValues.get(i + 1); - } - } - return null; - } - - public Set queryParameterNames() { - if (queryNamesAndValues == null) return Collections.emptySet(); - Set result = new LinkedHashSet<>(); - for (int i = 0, size = queryNamesAndValues.size(); i < size; i += 2) { - result.add(queryNamesAndValues.get(i)); - } - return Collections.unmodifiableSet(result); - } - - public List queryParameterValues(String name) { - if (queryNamesAndValues == null) return Collections.emptyList(); - List result = new ArrayList<>(); - for (int i = 0, size = queryNamesAndValues.size(); i < size; i += 2) { - if (name.equals(queryNamesAndValues.get(i))) { - result.add(queryNamesAndValues.get(i + 1)); - } - } - return Collections.unmodifiableList(result); - } - - public String queryParameterName(int index) { - return queryNamesAndValues.get(index * 2); - } - - public String queryParameterValue(int index) { - return queryNamesAndValues.get(index * 2 + 1); - } - - public String encodedFragment() { - if (fragment == null) return null; - int fragmentStart = url.indexOf('#') + 1; - return url.substring(fragmentStart); - } - - public String fragment() { - return fragment; - } - - /** - * Returns the URL that would be retrieved by following {@code link} from this URL, or null if - * the resulting URL is not well-formed. - */ - public HttpUrl resolve(String link) { - Builder builder = newBuilder(link); - return builder != null ? builder.build() : null; - } - - public Builder newBuilder() { - Builder result = new Builder(); - result.scheme = scheme; - result.encodedUsername = encodedUsername(); - result.encodedPassword = encodedPassword(); - result.host = host; - // If we're set to a default port, unset it in case of a scheme change. - result.port = port != defaultPort(scheme) ? port : -1; - result.encodedPathSegments.clear(); - result.encodedPathSegments.addAll(encodedPathSegments()); - result.encodedQuery(encodedQuery()); - result.encodedFragment = encodedFragment(); - return result; - } - - /** - * Returns a builder for the URL that would be retrieved by following {@code link} from this URL, - * or null if the resulting URL is not well-formed. - */ - public Builder newBuilder(String link) { - Builder builder = new Builder(); - Builder.ParseResult result = builder.parse(this, link); - return result == Builder.ParseResult.SUCCESS ? builder : null; - } - - /** - * Returns a new {@code HttpUrl} representing {@code url} if it is a well-formed HTTP or HTTPS - * URL, or null if it isn't. - */ - public static HttpUrl parse(String url) { - Builder builder = new Builder(); - Builder.ParseResult result = builder.parse(null, url); - return result == Builder.ParseResult.SUCCESS ? builder.build() : null; - } - - /** - * Returns an {@link HttpUrl} for {@code url} if its protocol is {@code http} or {@code https}, or - * null if it has any other protocol. - */ - public static HttpUrl get(URL url) { - return parse(url.toString()); - } - - /** - * Returns a new {@code HttpUrl} representing {@code url} if it is a well-formed HTTP or HTTPS - * URL, or throws an exception if it isn't. - * - * @throws MalformedURLException if there was a non-host related URL issue - * @throws UnknownHostException if the host was invalid - */ - static HttpUrl getChecked(String url) throws MalformedURLException, UnknownHostException { - Builder builder = new Builder(); - Builder.ParseResult result = builder.parse(null, url); - switch (result) { - case SUCCESS: - return builder.build(); - case INVALID_HOST: - throw new UnknownHostException("Invalid host: " + url); - case UNSUPPORTED_SCHEME: - case MISSING_SCHEME: - case INVALID_PORT: - default: - throw new MalformedURLException("Invalid URL: " + result + " for " + url); - } - } - - public static HttpUrl get(URI uri) { - return parse(uri.toString()); - } - - @Override public boolean equals(Object o) { - return o instanceof HttpUrl && ((HttpUrl) o).url.equals(url); - } - - @Override public int hashCode() { - return url.hashCode(); - } - - @Override public String toString() { - return url; - } - - public static final class Builder { - String scheme; - String encodedUsername = ""; - String encodedPassword = ""; - String host; - int port = -1; - final List encodedPathSegments = new ArrayList<>(); - List encodedQueryNamesAndValues; - String encodedFragment; - - public Builder() { - encodedPathSegments.add(""); // The default path is '/' which needs a trailing space. - } - - public Builder scheme(String scheme) { - if (scheme == null) { - throw new IllegalArgumentException("scheme == null"); - } else if (scheme.equalsIgnoreCase("http")) { - this.scheme = "http"; - } else if (scheme.equalsIgnoreCase("https")) { - this.scheme = "https"; - } else { - throw new IllegalArgumentException("unexpected scheme: " + scheme); - } - return this; - } - - public Builder username(String username) { - if (username == null) throw new IllegalArgumentException("username == null"); - this.encodedUsername = canonicalize(username, USERNAME_ENCODE_SET, false, false, false, true); - return this; - } - - public Builder encodedUsername(String encodedUsername) { - if (encodedUsername == null) throw new IllegalArgumentException("encodedUsername == null"); - this.encodedUsername = canonicalize( - encodedUsername, USERNAME_ENCODE_SET, true, false, false, true); - return this; - } - - public Builder password(String password) { - if (password == null) throw new IllegalArgumentException("password == null"); - this.encodedPassword = canonicalize(password, PASSWORD_ENCODE_SET, false, false, false, true); - return this; - } - - public Builder encodedPassword(String encodedPassword) { - if (encodedPassword == null) throw new IllegalArgumentException("encodedPassword == null"); - this.encodedPassword = canonicalize( - encodedPassword, PASSWORD_ENCODE_SET, true, false, false, true); - return this; - } - - /** - * @param host either a regular hostname, International Domain Name, IPv4 address, or IPv6 - * address. - */ - public Builder host(String host) { - if (host == null) throw new IllegalArgumentException("host == null"); - String encoded = canonicalizeHost(host, 0, host.length()); - if (encoded == null) throw new IllegalArgumentException("unexpected host: " + host); - this.host = encoded; - return this; - } - - public Builder port(int port) { - if (port <= 0 || port > 65535) throw new IllegalArgumentException("unexpected port: " + port); - this.port = port; - return this; - } - - int effectivePort() { - return port != -1 ? port : defaultPort(scheme); - } - - public Builder addPathSegment(String pathSegment) { - if (pathSegment == null) throw new IllegalArgumentException("pathSegment == null"); - push(pathSegment, 0, pathSegment.length(), false, false); - return this; - } - - /** - * Adds a set of path segments separated by a slash (either {@code \} or {@code /}). If - * {@code pathSegments} starts with a slash, the resulting URL will have empty path segment. - */ - public Builder addPathSegments(String pathSegments) { - if (pathSegments == null) throw new IllegalArgumentException("pathSegments == null"); - return addPathSegments(pathSegments, false); - } - - public Builder addEncodedPathSegment(String encodedPathSegment) { - if (encodedPathSegment == null) { - throw new IllegalArgumentException("encodedPathSegment == null"); - } - push(encodedPathSegment, 0, encodedPathSegment.length(), false, true); - return this; - } - - /** - * Adds a set of encoded path segments separated by a slash (either {@code \} or {@code /}). If - * {@code encodedPathSegments} starts with a slash, the resulting URL will have empty path - * segment. - */ - public Builder addEncodedPathSegments(String encodedPathSegments) { - if (encodedPathSegments == null) { - throw new IllegalArgumentException("encodedPathSegments == null"); - } - return addPathSegments(encodedPathSegments, true); - } - - private Builder addPathSegments(String pathSegments, boolean alreadyEncoded) { - int offset = 0; - do { - int segmentEnd = delimiterOffset(pathSegments, offset, pathSegments.length(), "/\\"); - boolean addTrailingSlash = segmentEnd < pathSegments.length(); - push(pathSegments, offset, segmentEnd, addTrailingSlash, alreadyEncoded); - offset = segmentEnd + 1; - } while (offset <= pathSegments.length()); - return this; - } - - public Builder setPathSegment(int index, String pathSegment) { - if (pathSegment == null) throw new IllegalArgumentException("pathSegment == null"); - String canonicalPathSegment = canonicalize( - pathSegment, 0, pathSegment.length(), PATH_SEGMENT_ENCODE_SET, false, false, false, true); - if (isDot(canonicalPathSegment) || isDotDot(canonicalPathSegment)) { - throw new IllegalArgumentException("unexpected path segment: " + pathSegment); - } - encodedPathSegments.set(index, canonicalPathSegment); - return this; - } - - public Builder setEncodedPathSegment(int index, String encodedPathSegment) { - if (encodedPathSegment == null) { - throw new IllegalArgumentException("encodedPathSegment == null"); - } - String canonicalPathSegment = canonicalize(encodedPathSegment, - 0, encodedPathSegment.length(), PATH_SEGMENT_ENCODE_SET, true, false, false, true); - encodedPathSegments.set(index, canonicalPathSegment); - if (isDot(canonicalPathSegment) || isDotDot(canonicalPathSegment)) { - throw new IllegalArgumentException("unexpected path segment: " + encodedPathSegment); - } - return this; - } - - public Builder removePathSegment(int index) { - encodedPathSegments.remove(index); - if (encodedPathSegments.isEmpty()) { - encodedPathSegments.add(""); // Always leave at least one '/'. - } - return this; - } - - public Builder encodedPath(String encodedPath) { - if (encodedPath == null) throw new IllegalArgumentException("encodedPath == null"); - if (!encodedPath.startsWith("/")) { - throw new IllegalArgumentException("unexpected encodedPath: " + encodedPath); - } - resolvePath(encodedPath, 0, encodedPath.length()); - return this; - } - - public Builder query(String query) { - this.encodedQueryNamesAndValues = query != null - ? queryStringToNamesAndValues(canonicalize( - query, QUERY_ENCODE_SET, false, false, true, true)) - : null; - return this; - } - - public Builder encodedQuery(String encodedQuery) { - this.encodedQueryNamesAndValues = encodedQuery != null - ? queryStringToNamesAndValues( - canonicalize(encodedQuery, QUERY_ENCODE_SET, true, false, true, true)) - : null; - return this; - } - - /** Encodes the query parameter using UTF-8 and adds it to this URL's query string. */ - public Builder addQueryParameter(String name, String value) { - if (name == null) throw new IllegalArgumentException("name == null"); - if (encodedQueryNamesAndValues == null) encodedQueryNamesAndValues = new ArrayList<>(); - encodedQueryNamesAndValues.add( - canonicalize(name, QUERY_COMPONENT_ENCODE_SET, false, false, true, true)); - encodedQueryNamesAndValues.add(value != null - ? canonicalize(value, QUERY_COMPONENT_ENCODE_SET, false, false, true, true) - : null); - return this; - } - - /** Adds the pre-encoded query parameter to this URL's query string. */ - public Builder addEncodedQueryParameter(String encodedName, String encodedValue) { - if (encodedName == null) throw new IllegalArgumentException("encodedName == null"); - if (encodedQueryNamesAndValues == null) encodedQueryNamesAndValues = new ArrayList<>(); - encodedQueryNamesAndValues.add( - canonicalize(encodedName, QUERY_COMPONENT_ENCODE_SET, true, false, true, true)); - encodedQueryNamesAndValues.add(encodedValue != null - ? canonicalize(encodedValue, QUERY_COMPONENT_ENCODE_SET, true, false, true, true) - : null); - return this; - } - - public Builder setQueryParameter(String name, String value) { - removeAllQueryParameters(name); - addQueryParameter(name, value); - return this; - } - - public Builder setEncodedQueryParameter(String encodedName, String encodedValue) { - removeAllEncodedQueryParameters(encodedName); - addEncodedQueryParameter(encodedName, encodedValue); - return this; - } - - public Builder removeAllQueryParameters(String name) { - if (name == null) throw new IllegalArgumentException("name == null"); - if (encodedQueryNamesAndValues == null) return this; - String nameToRemove = canonicalize( - name, QUERY_COMPONENT_ENCODE_SET, false, false, true, true); - removeAllCanonicalQueryParameters(nameToRemove); - return this; - } - - public Builder removeAllEncodedQueryParameters(String encodedName) { - if (encodedName == null) throw new IllegalArgumentException("encodedName == null"); - if (encodedQueryNamesAndValues == null) return this; - removeAllCanonicalQueryParameters( - canonicalize(encodedName, QUERY_COMPONENT_ENCODE_SET, true, false, true, true)); - return this; - } - - private void removeAllCanonicalQueryParameters(String canonicalName) { - for (int i = encodedQueryNamesAndValues.size() - 2; i >= 0; i -= 2) { - if (canonicalName.equals(encodedQueryNamesAndValues.get(i))) { - encodedQueryNamesAndValues.remove(i + 1); - encodedQueryNamesAndValues.remove(i); - if (encodedQueryNamesAndValues.isEmpty()) { - encodedQueryNamesAndValues = null; - return; - } - } - } - } - - public Builder fragment(String fragment) { - this.encodedFragment = fragment != null - ? canonicalize(fragment, FRAGMENT_ENCODE_SET, false, false, false, false) - : null; - return this; - } - - public Builder encodedFragment(String encodedFragment) { - this.encodedFragment = encodedFragment != null - ? canonicalize(encodedFragment, FRAGMENT_ENCODE_SET, true, false, false, false) - : null; - return this; - } - - /** - * Re-encodes the components of this URL so that it satisfies (obsolete) RFC 2396, which is - * particularly strict for certain components. - */ - Builder reencodeForUri() { - for (int i = 0, size = encodedPathSegments.size(); i < size; i++) { - String pathSegment = encodedPathSegments.get(i); - encodedPathSegments.set(i, - canonicalize(pathSegment, PATH_SEGMENT_ENCODE_SET_URI, true, true, false, true)); - } - if (encodedQueryNamesAndValues != null) { - for (int i = 0, size = encodedQueryNamesAndValues.size(); i < size; i++) { - String component = encodedQueryNamesAndValues.get(i); - if (component != null) { - encodedQueryNamesAndValues.set(i, - canonicalize(component, QUERY_COMPONENT_ENCODE_SET_URI, true, true, true, true)); - } - } - } - if (encodedFragment != null) { - encodedFragment = canonicalize( - encodedFragment, FRAGMENT_ENCODE_SET_URI, true, true, false, false); - } - return this; - } - - public HttpUrl build() { - if (scheme == null) throw new IllegalStateException("scheme == null"); - if (host == null) throw new IllegalStateException("host == null"); - return new HttpUrl(this); - } - - @Override public String toString() { - StringBuilder result = new StringBuilder(); - result.append(scheme); - result.append("://"); - - if (!encodedUsername.isEmpty() || !encodedPassword.isEmpty()) { - result.append(encodedUsername); - if (!encodedPassword.isEmpty()) { - result.append(':'); - result.append(encodedPassword); - } - result.append('@'); - } - - if (host.indexOf(':') != -1) { - // Host is an IPv6 address. - result.append('['); - result.append(host); - result.append(']'); - } else { - result.append(host); - } - - int effectivePort = effectivePort(); - if (effectivePort != defaultPort(scheme)) { - result.append(':'); - result.append(effectivePort); - } - - pathSegmentsToString(result, encodedPathSegments); - - if (encodedQueryNamesAndValues != null) { - result.append('?'); - namesAndValuesToQueryString(result, encodedQueryNamesAndValues); - } - - if (encodedFragment != null) { - result.append('#'); - result.append(encodedFragment); - } - - return result.toString(); - } - - enum ParseResult { - SUCCESS, - MISSING_SCHEME, - UNSUPPORTED_SCHEME, - INVALID_PORT, - INVALID_HOST, - } - - ParseResult parse(HttpUrl base, String input) { - int pos = skipLeadingAsciiWhitespace(input, 0, input.length()); - int limit = skipTrailingAsciiWhitespace(input, pos, input.length()); - - // Scheme. - int schemeDelimiterOffset = schemeDelimiterOffset(input, pos, limit); - if (schemeDelimiterOffset != -1) { - if (input.regionMatches(true, pos, "https:", 0, 6)) { - this.scheme = "https"; - pos += "https:".length(); - } else if (input.regionMatches(true, pos, "http:", 0, 5)) { - this.scheme = "http"; - pos += "http:".length(); - } else { - return ParseResult.UNSUPPORTED_SCHEME; // Not an HTTP scheme. - } - } else if (base != null) { - this.scheme = base.scheme; - } else { - return ParseResult.MISSING_SCHEME; // No scheme. - } - - // Authority. - boolean hasUsername = false; - boolean hasPassword = false; - int slashCount = slashCount(input, pos, limit); - if (slashCount >= 2 || base == null || !base.scheme.equals(this.scheme)) { - // Read an authority if either: - // * The input starts with 2 or more slashes. These follow the scheme if it exists. - // * The input scheme exists and is different from the base URL's scheme. - // - // The structure of an authority is: - // username:password@host:port - // - // Username, password and port are optional. - // [username[:password]@]host[:port] - pos += slashCount; - authority: - while (true) { - int componentDelimiterOffset = delimiterOffset(input, pos, limit, "@/\\?#"); - int c = componentDelimiterOffset != limit - ? input.charAt(componentDelimiterOffset) - : -1; - switch (c) { - case '@': - // User info precedes. - if (!hasPassword) { - int passwordColonOffset = delimiterOffset( - input, pos, componentDelimiterOffset, ':'); - String canonicalUsername = canonicalize( - input, pos, passwordColonOffset, USERNAME_ENCODE_SET, true, false, false, true); - this.encodedUsername = hasUsername - ? this.encodedUsername + "%40" + canonicalUsername - : canonicalUsername; - if (passwordColonOffset != componentDelimiterOffset) { - hasPassword = true; - this.encodedPassword = canonicalize(input, passwordColonOffset + 1, - componentDelimiterOffset, PASSWORD_ENCODE_SET, true, false, false, true); - } - hasUsername = true; - } else { - this.encodedPassword = this.encodedPassword + "%40" + canonicalize(input, pos, - componentDelimiterOffset, PASSWORD_ENCODE_SET, true, false, false, true); - } - pos = componentDelimiterOffset + 1; - break; - - case -1: - case '/': - case '\\': - case '?': - case '#': - // Host info precedes. - int portColonOffset = portColonOffset(input, pos, componentDelimiterOffset); - if (portColonOffset + 1 < componentDelimiterOffset) { - this.host = canonicalizeHost(input, pos, portColonOffset); - this.port = parsePort(input, portColonOffset + 1, componentDelimiterOffset); - if (this.port == -1) return ParseResult.INVALID_PORT; // Invalid port. - } else { - this.host = canonicalizeHost(input, pos, portColonOffset); - this.port = defaultPort(this.scheme); - } - if (this.host == null) return ParseResult.INVALID_HOST; // Invalid host. - pos = componentDelimiterOffset; - break authority; - } - } - } else { - // This is a relative link. Copy over all authority components. Also maybe the path & query. - this.encodedUsername = base.encodedUsername(); - this.encodedPassword = base.encodedPassword(); - this.host = base.host; - this.port = base.port; - this.encodedPathSegments.clear(); - this.encodedPathSegments.addAll(base.encodedPathSegments()); - if (pos == limit || input.charAt(pos) == '#') { - encodedQuery(base.encodedQuery()); - } - } - - // Resolve the relative path. - int pathDelimiterOffset = delimiterOffset(input, pos, limit, "?#"); - resolvePath(input, pos, pathDelimiterOffset); - pos = pathDelimiterOffset; - - // Query. - if (pos < limit && input.charAt(pos) == '?') { - int queryDelimiterOffset = delimiterOffset(input, pos, limit, '#'); - this.encodedQueryNamesAndValues = queryStringToNamesAndValues(canonicalize( - input, pos + 1, queryDelimiterOffset, QUERY_ENCODE_SET, true, false, true, true)); - pos = queryDelimiterOffset; - } - - // Fragment. - if (pos < limit && input.charAt(pos) == '#') { - this.encodedFragment = canonicalize( - input, pos + 1, limit, FRAGMENT_ENCODE_SET, true, false, false, false); - } - - return ParseResult.SUCCESS; - } - - private void resolvePath(String input, int pos, int limit) { - // Read a delimiter. - if (pos == limit) { - // Empty path: keep the base path as-is. - return; - } - char c = input.charAt(pos); - if (c == '/' || c == '\\') { - // Absolute path: reset to the default "/". - encodedPathSegments.clear(); - encodedPathSegments.add(""); - pos++; - } else { - // Relative path: clear everything after the last '/'. - encodedPathSegments.set(encodedPathSegments.size() - 1, ""); - } - - // Read path segments. - for (int i = pos; i < limit; ) { - int pathSegmentDelimiterOffset = delimiterOffset(input, i, limit, "/\\"); - boolean segmentHasTrailingSlash = pathSegmentDelimiterOffset < limit; - push(input, i, pathSegmentDelimiterOffset, segmentHasTrailingSlash, true); - i = pathSegmentDelimiterOffset; - if (segmentHasTrailingSlash) i++; - } - } - - /** Adds a path segment. If the input is ".." or equivalent, this pops a path segment. */ - private void push(String input, int pos, int limit, boolean addTrailingSlash, - boolean alreadyEncoded) { - String segment = canonicalize( - input, pos, limit, PATH_SEGMENT_ENCODE_SET, alreadyEncoded, false, false, true); - if (isDot(segment)) { - return; // Skip '.' path segments. - } - if (isDotDot(segment)) { - pop(); - return; - } - if (encodedPathSegments.get(encodedPathSegments.size() - 1).isEmpty()) { - encodedPathSegments.set(encodedPathSegments.size() - 1, segment); - } else { - encodedPathSegments.add(segment); - } - if (addTrailingSlash) { - encodedPathSegments.add(""); - } - } - - private boolean isDot(String input) { - return input.equals(".") || input.equalsIgnoreCase("%2e"); - } - - private boolean isDotDot(String input) { - return input.equals("..") - || input.equalsIgnoreCase("%2e.") - || input.equalsIgnoreCase(".%2e") - || input.equalsIgnoreCase("%2e%2e"); - } - - /** - * Removes a path segment. When this method returns the last segment is always "", which means - * the encoded path will have a trailing '/'. - * - *

Popping "/a/b/c/" yields "/a/b/". In this case the list of path segments goes from ["a", - * "b", "c", ""] to ["a", "b", ""]. - * - *

Popping "/a/b/c" also yields "/a/b/". The list of path segments goes from ["a", "b", "c"] - * to ["a", "b", ""]. - */ - private void pop() { - String removed = encodedPathSegments.remove(encodedPathSegments.size() - 1); - - // Make sure the path ends with a '/' by either adding an empty string or clearing a segment. - if (removed.isEmpty() && !encodedPathSegments.isEmpty()) { - encodedPathSegments.set(encodedPathSegments.size() - 1, ""); - } else { - encodedPathSegments.add(""); - } - } - - /** - * Returns the index of the ':' in {@code input} that is after scheme characters. Returns -1 if - * {@code input} does not have a scheme that starts at {@code pos}. - */ - private static int schemeDelimiterOffset(String input, int pos, int limit) { - if (limit - pos < 2) return -1; - - char c0 = input.charAt(pos); - if ((c0 < 'a' || c0 > 'z') && (c0 < 'A' || c0 > 'Z')) return -1; // Not a scheme start char. - - for (int i = pos + 1; i < limit; i++) { - char c = input.charAt(i); - - if ((c >= 'a' && c <= 'z') - || (c >= 'A' && c <= 'Z') - || (c >= '0' && c <= '9') - || c == '+' - || c == '-' - || c == '.') { - continue; // Scheme character. Keep going. - } else if (c == ':') { - return i; // Scheme prefix! - } else { - return -1; // Non-scheme character before the first ':'. - } - } - - return -1; // No ':'; doesn't start with a scheme. - } - - /** Returns the number of '/' and '\' slashes in {@code input}, starting at {@code pos}. */ - private static int slashCount(String input, int pos, int limit) { - int slashCount = 0; - while (pos < limit) { - char c = input.charAt(pos); - if (c == '\\' || c == '/') { - slashCount++; - pos++; - } else { - break; - } - } - return slashCount; - } - - /** Finds the first ':' in {@code input}, skipping characters between square braces "[...]". */ - private static int portColonOffset(String input, int pos, int limit) { - for (int i = pos; i < limit; i++) { - switch (input.charAt(i)) { - case '[': - while (++i < limit) { - if (input.charAt(i) == ']') break; - } - break; - case ':': - return i; - } - } - return limit; // No colon. - } - - private static String canonicalizeHost(String input, int pos, int limit) { - // Start by percent decoding the host. The WHATWG spec suggests doing this only after we've - // checked for IPv6 square braces. But Chrome does it first, and that's more lenient. - String percentDecoded = percentDecode(input, pos, limit, false); - - // If the input contains a :, it’s an IPv6 address. - if (percentDecoded.contains(":")) { - // If the input is encased in square braces "[...]", drop 'em. - InetAddress inetAddress = percentDecoded.startsWith("[") && percentDecoded.endsWith("]") - ? decodeIpv6(percentDecoded, 1, percentDecoded.length() - 1) - : decodeIpv6(percentDecoded, 0, percentDecoded.length()); - if (inetAddress == null) return null; - byte[] address = inetAddress.getAddress(); - if (address.length == 16) return inet6AddressToAscii(address); - throw new AssertionError(); - } - - return domainToAscii(percentDecoded); - } - - /** Decodes an IPv6 address like 1111:2222:3333:4444:5555:6666:7777:8888 or ::1. */ - private static InetAddress decodeIpv6(String input, int pos, int limit) { - byte[] address = new byte[16]; - int b = 0; - int compress = -1; - int groupOffset = -1; - - for (int i = pos; i < limit; ) { - if (b == address.length) return null; // Too many groups. - - // Read a delimiter. - if (i + 2 <= limit && input.regionMatches(i, "::", 0, 2)) { - // Compression "::" delimiter, which is anywhere in the input, including its prefix. - if (compress != -1) return null; // Multiple "::" delimiters. - i += 2; - b += 2; - compress = b; - if (i == limit) break; - } else if (b != 0) { - // Group separator ":" delimiter. - if (input.regionMatches(i, ":", 0, 1)) { - i++; - } else if (input.regionMatches(i, ".", 0, 1)) { - // If we see a '.', rewind to the beginning of the previous group and parse as IPv4. - if (!decodeIpv4Suffix(input, groupOffset, limit, address, b - 2)) return null; - b += 2; // We rewound two bytes and then added four. - break; - } else { - return null; // Wrong delimiter. - } - } - - // Read a group, one to four hex digits. - int value = 0; - groupOffset = i; - for (; i < limit; i++) { - char c = input.charAt(i); - int hexDigit = decodeHexDigit(c); - if (hexDigit == -1) break; - value = (value << 4) + hexDigit; - } - int groupLength = i - groupOffset; - if (groupLength == 0 || groupLength > 4) return null; // Group is the wrong size. - - // We've successfully read a group. Assign its value to our byte array. - address[b++] = (byte) ((value >>> 8) & 0xff); - address[b++] = (byte) (value & 0xff); - } - - // All done. If compression happened, we need to move bytes to the right place in the - // address. Here's a sample: - // - // input: "1111:2222:3333::7777:8888" - // before: { 11, 11, 22, 22, 33, 33, 00, 00, 77, 77, 88, 88, 00, 00, 00, 00 } - // compress: 6 - // b: 10 - // after: { 11, 11, 22, 22, 33, 33, 00, 00, 00, 00, 00, 00, 77, 77, 88, 88 } - // - if (b != address.length) { - if (compress == -1) return null; // Address didn't have compression or enough groups. - System.arraycopy(address, compress, address, address.length - (b - compress), b - compress); - Arrays.fill(address, compress, compress + (address.length - b), (byte) 0); - } - - try { - return InetAddress.getByAddress(address); - } catch (UnknownHostException e) { - throw new AssertionError(); - } - } - - /** Decodes an IPv4 address suffix of an IPv6 address, like 1111::5555:6666:192.168.0.1. */ - private static boolean decodeIpv4Suffix( - String input, int pos, int limit, byte[] address, int addressOffset) { - int b = addressOffset; - - for (int i = pos; i < limit; ) { - if (b == address.length) return false; // Too many groups. - - // Read a delimiter. - if (b != addressOffset) { - if (input.charAt(i) != '.') return false; // Wrong delimiter. - i++; - } - - // Read 1 or more decimal digits for a value in 0..255. - int value = 0; - int groupOffset = i; - for (; i < limit; i++) { - char c = input.charAt(i); - if (c < '0' || c > '9') break; - if (value == 0 && groupOffset != i) return false; // Reject unnecessary leading '0's. - value = (value * 10) + c - '0'; - if (value > 255) return false; // Value out of range. - } - int groupLength = i - groupOffset; - if (groupLength == 0) return false; // No digits. - - // We've successfully read a byte. - address[b++] = (byte) value; - } - - if (b != addressOffset + 4) return false; // Too few groups. We wanted exactly four. - return true; // Success. - } - - private static String inet6AddressToAscii(byte[] address) { - // Go through the address looking for the longest run of 0s. Each group is 2-bytes. - int longestRunOffset = -1; - int longestRunLength = 0; - for (int i = 0; i < address.length; i += 2) { - int currentRunOffset = i; - while (i < 16 && address[i] == 0 && address[i + 1] == 0) { - i += 2; - } - int currentRunLength = i - currentRunOffset; - if (currentRunLength > longestRunLength) { - longestRunOffset = currentRunOffset; - longestRunLength = currentRunLength; - } - } - - // Emit each 2-byte group in hex, separated by ':'. The longest run of zeroes is "::". - Buffer result = new Buffer(); - for (int i = 0; i < address.length; ) { - if (i == longestRunOffset) { - result.writeByte(':'); - i += longestRunLength; - if (i == 16) result.writeByte(':'); - } else { - if (i > 0) result.writeByte(':'); - int group = (address[i] & 0xff) << 8 | address[i + 1] & 0xff; - result.writeHexadecimalUnsignedLong(group); - i += 2; - } - } - return result.readUtf8(); - } - - private static int parsePort(String input, int pos, int limit) { - try { - // Canonicalize the port string to skip '\n' etc. - String portString = canonicalize(input, pos, limit, "", false, false, false, true); - int i = Integer.parseInt(portString); - if (i > 0 && i <= 65535) return i; - return -1; - } catch (NumberFormatException e) { - return -1; // Invalid port. - } - } - } - - static String percentDecode(String encoded, boolean plusIsSpace) { - return percentDecode(encoded, 0, encoded.length(), plusIsSpace); - } - - private List percentDecode(List list, boolean plusIsSpace) { - List result = new ArrayList<>(list.size()); - for (String s : list) { - result.add(s != null ? percentDecode(s, plusIsSpace) : null); - } - return Collections.unmodifiableList(result); - } - - static String percentDecode(String encoded, int pos, int limit, boolean plusIsSpace) { - for (int i = pos; i < limit; i++) { - char c = encoded.charAt(i); - if (c == '%' || (c == '+' && plusIsSpace)) { - // Slow path: the character at i requires decoding! - Buffer out = new Buffer(); - out.writeUtf8(encoded, pos, i); - percentDecode(out, encoded, i, limit, plusIsSpace); - return out.readUtf8(); - } - } - - // Fast path: no characters in [pos..limit) required decoding. - return encoded.substring(pos, limit); - } - - static void percentDecode(Buffer out, String encoded, int pos, int limit, boolean plusIsSpace) { - int codePoint; - for (int i = pos; i < limit; i += Character.charCount(codePoint)) { - codePoint = encoded.codePointAt(i); - if (codePoint == '%' && i + 2 < limit) { - int d1 = decodeHexDigit(encoded.charAt(i + 1)); - int d2 = decodeHexDigit(encoded.charAt(i + 2)); - if (d1 != -1 && d2 != -1) { - out.writeByte((d1 << 4) + d2); - i += 2; - continue; - } - } else if (codePoint == '+' && plusIsSpace) { - out.writeByte(' '); - continue; - } - out.writeUtf8CodePoint(codePoint); - } - } - - static boolean percentEncoded(String encoded, int pos, int limit) { - return pos + 2 < limit - && encoded.charAt(pos) == '%' - && decodeHexDigit(encoded.charAt(pos + 1)) != -1 - && decodeHexDigit(encoded.charAt(pos + 2)) != -1; - } - - static int decodeHexDigit(char c) { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - return -1; - } - - /** - * Returns a substring of {@code input} on the range {@code [pos..limit)} with the following - * transformations: - *

    - *
  • Tabs, newlines, form feeds and carriage returns are skipped. - *
  • In queries, ' ' is encoded to '+' and '+' is encoded to "%2B". - *
  • Characters in {@code encodeSet} are percent-encoded. - *
  • Control characters and non-ASCII characters are percent-encoded. - *
  • All other characters are copied without transformation. - *
- * - * @param alreadyEncoded true to leave '%' as-is; false to convert it to '%25'. - * @param strict true to encode '%' if it is not the prefix of a valid percent encoding. - * @param plusIsSpace true to encode '+' as "%2B" if it is not already encoded. - * @param asciiOnly true to encode all non-ASCII codepoints. - */ - static String canonicalize(String input, int pos, int limit, String encodeSet, - boolean alreadyEncoded, boolean strict, boolean plusIsSpace, boolean asciiOnly) { - int codePoint; - for (int i = pos; i < limit; i += Character.charCount(codePoint)) { - codePoint = input.codePointAt(i); - if (codePoint < 0x20 - || codePoint == 0x7f - || codePoint >= 0x80 && asciiOnly - || encodeSet.indexOf(codePoint) != -1 - || codePoint == '%' && (!alreadyEncoded || strict && !percentEncoded(input, i, limit)) - || codePoint == '+' && plusIsSpace) { - // Slow path: the character at i requires encoding! - Buffer out = new Buffer(); - out.writeUtf8(input, pos, i); - canonicalize(out, input, i, limit, encodeSet, alreadyEncoded, strict, plusIsSpace, - asciiOnly); - return out.readUtf8(); - } - } - - // Fast path: no characters in [pos..limit) required encoding. - return input.substring(pos, limit); - } - - static void canonicalize(Buffer out, String input, int pos, int limit, String encodeSet, - boolean alreadyEncoded, boolean strict, boolean plusIsSpace, boolean asciiOnly) { - Buffer utf8Buffer = null; // Lazily allocated. - int codePoint; - for (int i = pos; i < limit; i += Character.charCount(codePoint)) { - codePoint = input.codePointAt(i); - if (alreadyEncoded - && (codePoint == '\t' || codePoint == '\n' || codePoint == '\f' || codePoint == '\r')) { - // Skip this character. - } else if (codePoint == '+' && plusIsSpace) { - // Encode '+' as '%2B' since we permit ' ' to be encoded as either '+' or '%20'. - out.writeUtf8(alreadyEncoded ? "+" : "%2B"); - } else if (codePoint < 0x20 - || codePoint == 0x7f - || codePoint >= 0x80 && asciiOnly - || encodeSet.indexOf(codePoint) != -1 - || codePoint == '%' && (!alreadyEncoded || strict && !percentEncoded(input, i, limit))) { - // Percent encode this character. - if (utf8Buffer == null) { - utf8Buffer = new Buffer(); - } - utf8Buffer.writeUtf8CodePoint(codePoint); - while (!utf8Buffer.exhausted()) { - int b = utf8Buffer.readByte() & 0xff; - out.writeByte('%'); - out.writeByte(HEX_DIGITS[(b >> 4) & 0xf]); - out.writeByte(HEX_DIGITS[b & 0xf]); - } - } else { - // This character doesn't need encoding. Just copy it over. - out.writeUtf8CodePoint(codePoint); - } - } - } - - static String canonicalize(String input, String encodeSet, boolean alreadyEncoded, boolean strict, - boolean plusIsSpace, boolean asciiOnly) { - return canonicalize( - input, 0, input.length(), encodeSet, alreadyEncoded, strict, plusIsSpace, asciiOnly); - } -} diff --git a/okhttp3/src/main/java/okhttp3/Interceptor.java b/okhttp3/src/main/java/okhttp3/Interceptor.java deleted file mode 100644 index 94effbdf4..000000000 --- a/okhttp3/src/main/java/okhttp3/Interceptor.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.io.IOException; - -/** - * Observes, modifies, and potentially short-circuits requests going out and the corresponding - * requests coming back in. Typically interceptors will be used to add, remove, or transform headers - * on the request or response. - */ -public interface Interceptor { - Response intercept(Chain chain) throws IOException; - - interface Chain { - Request request(); - - Response proceed(Request request) throws IOException; - - Connection connection(); - } -} diff --git a/okhttp3/src/main/java/okhttp3/MediaType.java b/okhttp3/src/main/java/okhttp3/MediaType.java deleted file mode 100644 index bbdcedf68..000000000 --- a/okhttp3/src/main/java/okhttp3/MediaType.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.nio.charset.Charset; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * An RFC 2045 Media Type, appropriate to describe - * the content type of an HTTP request or response body. - */ -public final class MediaType { - private static final String TOKEN = "([a-zA-Z0-9-!#$%&'*+.^_`{|}~]+)"; - private static final String QUOTED = "\"([^\"]*)\""; - private static final Pattern TYPE_SUBTYPE = Pattern.compile(TOKEN + "/" + TOKEN); - private static final Pattern PARAMETER = Pattern.compile( - ";\\s*(?:" + TOKEN + "=(?:" + TOKEN + "|" + QUOTED + "))?"); - - private final String mediaType; - private final String type; - private final String subtype; - private final String charset; - - private MediaType(String mediaType, String type, String subtype, String charset) { - this.mediaType = mediaType; - this.type = type; - this.subtype = subtype; - this.charset = charset; - } - - /** - * Returns a media type for {@code string}, or null if {@code string} is not a well-formed media - * type. - */ - public static MediaType parse(String string) { - Matcher typeSubtype = TYPE_SUBTYPE.matcher(string); - if (!typeSubtype.lookingAt()) return null; - String type = typeSubtype.group(1).toLowerCase(Locale.US); - String subtype = typeSubtype.group(2).toLowerCase(Locale.US); - - String charset = null; - Matcher parameter = PARAMETER.matcher(string); - for (int s = typeSubtype.end(); s < string.length(); s = parameter.end()) { - parameter.region(s, string.length()); - if (!parameter.lookingAt()) return null; // This is not a well-formed media type. - - String name = parameter.group(1); - if (name == null || !name.equalsIgnoreCase("charset")) continue; - String charsetParameter = parameter.group(2) != null - ? parameter.group(2) // Value is a token. - : parameter.group(3); // Value is a quoted string. - if (charset != null && !charsetParameter.equalsIgnoreCase(charset)) { - throw new IllegalArgumentException("Multiple different charsets: " + string); - } - charset = charsetParameter; - } - - return new MediaType(string, type, subtype, charset); - } - - /** - * Returns the high-level media type, such as "text", "image", "audio", "video", or - * "application". - */ - public String type() { - return type; - } - - /** - * Returns a specific media subtype, such as "plain" or "png", "mpeg", "mp4" or "xml". - */ - public String subtype() { - return subtype; - } - - /** - * Returns the charset of this media type, or null if this media type doesn't specify a charset. - */ - public Charset charset() { - return charset != null ? Charset.forName(charset) : null; - } - - /** - * Returns the charset of this media type, or {@code defaultValue} if this media type doesn't - * specify a charset. - */ - public Charset charset(Charset defaultValue) { - return charset != null ? Charset.forName(charset) : defaultValue; - } - - /** - * Returns the encoded media type, like "text/plain; charset=utf-8", appropriate for use in a - * Content-Type header. - */ - @Override public String toString() { - return mediaType; - } - - @Override public boolean equals(Object o) { - return o instanceof MediaType && ((MediaType) o).mediaType.equals(mediaType); - } - - @Override public int hashCode() { - return mediaType.hashCode(); - } -} diff --git a/okhttp3/src/main/java/okhttp3/MultipartBody.java b/okhttp3/src/main/java/okhttp3/MultipartBody.java deleted file mode 100644 index 7ac584d1d..000000000 --- a/okhttp3/src/main/java/okhttp3/MultipartBody.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import okhttp3.internal.Util; -import okio.Buffer; -import okio.BufferedSink; -import okio.ByteString; - -/** An RFC 2387-compliant request body. */ -public final class MultipartBody extends RequestBody { - /** - * The "mixed" subtype of "multipart" is intended for use when the body parts are independent and - * need to be bundled in a particular order. Any "multipart" subtypes that an implementation does - * not recognize must be treated as being of subtype "mixed". - */ - public static final MediaType MIXED = MediaType.parse("multipart/mixed"); - - /** - * The "multipart/alternative" type is syntactically identical to "multipart/mixed", but the - * semantics are different. In particular, each of the body parts is an "alternative" version of - * the same information. - */ - public static final MediaType ALTERNATIVE = MediaType.parse("multipart/alternative"); - - /** - * This type is syntactically identical to "multipart/mixed", but the semantics are different. In - * particular, in a digest, the default {@code Content-Type} value for a body part is changed from - * "text/plain" to "message/rfc822". - */ - public static final MediaType DIGEST = MediaType.parse("multipart/digest"); - - /** - * This type is syntactically identical to "multipart/mixed", but the semantics are different. In - * particular, in a parallel entity, the order of body parts is not significant. - */ - public static final MediaType PARALLEL = MediaType.parse("multipart/parallel"); - - /** - * The media-type multipart/form-data follows the rules of all multipart MIME data streams as - * outlined in RFC 2046. In forms, there are a series of fields to be supplied by the user who - * fills out the form. Each field has a name. Within a given form, the names are unique. - */ - public static final MediaType FORM = MediaType.parse("multipart/form-data"); - - private static final byte[] COLONSPACE = {':', ' '}; - private static final byte[] CRLF = {'\r', '\n'}; - private static final byte[] DASHDASH = {'-', '-'}; - - private final ByteString boundary; - private final MediaType originalType; - private final MediaType contentType; - private final List parts; - private long contentLength = -1L; - - MultipartBody(ByteString boundary, MediaType type, List parts) { - this.boundary = boundary; - this.originalType = type; - this.contentType = MediaType.parse(type + "; boundary=" + boundary.utf8()); - this.parts = Util.immutableList(parts); - } - - public MediaType type() { - return originalType; - } - - public String boundary() { - return boundary.utf8(); - } - - /** The number of parts in this multipart body. */ - public int size() { - return parts.size(); - } - - public List parts() { - return parts; - } - - public Part part(int index) { - return parts.get(index); - } - - /** A combination of {@link #type()} and {@link #boundary()}. */ - @Override public MediaType contentType() { - return contentType; - } - - @Override public long contentLength() throws IOException { - long result = contentLength; - if (result != -1L) return result; - return contentLength = writeOrCountBytes(null, true); - } - - @Override public void writeTo(BufferedSink sink) throws IOException { - writeOrCountBytes(sink, false); - } - - /** - * Either writes this request to {@code sink} or measures its content length. We have one method - * do double-duty to make sure the counting and content are consistent, particularly when it comes - * to awkward operations like measuring the encoded length of header strings, or the - * length-in-digits of an encoded integer. - */ - private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException { - long byteCount = 0L; - - Buffer byteCountBuffer = null; - if (countBytes) { - sink = byteCountBuffer = new Buffer(); - } - - for (int p = 0, partCount = parts.size(); p < partCount; p++) { - Part part = parts.get(p); - Headers headers = part.headers; - RequestBody body = part.body; - - sink.write(DASHDASH); - sink.write(boundary); - sink.write(CRLF); - - if (headers != null) { - for (int h = 0, headerCount = headers.size(); h < headerCount; h++) { - sink.writeUtf8(headers.name(h)) - .write(COLONSPACE) - .writeUtf8(headers.value(h)) - .write(CRLF); - } - } - - MediaType contentType = body.contentType(); - if (contentType != null) { - sink.writeUtf8("Content-Type: ") - .writeUtf8(contentType.toString()) - .write(CRLF); - } - - long contentLength = body.contentLength(); - if (contentLength != -1) { - sink.writeUtf8("Content-Length: ") - .writeDecimalLong(contentLength) - .write(CRLF); - } else if (countBytes) { - // We can't measure the body's size without the sizes of its components. - byteCountBuffer.clear(); - return -1L; - } - - sink.write(CRLF); - - if (countBytes) { - byteCount += contentLength; - } else { - body.writeTo(sink); - } - - sink.write(CRLF); - } - - sink.write(DASHDASH); - sink.write(boundary); - sink.write(DASHDASH); - sink.write(CRLF); - - if (countBytes) { - byteCount += byteCountBuffer.size(); - byteCountBuffer.clear(); - } - - return byteCount; - } - - /** - * Appends a quoted-string to a StringBuilder. - * - *

RFC 2388 is rather vague about how one should escape special characters in form-data - * parameters, and as it turns out Firefox and Chrome actually do rather different things, and - * both say in their comments that they're not really sure what the right approach is. We go with - * Chrome's behavior (which also experimentally seems to match what IE does), but if you actually - * want to have a good chance of things working, please avoid double-quotes, newlines, percent - * signs, and the like in your field names. - */ - static StringBuilder appendQuotedString(StringBuilder target, String key) { - target.append('"'); - for (int i = 0, len = key.length(); i < len; i++) { - char ch = key.charAt(i); - switch (ch) { - case '\n': - target.append("%0A"); - break; - case '\r': - target.append("%0D"); - break; - case '"': - target.append("%22"); - break; - default: - target.append(ch); - break; - } - } - target.append('"'); - return target; - } - - public static final class Part { - public static Part create(RequestBody body) { - return create(null, body); - } - - public static Part create(Headers headers, RequestBody body) { - if (body == null) { - throw new NullPointerException("body == null"); - } - if (headers != null && headers.get("Content-Type") != null) { - throw new IllegalArgumentException("Unexpected header: Content-Type"); - } - if (headers != null && headers.get("Content-Length") != null) { - throw new IllegalArgumentException("Unexpected header: Content-Length"); - } - return new Part(headers, body); - } - - public static Part createFormData(String name, String value) { - return createFormData(name, null, RequestBody.create(null, value)); - } - - public static Part createFormData(String name, String filename, RequestBody body) { - if (name == null) { - throw new NullPointerException("name == null"); - } - StringBuilder disposition = new StringBuilder("form-data; name="); - appendQuotedString(disposition, name); - - if (filename != null) { - disposition.append("; filename="); - appendQuotedString(disposition, filename); - } - - return create(Headers.of("Content-Disposition", disposition.toString()), body); - } - - private final Headers headers; - private final RequestBody body; - - private Part(Headers headers, RequestBody body) { - this.headers = headers; - this.body = body; - } - } - - public static final class Builder { - private final ByteString boundary; - private MediaType type = MIXED; - private final List parts = new ArrayList<>(); - - public Builder() { - this(UUID.randomUUID().toString()); - } - - public Builder(String boundary) { - this.boundary = ByteString.encodeUtf8(boundary); - } - - /** - * Set the MIME type. Expected values for {@code type} are {@link #MIXED} (the default), {@link - * #ALTERNATIVE}, {@link #DIGEST}, {@link #PARALLEL} and {@link #FORM}. - */ - public Builder setType(MediaType type) { - if (type == null) { - throw new NullPointerException("type == null"); - } - if (!type.type().equals("multipart")) { - throw new IllegalArgumentException("multipart != " + type); - } - this.type = type; - return this; - } - - /** Add a part to the body. */ - public Builder addPart(RequestBody body) { - return addPart(Part.create(body)); - } - - /** Add a part to the body. */ - public Builder addPart(Headers headers, RequestBody body) { - return addPart(Part.create(headers, body)); - } - - /** Add a form data part to the body. */ - public Builder addFormDataPart(String name, String value) { - return addPart(Part.createFormData(name, value)); - } - - /** Add a form data part to the body. */ - public Builder addFormDataPart(String name, String filename, RequestBody body) { - return addPart(Part.createFormData(name, filename, body)); - } - - /** Add a part to the body. */ - public Builder addPart(Part part) { - if (part == null) throw new NullPointerException("part == null"); - parts.add(part); - return this; - } - - /** Assemble the specified parts into a request body. */ - public MultipartBody build() { - if (parts.isEmpty()) { - throw new IllegalStateException("Multipart body must have at least one part."); - } - return new MultipartBody(boundary, type, parts); - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/OkHttpClient.java b/okhttp3/src/main/java/okhttp3/OkHttpClient.java deleted file mode 100644 index 4d5cbce18..000000000 --- a/okhttp3/src/main/java/okhttp3/OkHttpClient.java +++ /dev/null @@ -1,721 +0,0 @@ -/* - * Copyright (C) 2012 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.net.MalformedURLException; -import java.net.Proxy; -import java.net.ProxySelector; -import java.net.UnknownHostException; -import java.security.GeneralSecurityException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import javax.net.SocketFactory; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.X509TrustManager; -import okhttp3.internal.Internal; -import okhttp3.internal.InternalCache; -import okhttp3.internal.Platform; -import okhttp3.internal.RouteDatabase; -import okhttp3.internal.Util; -import okhttp3.internal.http.StreamAllocation; -import okhttp3.internal.io.RealConnection; -import okhttp3.internal.tls.OkHostnameVerifier; -import okhttp3.internal.tls.TrustRootIndex; - -/** - * Factory for {@linkplain Call calls}, which can be used to send HTTP requests and read their - * responses. Most applications can use a single OkHttpClient for all of their HTTP requests, - * benefiting from a shared response cache, thread pool, connection re-use, etc. - * - *

To create an {@code OkHttpClient} with the default settings, use the {@linkplain - * #OkHttpClient() default constructor}. Or create a configured instance with {@link - * OkHttpClient.Builder}. To adjust an existing client before making a request, use {@link - * #newBuilder()}. This example shows a call with a 30 second timeout: - *

   {@code
- *
- *   OkHttpClient client = ...
- *   OkHttpClient clientWith30sTimeout = client.newBuilder()
- *       .readTimeout(30, TimeUnit.SECONDS)
- *       .build();
- *   Response response = clientWith30sTimeout.newCall(request).execute();
- * }
- */ -public class OkHttpClient implements Cloneable, Call.Factory { - private static final List DEFAULT_PROTOCOLS = Util.immutableList( - Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1); - - private static final List DEFAULT_CONNECTION_SPECS = Util.immutableList( - ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT); - - static { - Internal.instance = new Internal() { - @Override public void addLenient(Headers.Builder builder, String line) { - builder.addLenient(line); - } - - @Override public void addLenient(Headers.Builder builder, String name, String value) { - builder.addLenient(name, value); - } - - @Override public void setCache(OkHttpClient.Builder builder, InternalCache internalCache) { - builder.setInternalCache(internalCache); - } - - @Override public InternalCache internalCache(OkHttpClient client) { - return client.internalCache(); - } - - @Override public boolean connectionBecameIdle( - ConnectionPool pool, RealConnection connection) { - return pool.connectionBecameIdle(connection); - } - - @Override public RealConnection get( - ConnectionPool pool, Address address, StreamAllocation streamAllocation) { - return pool.get(address, streamAllocation); - } - - @Override public void put(ConnectionPool pool, RealConnection connection) { - pool.put(connection); - } - - @Override public RouteDatabase routeDatabase(ConnectionPool connectionPool) { - return connectionPool.routeDatabase; - } - - @Override - public void callEnqueue(Call call, Callback responseCallback, boolean forWebSocket) { - ((RealCall) call).enqueue(responseCallback, forWebSocket); - } - - @Override public StreamAllocation callEngineGetStreamAllocation(Call call) { - return ((RealCall) call).engine.streamAllocation; - } - - @Override - public void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket, boolean isFallback) { - tlsConfiguration.apply(sslSocket, isFallback); - } - - @Override public HttpUrl getHttpUrlChecked(String url) - throws MalformedURLException, UnknownHostException { - return HttpUrl.getChecked(url); - } - }; - } - - final Dispatcher dispatcher; - final Proxy proxy; - final List protocols; - final List connectionSpecs; - final List interceptors; - final List networkInterceptors; - final ProxySelector proxySelector; - final CookieJar cookieJar; - final Cache cache; - final InternalCache internalCache; - final SocketFactory socketFactory; - final SSLSocketFactory sslSocketFactory; - final TrustRootIndex trustRootIndex; - final HostnameVerifier hostnameVerifier; - final CertificatePinner certificatePinner; - final Authenticator proxyAuthenticator; - final Authenticator authenticator; - final ConnectionPool connectionPool; - final Dns dns; - final boolean followSslRedirects; - final boolean followRedirects; - final boolean retryOnConnectionFailure; - final int connectTimeout; - final int readTimeout; - final int writeTimeout; - - public OkHttpClient() { - this(new Builder()); - } - - private OkHttpClient(Builder builder) { - this.dispatcher = builder.dispatcher; - this.proxy = builder.proxy; - this.protocols = builder.protocols; - this.connectionSpecs = builder.connectionSpecs; - this.interceptors = Util.immutableList(builder.interceptors); - this.networkInterceptors = Util.immutableList(builder.networkInterceptors); - this.proxySelector = builder.proxySelector; - this.cookieJar = builder.cookieJar; - this.cache = builder.cache; - this.internalCache = builder.internalCache; - this.socketFactory = builder.socketFactory; - - boolean isTLS = false; - for (ConnectionSpec spec : connectionSpecs) { - isTLS = isTLS || spec.isTls(); - } - - if (builder.sslSocketFactory != null || !isTLS) { - this.sslSocketFactory = builder.sslSocketFactory; - } else { - try { - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, null, null); - this.sslSocketFactory = sslContext.getSocketFactory(); - } catch (GeneralSecurityException e) { - throw new AssertionError(); // The system has no TLS. Just give up. - } - } - if (sslSocketFactory != null && builder.trustRootIndex == null) { - X509TrustManager trustManager = Platform.get().trustManager(sslSocketFactory); - if (trustManager == null) { - throw new IllegalStateException("Unable to extract the trust manager on " + Platform.get() - + ", sslSocketFactory is " + sslSocketFactory.getClass()); - } - this.trustRootIndex = Platform.get().trustRootIndex(trustManager); - this.certificatePinner = builder.certificatePinner.newBuilder() - .trustRootIndex(trustRootIndex) - .build(); - } else { - this.trustRootIndex = builder.trustRootIndex; - this.certificatePinner = builder.certificatePinner; - } - this.hostnameVerifier = builder.hostnameVerifier; - this.proxyAuthenticator = builder.proxyAuthenticator; - this.authenticator = builder.authenticator; - this.connectionPool = builder.connectionPool; - this.dns = builder.dns; - this.followSslRedirects = builder.followSslRedirects; - this.followRedirects = builder.followRedirects; - this.retryOnConnectionFailure = builder.retryOnConnectionFailure; - this.connectTimeout = builder.connectTimeout; - this.readTimeout = builder.readTimeout; - this.writeTimeout = builder.writeTimeout; - } - - /** Default connect timeout (in milliseconds). */ - public int connectTimeoutMillis() { - return connectTimeout; - } - - /** Default read timeout (in milliseconds). */ - public int readTimeoutMillis() { - return readTimeout; - } - - /** Default write timeout (in milliseconds). */ - public int writeTimeoutMillis() { - return writeTimeout; - } - - public Proxy proxy() { - return proxy; - } - - public ProxySelector proxySelector() { - return proxySelector; - } - - public CookieJar cookieJar() { - return cookieJar; - } - - public Cache cache() { - return cache; - } - - InternalCache internalCache() { - return cache != null ? cache.internalCache : internalCache; - } - - public Dns dns() { - return dns; - } - - public SocketFactory socketFactory() { - return socketFactory; - } - - public SSLSocketFactory sslSocketFactory() { - return sslSocketFactory; - } - - public HostnameVerifier hostnameVerifier() { - return hostnameVerifier; - } - - public CertificatePinner certificatePinner() { - return certificatePinner; - } - - public Authenticator authenticator() { - return authenticator; - } - - public Authenticator proxyAuthenticator() { - return proxyAuthenticator; - } - - public ConnectionPool connectionPool() { - return connectionPool; - } - - public boolean followSslRedirects() { - return followSslRedirects; - } - - public boolean followRedirects() { - return followRedirects; - } - - public boolean retryOnConnectionFailure() { - return retryOnConnectionFailure; - } - - public Dispatcher dispatcher() { - return dispatcher; - } - - public List protocols() { - return protocols; - } - - public List connectionSpecs() { - return connectionSpecs; - } - - /** - * Returns an immutable list of interceptors that observe the full span of each call: from before - * the connection is established (if any) until after the response source is selected (either the - * origin server, cache, or both). - */ - public List interceptors() { - return interceptors; - } - - /** - * Returns an immutable list of interceptors that observe a single network request and response. - * These interceptors must call {@link Interceptor.Chain#proceed} exactly once: it is an error for - * a network interceptor to short-circuit or repeat a network request. - */ - public List networkInterceptors() { - return networkInterceptors; - } - - /** - * Prepares the {@code request} to be executed at some point in the future. - */ - @Override public Call newCall(Request request) { - return new RealCall(this, request); - } - - public Builder newBuilder() { - return new Builder(this); - } - - public static final class Builder { - Dispatcher dispatcher; - Proxy proxy; - List protocols; - List connectionSpecs; - final List interceptors = new ArrayList<>(); - final List networkInterceptors = new ArrayList<>(); - ProxySelector proxySelector; - CookieJar cookieJar; - Cache cache; - InternalCache internalCache; - SocketFactory socketFactory; - SSLSocketFactory sslSocketFactory; - TrustRootIndex trustRootIndex; - HostnameVerifier hostnameVerifier; - CertificatePinner certificatePinner; - Authenticator proxyAuthenticator; - Authenticator authenticator; - ConnectionPool connectionPool; - Dns dns; - boolean followSslRedirects; - boolean followRedirects; - boolean retryOnConnectionFailure; - int connectTimeout; - int readTimeout; - int writeTimeout; - - public Builder() { - dispatcher = new Dispatcher(); - protocols = DEFAULT_PROTOCOLS; - connectionSpecs = DEFAULT_CONNECTION_SPECS; - proxySelector = ProxySelector.getDefault(); - cookieJar = CookieJar.NO_COOKIES; - socketFactory = SocketFactory.getDefault(); - hostnameVerifier = OkHostnameVerifier.INSTANCE; - certificatePinner = CertificatePinner.DEFAULT; - proxyAuthenticator = Authenticator.NONE; - authenticator = Authenticator.NONE; - connectionPool = new ConnectionPool(); - dns = Dns.SYSTEM; - followSslRedirects = true; - followRedirects = true; - retryOnConnectionFailure = true; - connectTimeout = 10_000; - readTimeout = 10_000; - writeTimeout = 10_000; - } - - Builder(OkHttpClient okHttpClient) { - this.dispatcher = okHttpClient.dispatcher; - this.proxy = okHttpClient.proxy; - this.protocols = okHttpClient.protocols; - this.connectionSpecs = okHttpClient.connectionSpecs; - this.interceptors.addAll(okHttpClient.interceptors); - this.networkInterceptors.addAll(okHttpClient.networkInterceptors); - this.proxySelector = okHttpClient.proxySelector; - this.cookieJar = okHttpClient.cookieJar; - this.internalCache = okHttpClient.internalCache; - this.cache = okHttpClient.cache; - this.socketFactory = okHttpClient.socketFactory; - this.sslSocketFactory = okHttpClient.sslSocketFactory; - this.trustRootIndex = okHttpClient.trustRootIndex; - this.hostnameVerifier = okHttpClient.hostnameVerifier; - this.certificatePinner = okHttpClient.certificatePinner; - this.proxyAuthenticator = okHttpClient.proxyAuthenticator; - this.authenticator = okHttpClient.authenticator; - this.connectionPool = okHttpClient.connectionPool; - this.dns = okHttpClient.dns; - this.followSslRedirects = okHttpClient.followSslRedirects; - this.followRedirects = okHttpClient.followRedirects; - this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure; - this.connectTimeout = okHttpClient.connectTimeout; - this.readTimeout = okHttpClient.readTimeout; - this.writeTimeout = okHttpClient.writeTimeout; - } - - /** - * Sets the default connect timeout for new connections. A value of 0 means no timeout, - * otherwise values must be between 1 and {@link Integer#MAX_VALUE} when converted to - * milliseconds. - */ - public Builder connectTimeout(long timeout, TimeUnit unit) { - if (timeout < 0) throw new IllegalArgumentException("timeout < 0"); - if (unit == null) throw new IllegalArgumentException("unit == null"); - long millis = unit.toMillis(timeout); - if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large."); - if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small."); - connectTimeout = (int) millis; - return this; - } - - /** - * Sets the default read timeout for new connections. A value of 0 means no timeout, otherwise - * values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds. - */ - public Builder readTimeout(long timeout, TimeUnit unit) { - if (timeout < 0) throw new IllegalArgumentException("timeout < 0"); - if (unit == null) throw new IllegalArgumentException("unit == null"); - long millis = unit.toMillis(timeout); - if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large."); - if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small."); - readTimeout = (int) millis; - return this; - } - - /** - * Sets the default write timeout for new connections. A value of 0 means no timeout, otherwise - * values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds. - */ - public Builder writeTimeout(long timeout, TimeUnit unit) { - if (timeout < 0) throw new IllegalArgumentException("timeout < 0"); - if (unit == null) throw new IllegalArgumentException("unit == null"); - long millis = unit.toMillis(timeout); - if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large."); - if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small."); - writeTimeout = (int) millis; - return this; - } - - /** - * Sets the HTTP proxy that will be used by connections created by this client. This takes - * precedence over {@link #proxySelector}, which is only honored when this proxy is null (which - * it is by default). To disable proxy use completely, call {@code setProxy(Proxy.NO_PROXY)}. - */ - public Builder proxy(Proxy proxy) { - this.proxy = proxy; - return this; - } - - /** - * Sets the proxy selection policy to be used if no {@link #proxy proxy} is specified - * explicitly. The proxy selector may return multiple proxies; in that case they will be tried - * in sequence until a successful connection is established. - * - *

If unset, the {@link ProxySelector#getDefault() system-wide default} proxy selector will - * be used. - */ - public Builder proxySelector(ProxySelector proxySelector) { - this.proxySelector = proxySelector; - return this; - } - - /** - * Sets the handler that can accept cookies from incoming HTTP responses and provides cookies to - * outgoing HTTP requests. - * - *

If unset, {@linkplain CookieJar#NO_COOKIES no cookies} will be accepted nor provided. - */ - public Builder cookieJar(CookieJar cookieJar) { - if (cookieJar == null) throw new NullPointerException("cookieJar == null"); - this.cookieJar = cookieJar; - return this; - } - - /** Sets the response cache to be used to read and write cached responses. */ - void setInternalCache(InternalCache internalCache) { - this.internalCache = internalCache; - this.cache = null; - } - - public Builder cache(Cache cache) { - this.cache = cache; - this.internalCache = null; - return this; - } - - /** - * Sets the DNS service used to lookup IP addresses for hostnames. - * - *

If unset, the {@link Dns#SYSTEM system-wide default} DNS will be used. - */ - public Builder dns(Dns dns) { - if (dns == null) throw new NullPointerException("dns == null"); - this.dns = dns; - return this; - } - - /** - * Sets the socket factory used to create connections. OkHttp only uses the parameterless {@link - * SocketFactory#createSocket() createSocket()} method to create unconnected sockets. Overriding - * this method, e. g., allows the socket to be bound to a specific local address. - * - *

If unset, the {@link SocketFactory#getDefault() system-wide default} socket factory will - * be used. - */ - public Builder socketFactory(SocketFactory socketFactory) { - if (socketFactory == null) throw new NullPointerException("socketFactory == null"); - this.socketFactory = socketFactory; - return this; - } - - /** - * Sets the socket factory used to secure HTTPS connections. - * - *

If unset, a lazily created SSL socket factory will be used. - */ - public Builder sslSocketFactory(SSLSocketFactory sslSocketFactory) { - if (sslSocketFactory == null) throw new NullPointerException("sslSocketFactory == null"); - this.sslSocketFactory = sslSocketFactory; - this.trustRootIndex = null; - return this; - } - - /** - * Sets the verifier used to confirm that response certificates apply to requested hostnames for - * HTTPS connections. - * - *

If unset, a default hostname verifier will be used. - */ - public Builder hostnameVerifier(HostnameVerifier hostnameVerifier) { - if (hostnameVerifier == null) throw new NullPointerException("hostnameVerifier == null"); - this.hostnameVerifier = hostnameVerifier; - return this; - } - - /** - * Sets the certificate pinner that constrains which certificates are trusted. By default HTTPS - * connections rely on only the {@link #sslSocketFactory SSL socket factory} to establish trust. - * Pinning certificates avoids the need to trust certificate authorities. - */ - public Builder certificatePinner(CertificatePinner certificatePinner) { - if (certificatePinner == null) throw new NullPointerException("certificatePinner == null"); - this.certificatePinner = certificatePinner; - return this; - } - - /** - * Sets the authenticator used to respond to challenges from origin servers. Use {@link - * #proxyAuthenticator} to set the authenticator for proxy servers. - * - *

If unset, the {@linkplain Authenticator#NONE no authentication will be attempted}. - */ - public Builder authenticator(Authenticator authenticator) { - if (authenticator == null) throw new NullPointerException("authenticator == null"); - this.authenticator = authenticator; - return this; - } - - /** - * Sets the authenticator used to respond to challenges from proxy servers. Use {@link - * #authenticator} to set the authenticator for origin servers. - * - *

If unset, the {@linkplain Authenticator#NONE no authentication will be attempted}. - */ - public Builder proxyAuthenticator(Authenticator proxyAuthenticator) { - if (proxyAuthenticator == null) throw new NullPointerException("proxyAuthenticator == null"); - this.proxyAuthenticator = proxyAuthenticator; - return this; - } - - /** - * Sets the connection pool used to recycle HTTP and HTTPS connections. - * - *

If unset, a new connection pool will be used. - */ - public Builder connectionPool(ConnectionPool connectionPool) { - if (connectionPool == null) throw new NullPointerException("connectionPool == null"); - this.connectionPool = connectionPool; - return this; - } - - /** - * Configure this client to follow redirects from HTTPS to HTTP and from HTTP to HTTPS. - * - *

If unset, protocol redirects will be followed. This is different than the built-in {@code - * HttpURLConnection}'s default. - */ - public Builder followSslRedirects(boolean followProtocolRedirects) { - this.followSslRedirects = followProtocolRedirects; - return this; - } - - /** Configure this client to follow redirects. If unset, redirects be followed. */ - public Builder followRedirects(boolean followRedirects) { - this.followRedirects = followRedirects; - return this; - } - - /** - * Configure this client to retry or not when a connectivity problem is encountered. By default, - * this client silently recovers from the following problems: - * - *

    - *
  • Unreachable IP addresses. If the URL's host has multiple IP addresses, - * failure to reach any individual IP address doesn't fail the overall request. This can - * increase availability of multi-homed services. - *
  • Stale pooled connections. The {@link ConnectionPool} reuses sockets - * to decrease request latency, but these connections will occasionally time out. - *
  • Unreachable proxy servers. A {@link ProxySelector} can be used to - * attempt multiple proxy servers in sequence, eventually falling back to a direct - * connection. - *
- * - * Set this to false to avoid retrying requests when doing so is destructive. In this case the - * calling application should do its own recovery of connectivity failures. - */ - public Builder retryOnConnectionFailure(boolean retryOnConnectionFailure) { - this.retryOnConnectionFailure = retryOnConnectionFailure; - return this; - } - - /** - * Sets the dispatcher used to set policy and execute asynchronous requests. Must not be null. - */ - public Builder dispatcher(Dispatcher dispatcher) { - if (dispatcher == null) throw new IllegalArgumentException("dispatcher == null"); - this.dispatcher = dispatcher; - return this; - } - - /** - * Configure the protocols used by this client to communicate with remote servers. By default - * this client will prefer the most efficient transport available, falling back to more - * ubiquitous protocols. Applications should only call this method to avoid specific - * compatibility problems, such as web servers that behave incorrectly when SPDY is enabled. - * - *

The following protocols are currently supported: - * - *

- * - *

This is an evolving set. Future releases include support for transitional - * protocols. The http/1.1 transport will never be dropped. - * - *

If multiple protocols are specified, ALPN will be used to - * negotiate a transport. - * - *

{@link Protocol#HTTP_1_0} is not supported in this set. Requests are initiated with {@code - * HTTP/1.1} only. If the server responds with {@code HTTP/1.0}, that will be exposed by {@link - * Response#protocol()}. - * - * @param protocols the protocols to use, in order of preference. The list must contain {@link - * Protocol#HTTP_1_1}. It must not contain null or {@link Protocol#HTTP_1_0}. - */ - public Builder protocols(List protocols) { - protocols = Util.immutableList(protocols); - if (!protocols.contains(Protocol.HTTP_1_1)) { - throw new IllegalArgumentException("protocols doesn't contain http/1.1: " + protocols); - } - if (protocols.contains(Protocol.HTTP_1_0)) { - throw new IllegalArgumentException("protocols must not contain http/1.0: " + protocols); - } - if (protocols.contains(null)) { - throw new IllegalArgumentException("protocols must not contain null"); - } - this.protocols = Util.immutableList(protocols); - return this; - } - - public Builder connectionSpecs(List connectionSpecs) { - this.connectionSpecs = Util.immutableList(connectionSpecs); - return this; - } - - /** - * Returns a modifiable list of interceptors that observe the full span of each call: from - * before the connection is established (if any) until after the response source is selected - * (either the origin server, cache, or both). - */ - public List interceptors() { - return interceptors; - } - - public Builder addInterceptor(Interceptor interceptor) { - interceptors.add(interceptor); - return this; - } - - /** - * Returns a modifiable list of interceptors that observe a single network request and response. - * These interceptors must call {@link Interceptor.Chain#proceed} exactly once: it is an error - * for a network interceptor to short-circuit or repeat a network request. - */ - public List networkInterceptors() { - return networkInterceptors; - } - - public Builder addNetworkInterceptor(Interceptor interceptor) { - networkInterceptors.add(interceptor); - return this; - } - - public OkHttpClient build() { - return new OkHttpClient(this); - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/Protocol.java b/okhttp3/src/main/java/okhttp3/Protocol.java deleted file mode 100644 index eec1c1219..000000000 --- a/okhttp3/src/main/java/okhttp3/Protocol.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.io.IOException; - -/** - * Protocols that OkHttp implements for ALPN selection. - * - *

Protocol vs Scheme

Despite its name, {@link java.net.URL#getProtocol()} returns the - * {@linkplain java.net.URI#getScheme() scheme} (http, https, etc.) of the URL, not the protocol - * (http/1.1, spdy/3.1, etc.). OkHttp uses the word protocol to identify how HTTP messages - * are framed. - */ -public enum Protocol { - /** - * An obsolete plaintext framing that does not use persistent sockets by default. - */ - HTTP_1_0("http/1.0"), - - /** - * A plaintext framing that includes persistent connections. - * - *

This version of OkHttp implements RFC - * 2616, and tracks revisions to that spec. - */ - HTTP_1_1("http/1.1"), - - /** - * Chromium's binary-framed protocol that includes header compression, multiplexing multiple - * requests on the same socket, and server-push. HTTP/1.1 semantics are layered on SPDY/3. - * - *

This version of OkHttp implements SPDY 3 draft 3.1. Future - * releases of OkHttp may use this identifier for a newer draft of the SPDY spec. - */ - SPDY_3("spdy/3.1"), - - /** - * The IETF's binary-framed protocol that includes header compression, multiplexing multiple - * requests on the same socket, and server-push. HTTP/1.1 semantics are layered on HTTP/2. - * - *

HTTP/2 requires deployments of HTTP/2 that use TLS 1.2 support {@linkplain - * CipherSuite#TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256} , present in Java 8+ and Android 5+. Servers - * that enforce this may send an exception message including the string {@code - * INADEQUATE_SECURITY}. - */ - HTTP_2("h2"); - - private final String protocol; - - Protocol(String protocol) { - this.protocol = protocol; - } - - /** - * Returns the protocol identified by {@code protocol}. - * - * @throws IOException if {@code protocol} is unknown. - */ - public static Protocol get(String protocol) throws IOException { - // Unroll the loop over values() to save an allocation. - if (protocol.equals(HTTP_1_0.protocol)) return HTTP_1_0; - if (protocol.equals(HTTP_1_1.protocol)) return HTTP_1_1; - if (protocol.equals(HTTP_2.protocol)) return HTTP_2; - if (protocol.equals(SPDY_3.protocol)) return SPDY_3; - throw new IOException("Unexpected protocol: " + protocol); - } - - /** - * Returns the string used to identify this protocol for ALPN, like "http/1.1", "spdy/3.1" or - * "h2". - */ - @Override public String toString() { - return protocol; - } -} diff --git a/okhttp3/src/main/java/okhttp3/RealCall.java b/okhttp3/src/main/java/okhttp3/RealCall.java deleted file mode 100644 index 16cb67cdc..000000000 --- a/okhttp3/src/main/java/okhttp3/RealCall.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.io.IOException; -import java.net.ProtocolException; -import java.util.logging.Level; -import okhttp3.internal.NamedRunnable; -import okhttp3.internal.http.HttpEngine; -import okhttp3.internal.http.RequestException; -import okhttp3.internal.http.RouteException; -import okhttp3.internal.http.StreamAllocation; - -import static okhttp3.internal.Internal.logger; -import static okhttp3.internal.http.HttpEngine.MAX_FOLLOW_UPS; - -final class RealCall implements Call { - private final OkHttpClient client; - - // Guarded by this. - private boolean executed; - volatile boolean canceled; - - /** The application's original request unadulterated by redirects or auth headers. */ - Request originalRequest; - HttpEngine engine; - - protected RealCall(OkHttpClient client, Request originalRequest) { - this.client = client; - this.originalRequest = originalRequest; - } - - @Override public Request request() { - return originalRequest; - } - - @Override public Response execute() throws IOException { - synchronized (this) { - if (executed) throw new IllegalStateException("Already Executed"); - executed = true; - } - try { - client.dispatcher().executed(this); - Response result = getResponseWithInterceptorChain(false); - if (result == null) throw new IOException("Canceled"); - return result; - } finally { - client.dispatcher().finished(this); - } - } - - Object tag() { - return originalRequest.tag(); - } - - @Override public void enqueue(Callback responseCallback) { - enqueue(responseCallback, false); - } - - void enqueue(Callback responseCallback, boolean forWebSocket) { - synchronized (this) { - if (executed) throw new IllegalStateException("Already Executed"); - executed = true; - } - client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket)); - } - - @Override public void cancel() { - canceled = true; - if (engine != null) engine.cancel(); - } - - @Override public synchronized boolean isExecuted() { - return executed; - } - - @Override public boolean isCanceled() { - return canceled; - } - - final class AsyncCall extends NamedRunnable { - private final Callback responseCallback; - private final boolean forWebSocket; - - private AsyncCall(Callback responseCallback, boolean forWebSocket) { - super("OkHttp %s", originalRequest.url().toString()); - this.responseCallback = responseCallback; - this.forWebSocket = forWebSocket; - } - - String host() { - return originalRequest.url().host(); - } - - Request request() { - return originalRequest; - } - - Object tag() { - return originalRequest.tag(); - } - - void cancel() { - RealCall.this.cancel(); - } - - RealCall get() { - return RealCall.this; - } - - @Override protected void execute() { - boolean signalledCallback = false; - try { - Response response = getResponseWithInterceptorChain(forWebSocket); - if (canceled) { - signalledCallback = true; - responseCallback.onFailure(RealCall.this, new IOException("Canceled")); - } else { - signalledCallback = true; - responseCallback.onResponse(RealCall.this, response); - } - } catch (IOException e) { - if (signalledCallback) { - // Do not signal the callback twice! - logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e); - } else { - responseCallback.onFailure(RealCall.this, e); - } - } finally { - client.dispatcher().finished(this); - } - } - } - - /** - * Returns a string that describes this call. Doesn't include a full URL as that might contain - * sensitive information. - */ - private String toLoggableString() { - String string = canceled ? "canceled call" : "call"; - HttpUrl redactedUrl = originalRequest.url().resolve("/..."); - return string + " to " + redactedUrl; - } - - private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException { - Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket); - return chain.proceed(originalRequest); - } - - class ApplicationInterceptorChain implements Interceptor.Chain { - private final int index; - private final Request request; - private final boolean forWebSocket; - - ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) { - this.index = index; - this.request = request; - this.forWebSocket = forWebSocket; - } - - @Override public Connection connection() { - return null; - } - - @Override public Request request() { - return request; - } - - @Override public Response proceed(Request request) throws IOException { - // If there's another interceptor in the chain, call that. - if (index < client.interceptors().size()) { - Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket); - Interceptor interceptor = client.interceptors().get(index); - Response interceptedResponse = interceptor.intercept(chain); - - if (interceptedResponse == null) { - throw new NullPointerException("application interceptor " + interceptor - + " returned null"); - } - - return interceptedResponse; - } - - // No more interceptors. Do HTTP. - return getResponse(request, forWebSocket); - } - } - - /** - * Performs the request and returns the response. May return null if this call was canceled. - */ - Response getResponse(Request request, boolean forWebSocket) throws IOException { - // Copy body metadata to the appropriate request headers. - RequestBody body = request.body(); - if (body != null) { - Request.Builder requestBuilder = request.newBuilder(); - - MediaType contentType = body.contentType(); - if (contentType != null) { - requestBuilder.header("Content-Type", contentType.toString()); - } - - long contentLength = body.contentLength(); - if (contentLength != -1) { - requestBuilder.header("Content-Length", Long.toString(contentLength)); - requestBuilder.removeHeader("Transfer-Encoding"); - } else { - requestBuilder.header("Transfer-Encoding", "chunked"); - requestBuilder.removeHeader("Content-Length"); - } - - request = requestBuilder.build(); - } - - // Create the initial HTTP engine. Retries and redirects need new engine for each attempt. - engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null); - - int followUpCount = 0; - while (true) { - if (canceled) { - engine.releaseStreamAllocation(); - throw new IOException("Canceled"); - } - - boolean releaseConnection = true; - try { - engine.sendRequest(); - engine.readResponse(); - releaseConnection = false; - } catch (RequestException e) { - // The attempt to interpret the request failed. Give up. - throw e.getCause(); - } catch (RouteException e) { - // The attempt to connect via a route failed. The request will not have been sent. - HttpEngine retryEngine = engine.recover(e.getLastConnectException(), null); - if (retryEngine != null) { - releaseConnection = false; - engine = retryEngine; - continue; - } - // Give up; recovery is not possible. - throw e.getLastConnectException(); - } catch (IOException e) { - // An attempt to communicate with a server failed. The request may have been sent. - HttpEngine retryEngine = engine.recover(e, null); - if (retryEngine != null) { - releaseConnection = false; - engine = retryEngine; - continue; - } - - // Give up; recovery is not possible. - throw e; - } finally { - // We're throwing an unchecked exception. Release any resources. - if (releaseConnection) { - StreamAllocation streamAllocation = engine.close(); - streamAllocation.release(); - } - } - - Response response = engine.getResponse(); - Request followUp = engine.followUpRequest(); - - if (followUp == null) { - if (!forWebSocket) { - engine.releaseStreamAllocation(); - } - return response; - } - - StreamAllocation streamAllocation = engine.close(); - - if (++followUpCount > MAX_FOLLOW_UPS) { - streamAllocation.release(); - throw new ProtocolException("Too many follow-up requests: " + followUpCount); - } - - if (!engine.sameConnection(followUp.url())) { - streamAllocation.release(); - streamAllocation = null; - } - - request = followUp; - engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null, - response); - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/Request.java b/okhttp3/src/main/java/okhttp3/Request.java deleted file mode 100644 index 2daa0ab9b..000000000 --- a/okhttp3/src/main/java/okhttp3/Request.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.net.URI; -import java.net.URL; -import java.util.List; -import okhttp3.internal.http.HttpMethod; - -/** - * An HTTP request. Instances of this class are immutable if their {@link #body} is null or itself - * immutable. - */ -public final class Request { - private final HttpUrl url; - private final String method; - private final Headers headers; - private final RequestBody body; - private final Object tag; - - private volatile URI javaNetUri; // Lazily initialized. - private volatile CacheControl cacheControl; // Lazily initialized. - - private Request(Builder builder) { - this.url = builder.url; - this.method = builder.method; - this.headers = builder.headers.build(); - this.body = builder.body; - this.tag = builder.tag != null ? builder.tag : this; - } - - public HttpUrl url() { - return url; - } - - public String method() { - return method; - } - - public Headers headers() { - return headers; - } - - public String header(String name) { - return headers.get(name); - } - - public List headers(String name) { - return headers.values(name); - } - - public RequestBody body() { - return body; - } - - public Object tag() { - return tag; - } - - public Builder newBuilder() { - return new Builder(this); - } - - /** - * Returns the cache control directives for this response. This is never null, even if this - * response contains no {@code Cache-Control} header. - */ - public CacheControl cacheControl() { - CacheControl result = cacheControl; - return result != null ? result : (cacheControl = CacheControl.parse(headers)); - } - - public boolean isHttps() { - return url.isHttps(); - } - - @Override public String toString() { - return "Request{method=" - + method - + ", url=" - + url - + ", tag=" - + (tag != this ? tag : null) - + '}'; - } - - public static class Builder { - private HttpUrl url; - private String method; - private Headers.Builder headers; - private RequestBody body; - private Object tag; - - public Builder() { - this.method = "GET"; - this.headers = new Headers.Builder(); - } - - private Builder(Request request) { - this.url = request.url; - this.method = request.method; - this.body = request.body; - this.tag = request.tag; - this.headers = request.headers.newBuilder(); - } - - public Builder url(HttpUrl url) { - if (url == null) throw new IllegalArgumentException("url == null"); - this.url = url; - return this; - } - - /** - * Sets the URL target of this request. - * - * @throws IllegalArgumentException if {@code url} is not a valid HTTP or HTTPS URL. Avoid this - * exception by calling {@link HttpUrl#parse}; it returns null for invalid URLs. - */ - public Builder url(String url) { - if (url == null) throw new IllegalArgumentException("url == null"); - - // Silently replace websocket URLs with HTTP URLs. - if (url.regionMatches(true, 0, "ws:", 0, 3)) { - url = "http:" + url.substring(3); - } else if (url.regionMatches(true, 0, "wss:", 0, 4)) { - url = "https:" + url.substring(4); - } - - HttpUrl parsed = HttpUrl.parse(url); - if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url); - return url(parsed); - } - - /** - * Sets the URL target of this request. - * - * @throws IllegalArgumentException if the scheme of {@code url} is not {@code http} or {@code - * https}. - */ - public Builder url(URL url) { - if (url == null) throw new IllegalArgumentException("url == null"); - HttpUrl parsed = HttpUrl.get(url); - if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url); - return url(parsed); - } - - /** - * Sets the header named {@code name} to {@code value}. If this request already has any headers - * with that name, they are all replaced. - */ - public Builder header(String name, String value) { - headers.set(name, value); - return this; - } - - /** - * Adds a header with {@code name} and {@code value}. Prefer this method for multiply-valued - * headers like "Cookie". - * - *

Note that for some headers including {@code Content-Length} and {@code Content-Encoding}, - * OkHttp may replace {@code value} with a header derived from the request body. - */ - public Builder addHeader(String name, String value) { - headers.add(name, value); - return this; - } - - public Builder removeHeader(String name) { - headers.removeAll(name); - return this; - } - - /** Removes all headers on this builder and adds {@code headers}. */ - public Builder headers(Headers headers) { - this.headers = headers.newBuilder(); - return this; - } - - /** - * Sets this request's {@code Cache-Control} header, replacing any cache control headers already - * present. If {@code cacheControl} doesn't define any directives, this clears this request's - * cache-control headers. - */ - public Builder cacheControl(CacheControl cacheControl) { - String value = cacheControl.toString(); - if (value.isEmpty()) return removeHeader("Cache-Control"); - return header("Cache-Control", value); - } - - public Builder get() { - return method("GET", null); - } - - public Builder head() { - return method("HEAD", null); - } - - public Builder post(RequestBody body) { - return method("POST", body); - } - - public Builder delete(RequestBody body) { - return method("DELETE", body); - } - - public Builder delete() { - return delete(RequestBody.create(null, new byte[0])); - } - - public Builder put(RequestBody body) { - return method("PUT", body); - } - - public Builder patch(RequestBody body) { - return method("PATCH", body); - } - - public Builder method(String method, RequestBody body) { - if (method == null || method.length() == 0) { - throw new IllegalArgumentException("method == null || method.length() == 0"); - } - if (body != null && !HttpMethod.permitsRequestBody(method)) { - throw new IllegalArgumentException("method " + method + " must not have a request body."); - } - if (body == null && HttpMethod.requiresRequestBody(method)) { - throw new IllegalArgumentException("method " + method + " must have a request body."); - } - this.method = method; - this.body = body; - return this; - } - - /** - * Attaches {@code tag} to the request. It can be used later to cancel the request. If the tag - * is unspecified or null, the request is canceled by using the request itself as the tag. - */ - public Builder tag(Object tag) { - this.tag = tag; - return this; - } - - public Request build() { - if (url == null) throw new IllegalStateException("url == null"); - return new Request(this); - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/RequestBody.java b/okhttp3/src/main/java/okhttp3/RequestBody.java deleted file mode 100644 index 136cfdc4e..000000000 --- a/okhttp3/src/main/java/okhttp3/RequestBody.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.Charset; -import okhttp3.internal.Util; -import okio.BufferedSink; -import okio.ByteString; -import okio.Okio; -import okio.Source; - -public abstract class RequestBody { - /** Returns the Content-Type header for this body. */ - public abstract MediaType contentType(); - - /** - * Returns the number of bytes that will be written to {@code out} in a call to {@link #writeTo}, - * or -1 if that count is unknown. - */ - public long contentLength() throws IOException { - return -1; - } - - /** Writes the content of this request to {@code out}. */ - public abstract void writeTo(BufferedSink sink) throws IOException; - - /** - * Returns a new request body that transmits {@code content}. If {@code contentType} is non-null - * and lacks a charset, this will use UTF-8. - */ - public static RequestBody create(MediaType contentType, String content) { - Charset charset = Util.UTF_8; - if (contentType != null) { - charset = contentType.charset(); - if (charset == null) { - charset = Util.UTF_8; - contentType = MediaType.parse(contentType + "; charset=utf-8"); - } - } - byte[] bytes = content.getBytes(charset); - return create(contentType, bytes); - } - - /** Returns a new request body that transmits {@code content}. */ - public static RequestBody create(final MediaType contentType, final ByteString content) { - return new RequestBody() { - @Override public MediaType contentType() { - return contentType; - } - - @Override public long contentLength() throws IOException { - return content.size(); - } - - @Override public void writeTo(BufferedSink sink) throws IOException { - sink.write(content); - } - }; - } - - /** Returns a new request body that transmits {@code content}. */ - public static RequestBody create(final MediaType contentType, final byte[] content) { - return create(contentType, content, 0, content.length); - } - - /** Returns a new request body that transmits {@code content}. */ - public static RequestBody create(final MediaType contentType, final byte[] content, - final int offset, final int byteCount) { - if (content == null) throw new NullPointerException("content == null"); - Util.checkOffsetAndCount(content.length, offset, byteCount); - return new RequestBody() { - @Override public MediaType contentType() { - return contentType; - } - - @Override public long contentLength() { - return byteCount; - } - - @Override public void writeTo(BufferedSink sink) throws IOException { - sink.write(content, offset, byteCount); - } - }; - } - - /** Returns a new request body that transmits the content of {@code file}. */ - public static RequestBody create(final MediaType contentType, final File file) { - if (file == null) throw new NullPointerException("content == null"); - - return new RequestBody() { - @Override public MediaType contentType() { - return contentType; - } - - @Override public long contentLength() { - return file.length(); - } - - @Override public void writeTo(BufferedSink sink) throws IOException { - Source source = null; - try { - source = Okio.source(file); - sink.writeAll(source); - } finally { - Util.closeQuietly(source); - } - } - }; - } -} diff --git a/okhttp3/src/main/java/okhttp3/Response.java b/okhttp3/src/main/java/okhttp3/Response.java deleted file mode 100644 index 7a2d717e2..000000000 --- a/okhttp3/src/main/java/okhttp3/Response.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.io.IOException; -import java.util.Collections; -import java.util.List; -import okhttp3.internal.http.OkHeaders; -import okio.Buffer; -import okio.BufferedSource; - -import static java.net.HttpURLConnection.HTTP_MOVED_PERM; -import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; -import static java.net.HttpURLConnection.HTTP_MULT_CHOICE; -import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; -import static java.net.HttpURLConnection.HTTP_SEE_OTHER; -import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; -import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT; -import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT; - -/** - * An HTTP response. Instances of this class are not immutable: the response body is a one-shot - * value that may be consumed only once. All other properties are immutable. - */ -public final class Response { - private final Request request; - private final Protocol protocol; - private final int code; - private final String message; - private final Handshake handshake; - private final Headers headers; - private final ResponseBody body; - private Response networkResponse; - private Response cacheResponse; - private final Response priorResponse; - - private volatile CacheControl cacheControl; // Lazily initialized. - - private Response(Builder builder) { - this.request = builder.request; - this.protocol = builder.protocol; - this.code = builder.code; - this.message = builder.message; - this.handshake = builder.handshake; - this.headers = builder.headers.build(); - this.body = builder.body; - this.networkResponse = builder.networkResponse; - this.cacheResponse = builder.cacheResponse; - this.priorResponse = builder.priorResponse; - } - - /** - * The wire-level request that initiated this HTTP response. This is not - * necessarily the same request issued by the application: - * - *

    - *
  • It may be transformed by the HTTP client. For example, the client - * may copy headers like {@code Content-Length} from the request body. - *
  • It may be the request generated in response to an HTTP redirect or - * authentication challenge. In this case the request URL may be - * different than the initial request URL. - *
- */ - public Request request() { - return request; - } - - /** - * Returns the HTTP protocol, such as {@link Protocol#HTTP_1_1} or {@link Protocol#HTTP_1_0}. - */ - public Protocol protocol() { - return protocol; - } - - /** Returns the HTTP status code. */ - public int code() { - return code; - } - - /** - * Returns true if the code is in [200..300), which means the request was successfully received, - * understood, and accepted. - */ - public boolean isSuccessful() { - return code >= 200 && code < 300; - } - - /** Returns the HTTP status message or null if it is unknown. */ - public String message() { - return message; - } - - /** - * Returns the TLS handshake of the connection that carried this response, or null if the response - * was received without TLS. - */ - public Handshake handshake() { - return handshake; - } - - public List headers(String name) { - return headers.values(name); - } - - public String header(String name) { - return header(name, null); - } - - public String header(String name, String defaultValue) { - String result = headers.get(name); - return result != null ? result : defaultValue; - } - - public Headers headers() { - return headers; - } - - /** - * Peeks up to {@code byteCount} bytes from the response body and returns them as a new response - * body. If fewer than {@code byteCount} bytes are in the response body, the full response body is - * returned. If more than {@code byteCount} bytes are in the response body, the returned value - * will be truncated to {@code byteCount} bytes. - * - *

It is an error to call this method after the body has been consumed. - * - *

Warning: this method loads the requested bytes into memory. Most - * applications should set a modest limit on {@code byteCount}, such as 1 MiB. - */ - public ResponseBody peekBody(long byteCount) throws IOException { - BufferedSource source = body.source(); - source.request(byteCount); - Buffer copy = source.buffer().clone(); - - // There may be more than byteCount bytes in source.buffer(). If there is, return a prefix. - Buffer result; - if (copy.size() > byteCount) { - result = new Buffer(); - result.write(copy, byteCount); - copy.clear(); - } else { - result = copy; - } - - return ResponseBody.create(body.contentType(), result.size(), result); - } - - public ResponseBody body() { - return body; - } - - public Builder newBuilder() { - return new Builder(this); - } - - /** Returns true if this response redirects to another resource. */ - public boolean isRedirect() { - switch (code) { - case HTTP_PERM_REDIRECT: - case HTTP_TEMP_REDIRECT: - case HTTP_MULT_CHOICE: - case HTTP_MOVED_PERM: - case HTTP_MOVED_TEMP: - case HTTP_SEE_OTHER: - return true; - default: - return false; - } - } - - /** - * Returns the raw response received from the network. Will be null if this response didn't use - * the network, such as when the response is fully cached. The body of the returned response - * should not be read. - */ - public Response networkResponse() { - return networkResponse; - } - - /** - * Returns the raw response received from the cache. Will be null if this response didn't use the - * cache. For conditional get requests the cache response and network response may both be - * non-null. The body of the returned response should not be read. - */ - public Response cacheResponse() { - return cacheResponse; - } - - /** - * Returns the response for the HTTP redirect or authorization challenge that triggered this - * response, or null if this response wasn't triggered by an automatic retry. The body of the - * returned response should not be read because it has already been consumed by the redirecting - * client. - */ - public Response priorResponse() { - return priorResponse; - } - - /** - * Returns the authorization challenges appropriate for this response's code. If the response code - * is 401 unauthorized, this returns the "WWW-Authenticate" challenges. If the response code is - * 407 proxy unauthorized, this returns the "Proxy-Authenticate" challenges. Otherwise this - * returns an empty list of challenges. - */ - public List challenges() { - String responseField; - if (code == HTTP_UNAUTHORIZED) { - responseField = "WWW-Authenticate"; - } else if (code == HTTP_PROXY_AUTH) { - responseField = "Proxy-Authenticate"; - } else { - return Collections.emptyList(); - } - return OkHeaders.parseChallenges(headers(), responseField); - } - - /** - * Returns the cache control directives for this response. This is never null, even if this - * response contains no {@code Cache-Control} header. - */ - public CacheControl cacheControl() { - CacheControl result = cacheControl; - return result != null ? result : (cacheControl = CacheControl.parse(headers)); - } - - @Override public String toString() { - return "Response{protocol=" - + protocol - + ", code=" - + code - + ", message=" - + message - + ", url=" - + request.url() - + '}'; - } - - public static class Builder { - private Request request; - private Protocol protocol; - private int code = -1; - private String message; - private Handshake handshake; - private Headers.Builder headers; - private ResponseBody body; - private Response networkResponse; - private Response cacheResponse; - private Response priorResponse; - - public Builder() { - headers = new Headers.Builder(); - } - - private Builder(Response response) { - this.request = response.request; - this.protocol = response.protocol; - this.code = response.code; - this.message = response.message; - this.handshake = response.handshake; - this.headers = response.headers.newBuilder(); - this.body = response.body; - this.networkResponse = response.networkResponse; - this.cacheResponse = response.cacheResponse; - this.priorResponse = response.priorResponse; - } - - public Builder request(Request request) { - this.request = request; - return this; - } - - public Builder protocol(Protocol protocol) { - this.protocol = protocol; - return this; - } - - public Builder code(int code) { - this.code = code; - return this; - } - - public Builder message(String message) { - this.message = message; - return this; - } - - public Builder handshake(Handshake handshake) { - this.handshake = handshake; - return this; - } - - /** - * Sets the header named {@code name} to {@code value}. If this request already has any headers - * with that name, they are all replaced. - */ - public Builder header(String name, String value) { - headers.set(name, value); - return this; - } - - /** - * Adds a header with {@code name} and {@code value}. Prefer this method for multiply-valued - * headers like "Set-Cookie". - */ - public Builder addHeader(String name, String value) { - headers.add(name, value); - return this; - } - - public Builder removeHeader(String name) { - headers.removeAll(name); - return this; - } - - /** Removes all headers on this builder and adds {@code headers}. */ - public Builder headers(Headers headers) { - this.headers = headers.newBuilder(); - return this; - } - - public Builder body(ResponseBody body) { - this.body = body; - return this; - } - - public Builder networkResponse(Response networkResponse) { - if (networkResponse != null) checkSupportResponse("networkResponse", networkResponse); - this.networkResponse = networkResponse; - return this; - } - - public Builder cacheResponse(Response cacheResponse) { - if (cacheResponse != null) checkSupportResponse("cacheResponse", cacheResponse); - this.cacheResponse = cacheResponse; - return this; - } - - private void checkSupportResponse(String name, Response response) { - if (response.body != null) { - throw new IllegalArgumentException(name + ".body != null"); - } else if (response.networkResponse != null) { - throw new IllegalArgumentException(name + ".networkResponse != null"); - } else if (response.cacheResponse != null) { - throw new IllegalArgumentException(name + ".cacheResponse != null"); - } else if (response.priorResponse != null) { - throw new IllegalArgumentException(name + ".priorResponse != null"); - } - } - - public Builder priorResponse(Response priorResponse) { - if (priorResponse != null) checkPriorResponse(priorResponse); - this.priorResponse = priorResponse; - return this; - } - - private void checkPriorResponse(Response response) { - if (response.body != null) { - throw new IllegalArgumentException("priorResponse.body != null"); - } - } - - public Response build() { - if (request == null) throw new IllegalStateException("request == null"); - if (protocol == null) throw new IllegalStateException("protocol == null"); - if (code < 0) throw new IllegalStateException("code < 0: " + code); - return new Response(this); - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/ResponseBody.java b/okhttp3/src/main/java/okhttp3/ResponseBody.java deleted file mode 100644 index 98223bbad..000000000 --- a/okhttp3/src/main/java/okhttp3/ResponseBody.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.nio.charset.Charset; -import okhttp3.internal.Util; -import okio.Buffer; -import okio.BufferedSource; - -import static okhttp3.internal.Util.UTF_8; - -/** - * A one-shot stream from the origin server to the client application with the raw bytes of the - * response body. Each response body is supported by an active connection to the webserver. This - * imposes both obligations and limits on the client application. - * - *

The response body must be closed.

- * - *

Each response body is backed by a limited resource like a socket (live network responses) or - * an open file (for cached responses). Failing to close the response body will leak these resources - * and may ultimately cause the application to slow down or crash. Close the response body by - * calling either {@link ResponseBody#close close()}, {@link InputStream#close() - * byteStream().close()}, or {@link Reader#close() reader().close()}. The {@link #bytes()} and - * {@link #string()} methods both close the response body automatically. - * - *

The response body can be consumed only once.

- * - *

This class may be used to stream very large responses. For example, it is possible to use this - * class to read a response that is larger than the entire memory allocated to the current process. - * It can even stream a response larger than the total storage on the current device, which is a - * common requirement for video streaming applications. - * - *

Because this class does not buffer the full response in memory, the application may not - * re-read the bytes of the response. Use this one shot to read the entire response into memory with - * {@link #bytes()} or {@link #string()}. Or stream the response with either {@link #source()}, - * {@link #byteStream()}, or {@link #charStream()}. - */ -public abstract class ResponseBody implements Closeable { - /** Multiple calls to {@link #charStream()} must return the same instance. */ - private Reader reader; - - public abstract MediaType contentType(); - - /** - * Returns the number of bytes in that will returned by {@link #bytes}, or {@link #byteStream}, or - * -1 if unknown. - */ - public abstract long contentLength(); - - public final InputStream byteStream() { - return source().inputStream(); - } - - public abstract BufferedSource source(); - - public final byte[] bytes() throws IOException { - long contentLength = contentLength(); - if (contentLength > Integer.MAX_VALUE) { - throw new IOException("Cannot buffer entire body for content length: " + contentLength); - } - - BufferedSource source = source(); - byte[] bytes; - try { - bytes = source.readByteArray(); - } finally { - Util.closeQuietly(source); - } - if (contentLength != -1 && contentLength != bytes.length) { - throw new IOException("Content-Length and stream length disagree"); - } - return bytes; - } - - /** - * Returns the response as a character stream decoded with the charset of the Content-Type header. - * If that header is either absent or lacks a charset, this will attempt to decode the response - * body as UTF-8. - */ - public final Reader charStream() { - Reader r = reader; - return r != null ? r : (reader = new InputStreamReader(byteStream(), charset())); - } - - /** - * Returns the response as a string decoded with the charset of the Content-Type header. If that - * header is either absent or lacks a charset, this will attempt to decode the response body as - * UTF-8. - */ - public final String string() throws IOException { - return new String(bytes(), charset().name()); - } - - private Charset charset() { - MediaType contentType = contentType(); - return contentType != null ? contentType.charset(UTF_8) : UTF_8; - } - - @Override public void close() { - Util.closeQuietly(source()); - } - - /** - * Returns a new response body that transmits {@code content}. If {@code contentType} is non-null - * and lacks a charset, this will use UTF-8. - */ - public static ResponseBody create(MediaType contentType, String content) { - Charset charset = UTF_8; - if (contentType != null) { - charset = contentType.charset(); - if (charset == null) { - charset = UTF_8; - contentType = MediaType.parse(contentType + "; charset=utf-8"); - } - } - Buffer buffer = new Buffer().writeString(content, charset); - return create(contentType, buffer.size(), buffer); - } - - /** Returns a new response body that transmits {@code content}. */ - public static ResponseBody create(final MediaType contentType, byte[] content) { - Buffer buffer = new Buffer().write(content); - return create(contentType, content.length, buffer); - } - - /** Returns a new response body that transmits {@code content}. */ - public static ResponseBody create( - final MediaType contentType, final long contentLength, final BufferedSource content) { - if (content == null) throw new NullPointerException("source == null"); - return new ResponseBody() { - @Override public MediaType contentType() { - return contentType; - } - - @Override public long contentLength() { - return contentLength; - } - - @Override public BufferedSource source() { - return content; - } - }; - } -} diff --git a/okhttp3/src/main/java/okhttp3/Route.java b/okhttp3/src/main/java/okhttp3/Route.java deleted file mode 100644 index 8aaa0f5b8..000000000 --- a/okhttp3/src/main/java/okhttp3/Route.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -import java.net.InetSocketAddress; -import java.net.Proxy; - -/** - * The concrete route used by a connection to reach an abstract origin server. When creating a - * connection the client has many options: - * - *

    - *
  • HTTP proxy: a proxy server may be explicitly configured for the client. - * Otherwise the {@linkplain java.net.ProxySelector proxy selector} is used. It may return - * multiple proxies to attempt. - *
  • IP address: whether connecting directly to an origin server or a proxy, - * opening a socket requires an IP address. The DNS server may return multiple IP addresses - * to attempt. - *
- * - *

Each route is a specific selection of these options. - */ -public final class Route { - final Address address; - final Proxy proxy; - final InetSocketAddress inetSocketAddress; - - public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress) { - if (address == null) { - throw new NullPointerException("address == null"); - } - if (proxy == null) { - throw new NullPointerException("proxy == null"); - } - if (inetSocketAddress == null) { - throw new NullPointerException("inetSocketAddress == null"); - } - this.address = address; - this.proxy = proxy; - this.inetSocketAddress = inetSocketAddress; - } - - public Address address() { - return address; - } - - /** - * Returns the {@link Proxy} of this route. - * - * Warning: This may disagree with {@link Address#proxy} when it is null. When - * the address's proxy is null, the proxy selector is used. - */ - public Proxy proxy() { - return proxy; - } - - public InetSocketAddress socketAddress() { - return inetSocketAddress; - } - - /** - * Returns true if this route tunnels HTTPS through an HTTP proxy. See RFC 2817, Section 5.2. - */ - public boolean requiresTunnel() { - return address.sslSocketFactory != null && proxy.type() == Proxy.Type.HTTP; - } - - @Override public boolean equals(Object obj) { - if (obj instanceof Route) { - Route other = (Route) obj; - return address.equals(other.address) - && proxy.equals(other.proxy) - && inetSocketAddress.equals(other.inetSocketAddress); - } - return false; - } - - @Override public int hashCode() { - int result = 17; - result = 31 * result + address.hashCode(); - result = 31 * result + proxy.hashCode(); - result = 31 * result + inetSocketAddress.hashCode(); - return result; - } -} diff --git a/okhttp3/src/main/java/okhttp3/TlsVersion.java b/okhttp3/src/main/java/okhttp3/TlsVersion.java deleted file mode 100644 index 668cd9732..000000000 --- a/okhttp3/src/main/java/okhttp3/TlsVersion.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3; - -/** - * Versions of TLS that can be offered when negotiating a secure socket. See {@link - * javax.net.ssl.SSLSocket#setEnabledProtocols}. - */ -public enum TlsVersion { - TLS_1_2("TLSv1.2"), // 2008. - TLS_1_1("TLSv1.1"), // 2006. - TLS_1_0("TLSv1"), // 1999. - SSL_3_0("SSLv3"), // 1996. - ; - - final String javaName; - - TlsVersion(String javaName) { - this.javaName = javaName; - } - - public static TlsVersion forJavaName(String javaName) { - switch (javaName) { - case "TLSv1.2": - return TLS_1_2; - case "TLSv1.1": - return TLS_1_1; - case "TLSv1": - return TLS_1_0; - case "SSLv3": - return SSL_3_0; - } - throw new IllegalArgumentException("Unexpected TLS version: " + javaName); - } - - public String javaName() { - return javaName; - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/ConnectionSpecSelector.java b/okhttp3/src/main/java/okhttp3/internal/ConnectionSpecSelector.java deleted file mode 100644 index 1393910b1..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/ConnectionSpecSelector.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2015 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal; - -import java.io.IOException; -import java.io.InterruptedIOException; -import java.net.ProtocolException; -import java.net.UnknownServiceException; -import java.security.cert.CertificateException; -import java.util.Arrays; -import java.util.List; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLProtocolException; -import javax.net.ssl.SSLSocket; -import okhttp3.ConnectionSpec; - -/** - * Handles the connection spec fallback strategy: When a secure socket connection fails due to a - * handshake / protocol problem the connection may be retried with different protocols. Instances - * are stateful and should be created and used for a single connection attempt. - */ -public final class ConnectionSpecSelector { - - private final List connectionSpecs; - private int nextModeIndex; - private boolean isFallbackPossible; - private boolean isFallback; - - public ConnectionSpecSelector(List connectionSpecs) { - this.nextModeIndex = 0; - this.connectionSpecs = connectionSpecs; - } - - /** - * Configures the supplied {@link SSLSocket} to connect to the specified host using an appropriate - * {@link ConnectionSpec}. Returns the chosen {@link ConnectionSpec}, never {@code null}. - * - * @throws IOException if the socket does not support any of the TLS modes available - */ - public ConnectionSpec configureSecureSocket(SSLSocket sslSocket) throws IOException { - ConnectionSpec tlsConfiguration = null; - for (int i = nextModeIndex, size = connectionSpecs.size(); i < size; i++) { - ConnectionSpec connectionSpec = connectionSpecs.get(i); - if (connectionSpec.isCompatible(sslSocket)) { - tlsConfiguration = connectionSpec; - nextModeIndex = i + 1; - break; - } - } - - if (tlsConfiguration == null) { - // This may be the first time a connection has been attempted and the socket does not support - // any the required protocols, or it may be a retry (but this socket supports fewer - // protocols than was suggested by a prior socket). - throw new UnknownServiceException( - "Unable to find acceptable protocols. isFallback=" + isFallback - + ", modes=" + connectionSpecs - + ", supported protocols=" + Arrays.toString(sslSocket.getEnabledProtocols())); - } - - isFallbackPossible = isFallbackPossible(sslSocket); - - Internal.instance.apply(tlsConfiguration, sslSocket, isFallback); - - return tlsConfiguration; - } - - /** - * Reports a failure to complete a connection. Determines the next {@link ConnectionSpec} to try, - * if any. - * - * @return {@code true} if the connection should be retried using {@link - * #configureSecureSocket(SSLSocket)} or {@code false} if not - */ - public boolean connectionFailed(IOException e) { - // Any future attempt to connect using this strategy will be a fallback attempt. - isFallback = true; - - if (!isFallbackPossible) { - return false; - } - - // If there was a protocol problem, don't recover. - if (e instanceof ProtocolException) { - return false; - } - - // If there was an interruption or timeout (SocketTimeoutException), don't recover. - // For the socket connect timeout case we do not try the same host with a different - // ConnectionSpec: we assume it is unreachable. - if (e instanceof InterruptedIOException) { - return false; - } - - // Look for known client-side or negotiation errors that are unlikely to be fixed by trying - // again with a different connection spec. - if (e instanceof SSLHandshakeException) { - // If the problem was a CertificateException from the X509TrustManager, - // do not retry. - if (e.getCause() instanceof CertificateException) { - return false; - } - } - if (e instanceof SSLPeerUnverifiedException) { - // e.g. a certificate pinning error. - return false; - } - - // On Android, SSLProtocolExceptions can be caused by TLS_FALLBACK_SCSV failures, which means we - // retry those when we probably should not. - return (e instanceof SSLHandshakeException || e instanceof SSLProtocolException); - } - - /** - * Returns {@code true} if any later {@link ConnectionSpec} in the fallback strategy looks - * possible based on the supplied {@link SSLSocket}. It assumes that a future socket will have the - * same capabilities as the supplied socket. - */ - private boolean isFallbackPossible(SSLSocket socket) { - for (int i = nextModeIndex; i < connectionSpecs.size(); i++) { - if (connectionSpecs.get(i).isCompatible(socket)) { - return true; - } - } - return false; - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/DiskLruCache.java b/okhttp3/src/main/java/okhttp3/internal/DiskLruCache.java deleted file mode 100644 index 1be6a93b4..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/DiskLruCache.java +++ /dev/null @@ -1,1020 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal; - -import java.io.Closeable; -import java.io.EOFException; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.Flushable; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.NoSuchElementException; -import java.util.concurrent.Executor; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import okhttp3.internal.io.FileSystem; -import okio.Buffer; -import okio.BufferedSink; -import okio.BufferedSource; -import okio.Okio; -import okio.Sink; -import okio.Source; -import okio.Timeout; - -/** - * A cache that uses a bounded amount of space on a filesystem. Each cache entry has a string key - * and a fixed number of values. Each key must match the regex [a-z0-9_-]{1,64}. - * Values are byte sequences, accessible as streams or files. Each value must be between {@code 0} - * and {@code Integer.MAX_VALUE} bytes in length. - * - *

The cache stores its data in a directory on the filesystem. This directory must be exclusive - * to the cache; the cache may delete or overwrite files from its directory. It is an error for - * multiple processes to use the same cache directory at the same time. - * - *

This cache limits the number of bytes that it will store on the filesystem. When the number of - * stored bytes exceeds the limit, the cache will remove entries in the background until the limit - * is satisfied. The limit is not strict: the cache may temporarily exceed it while waiting for - * files to be deleted. The limit does not include filesystem overhead or the cache journal so - * space-sensitive applications should set a conservative limit. - * - *

Clients call {@link #edit} to create or update the values of an entry. An entry may have only - * one editor at one time; if a value is not available to be edited then {@link #edit} will return - * null. - * - *

    - *
  • When an entry is being created it is necessary to supply a full set of - * values; the empty value should be used as a placeholder if necessary. - *
  • When an entry is being edited, it is not necessary to supply data for - * every value; values default to their previous value. - *
- * - *

Every {@link #edit} call must be matched by a call to {@link Editor#commit} or {@link - * Editor#abort}. Committing is atomic: a read observes the full set of values as they were before - * or after the commit, but never a mix of values. - * - *

Clients call {@link #get} to read a snapshot of an entry. The read will observe the value at - * the time that {@link #get} was called. Updates and removals after the call do not impact ongoing - * reads. - * - *

This class is tolerant of some I/O errors. If files are missing from the filesystem, the - * corresponding entries will be dropped from the cache. If an error occurs while writing a cache - * value, the edit will fail silently. Callers should handle other problems by catching {@code - * IOException} and responding appropriately. - */ -public final class DiskLruCache implements Closeable, Flushable { - static final String JOURNAL_FILE = "journal"; - static final String JOURNAL_FILE_TEMP = "journal.tmp"; - static final String JOURNAL_FILE_BACKUP = "journal.bkp"; - static final String MAGIC = "libcore.io.DiskLruCache"; - static final String VERSION_1 = "1"; - static final long ANY_SEQUENCE_NUMBER = -1; - static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,120}"); - private static final String CLEAN = "CLEAN"; - private static final String DIRTY = "DIRTY"; - private static final String REMOVE = "REMOVE"; - private static final String READ = "READ"; - - /* - * This cache uses a journal file named "journal". A typical journal file - * looks like this: - * libcore.io.DiskLruCache - * 1 - * 100 - * 2 - * - * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 - * DIRTY 335c4c6028171cfddfbaae1a9c313c52 - * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 - * REMOVE 335c4c6028171cfddfbaae1a9c313c52 - * DIRTY 1ab96a171faeeee38496d8b330771a7a - * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 - * READ 335c4c6028171cfddfbaae1a9c313c52 - * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 - * - * The first five lines of the journal form its header. They are the - * constant string "libcore.io.DiskLruCache", the disk cache's version, - * the application's version, the value count, and a blank line. - * - * Each of the subsequent lines in the file is a record of the state of a - * cache entry. Each line contains space-separated values: a state, a key, - * and optional state-specific values. - * o DIRTY lines track that an entry is actively being created or updated. - * Every successful DIRTY action should be followed by a CLEAN or REMOVE - * action. DIRTY lines without a matching CLEAN or REMOVE indicate that - * temporary files may need to be deleted. - * o CLEAN lines track a cache entry that has been successfully published - * and may be read. A publish line is followed by the lengths of each of - * its values. - * o READ lines track accesses for LRU. - * o REMOVE lines track entries that have been deleted. - * - * The journal file is appended to as cache operations occur. The journal may - * occasionally be compacted by dropping redundant lines. A temporary file named - * "journal.tmp" will be used during compaction; that file should be deleted if - * it exists when the cache is opened. - */ - - private final FileSystem fileSystem; - private final File directory; - private final File journalFile; - private final File journalFileTmp; - private final File journalFileBackup; - private final int appVersion; - private long maxSize; - private final int valueCount; - private long size = 0; - private BufferedSink journalWriter; - private final LinkedHashMap lruEntries = new LinkedHashMap<>(0, 0.75f, true); - private int redundantOpCount; - private boolean hasJournalErrors; - - // Must be read and written when synchronized on 'this'. - private boolean initialized; - private boolean closed; - private boolean mostRecentTrimFailed; - - /** - * To differentiate between old and current snapshots, each entry is given a sequence number each - * time an edit is committed. A snapshot is stale if its sequence number is not equal to its - * entry's sequence number. - */ - private long nextSequenceNumber = 0; - - /** Used to run 'cleanupRunnable' for journal rebuilds. */ - private final Executor executor; - private final Runnable cleanupRunnable = new Runnable() { - public void run() { - synchronized (DiskLruCache.this) { - if (!initialized | closed) { - return; // Nothing to do - } - - try { - trimToSize(); - } catch (IOException ignored) { - mostRecentTrimFailed = true; - } - - try { - if (journalRebuildRequired()) { - rebuildJournal(); - redundantOpCount = 0; - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - }; - - DiskLruCache(FileSystem fileSystem, File directory, int appVersion, int valueCount, long maxSize, - Executor executor) { - this.fileSystem = fileSystem; - this.directory = directory; - this.appVersion = appVersion; - this.journalFile = new File(directory, JOURNAL_FILE); - this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP); - this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP); - this.valueCount = valueCount; - this.maxSize = maxSize; - this.executor = executor; - } - - public synchronized void initialize() throws IOException { - assert Thread.holdsLock(this); - - if (initialized) { - return; // Already initialized. - } - - // If a bkp file exists, use it instead. - if (fileSystem.exists(journalFileBackup)) { - // If journal file also exists just delete backup file. - if (fileSystem.exists(journalFile)) { - fileSystem.delete(journalFileBackup); - } else { - fileSystem.rename(journalFileBackup, journalFile); - } - } - - // Prefer to pick up where we left off. - if (fileSystem.exists(journalFile)) { - try { - readJournal(); - processJournal(); - initialized = true; - return; - } catch (IOException journalIsCorrupt) { - Platform.get().logW("DiskLruCache " + directory + " is corrupt: " - + journalIsCorrupt.getMessage() + ", removing"); - delete(); - closed = false; - } - } - - rebuildJournal(); - - initialized = true; - } - - /** - * Create a cache which will reside in {@code directory}. This cache is lazily initialized on - * first access and will be created if it does not exist. - * - * @param directory a writable directory - * @param valueCount the number of values per cache entry. Must be positive. - * @param maxSize the maximum number of bytes this cache should use to store - */ - public static DiskLruCache create(FileSystem fileSystem, File directory, int appVersion, - int valueCount, long maxSize) { - if (maxSize <= 0) { - throw new IllegalArgumentException("maxSize <= 0"); - } - if (valueCount <= 0) { - throw new IllegalArgumentException("valueCount <= 0"); - } - - // Use a single background thread to evict entries. - Executor executor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, - new LinkedBlockingQueue(), Util.threadFactory("OkHttp DiskLruCache", true)); - - return new DiskLruCache(fileSystem, directory, appVersion, valueCount, maxSize, executor); - } - - private void readJournal() throws IOException { - BufferedSource source = Okio.buffer(fileSystem.source(journalFile)); - try { - String magic = source.readUtf8LineStrict(); - String version = source.readUtf8LineStrict(); - String appVersionString = source.readUtf8LineStrict(); - String valueCountString = source.readUtf8LineStrict(); - String blank = source.readUtf8LineStrict(); - if (!MAGIC.equals(magic) - || !VERSION_1.equals(version) - || !Integer.toString(appVersion).equals(appVersionString) - || !Integer.toString(valueCount).equals(valueCountString) - || !"".equals(blank)) { - throw new IOException("unexpected journal header: [" + magic + ", " + version + ", " - + valueCountString + ", " + blank + "]"); - } - - int lineCount = 0; - while (true) { - try { - readJournalLine(source.readUtf8LineStrict()); - lineCount++; - } catch (EOFException endOfJournal) { - break; - } - } - redundantOpCount = lineCount - lruEntries.size(); - - // If we ended on a truncated line, rebuild the journal before appending to it. - if (!source.exhausted()) { - rebuildJournal(); - } else { - journalWriter = newJournalWriter(); - } - } finally { - Util.closeQuietly(source); - } - } - - private BufferedSink newJournalWriter() throws FileNotFoundException { - Sink fileSink = fileSystem.appendingSink(journalFile); - Sink faultHidingSink = new FaultHidingSink(fileSink) { - @Override protected void onException(IOException e) { - assert (Thread.holdsLock(DiskLruCache.this)); - hasJournalErrors = true; - } - }; - return Okio.buffer(faultHidingSink); - } - - private void readJournalLine(String line) throws IOException { - int firstSpace = line.indexOf(' '); - if (firstSpace == -1) { - throw new IOException("unexpected journal line: " + line); - } - - int keyBegin = firstSpace + 1; - int secondSpace = line.indexOf(' ', keyBegin); - final String key; - if (secondSpace == -1) { - key = line.substring(keyBegin); - if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) { - lruEntries.remove(key); - return; - } - } else { - key = line.substring(keyBegin, secondSpace); - } - - Entry entry = lruEntries.get(key); - if (entry == null) { - entry = new Entry(key); - lruEntries.put(key, entry); - } - - if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) { - String[] parts = line.substring(secondSpace + 1).split(" "); - entry.readable = true; - entry.currentEditor = null; - entry.setLengths(parts); - } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) { - entry.currentEditor = new Editor(entry); - } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) { - // This work was already done by calling lruEntries.get(). - } else { - throw new IOException("unexpected journal line: " + line); - } - } - - /** - * Computes the initial size and collects garbage as a part of opening the cache. Dirty entries - * are assumed to be inconsistent and will be deleted. - */ - private void processJournal() throws IOException { - fileSystem.delete(journalFileTmp); - for (Iterator i = lruEntries.values().iterator(); i.hasNext(); ) { - Entry entry = i.next(); - if (entry.currentEditor == null) { - for (int t = 0; t < valueCount; t++) { - size += entry.lengths[t]; - } - } else { - entry.currentEditor = null; - for (int t = 0; t < valueCount; t++) { - fileSystem.delete(entry.cleanFiles[t]); - fileSystem.delete(entry.dirtyFiles[t]); - } - i.remove(); - } - } - } - - /** - * Creates a new journal that omits redundant information. This replaces the current journal if it - * exists. - */ - private synchronized void rebuildJournal() throws IOException { - if (journalWriter != null) { - journalWriter.close(); - } - - BufferedSink writer = Okio.buffer(fileSystem.sink(journalFileTmp)); - try { - writer.writeUtf8(MAGIC).writeByte('\n'); - writer.writeUtf8(VERSION_1).writeByte('\n'); - writer.writeDecimalLong(appVersion).writeByte('\n'); - writer.writeDecimalLong(valueCount).writeByte('\n'); - writer.writeByte('\n'); - - for (Entry entry : lruEntries.values()) { - if (entry.currentEditor != null) { - writer.writeUtf8(DIRTY).writeByte(' '); - writer.writeUtf8(entry.key); - writer.writeByte('\n'); - } else { - writer.writeUtf8(CLEAN).writeByte(' '); - writer.writeUtf8(entry.key); - entry.writeLengths(writer); - writer.writeByte('\n'); - } - } - } finally { - writer.close(); - } - - if (fileSystem.exists(journalFile)) { - fileSystem.rename(journalFile, journalFileBackup); - } - fileSystem.rename(journalFileTmp, journalFile); - fileSystem.delete(journalFileBackup); - - journalWriter = newJournalWriter(); - hasJournalErrors = false; - } - - /** - * Returns a snapshot of the entry named {@code key}, or null if it doesn't exist is not currently - * readable. If a value is returned, it is moved to the head of the LRU queue. - */ - public synchronized Snapshot get(String key) throws IOException { - initialize(); - - checkNotClosed(); - validateKey(key); - Entry entry = lruEntries.get(key); - if (entry == null || !entry.readable) return null; - - Snapshot snapshot = entry.snapshot(); - if (snapshot == null) return null; - - redundantOpCount++; - journalWriter.writeUtf8(READ).writeByte(' ').writeUtf8(key).writeByte('\n'); - if (journalRebuildRequired()) { - executor.execute(cleanupRunnable); - } - - return snapshot; - } - - /** - * Returns an editor for the entry named {@code key}, or null if another edit is in progress. - */ - public Editor edit(String key) throws IOException { - return edit(key, ANY_SEQUENCE_NUMBER); - } - - private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { - initialize(); - - checkNotClosed(); - validateKey(key); - Entry entry = lruEntries.get(key); - if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null - || entry.sequenceNumber != expectedSequenceNumber)) { - return null; // Snapshot is stale. - } - if (entry != null && entry.currentEditor != null) { - return null; // Another edit is in progress. - } - if (mostRecentTrimFailed) { - // Prevent new writes so the cache doesn't grow any further and retry the clean up operation. - executor.execute(cleanupRunnable); - return null; - } - - // Flush the journal before creating files to prevent file leaks. - journalWriter.writeUtf8(DIRTY).writeByte(' ').writeUtf8(key).writeByte('\n'); - journalWriter.flush(); - - if (hasJournalErrors) { - return null; // Don't edit; the journal can't be written. - } - - if (entry == null) { - entry = new Entry(key); - lruEntries.put(key, entry); - } - Editor editor = new Editor(entry); - entry.currentEditor = editor; - return editor; - } - - /** Returns the directory where this cache stores its data. */ - public File getDirectory() { - return directory; - } - - /** - * Returns the maximum number of bytes that this cache should use to store its data. - */ - public synchronized long getMaxSize() { - return maxSize; - } - - /** - * Changes the maximum number of bytes the cache can store and queues a job to trim the existing - * store, if necessary. - */ - public synchronized void setMaxSize(long maxSize) { - this.maxSize = maxSize; - if (initialized) { - executor.execute(cleanupRunnable); - } - } - - /** - * Returns the number of bytes currently being used to store the values in this cache. This may be - * greater than the max size if a background deletion is pending. - */ - public synchronized long size() throws IOException { - initialize(); - return size; - } - - private synchronized void completeEdit(Editor editor, boolean success) throws IOException { - Entry entry = editor.entry; - if (entry.currentEditor != editor) { - throw new IllegalStateException(); - } - - // If this edit is creating the entry for the first time, every index must have a value. - if (success && !entry.readable) { - for (int i = 0; i < valueCount; i++) { - if (!editor.written[i]) { - editor.abort(); - throw new IllegalStateException("Newly created entry didn't create value for index " + i); - } - if (!fileSystem.exists(entry.dirtyFiles[i])) { - editor.abort(); - return; - } - } - } - - for (int i = 0; i < valueCount; i++) { - File dirty = entry.dirtyFiles[i]; - if (success) { - if (fileSystem.exists(dirty)) { - File clean = entry.cleanFiles[i]; - fileSystem.rename(dirty, clean); - long oldLength = entry.lengths[i]; - long newLength = fileSystem.size(clean); - entry.lengths[i] = newLength; - size = size - oldLength + newLength; - } - } else { - fileSystem.delete(dirty); - } - } - - redundantOpCount++; - entry.currentEditor = null; - if (entry.readable | success) { - entry.readable = true; - journalWriter.writeUtf8(CLEAN).writeByte(' '); - journalWriter.writeUtf8(entry.key); - entry.writeLengths(journalWriter); - journalWriter.writeByte('\n'); - if (success) { - entry.sequenceNumber = nextSequenceNumber++; - } - } else { - lruEntries.remove(entry.key); - journalWriter.writeUtf8(REMOVE).writeByte(' '); - journalWriter.writeUtf8(entry.key); - journalWriter.writeByte('\n'); - } - journalWriter.flush(); - - if (size > maxSize || journalRebuildRequired()) { - executor.execute(cleanupRunnable); - } - } - - /** - * We only rebuild the journal when it will halve the size of the journal and eliminate at least - * 2000 ops. - */ - private boolean journalRebuildRequired() { - final int redundantOpCompactThreshold = 2000; - return redundantOpCount >= redundantOpCompactThreshold - && redundantOpCount >= lruEntries.size(); - } - - /** - * Drops the entry for {@code key} if it exists and can be removed. If the entry for {@code key} - * is currently being edited, that edit will complete normally but its value will not be stored. - * - * @return true if an entry was removed. - */ - public synchronized boolean remove(String key) throws IOException { - initialize(); - - checkNotClosed(); - validateKey(key); - Entry entry = lruEntries.get(key); - if (entry == null) return false; - boolean removed = removeEntry(entry); - if (removed && size <= maxSize) mostRecentTrimFailed = false; - return removed; - } - - private boolean removeEntry(Entry entry) throws IOException { - if (entry.currentEditor != null) { - entry.currentEditor.hasErrors = true; // Prevent the edit from completing normally. - } - - for (int i = 0; i < valueCount; i++) { - fileSystem.delete(entry.cleanFiles[i]); - size -= entry.lengths[i]; - entry.lengths[i] = 0; - } - - redundantOpCount++; - journalWriter.writeUtf8(REMOVE).writeByte(' ').writeUtf8(entry.key).writeByte('\n'); - lruEntries.remove(entry.key); - - if (journalRebuildRequired()) { - executor.execute(cleanupRunnable); - } - - return true; - } - - /** Returns true if this cache has been closed. */ - public synchronized boolean isClosed() { - return closed; - } - - private synchronized void checkNotClosed() { - if (isClosed()) { - throw new IllegalStateException("cache is closed"); - } - } - - /** Force buffered operations to the filesystem. */ - @Override public synchronized void flush() throws IOException { - if (!initialized) return; - - checkNotClosed(); - trimToSize(); - journalWriter.flush(); - } - - /** Closes this cache. Stored values will remain on the filesystem. */ - @Override public synchronized void close() throws IOException { - if (!initialized || closed) { - closed = true; - return; - } - // Copying for safe iteration. - for (Entry entry : lruEntries.values().toArray(new Entry[lruEntries.size()])) { - if (entry.currentEditor != null) { - entry.currentEditor.abort(); - } - } - trimToSize(); - journalWriter.close(); - journalWriter = null; - closed = true; - } - - private void trimToSize() throws IOException { - while (size > maxSize) { - Entry toEvict = lruEntries.values().iterator().next(); - removeEntry(toEvict); - } - mostRecentTrimFailed = false; - } - - /** - * Closes the cache and deletes all of its stored values. This will delete all files in the cache - * directory including files that weren't created by the cache. - */ - public void delete() throws IOException { - close(); - fileSystem.deleteContents(directory); - } - - /** - * Deletes all stored values from the cache. In-flight edits will complete normally but their - * values will not be stored. - */ - public synchronized void evictAll() throws IOException { - initialize(); - // Copying for safe iteration. - for (Entry entry : lruEntries.values().toArray(new Entry[lruEntries.size()])) { - removeEntry(entry); - } - mostRecentTrimFailed = false; - } - - private void validateKey(String key) { - Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); - if (!matcher.matches()) { - throw new IllegalArgumentException( - "keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\""); - } - } - - /** - * Returns an iterator over the cache's current entries. This iterator doesn't throw {@code - * ConcurrentModificationException}, but if new entries are added while iterating, those new - * entries will not be returned by the iterator. If existing entries are removed during iteration, - * they will be absent (unless they were already returned). - * - *

If there are I/O problems during iteration, this iterator fails silently. For example, if - * the hosting filesystem becomes unreachable, the iterator will omit elements rather than - * throwing exceptions. - * - *

The caller must {@link Snapshot#close close} each snapshot returned by - * {@link Iterator#next}. Failing to do so leaks open files! - * - *

The returned iterator supports {@link Iterator#remove}. - */ - public synchronized Iterator snapshots() throws IOException { - initialize(); - return new Iterator() { - /** Iterate a copy of the entries to defend against concurrent modification errors. */ - final Iterator delegate = new ArrayList<>(lruEntries.values()).iterator(); - - /** The snapshot to return from {@link #next}. Null if we haven't computed that yet. */ - Snapshot nextSnapshot; - - /** The snapshot to remove with {@link #remove}. Null if removal is illegal. */ - Snapshot removeSnapshot; - - @Override public boolean hasNext() { - if (nextSnapshot != null) return true; - - synchronized (DiskLruCache.this) { - // If the cache is closed, truncate the iterator. - if (closed) return false; - - while (delegate.hasNext()) { - Entry entry = delegate.next(); - Snapshot snapshot = entry.snapshot(); - if (snapshot == null) continue; // Evicted since we copied the entries. - nextSnapshot = snapshot; - return true; - } - } - - return false; - } - - @Override public Snapshot next() { - if (!hasNext()) throw new NoSuchElementException(); - removeSnapshot = nextSnapshot; - nextSnapshot = null; - return removeSnapshot; - } - - @Override public void remove() { - if (removeSnapshot == null) throw new IllegalStateException("remove() before next()"); - try { - DiskLruCache.this.remove(removeSnapshot.key); - } catch (IOException ignored) { - // Nothing useful to do here. We failed to remove from the cache. Most likely that's - // because we couldn't update the journal, but the cached entry will still be gone. - } finally { - removeSnapshot = null; - } - } - }; - } - - /** A snapshot of the values for an entry. */ - public final class Snapshot implements Closeable { - private final String key; - private final long sequenceNumber; - private final Source[] sources; - private final long[] lengths; - - private Snapshot(String key, long sequenceNumber, Source[] sources, long[] lengths) { - this.key = key; - this.sequenceNumber = sequenceNumber; - this.sources = sources; - this.lengths = lengths; - } - - public String key() { - return key; - } - - /** - * Returns an editor for this snapshot's entry, or null if either the entry has changed since - * this snapshot was created or if another edit is in progress. - */ - public Editor edit() throws IOException { - return DiskLruCache.this.edit(key, sequenceNumber); - } - - /** Returns the unbuffered stream with the value for {@code index}. */ - public Source getSource(int index) { - return sources[index]; - } - - /** Returns the byte length of the value for {@code index}. */ - public long getLength(int index) { - return lengths[index]; - } - - public void close() { - for (Source in : sources) { - Util.closeQuietly(in); - } - } - } - - private static final Sink NULL_SINK = new Sink() { - @Override public void write(Buffer source, long byteCount) throws IOException { - source.skip(byteCount); - } - - @Override public void flush() throws IOException { - } - - @Override public Timeout timeout() { - return Timeout.NONE; - } - - @Override public void close() throws IOException { - } - }; - - /** Edits the values for an entry. */ - public final class Editor { - private final Entry entry; - private final boolean[] written; - private boolean hasErrors; - private boolean committed; - - private Editor(Entry entry) { - this.entry = entry; - this.written = (entry.readable) ? null : new boolean[valueCount]; - } - - /** - * Returns an unbuffered input stream to read the last committed value, or null if no value has - * been committed. - */ - public Source newSource(int index) throws IOException { - synchronized (DiskLruCache.this) { - if (entry.currentEditor != this) { - throw new IllegalStateException(); - } - if (!entry.readable) { - return null; - } - try { - return fileSystem.source(entry.cleanFiles[index]); - } catch (FileNotFoundException e) { - return null; - } - } - } - - /** - * Returns a new unbuffered output stream to write the value at {@code index}. If the underlying - * output stream encounters errors when writing to the filesystem, this edit will be aborted - * when {@link #commit} is called. The returned output stream does not throw IOExceptions. - */ - public Sink newSink(int index) throws IOException { - synchronized (DiskLruCache.this) { - if (entry.currentEditor != this) { - throw new IllegalStateException(); - } - if (!entry.readable) { - written[index] = true; - } - File dirtyFile = entry.dirtyFiles[index]; - Sink sink; - try { - sink = fileSystem.sink(dirtyFile); - } catch (FileNotFoundException e) { - return NULL_SINK; - } - return new FaultHidingSink(sink) { - @Override protected void onException(IOException e) { - synchronized (DiskLruCache.this) { - hasErrors = true; - } - } - }; - } - } - - /** - * Commits this edit so it is visible to readers. This releases the edit lock so another edit - * may be started on the same key. - */ - public void commit() throws IOException { - synchronized (DiskLruCache.this) { - if (hasErrors) { - completeEdit(this, false); - removeEntry(entry); // The previous entry is stale. - } else { - completeEdit(this, true); - } - committed = true; - } - } - - /** - * Aborts this edit. This releases the edit lock so another edit may be started on the same - * key. - */ - public void abort() throws IOException { - synchronized (DiskLruCache.this) { - completeEdit(this, false); - } - } - - public void abortUnlessCommitted() { - synchronized (DiskLruCache.this) { - if (!committed) { - try { - completeEdit(this, false); - } catch (IOException ignored) { - } - } - } - } - } - - private final class Entry { - private final String key; - - /** Lengths of this entry's files. */ - private final long[] lengths; - private final File[] cleanFiles; - private final File[] dirtyFiles; - - /** True if this entry has ever been published. */ - private boolean readable; - - /** The ongoing edit or null if this entry is not being edited. */ - private Editor currentEditor; - - /** The sequence number of the most recently committed edit to this entry. */ - private long sequenceNumber; - - private Entry(String key) { - this.key = key; - - lengths = new long[valueCount]; - cleanFiles = new File[valueCount]; - dirtyFiles = new File[valueCount]; - - // The names are repetitive so re-use the same builder to avoid allocations. - StringBuilder fileBuilder = new StringBuilder(key).append('.'); - int truncateTo = fileBuilder.length(); - for (int i = 0; i < valueCount; i++) { - fileBuilder.append(i); - cleanFiles[i] = new File(directory, fileBuilder.toString()); - fileBuilder.append(".tmp"); - dirtyFiles[i] = new File(directory, fileBuilder.toString()); - fileBuilder.setLength(truncateTo); - } - } - - /** Set lengths using decimal numbers like "10123". */ - private void setLengths(String[] strings) throws IOException { - if (strings.length != valueCount) { - throw invalidLengths(strings); - } - - try { - for (int i = 0; i < strings.length; i++) { - lengths[i] = Long.parseLong(strings[i]); - } - } catch (NumberFormatException e) { - throw invalidLengths(strings); - } - } - - /** Append space-prefixed lengths to {@code writer}. */ - void writeLengths(BufferedSink writer) throws IOException { - for (long length : lengths) { - writer.writeByte(' ').writeDecimalLong(length); - } - } - - private IOException invalidLengths(String[] strings) throws IOException { - throw new IOException("unexpected journal line: " + Arrays.toString(strings)); - } - - /** - * Returns a snapshot of this entry. This opens all streams eagerly to guarantee that we see a - * single published snapshot. If we opened streams lazily then the streams could come from - * different edits. - */ - Snapshot snapshot() { - if (!Thread.holdsLock(DiskLruCache.this)) throw new AssertionError(); - - Source[] sources = new Source[valueCount]; - long[] lengths = this.lengths.clone(); // Defensive copy since these can be zeroed out. - try { - for (int i = 0; i < valueCount; i++) { - sources[i] = fileSystem.source(cleanFiles[i]); - } - return new Snapshot(key, sequenceNumber, sources, lengths); - } catch (FileNotFoundException e) { - // A file must have been deleted manually! - for (int i = 0; i < valueCount; i++) { - if (sources[i] != null) { - Util.closeQuietly(sources[i]); - } else { - break; - } - } - return null; - } - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/FaultHidingSink.java b/okhttp3/src/main/java/okhttp3/internal/FaultHidingSink.java deleted file mode 100644 index e77f6bb44..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/FaultHidingSink.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2015 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal; - -import java.io.IOException; -import okio.Buffer; -import okio.ForwardingSink; -import okio.Sink; - -/** A sink that never throws IOExceptions, even if the underlying sink does. */ -class FaultHidingSink extends ForwardingSink { - private boolean hasErrors; - - public FaultHidingSink(Sink delegate) { - super(delegate); - } - - @Override public void write(Buffer source, long byteCount) throws IOException { - if (hasErrors) { - source.skip(byteCount); - return; - } - try { - super.write(source, byteCount); - } catch (IOException e) { - hasErrors = true; - onException(e); - } - } - - @Override public void flush() throws IOException { - if (hasErrors) return; - try { - super.flush(); - } catch (IOException e) { - hasErrors = true; - onException(e); - } - } - - @Override public void close() throws IOException { - if (hasErrors) return; - try { - super.close(); - } catch (IOException e) { - hasErrors = true; - onException(e); - } - } - - protected void onException(IOException e) { - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/Internal.java b/okhttp3/src/main/java/okhttp3/internal/Internal.java deleted file mode 100644 index 54252084f..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/Internal.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal; - -import java.net.MalformedURLException; -import java.net.UnknownHostException; -import java.util.logging.Logger; -import javax.net.ssl.SSLSocket; -import okhttp3.Address; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.ConnectionPool; -import okhttp3.ConnectionSpec; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.internal.http.StreamAllocation; -import okhttp3.internal.io.RealConnection; - -/** - * Escalate internal APIs in {@code okhttp3} so they can be used from OkHttp's implementation - * packages. The only implementation of this interface is in {@link OkHttpClient}. - */ -public abstract class Internal { - public static final Logger logger = Logger.getLogger(OkHttpClient.class.getName()); - - public static void initializeInstanceForTests() { - // Needed in tests to ensure that the instance is actually pointing to something. - new OkHttpClient(); - } - - public static Internal instance; - - public abstract void addLenient(Headers.Builder builder, String line); - - public abstract void addLenient(Headers.Builder builder, String name, String value); - - public abstract void setCache(OkHttpClient.Builder builder, InternalCache internalCache); - - public abstract InternalCache internalCache(OkHttpClient client); - - public abstract RealConnection get( - ConnectionPool pool, Address address, StreamAllocation streamAllocation); - - public abstract void put(ConnectionPool pool, RealConnection connection); - - public abstract boolean connectionBecameIdle(ConnectionPool pool, RealConnection connection); - - public abstract RouteDatabase routeDatabase(ConnectionPool connectionPool); - - public abstract void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket, - boolean isFallback); - - public abstract HttpUrl getHttpUrlChecked(String url) - throws MalformedURLException, UnknownHostException; - - // TODO delete the following when web sockets move into the main package. - public abstract void callEnqueue(Call call, Callback responseCallback, boolean forWebSocket); - - public abstract StreamAllocation callEngineGetStreamAllocation(Call call); -} diff --git a/okhttp3/src/main/java/okhttp3/internal/InternalCache.java b/okhttp3/src/main/java/okhttp3/internal/InternalCache.java deleted file mode 100644 index 9752002bc..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/InternalCache.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal; - -import java.io.IOException; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.internal.http.CacheRequest; -import okhttp3.internal.http.CacheStrategy; - -/** - * OkHttp's internal cache interface. Applications shouldn't implement this: instead use {@link - * okhttp3.Cache}. - */ -public interface InternalCache { - Response get(Request request) throws IOException; - - CacheRequest put(Response response) throws IOException; - - /** - * Remove any cache entries for the supplied {@code request}. This is invoked when the client - * invalidates the cache, such as when making POST requests. - */ - void remove(Request request) throws IOException; - - /** - * Handles a conditional request hit by updating the stored cache response with the headers from - * {@code network}. The cached response body is not updated. If the stored response has changed - * since {@code cached} was returned, this does nothing. - */ - void update(Response cached, Response network) throws IOException; - - /** Track an conditional GET that was satisfied by this cache. */ - void trackConditionalCacheHit(); - - /** Track an HTTP response being satisfied with {@code cacheStrategy}. */ - void trackResponse(CacheStrategy cacheStrategy); -} diff --git a/okhttp3/src/main/java/okhttp3/internal/NamedRunnable.java b/okhttp3/src/main/java/okhttp3/internal/NamedRunnable.java deleted file mode 100644 index b5a410360..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/NamedRunnable.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal; - -/** - * Runnable implementation which always sets its thread name. - */ -public abstract class NamedRunnable implements Runnable { - protected final String name; - - public NamedRunnable(String format, Object... args) { - this.name = String.format(format, args); - } - - @Override public final void run() { - String oldName = Thread.currentThread().getName(); - Thread.currentThread().setName(name); - try { - execute(); - } finally { - Thread.currentThread().setName(oldName); - } - } - - protected abstract void execute(); -} diff --git a/okhttp3/src/main/java/okhttp3/internal/OptionalMethod.java b/okhttp3/src/main/java/okhttp3/internal/OptionalMethod.java deleted file mode 100644 index 108ec292d..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/OptionalMethod.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package okhttp3.internal; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; - -/** - * Duck-typing for methods: Represents a method that may or may not be present on an object. - * - * @param the type of the object the method might be on, typically an interface or base class - */ -class OptionalMethod { - - /** The return type of the method. null means "don't care". */ - private final Class returnType; - - private final String methodName; - - private final Class[] methodParams; - - /** - * Creates an optional method. - * - * @param returnType the return type to required, null if it does not matter - * @param methodName the name of the method - * @param methodParams the method parameter types - */ - public OptionalMethod(Class returnType, String methodName, Class... methodParams) { - this.returnType = returnType; - this.methodName = methodName; - this.methodParams = methodParams; - } - - /** - * Returns true if the method exists on the supplied {@code target}. - */ - public boolean isSupported(T target) { - return getMethod(target.getClass()) != null; - } - - /** - * Invokes the method on {@code target} with {@code args}. If the method does not exist or is not - * public then {@code null} is returned. See also {@link #invokeOptionalWithoutCheckedException}. - * - * @throws IllegalArgumentException if the arguments are invalid - * @throws InvocationTargetException if the invocation throws an exception - */ - public Object invokeOptional(T target, Object... args) throws InvocationTargetException { - Method m = getMethod(target.getClass()); - if (m == null) { - return null; - } - try { - return m.invoke(target, args); - } catch (IllegalAccessException e) { - return null; - } - } - - /** - * Invokes the method on {@code target}. If the method does not exist or is not public then - * {@code null} is returned. Any RuntimeException thrown by the method is thrown, checked - * exceptions are wrapped in an {@link AssertionError}. - * - * @throws IllegalArgumentException if the arguments are invalid - */ - public Object invokeOptionalWithoutCheckedException(T target, Object... args) { - try { - return invokeOptional(target, args); - } catch (InvocationTargetException e) { - Throwable targetException = e.getTargetException(); - if (targetException instanceof RuntimeException) { - throw (RuntimeException) targetException; - } - AssertionError error = new AssertionError("Unexpected exception"); - error.initCause(targetException); - throw error; - } - } - - /** - * Invokes the method on {@code target} with {@code args}. Throws an error if the method is not - * supported. See also {@link #invokeWithoutCheckedException(Object, Object...)}. - * - * @throws IllegalArgumentException if the arguments are invalid - * @throws InvocationTargetException if the invocation throws an exception - */ - public Object invoke(T target, Object... args) throws InvocationTargetException { - Method m = getMethod(target.getClass()); - if (m == null) { - throw new AssertionError("Method " + methodName + " not supported for object " + target); - } - try { - return m.invoke(target, args); - } catch (IllegalAccessException e) { - // Method should be public: we checked. - AssertionError error = new AssertionError("Unexpectedly could not call: " + m); - error.initCause(e); - throw error; - } - } - - /** - * Invokes the method on {@code target}. Throws an error if the method is not supported. Any - * RuntimeException thrown by the method is thrown, checked exceptions are wrapped in an {@link - * AssertionError}. - * - * @throws IllegalArgumentException if the arguments are invalid - */ - public Object invokeWithoutCheckedException(T target, Object... args) { - try { - return invoke(target, args); - } catch (InvocationTargetException e) { - Throwable targetException = e.getTargetException(); - if (targetException instanceof RuntimeException) { - throw (RuntimeException) targetException; - } - AssertionError error = new AssertionError("Unexpected exception"); - error.initCause(targetException); - throw error; - } - } - - /** - * Perform a lookup for the method. No caching. In order to return a method the method name and - * arguments must match those specified when the {@link OptionalMethod} was created. If the return - * type is specified (i.e. non-null) it must also be compatible. The method must also be public. - */ - private Method getMethod(Class clazz) { - Method method = null; - if (methodName != null) { - method = getPublicMethod(clazz, methodName, methodParams); - if (method != null - && returnType != null - && !returnType.isAssignableFrom(method.getReturnType())) { - - // If the return type is non-null it must be compatible. - method = null; - } - } - return method; - } - - private static Method getPublicMethod(Class clazz, String methodName, Class[] parameterTypes) { - Method method = null; - try { - method = clazz.getMethod(methodName, parameterTypes); - if ((method.getModifiers() & Modifier.PUBLIC) == 0) { - method = null; - } - } catch (NoSuchMethodException e) { - // None. - } - return method; - } -} - diff --git a/okhttp3/src/main/java/okhttp3/internal/Platform.java b/okhttp3/src/main/java/okhttp3/internal/Platform.java deleted file mode 100644 index 91568ab4f..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/Platform.java +++ /dev/null @@ -1,435 +0,0 @@ -/* - * Copyright (C) 2012 Square, Inc. - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal; - -import android.util.Log; -import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.X509TrustManager; -import okhttp3.Protocol; -import okhttp3.internal.tls.AndroidTrustRootIndex; -import okhttp3.internal.tls.RealTrustRootIndex; -import okhttp3.internal.tls.TrustRootIndex; -import okio.Buffer; - -import static okhttp3.internal.Internal.logger; - -/** - * Access to platform-specific features. - * - *

Server name indication (SNI)

- * - *

Supported on Android 2.3+. - * - *

Session Tickets

- * - *

Supported on Android 2.3+. - * - *

Android Traffic Stats (Socket Tagging)

- * - *

Supported on Android 4.0+. - * - *

ALPN (Application Layer Protocol Negotiation)

- * - *

Supported on Android 5.0+. The APIs were present in Android 4.4, but that implementation was - * unstable. - * - * Supported on OpenJDK 7 and 8 (via the JettyALPN-boot library). - * - *

Trust Manager Extraction

- * - *

Supported on Android 2.3+ and OpenJDK 7+. There are no public APIs to recover the trust - * manager that was used to create an {@link SSLSocketFactory}. - */ -public class Platform { - private static final Platform PLATFORM = findPlatform(); - - public static Platform get() { - return PLATFORM; - } - - /** Prefix used on custom headers. */ - public String getPrefix() { - return "OkHttp"; - } - - public void logW(String warning) { - System.out.println(warning); - } - - public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) { - // Attempt to get the trust manager from an OpenJDK socket factory. We attempt this on all - // platforms in order to support Robolectric, which mixes classes from both Android and the - // Oracle JDK. Note that we don't support HTTP/2 or other nice features on Robolectric. - try { - Class sslContextClass = Class.forName("sun.security.ssl.SSLContextImpl"); - Object context = readFieldOrNull(sslSocketFactory, sslContextClass, "context"); - if (context == null) return null; - return readFieldOrNull(context, X509TrustManager.class, "trustManager"); - } catch (ClassNotFoundException e) { - return null; - } - } - - public TrustRootIndex trustRootIndex(X509TrustManager trustManager) { - return new RealTrustRootIndex(trustManager.getAcceptedIssuers()); - } - - /** - * Configure TLS extensions on {@code sslSocket} for {@code route}. - * - * @param hostname non-null for client-side handshakes; null for server-side handshakes. - */ - public void configureTlsExtensions(SSLSocket sslSocket, String hostname, - List protocols) { - } - - /** - * Called after the TLS handshake to release resources allocated by {@link - * #configureTlsExtensions}. - */ - public void afterHandshake(SSLSocket sslSocket) { - } - - /** Returns the negotiated protocol, or null if no protocol was negotiated. */ - public String getSelectedProtocol(SSLSocket socket) { - return null; - } - - public void connectSocket(Socket socket, InetSocketAddress address, - int connectTimeout) throws IOException { - socket.connect(address, connectTimeout); - } - - public void log(String message) { - System.out.println(message); - } - - /** Attempt to match the host runtime to a capable Platform implementation. */ - private static Platform findPlatform() { - // Attempt to find Android 2.3+ APIs. - try { - Class sslParametersClass; - try { - sslParametersClass = Class.forName("com.android.org.conscrypt.SSLParametersImpl"); - } catch (ClassNotFoundException e) { - // Older platform before being unbundled. - sslParametersClass = Class.forName( - "org.apache.harmony.xnet.provider.jsse.SSLParametersImpl"); - } - - OptionalMethod setUseSessionTickets = new OptionalMethod<>( - null, "setUseSessionTickets", boolean.class); - OptionalMethod setHostname = new OptionalMethod<>( - null, "setHostname", String.class); - OptionalMethod getAlpnSelectedProtocol = null; - OptionalMethod setAlpnProtocols = null; - - // Attempt to find Android 5.0+ APIs. - try { - Class.forName("android.net.Network"); // Arbitrary class added in Android 5.0. - getAlpnSelectedProtocol = new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol"); - setAlpnProtocols = new OptionalMethod<>(null, "setAlpnProtocols", byte[].class); - } catch (ClassNotFoundException ignored) { - } - - return new Android(sslParametersClass, setUseSessionTickets, setHostname, - getAlpnSelectedProtocol, setAlpnProtocols); - } catch (ClassNotFoundException ignored) { - // This isn't an Android runtime. - } - - // Find Jetty's ALPN extension for OpenJDK. - try { - String negoClassName = "org.eclipse.jetty.alpn.ALPN"; - Class negoClass = Class.forName(negoClassName); - Class providerClass = Class.forName(negoClassName + "$Provider"); - Class clientProviderClass = Class.forName(negoClassName + "$ClientProvider"); - Class serverProviderClass = Class.forName(negoClassName + "$ServerProvider"); - Method putMethod = negoClass.getMethod("put", SSLSocket.class, providerClass); - Method getMethod = negoClass.getMethod("get", SSLSocket.class); - Method removeMethod = negoClass.getMethod("remove", SSLSocket.class); - return new JdkWithJettyBootPlatform( - putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass); - } catch (ClassNotFoundException | NoSuchMethodException ignored) { - } - - // Probably an Oracle JDK like OpenJDK. - return new Platform(); - } - - /** Android 2.3 or better. */ - private static class Android extends Platform { - private static final int MAX_LOG_LENGTH = 4000; - - private final Class sslParametersClass; - private final OptionalMethod setUseSessionTickets; - private final OptionalMethod setHostname; - - // Non-null on Android 5.0+. - private final OptionalMethod getAlpnSelectedProtocol; - private final OptionalMethod setAlpnProtocols; - - public Android(Class sslParametersClass, OptionalMethod setUseSessionTickets, - OptionalMethod setHostname, OptionalMethod getAlpnSelectedProtocol, - OptionalMethod setAlpnProtocols) { - this.sslParametersClass = sslParametersClass; - this.setUseSessionTickets = setUseSessionTickets; - this.setHostname = setHostname; - this.getAlpnSelectedProtocol = getAlpnSelectedProtocol; - this.setAlpnProtocols = setAlpnProtocols; - } - - @Override public void connectSocket(Socket socket, InetSocketAddress address, - int connectTimeout) throws IOException { - try { - socket.connect(address, connectTimeout); - } catch (AssertionError e) { - if (Util.isAndroidGetsocknameError(e)) throw new IOException(e); - throw e; - } catch (SecurityException e) { - // Before android 4.3, socket.connect could throw a SecurityException - // if opening a socket resulted in an EACCES error. - IOException ioException = new IOException("Exception in connect"); - ioException.initCause(e); - throw ioException; - } - } - - @Override public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) { - Object context = readFieldOrNull(sslSocketFactory, sslParametersClass, "sslParameters"); - if (context == null) { - // If that didn't work, try the Google Play Services SSL provider before giving up. This - // must be loaded by the SSLSocketFactory's class loader. - try { - Class gmsSslParametersClass = Class.forName( - "com.google.android.gms.org.conscrypt.SSLParametersImpl", false, - sslSocketFactory.getClass().getClassLoader()); - context = readFieldOrNull(sslSocketFactory, gmsSslParametersClass, "sslParameters"); - } catch (ClassNotFoundException e) { - return super.trustManager(sslSocketFactory); - } - } - - X509TrustManager x509TrustManager = readFieldOrNull( - context, X509TrustManager.class, "x509TrustManager"); - if (x509TrustManager != null) return x509TrustManager; - - return readFieldOrNull(context, X509TrustManager.class, "trustManager"); - } - - @Override public TrustRootIndex trustRootIndex(X509TrustManager trustManager) { - TrustRootIndex result = AndroidTrustRootIndex.get(trustManager); - if (result != null) return result; - return super.trustRootIndex(trustManager); - } - - @Override public void configureTlsExtensions( - SSLSocket sslSocket, String hostname, List protocols) { - // Enable SNI and session tickets. - if (hostname != null) { - setUseSessionTickets.invokeOptionalWithoutCheckedException(sslSocket, true); - setHostname.invokeOptionalWithoutCheckedException(sslSocket, hostname); - } - - // Enable ALPN. - if (setAlpnProtocols != null && setAlpnProtocols.isSupported(sslSocket)) { - Object[] parameters = {concatLengthPrefixed(protocols)}; - setAlpnProtocols.invokeWithoutCheckedException(sslSocket, parameters); - } - } - - @Override public String getSelectedProtocol(SSLSocket socket) { - if (getAlpnSelectedProtocol == null) return null; - if (!getAlpnSelectedProtocol.isSupported(socket)) return null; - - byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invokeWithoutCheckedException(socket); - return alpnResult != null ? new String(alpnResult, Util.UTF_8) : null; - } - - @Override public void log(String message) { - // Split by line, then ensure each line can fit into Log's maximum length. - for (int i = 0, length = message.length(); i < length; i++) { - int newline = message.indexOf('\n', i); - newline = newline != -1 ? newline : length; - do { - int end = Math.min(newline, i + MAX_LOG_LENGTH); - Log.d("OkHttp", message.substring(i, end)); - i = end; - } while (i < newline); - } - } - } - - /** - * OpenJDK 7+ with {@code org.mortbay.jetty.alpn/alpn-boot} in the boot class path. - */ - private static class JdkWithJettyBootPlatform extends Platform { - private final Method putMethod; - private final Method getMethod; - private final Method removeMethod; - private final Class clientProviderClass; - private final Class serverProviderClass; - - public JdkWithJettyBootPlatform(Method putMethod, Method getMethod, Method removeMethod, - Class clientProviderClass, Class serverProviderClass) { - this.putMethod = putMethod; - this.getMethod = getMethod; - this.removeMethod = removeMethod; - this.clientProviderClass = clientProviderClass; - this.serverProviderClass = serverProviderClass; - } - - @Override public void configureTlsExtensions( - SSLSocket sslSocket, String hostname, List protocols) { - List names = new ArrayList<>(protocols.size()); - for (int i = 0, size = protocols.size(); i < size; i++) { - Protocol protocol = protocols.get(i); - if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN. - names.add(protocol.toString()); - } - try { - Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(), - new Class[] {clientProviderClass, serverProviderClass}, new JettyNegoProvider(names)); - putMethod.invoke(null, sslSocket, provider); - } catch (InvocationTargetException | IllegalAccessException e) { - throw new AssertionError(e); - } - } - - @Override public void afterHandshake(SSLSocket sslSocket) { - try { - removeMethod.invoke(null, sslSocket); - } catch (IllegalAccessException | InvocationTargetException ignored) { - throw new AssertionError(); - } - } - - @Override public String getSelectedProtocol(SSLSocket socket) { - try { - JettyNegoProvider provider = - (JettyNegoProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket)); - if (!provider.unsupported && provider.selected == null) { - logger.log(Level.INFO, "ALPN callback dropped: SPDY and HTTP/2 are disabled. " - + "Is alpn-boot on the boot class path?"); - return null; - } - return provider.unsupported ? null : provider.selected; - } catch (InvocationTargetException | IllegalAccessException e) { - throw new AssertionError(); - } - } - } - - /** - * Handle the methods of ALPN's ClientProvider and ServerProvider without a compile-time - * dependency on those interfaces. - */ - private static class JettyNegoProvider implements InvocationHandler { - /** This peer's supported protocols. */ - private final List protocols; - /** Set when remote peer notifies ALPN is unsupported. */ - private boolean unsupported; - /** The protocol the server selected. */ - private String selected; - - public JettyNegoProvider(List protocols) { - this.protocols = protocols; - } - - @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - String methodName = method.getName(); - Class returnType = method.getReturnType(); - if (args == null) { - args = Util.EMPTY_STRING_ARRAY; - } - if (methodName.equals("supports") && boolean.class == returnType) { - return true; // ALPN is supported. - } else if (methodName.equals("unsupported") && void.class == returnType) { - this.unsupported = true; // Peer doesn't support ALPN. - return null; - } else if (methodName.equals("protocols") && args.length == 0) { - return protocols; // Client advertises these protocols. - } else if ((methodName.equals("selectProtocol") || methodName.equals("select")) - && String.class == returnType && args.length == 1 && args[0] instanceof List) { - List peerProtocols = (List) args[0]; - // Pick the first known protocol the peer advertises. - for (int i = 0, size = peerProtocols.size(); i < size; i++) { - if (protocols.contains(peerProtocols.get(i))) { - return selected = peerProtocols.get(i); - } - } - return selected = protocols.get(0); // On no intersection, try peer's first protocol. - } else if ((methodName.equals("protocolSelected") || methodName.equals("selected")) - && args.length == 1) { - this.selected = (String) args[0]; // Server selected this protocol. - return null; - } else { - return method.invoke(this, args); - } - } - } - - /** - * Returns the concatenation of 8-bit, length prefixed protocol names. - * http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4 - */ - static byte[] concatLengthPrefixed(List protocols) { - Buffer result = new Buffer(); - for (int i = 0, size = protocols.size(); i < size; i++) { - Protocol protocol = protocols.get(i); - if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN. - result.writeByte(protocol.toString().length()); - result.writeUtf8(protocol.toString()); - } - return result.readByteArray(); - } - - static T readFieldOrNull(Object instance, Class fieldType, String fieldName) { - for (Class c = instance.getClass(); c != Object.class; c = c.getSuperclass()) { - try { - Field field = c.getDeclaredField(fieldName); - field.setAccessible(true); - Object value = field.get(instance); - if (value == null || !fieldType.isInstance(value)) return null; - return fieldType.cast(value); - } catch (NoSuchFieldException ignored) { - } catch (IllegalAccessException e) { - throw new AssertionError(); - } - } - - // Didn't find the field we wanted. As a last gasp attempt, try to find the value on a delegate. - if (!fieldName.equals("delegate")) { - Object delegate = readFieldOrNull(instance, Object.class, "delegate"); - if (delegate != null) return readFieldOrNull(delegate, fieldType, fieldName); - } - - return null; - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/RouteDatabase.java b/okhttp3/src/main/java/okhttp3/internal/RouteDatabase.java deleted file mode 100644 index 613e0bf21..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/RouteDatabase.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal; - -import java.util.LinkedHashSet; -import java.util.Set; -import okhttp3.Route; - -/** - * A blacklist of failed routes to avoid when creating a new connection to a target address. This is - * used so that OkHttp can learn from its mistakes: if there was a failure attempting to connect to - * a specific IP address or proxy server, that failure is remembered and alternate routes are - * preferred. - */ -public final class RouteDatabase { - private final Set failedRoutes = new LinkedHashSet<>(); - - /** Records a failure connecting to {@code failedRoute}. */ - public synchronized void failed(Route failedRoute) { - failedRoutes.add(failedRoute); - } - - /** Records success connecting to {@code failedRoute}. */ - public synchronized void connected(Route route) { - failedRoutes.remove(route); - } - - /** Returns true if {@code route} has failed recently and should be avoided. */ - public synchronized boolean shouldPostpone(Route route) { - return failedRoutes.contains(route); - } - - public synchronized int failedRoutesCount() { - return failedRoutes.size(); - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/Util.java b/okhttp3/src/main/java/okhttp3/internal/Util.java deleted file mode 100644 index 4dc139f4f..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/Util.java +++ /dev/null @@ -1,449 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal; - -import java.io.Closeable; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Array; -import java.net.IDN; -import java.net.ServerSocket; -import java.net.Socket; -import java.nio.charset.Charset; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; -import okhttp3.HttpUrl; -import okio.Buffer; -import okio.ByteString; -import okio.Source; - -/** Junk drawer of utility methods. */ -public final class Util { - public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - public static final String[] EMPTY_STRING_ARRAY = new String[0]; - - /** A cheap and type-safe constant for the UTF-8 Charset. */ - public static final Charset UTF_8 = Charset.forName("UTF-8"); - - /** GMT and UTC are equivalent for our purposes. */ - public static final TimeZone UTC = TimeZone.getTimeZone("GMT"); - - /** - * Quick and dirty pattern to differentiate IP addresses from hostnames. This is an approximation - * of Android's private InetAddress#isNumeric API. - * - *

This matches IPv6 addresses as a hex string containing at least one colon, and possibly - * including dots after the first colon. It matches IPv4 addresses as strings containing only - * decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither IP - * addresses nor hostnames; they will be verified as IP addresses (which is a more strict - * verification). - */ - private static final Pattern VERIFY_AS_IP_ADDRESS = Pattern.compile( - "([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)"); - - private Util() { - } - - public static void checkOffsetAndCount(long arrayLength, long offset, long count) { - if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) { - throw new ArrayIndexOutOfBoundsException(); - } - } - - /** Returns true if two possibly-null objects are equal. */ - public static boolean equal(Object a, Object b) { - return a == b || (a != null && a.equals(b)); - } - - /** - * Closes {@code closeable}, ignoring any checked exceptions. Does nothing if {@code closeable} is - * null. - */ - public static void closeQuietly(Closeable closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (RuntimeException rethrown) { - throw rethrown; - } catch (Exception ignored) { - } - } - } - - /** - * Closes {@code socket}, ignoring any checked exceptions. Does nothing if {@code socket} is - * null. - */ - public static void closeQuietly(Socket socket) { - if (socket != null) { - try { - socket.close(); - } catch (AssertionError e) { - if (!isAndroidGetsocknameError(e)) throw e; - } catch (RuntimeException rethrown) { - throw rethrown; - } catch (Exception ignored) { - } - } - } - - /** - * Closes {@code serverSocket}, ignoring any checked exceptions. Does nothing if {@code - * serverSocket} is null. - */ - public static void closeQuietly(ServerSocket serverSocket) { - if (serverSocket != null) { - try { - serverSocket.close(); - } catch (RuntimeException rethrown) { - throw rethrown; - } catch (Exception ignored) { - } - } - } - - /** - * Closes {@code a} and {@code b}. If either close fails, this completes the other close and - * rethrows the first encountered exception. - */ - public static void closeAll(Closeable a, Closeable b) throws IOException { - Throwable thrown = null; - try { - a.close(); - } catch (Throwable e) { - thrown = e; - } - try { - b.close(); - } catch (Throwable e) { - if (thrown == null) thrown = e; - } - if (thrown == null) return; - if (thrown instanceof IOException) throw (IOException) thrown; - if (thrown instanceof RuntimeException) throw (RuntimeException) thrown; - if (thrown instanceof Error) throw (Error) thrown; - throw new AssertionError(thrown); - } - - /** - * Attempts to exhaust {@code source}, returning true if successful. This is useful when reading a - * complete source is helpful, such as when doing so completes a cache body or frees a socket - * connection for reuse. - */ - public static boolean discard(Source source, int timeout, TimeUnit timeUnit) { - try { - return skipAll(source, timeout, timeUnit); - } catch (IOException e) { - return false; - } - } - - /** - * Reads until {@code in} is exhausted or the deadline has been reached. This is careful to not - * extend the deadline if one exists already. - */ - public static boolean skipAll(Source source, int duration, TimeUnit timeUnit) throws IOException { - long now = System.nanoTime(); - long originalDuration = source.timeout().hasDeadline() - ? source.timeout().deadlineNanoTime() - now - : Long.MAX_VALUE; - source.timeout().deadlineNanoTime(now + Math.min(originalDuration, timeUnit.toNanos(duration))); - try { - Buffer skipBuffer = new Buffer(); - while (source.read(skipBuffer, 2048) != -1) { - skipBuffer.clear(); - } - return true; // Success! The source has been exhausted. - } catch (InterruptedIOException e) { - return false; // We ran out of time before exhausting the source. - } finally { - if (originalDuration == Long.MAX_VALUE) { - source.timeout().clearDeadline(); - } else { - source.timeout().deadlineNanoTime(now + originalDuration); - } - } - } - - /** Returns a 32 character string containing an MD5 hash of {@code s}. */ - public static String md5Hex(String s) { - try { - MessageDigest messageDigest = MessageDigest.getInstance("MD5"); - byte[] md5bytes = messageDigest.digest(s.getBytes("UTF-8")); - return ByteString.of(md5bytes).hex(); - } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { - throw new AssertionError(e); - } - } - - /** Returns a Base 64-encoded string containing a SHA-1 hash of {@code s}. */ - public static String shaBase64(String s) { - try { - MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); - byte[] sha1Bytes = messageDigest.digest(s.getBytes("UTF-8")); - return ByteString.of(sha1Bytes).base64(); - } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { - throw new AssertionError(e); - } - } - - /** Returns a SHA-1 hash of {@code s}. */ - public static ByteString sha1(ByteString s) { - try { - MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); - byte[] sha1Bytes = messageDigest.digest(s.toByteArray()); - return ByteString.of(sha1Bytes); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } - } - - /** Returns a SHA-256 hash of {@code s}. */ - public static ByteString sha256(ByteString s) { - try { - MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); - byte[] sha1Bytes = messageDigest.digest(s.toByteArray()); - return ByteString.of(sha1Bytes); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } - } - - /** Returns an immutable copy of {@code list}. */ - public static List immutableList(List list) { - return Collections.unmodifiableList(new ArrayList<>(list)); - } - - /** Returns an immutable list containing {@code elements}. */ - public static List immutableList(T... elements) { - return Collections.unmodifiableList(Arrays.asList(elements.clone())); - } - - /** Returns an immutable copy of {@code map}. */ - public static Map immutableMap(Map map) { - return Collections.unmodifiableMap(new LinkedHashMap<>(map)); - } - - public static ThreadFactory threadFactory(final String name, final boolean daemon) { - return new ThreadFactory() { - @Override public Thread newThread(Runnable runnable) { - Thread result = new Thread(runnable, name); - result.setDaemon(daemon); - return result; - } - }; - } - - /** - * Returns an array containing containing only elements found in {@code first} and also in {@code - * second}. The returned elements are in the same order as in {@code first}. - */ - @SuppressWarnings("unchecked") - public static T[] intersect(Class arrayType, T[] first, T[] second) { - List result = intersect(first, second); - return result.toArray((T[]) Array.newInstance(arrayType, result.size())); - } - - /** - * Returns a list containing containing only elements found in {@code first} and also in {@code - * second}. The returned elements are in the same order as in {@code first}. - */ - private static List intersect(T[] first, T[] second) { - List result = new ArrayList<>(); - for (T a : first) { - for (T b : second) { - if (a.equals(b)) { - result.add(b); - break; - } - } - } - return result; - } - - public static String hostHeader(HttpUrl url, boolean includeDefaultPort) { - String host = url.host().contains(":") - ? "[" + url.host() + "]" - : url.host(); - return includeDefaultPort || url.port() != HttpUrl.defaultPort(url.scheme()) - ? host + ":" + url.port() - : host; - } - - /** Returns {@code s} with control characters and non-ASCII characters replaced with '?'. */ - public static String toHumanReadableAscii(String s) { - for (int i = 0, length = s.length(), c; i < length; i += Character.charCount(c)) { - c = s.codePointAt(i); - if (c > '\u001f' && c < '\u007f') continue; - - Buffer buffer = new Buffer(); - buffer.writeUtf8(s, 0, i); - for (int j = i; j < length; j += Character.charCount(c)) { - c = s.codePointAt(j); - buffer.writeUtf8CodePoint(c > '\u001f' && c < '\u007f' ? c : '?'); - } - return buffer.readUtf8(); - } - return s; - } - - /** - * Returns true if {@code e} is due to a firmware bug fixed after Android 4.2.2. - * https://code.google.com/p/android/issues/detail?id=54072 - */ - public static boolean isAndroidGetsocknameError(AssertionError e) { - return e.getCause() != null && e.getMessage() != null - && e.getMessage().contains("getsockname failed"); - } - - public static boolean contains(String[] array, String value) { - return Arrays.asList(array).contains(value); - } - - public static String[] concat(String[] array, String value) { - String[] result = new String[array.length + 1]; - System.arraycopy(array, 0, result, 0, array.length); - result[result.length - 1] = value; - return result; - } - - /** - * Increments {@code pos} until {@code input[pos]} is not ASCII whitespace. Stops at {@code - * limit}. - */ - public static int skipLeadingAsciiWhitespace(String input, int pos, int limit) { - for (int i = pos; i < limit; i++) { - switch (input.charAt(i)) { - case '\t': - case '\n': - case '\f': - case '\r': - case ' ': - continue; - default: - return i; - } - } - return limit; - } - - /** - * Decrements {@code limit} until {@code input[limit - 1]} is not ASCII whitespace. Stops at - * {@code pos}. - */ - public static int skipTrailingAsciiWhitespace(String input, int pos, int limit) { - for (int i = limit - 1; i >= pos; i--) { - switch (input.charAt(i)) { - case '\t': - case '\n': - case '\f': - case '\r': - case ' ': - continue; - default: - return i + 1; - } - } - return pos; - } - - /** Equivalent to {@code string.substring(pos, limit).trim()}. */ - public static String trimSubstring(String string, int pos, int limit) { - int start = skipLeadingAsciiWhitespace(string, pos, limit); - int end = skipTrailingAsciiWhitespace(string, start, limit); - return string.substring(start, end); - } - - /** - * Returns the index of the first character in {@code input} that contains a character in {@code - * delimiters}. Returns limit if there is no such character. - */ - public static int delimiterOffset(String input, int pos, int limit, String delimiters) { - for (int i = pos; i < limit; i++) { - if (delimiters.indexOf(input.charAt(i)) != -1) return i; - } - return limit; - } - - /** - * Returns the index of the first character in {@code input} that is {@code delimiter}. Returns - * limit if there is no such character. - */ - public static int delimiterOffset(String input, int pos, int limit, char delimiter) { - for (int i = pos; i < limit; i++) { - if (input.charAt(i) == delimiter) return i; - } - return limit; - } - - /** - * Performs IDN ToASCII encoding and canonicalize the result to lowercase. e.g. This converts - * {@code ☃.net} to {@code xn--n3h.net}, and {@code WwW.GoOgLe.cOm} to {@code www.google.com}. - * {@code null} will be returned if the input cannot be ToASCII encoded or if the result - * contains unsupported ASCII characters. - */ - public static String domainToAscii(String input) { - try { - String result = IDN.toASCII(input).toLowerCase(Locale.US); - if (result.isEmpty()) return null; - - // Confirm that the IDN ToASCII result doesn't contain any illegal characters. - if (containsInvalidHostnameAsciiCodes(result)) { - return null; - } - // TODO: implement all label limits. - return result; - } catch (IllegalArgumentException e) { - return null; - } - } - - private static boolean containsInvalidHostnameAsciiCodes(String hostnameAscii) { - for (int i = 0; i < hostnameAscii.length(); i++) { - char c = hostnameAscii.charAt(i); - // The WHATWG Host parsing rules accepts some character codes which are invalid by - // definition for OkHttp's host header checks (and the WHATWG Host syntax definition). Here - // we rule out characters that would cause problems in host headers. - if (c <= '\u001f' || c >= '\u007f') { - return true; - } - // Check for the characters mentioned in the WHATWG Host parsing spec: - // U+0000, U+0009, U+000A, U+000D, U+0020, "#", "%", "/", ":", "?", "@", "[", "\", and "]" - // (excluding the characters covered above). - if (" #%/:?@[\\]".indexOf(c) != -1) { - return true; - } - } - return false; - } - - /** Returns true if {@code host} is not a host name and might be an IP address. */ - public static boolean verifyAsIpAddress(String host) { - return VERIFY_AS_IP_ADDRESS.matcher(host).matches(); - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/Version.java b/okhttp3/src/main/java/okhttp3/internal/Version.java deleted file mode 100644 index d80d1265d..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/Version.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal; - -public final class Version { - public static String userAgent() { - return "okhttp/3.2.0"; - } - - private Version() { - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/ErrorCode.java b/okhttp3/src/main/java/okhttp3/internal/framed/ErrorCode.java deleted file mode 100644 index 1142319d7..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/ErrorCode.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -// http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-7 -public enum ErrorCode { - /** Not an error! For SPDY stream resets, prefer null over NO_ERROR. */ - NO_ERROR(0, -1, 0), - - PROTOCOL_ERROR(1, 1, 1), - - /** A subtype of PROTOCOL_ERROR used by SPDY. */ - INVALID_STREAM(1, 2, -1), - - /** A subtype of PROTOCOL_ERROR used by SPDY. */ - UNSUPPORTED_VERSION(1, 4, -1), - - /** A subtype of PROTOCOL_ERROR used by SPDY. */ - STREAM_IN_USE(1, 8, -1), - - /** A subtype of PROTOCOL_ERROR used by SPDY. */ - STREAM_ALREADY_CLOSED(1, 9, -1), - - INTERNAL_ERROR(2, 6, 2), - - FLOW_CONTROL_ERROR(3, 7, -1), - - STREAM_CLOSED(5, -1, -1), - - FRAME_TOO_LARGE(6, 11, -1), - - REFUSED_STREAM(7, 3, -1), - - CANCEL(8, 5, -1), - - COMPRESSION_ERROR(9, -1, -1), - - CONNECT_ERROR(10, -1, -1), - - ENHANCE_YOUR_CALM(11, -1, -1), - - INADEQUATE_SECURITY(12, -1, -1), - - HTTP_1_1_REQUIRED(13, -1, -1), - - INVALID_CREDENTIALS(-1, 10, -1); - - public final int httpCode; - public final int spdyRstCode; - public final int spdyGoAwayCode; - - private ErrorCode(int httpCode, int spdyRstCode, int spdyGoAwayCode) { - this.httpCode = httpCode; - this.spdyRstCode = spdyRstCode; - this.spdyGoAwayCode = spdyGoAwayCode; - } - - public static ErrorCode fromSpdy3Rst(int code) { - for (ErrorCode errorCode : ErrorCode.values()) { - if (errorCode.spdyRstCode == code) return errorCode; - } - return null; - } - - public static ErrorCode fromHttp2(int code) { - for (ErrorCode errorCode : ErrorCode.values()) { - if (errorCode.httpCode == code) return errorCode; - } - return null; - } - - public static ErrorCode fromSpdyGoAway(int code) { - for (ErrorCode errorCode : ErrorCode.values()) { - if (errorCode.spdyGoAwayCode == code) return errorCode; - } - return null; - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/FrameReader.java b/okhttp3/src/main/java/okhttp3/internal/framed/FrameReader.java deleted file mode 100644 index 741a568c0..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/FrameReader.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -import java.io.Closeable; -import java.io.IOException; -import java.util.List; -import okio.BufferedSource; -import okio.ByteString; - -/** Reads transport frames for SPDY/3 or HTTP/2. */ -public interface FrameReader extends Closeable { - void readConnectionPreface() throws IOException; - - boolean nextFrame(Handler handler) throws IOException; - - interface Handler { - void data(boolean inFinished, int streamId, BufferedSource source, int length) - throws IOException; - - /** - * Create or update incoming headers, creating the corresponding streams if necessary. Frames - * that trigger this are SPDY SYN_STREAM, HEADERS, and SYN_REPLY, and HTTP/2 HEADERS and - * PUSH_PROMISE. - * - * @param outFinished true if the receiver should not send further frames. - * @param inFinished true if the sender will not send further frames. - * @param streamId the stream owning these headers. - * @param associatedStreamId the stream that triggered the sender to create this stream. - */ - void headers(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId, - List

headerBlock, HeadersMode headersMode); - - void rstStream(int streamId, ErrorCode errorCode); - - void settings(boolean clearPrevious, Settings settings); - - /** HTTP/2 only. */ - void ackSettings(); - - /** - * Read a connection-level ping from the peer. {@code ack} indicates this is a reply. Payload - * parameters are different between SPDY/3 and HTTP/2. - * - *

In SPDY/3, only the first {@code payload1} parameter is set. If the reader is a client, - * it is an unsigned even number. Likewise, a server will receive an odd number. - * - *

In HTTP/2, both {@code payload1} and {@code payload2} parameters are set. The data is - * opaque binary, and there are no rules on the content. - */ - void ping(boolean ack, int payload1, int payload2); - - /** - * The peer tells us to stop creating streams. It is safe to replay streams with {@code ID > - * lastGoodStreamId} on a new connection. In- flight streams with {@code ID <= - * lastGoodStreamId} can only be replayed on a new connection if they are idempotent. - * - * @param lastGoodStreamId the last stream ID the peer processed before sending this message. If - * {@code lastGoodStreamId} is zero, the peer processed no frames. - * @param errorCode reason for closing the connection. - * @param debugData only valid for HTTP/2; opaque debug data to send. - */ - void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData); - - /** - * Notifies that an additional {@code windowSizeIncrement} bytes can be sent on {@code - * streamId}, or the connection if {@code streamId} is zero. - */ - void windowUpdate(int streamId, long windowSizeIncrement); - - /** - * Called when reading a headers or priority frame. This may be used to change the stream's - * weight from the default (16) to a new value. - * - * @param streamId stream which has a priority change. - * @param streamDependency the stream ID this stream is dependent on. - * @param weight relative proportion of priority in [1..256]. - * @param exclusive inserts this stream ID as the sole child of {@code streamDependency}. - */ - void priority(int streamId, int streamDependency, int weight, boolean exclusive); - - /** - * HTTP/2 only. Receive a push promise header block.

A push promise contains all the headers - * that pertain to a server-initiated request, and a {@code promisedStreamId} to which response - * frames will be delivered. Push promise frames are sent as a part of the response to {@code - * streamId}. - * - * @param streamId client-initiated stream ID. Must be an odd number. - * @param promisedStreamId server-initiated stream ID. Must be an even number. - * @param requestHeaders minimally includes {@code :method}, {@code :scheme}, {@code - * :authority}, and (@code :path}. - */ - void pushPromise(int streamId, int promisedStreamId, List

requestHeaders) - throws IOException; - - /** - * HTTP/2 only. Expresses that resources for the connection or a client- initiated stream are - * available from a different network location or protocol configuration. - * - *

See alt-svc - * - * @param streamId when a client-initiated stream ID (odd number), the origin of this alternate - * service is the origin of the stream. When zero, the origin is specified in the {@code origin} - * parameter. - * @param origin when present, the origin is - * typically represented as a combination of scheme, host and port. When empty, the origin is - * that of the {@code streamId}. - * @param protocol an ALPN protocol, such as {@code h2}. - * @param host an IP address or hostname. - * @param port the IP port associated with the service. - * @param maxAge time in seconds that this alternative is considered fresh. - */ - void alternateService(int streamId, String origin, ByteString protocol, String host, int port, - long maxAge); - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/FrameWriter.java b/okhttp3/src/main/java/okhttp3/internal/framed/FrameWriter.java deleted file mode 100644 index 5e8c7ebef..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/FrameWriter.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -import java.io.Closeable; -import java.io.IOException; -import java.util.List; -import okio.Buffer; - -/** Writes transport frames for SPDY/3 or HTTP/2. */ -public interface FrameWriter extends Closeable { - /** HTTP/2 only. */ - void connectionPreface() throws IOException; - - /** Informs the peer that we've applied its latest settings. */ - void ackSettings(Settings peerSettings) throws IOException; - - /** - * HTTP/2 only. Send a push promise header block. - * - *

A push promise contains all the headers that pertain to a server-initiated request, and a - * {@code promisedStreamId} to which response frames will be delivered. Push promise frames are - * sent as a part of the response to {@code streamId}. The {@code promisedStreamId} has a priority - * of one greater than {@code streamId}. - * - * @param streamId client-initiated stream ID. Must be an odd number. - * @param promisedStreamId server-initiated stream ID. Must be an even number. - * @param requestHeaders minimally includes {@code :method}, {@code :scheme}, {@code :authority}, - * and (@code :path}. - */ - void pushPromise(int streamId, int promisedStreamId, List

requestHeaders) - throws IOException; - - /** SPDY/3 only. */ - void flush() throws IOException; - - void synStream(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId, - List
headerBlock) throws IOException; - - void synReply(boolean outFinished, int streamId, List
headerBlock) - throws IOException; - - void headers(int streamId, List
headerBlock) throws IOException; - - void rstStream(int streamId, ErrorCode errorCode) throws IOException; - - /** The maximum size of bytes that may be sent in a single call to {@link #data}. */ - int maxDataLength(); - - /** - * {@code source.length} may be longer than the max length of the variant's data frame. - * Implementations must send multiple frames as necessary. - * - * @param source the buffer to draw bytes from. May be null if byteCount is 0. - * @param byteCount must be between 0 and the minimum of {code source.length} and {@link - * #maxDataLength}. - */ - void data(boolean outFinished, int streamId, Buffer source, int byteCount) throws IOException; - - /** Write okhttp's settings to the peer. */ - void settings(Settings okHttpSettings) throws IOException; - - /** - * Send a connection-level ping to the peer. {@code ack} indicates this is a reply. Payload - * parameters are different between SPDY/3 and HTTP/2. - * - *

In SPDY/3, only the first {@code payload1} parameter is sent. If the sender is a client, it - * is an unsigned odd number. Likewise, a server will send an even number. - * - *

In HTTP/2, both {@code payload1} and {@code payload2} parameters are sent. The data is - * opaque binary, and there are no rules on the content. - */ - void ping(boolean ack, int payload1, int payload2) throws IOException; - - /** - * Tell the peer to stop creating streams and that we last processed {@code lastGoodStreamId}, or - * zero if no streams were processed. - * - * @param lastGoodStreamId the last stream ID processed, or zero if no streams were processed. - * @param errorCode reason for closing the connection. - * @param debugData only valid for HTTP/2; opaque debug data to send. - */ - void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) throws IOException; - - /** - * Inform peer that an additional {@code windowSizeIncrement} bytes can be sent on {@code - * streamId}, or the connection if {@code streamId} is zero. - */ - void windowUpdate(int streamId, long windowSizeIncrement) throws IOException; -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/FramedConnection.java b/okhttp3/src/main/java/okhttp3/internal/framed/FramedConnection.java deleted file mode 100644 index 0cfcba72f..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/FramedConnection.java +++ /dev/null @@ -1,934 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -import java.io.Closeable; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import okhttp3.Protocol; -import okhttp3.internal.NamedRunnable; -import okhttp3.internal.Util; -import okio.Buffer; -import okio.BufferedSink; -import okio.BufferedSource; -import okio.ByteString; -import okio.Okio; - -import static okhttp3.internal.Internal.logger; -import static okhttp3.internal.framed.Settings.DEFAULT_INITIAL_WINDOW_SIZE; - -/** - * A socket connection to a remote peer. A connection hosts streams which can send and receive - * data. - * - *

Many methods in this API are synchronous: the call is completed before the - * method returns. This is typical for Java but atypical for SPDY. This is motivated by exception - * transparency: an IOException that was triggered by a certain caller can be caught and handled by - * that caller. - */ -public final class FramedConnection implements Closeable { - - // Internal state of this connection is guarded by 'this'. No blocking - // operations may be performed while holding this lock! - // - // Socket writes are guarded by frameWriter. - // - // Socket reads are unguarded but are only made by the reader thread. - // - // Certain operations (like SYN_STREAM) need to synchronize on both the - // frameWriter (to do blocking I/O) and this (to create streams). Such - // operations must synchronize on 'this' last. This ensures that we never - // wait for a blocking operation while holding 'this'. - - private static final ExecutorService executor = new ThreadPoolExecutor(0, - Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue(), - Util.threadFactory("OkHttp FramedConnection", true)); - - /** The protocol variant, like {@link Spdy3}. */ - final Protocol protocol; - - /** True if this peer initiated the connection. */ - final boolean client; - - /** - * User code to run in response to incoming streams or settings. Calls to this are always invoked - * on {@link #executor}. - */ - private final Listener listener; - private final Map streams = new HashMap<>(); - private final String hostname; - private int lastGoodStreamId; - private int nextStreamId; - private boolean shutdown; - private long idleStartTimeNs = System.nanoTime(); - - /** Ensures push promise callbacks events are sent in order per stream. */ - private final ExecutorService pushExecutor; - - /** Lazily-created map of in-flight pings awaiting a response. Guarded by this. */ - private Map pings; - /** User code to run in response to push promise events. */ - private final PushObserver pushObserver; - private int nextPingId; - - /** - * The total number of bytes consumed by the application, but not yet acknowledged by sending a - * {@code WINDOW_UPDATE} frame on this connection. - */ - // Visible for testing - long unacknowledgedBytesRead = 0; - - /** - * Count of bytes that can be written on the connection before receiving a window update. - */ - // Visible for testing - long bytesLeftInWriteWindow; - - /** Settings we communicate to the peer. */ - Settings okHttpSettings = new Settings(); - - private static final int OKHTTP_CLIENT_WINDOW_SIZE = 16 * 1024 * 1024; - - /** Settings we receive from the peer. */ - // TODO: MWS will need to guard on this setting before attempting to push. - final Settings peerSettings = new Settings(); - - private boolean receivedInitialPeerSettings = false; - final Variant variant; - final Socket socket; - final FrameWriter frameWriter; - - // Visible for testing - final Reader readerRunnable; - - private FramedConnection(Builder builder) throws IOException { - protocol = builder.protocol; - pushObserver = builder.pushObserver; - client = builder.client; - listener = builder.listener; - // http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-5.1.1 - nextStreamId = builder.client ? 1 : 2; - if (builder.client && protocol == Protocol.HTTP_2) { - nextStreamId += 2; // In HTTP/2, 1 on client is reserved for Upgrade. - } - - nextPingId = builder.client ? 1 : 2; - - // Flow control was designed more for servers, or proxies than edge clients. - // If we are a client, set the flow control window to 16MiB. This avoids - // thrashing window updates every 64KiB, yet small enough to avoid blowing - // up the heap. - if (builder.client) { - okHttpSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, OKHTTP_CLIENT_WINDOW_SIZE); - } - - hostname = builder.hostname; - - if (protocol == Protocol.HTTP_2) { - variant = new Http2(); - // Like newSingleThreadExecutor, except lazy creates the thread. - pushExecutor = new ThreadPoolExecutor(0, 1, 60, TimeUnit.SECONDS, - new LinkedBlockingQueue(), - Util.threadFactory(String.format("OkHttp %s Push Observer", hostname), true)); - // 1 less than SPDY http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-6.9.2 - peerSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, 65535); - peerSettings.set(Settings.MAX_FRAME_SIZE, 0, Http2.INITIAL_MAX_FRAME_SIZE); - } else if (protocol == Protocol.SPDY_3) { - variant = new Spdy3(); - pushExecutor = null; - } else { - throw new AssertionError(protocol); - } - bytesLeftInWriteWindow = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE); - socket = builder.socket; - frameWriter = variant.newWriter(builder.sink, client); - - readerRunnable = new Reader(variant.newReader(builder.source, client)); - new Thread(readerRunnable).start(); // Not a daemon thread. - } - - /** The protocol as selected using ALPN. */ - public Protocol getProtocol() { - return protocol; - } - - /** - * Returns the number of {@link FramedStream#isOpen() open streams} on this connection. - */ - public synchronized int openStreamCount() { - return streams.size(); - } - - synchronized FramedStream getStream(int id) { - return streams.get(id); - } - - synchronized FramedStream removeStream(int streamId) { - FramedStream stream = streams.remove(streamId); - if (stream != null && streams.isEmpty()) { - setIdle(true); - } - notifyAll(); // The removed stream may be blocked on a connection-wide window update. - return stream; - } - - private synchronized void setIdle(boolean value) { - idleStartTimeNs = value ? System.nanoTime() : Long.MAX_VALUE; - } - - /** Returns true if this connection is idle. */ - public synchronized boolean isIdle() { - return idleStartTimeNs != Long.MAX_VALUE; - } - - public synchronized int maxConcurrentStreams() { - return peerSettings.getMaxConcurrentStreams(Integer.MAX_VALUE); - } - - /** - * Returns the time in ns when this connection became idle or Long.MAX_VALUE if connection is not - * idle. - */ - public synchronized long getIdleStartTimeNs() { - return idleStartTimeNs; - } - - /** - * Returns a new server-initiated stream. - * - * @param associatedStreamId the stream that triggered the sender to create this stream. - * @param out true to create an output stream that we can use to send data to the remote peer. - * Corresponds to {@code FLAG_FIN}. - */ - public FramedStream pushStream(int associatedStreamId, List

requestHeaders, boolean out) - throws IOException { - if (client) throw new IllegalStateException("Client cannot push requests."); - if (protocol != Protocol.HTTP_2) throw new IllegalStateException("protocol != HTTP_2"); - return newStream(associatedStreamId, requestHeaders, out, false); - } - - /** - * Returns a new locally-initiated stream. - * - * @param out true to create an output stream that we can use to send data to the remote peer. - * Corresponds to {@code FLAG_FIN}. - * @param in true to create an input stream that the remote peer can use to send data to us. - * Corresponds to {@code FLAG_UNIDIRECTIONAL}. - */ - public FramedStream newStream(List
requestHeaders, boolean out, boolean in) - throws IOException { - return newStream(0, requestHeaders, out, in); - } - - private FramedStream newStream(int associatedStreamId, List
requestHeaders, boolean out, - boolean in) throws IOException { - boolean outFinished = !out; - boolean inFinished = !in; - FramedStream stream; - int streamId; - - synchronized (frameWriter) { - synchronized (this) { - if (shutdown) { - throw new IOException("shutdown"); - } - streamId = nextStreamId; - nextStreamId += 2; - stream = new FramedStream(streamId, this, outFinished, inFinished, requestHeaders); - if (stream.isOpen()) { - streams.put(streamId, stream); - setIdle(false); - } - } - if (associatedStreamId == 0) { - frameWriter.synStream(outFinished, inFinished, streamId, associatedStreamId, - requestHeaders); - } else if (client) { - throw new IllegalArgumentException("client streams shouldn't have associated stream IDs"); - } else { // HTTP/2 has a PUSH_PROMISE frame. - frameWriter.pushPromise(associatedStreamId, streamId, requestHeaders); - } - } - - if (!out) { - frameWriter.flush(); - } - - return stream; - } - - void writeSynReply(int streamId, boolean outFinished, List
alternating) - throws IOException { - frameWriter.synReply(outFinished, streamId, alternating); - } - - /** - * Callers of this method are not thread safe, and sometimes on application threads. Most often, - * this method will be called to send a buffer worth of data to the peer. - * - *

Writes are subject to the write window of the stream and the connection. Until there is a - * window sufficient to send {@code byteCount}, the caller will block. For example, a user of - * {@code HttpURLConnection} who flushes more bytes to the output stream than the connection's - * write window will block. - * - *

Zero {@code byteCount} writes are not subject to flow control and will not block. The only - * use case for zero {@code byteCount} is closing a flushed output stream. - */ - public void writeData(int streamId, boolean outFinished, Buffer buffer, long byteCount) - throws IOException { - if (byteCount == 0) { // Empty data frames are not flow-controlled. - frameWriter.data(outFinished, streamId, buffer, 0); - return; - } - - while (byteCount > 0) { - int toWrite; - synchronized (FramedConnection.this) { - try { - while (bytesLeftInWriteWindow <= 0) { - // Before blocking, confirm that the stream we're writing is still open. It's possible - // that the stream has since been closed (such as if this write timed out.) - if (!streams.containsKey(streamId)) { - throw new IOException("stream closed"); - } - FramedConnection.this.wait(); // Wait until we receive a WINDOW_UPDATE. - } - } catch (InterruptedException e) { - throw new InterruptedIOException(); - } - - toWrite = (int) Math.min(byteCount, bytesLeftInWriteWindow); - toWrite = Math.min(toWrite, frameWriter.maxDataLength()); - bytesLeftInWriteWindow -= toWrite; - } - - byteCount -= toWrite; - frameWriter.data(outFinished && byteCount == 0, streamId, buffer, toWrite); - } - } - - /** - * {@code delta} will be negative if a settings frame initial window is smaller than the last. - */ - void addBytesToWriteWindow(long delta) { - bytesLeftInWriteWindow += delta; - if (delta > 0) FramedConnection.this.notifyAll(); - } - - void writeSynResetLater(final int streamId, final ErrorCode errorCode) { - executor.submit(new NamedRunnable("OkHttp %s stream %d", hostname, streamId) { - @Override public void execute() { - try { - writeSynReset(streamId, errorCode); - } catch (IOException ignored) { - } - } - }); - } - - void writeSynReset(int streamId, ErrorCode statusCode) throws IOException { - frameWriter.rstStream(streamId, statusCode); - } - - void writeWindowUpdateLater(final int streamId, final long unacknowledgedBytesRead) { - executor.execute(new NamedRunnable("OkHttp Window Update %s stream %d", hostname, streamId) { - @Override public void execute() { - try { - frameWriter.windowUpdate(streamId, unacknowledgedBytesRead); - } catch (IOException ignored) { - } - } - }); - } - - /** - * Sends a ping frame to the peer. Use the returned object to await the ping's response and - * observe its round trip time. - */ - public Ping ping() throws IOException { - Ping ping = new Ping(); - int pingId; - synchronized (this) { - if (shutdown) { - throw new IOException("shutdown"); - } - pingId = nextPingId; - nextPingId += 2; - if (pings == null) pings = new HashMap<>(); - pings.put(pingId, ping); - } - writePing(false, pingId, 0x4f4b6f6b /* ASCII "OKok" */, ping); - return ping; - } - - private void writePingLater( - final boolean reply, final int payload1, final int payload2, final Ping ping) { - executor.execute(new NamedRunnable("OkHttp %s ping %08x%08x", - hostname, payload1, payload2) { - @Override public void execute() { - try { - writePing(reply, payload1, payload2, ping); - } catch (IOException ignored) { - } - } - }); - } - - private void writePing(boolean reply, int payload1, int payload2, Ping ping) throws IOException { - synchronized (frameWriter) { - // Observe the sent time immediately before performing I/O. - if (ping != null) ping.send(); - frameWriter.ping(reply, payload1, payload2); - } - } - - private synchronized Ping removePing(int id) { - return pings != null ? pings.remove(id) : null; - } - - public void flush() throws IOException { - frameWriter.flush(); - } - - /** - * Degrades this connection such that new streams can neither be created locally, nor accepted - * from the remote peer. Existing streams are not impacted. This is intended to permit an endpoint - * to gracefully stop accepting new requests without harming previously established streams. - */ - public void shutdown(ErrorCode statusCode) throws IOException { - synchronized (frameWriter) { - int lastGoodStreamId; - synchronized (this) { - if (shutdown) { - return; - } - shutdown = true; - lastGoodStreamId = this.lastGoodStreamId; - } - // TODO: propagate exception message into debugData - frameWriter.goAway(lastGoodStreamId, statusCode, Util.EMPTY_BYTE_ARRAY); - } - } - - /** - * Closes this connection. This cancels all open streams and unanswered pings. It closes the - * underlying input and output streams and shuts down internal executor services. - */ - @Override public void close() throws IOException { - close(ErrorCode.NO_ERROR, ErrorCode.CANCEL); - } - - private void close(ErrorCode connectionCode, ErrorCode streamCode) throws IOException { - assert (!Thread.holdsLock(this)); - IOException thrown = null; - try { - shutdown(connectionCode); - } catch (IOException e) { - thrown = e; - } - - FramedStream[] streamsToClose = null; - Ping[] pingsToCancel = null; - synchronized (this) { - if (!streams.isEmpty()) { - streamsToClose = streams.values().toArray(new FramedStream[streams.size()]); - streams.clear(); - setIdle(false); - } - if (pings != null) { - pingsToCancel = pings.values().toArray(new Ping[pings.size()]); - pings = null; - } - } - - if (streamsToClose != null) { - for (FramedStream stream : streamsToClose) { - try { - stream.close(streamCode); - } catch (IOException e) { - if (thrown != null) thrown = e; - } - } - } - - if (pingsToCancel != null) { - for (Ping ping : pingsToCancel) { - ping.cancel(); - } - } - - // Close the writer to release its resources (such as deflaters). - try { - frameWriter.close(); - } catch (IOException e) { - if (thrown == null) thrown = e; - } - - // Close the socket to break out the reader thread, which will clean up after itself. - try { - socket.close(); - } catch (IOException e) { - thrown = e; - } - - if (thrown != null) throw thrown; - } - - /** - * Sends a connection header if the current variant requires it. This should be called after - * {@link Builder#build} for all new connections. - */ - public void sendConnectionPreface() throws IOException { - frameWriter.connectionPreface(); - frameWriter.settings(okHttpSettings); - int windowSize = okHttpSettings.getInitialWindowSize(Settings.DEFAULT_INITIAL_WINDOW_SIZE); - if (windowSize != Settings.DEFAULT_INITIAL_WINDOW_SIZE) { - frameWriter.windowUpdate(0, windowSize - Settings.DEFAULT_INITIAL_WINDOW_SIZE); - } - } - - /** Merges {@code settings} into this peer's settings and sends them to the remote peer. */ - public void setSettings(Settings settings) throws IOException { - synchronized (frameWriter) { - synchronized (this) { - if (shutdown) { - throw new IOException("shutdown"); - } - okHttpSettings.merge(settings); - frameWriter.settings(settings); - } - } - } - - public static class Builder { - private Socket socket; - private String hostname; - private BufferedSource source; - private BufferedSink sink; - private Listener listener = Listener.REFUSE_INCOMING_STREAMS; - private Protocol protocol = Protocol.SPDY_3; - private PushObserver pushObserver = PushObserver.CANCEL; - private boolean client; - - /** - * @param client true if this peer initiated the connection; false if this peer accepted the - * connection. - */ - public Builder(boolean client) throws IOException { - this.client = client; - } - - public Builder socket(Socket socket) throws IOException { - return socket(socket, ((InetSocketAddress) socket.getRemoteSocketAddress()).getHostName(), - Okio.buffer(Okio.source(socket)), Okio.buffer(Okio.sink(socket))); - } - - public Builder socket( - Socket socket, String hostname, BufferedSource source, BufferedSink sink) { - this.socket = socket; - this.hostname = hostname; - this.source = source; - this.sink = sink; - return this; - } - - public Builder listener(Listener listener) { - this.listener = listener; - return this; - } - - public Builder protocol(Protocol protocol) { - this.protocol = protocol; - return this; - } - - public Builder pushObserver(PushObserver pushObserver) { - this.pushObserver = pushObserver; - return this; - } - - public FramedConnection build() throws IOException { - return new FramedConnection(this); - } - } - - /** - * Methods in this class must not lock FrameWriter. If a method needs to write a frame, create an - * async task to do so. - */ - class Reader extends NamedRunnable implements FrameReader.Handler { - final FrameReader frameReader; - - private Reader(FrameReader frameReader) { - super("OkHttp %s", hostname); - this.frameReader = frameReader; - } - - @Override protected void execute() { - ErrorCode connectionErrorCode = ErrorCode.INTERNAL_ERROR; - ErrorCode streamErrorCode = ErrorCode.INTERNAL_ERROR; - try { - if (!client) { - frameReader.readConnectionPreface(); - } - while (frameReader.nextFrame(this)) { - } - connectionErrorCode = ErrorCode.NO_ERROR; - streamErrorCode = ErrorCode.CANCEL; - } catch (IOException e) { - connectionErrorCode = ErrorCode.PROTOCOL_ERROR; - streamErrorCode = ErrorCode.PROTOCOL_ERROR; - } finally { - try { - close(connectionErrorCode, streamErrorCode); - } catch (IOException ignored) { - } - Util.closeQuietly(frameReader); - } - } - - @Override public void data(boolean inFinished, int streamId, BufferedSource source, int length) - throws IOException { - if (pushedStream(streamId)) { - pushDataLater(streamId, source, length, inFinished); - return; - } - FramedStream dataStream = getStream(streamId); - if (dataStream == null) { - writeSynResetLater(streamId, ErrorCode.INVALID_STREAM); - source.skip(length); - return; - } - dataStream.receiveData(source, length); - if (inFinished) { - dataStream.receiveFin(); - } - } - - @Override public void headers(boolean outFinished, boolean inFinished, int streamId, - int associatedStreamId, List

headerBlock, HeadersMode headersMode) { - if (pushedStream(streamId)) { - pushHeadersLater(streamId, headerBlock, inFinished); - return; - } - FramedStream stream; - synchronized (FramedConnection.this) { - // If we're shutdown, don't bother with this stream. - if (shutdown) return; - - stream = getStream(streamId); - - if (stream == null) { - // The headers claim to be for an existing stream, but we don't have one. - if (headersMode.failIfStreamAbsent()) { - writeSynResetLater(streamId, ErrorCode.INVALID_STREAM); - return; - } - - // If the stream ID is less than the last created ID, assume it's already closed. - if (streamId <= lastGoodStreamId) return; - - // If the stream ID is in the client's namespace, assume it's already closed. - if (streamId % 2 == nextStreamId % 2) return; - - // Create a stream. - final FramedStream - newStream = new FramedStream(streamId, FramedConnection.this, outFinished, - inFinished, headerBlock); - lastGoodStreamId = streamId; - streams.put(streamId, newStream); - executor.execute(new NamedRunnable("OkHttp %s stream %d", hostname, streamId) { - @Override public void execute() { - try { - listener.onStream(newStream); - } catch (IOException e) { - logger.log(Level.INFO, "FramedConnection.Listener failure for " + hostname, e); - try { - newStream.close(ErrorCode.PROTOCOL_ERROR); - } catch (IOException ignored) { - } - } - } - }); - return; - } - } - - // The headers claim to be for a new stream, but we already have one. - if (headersMode.failIfStreamPresent()) { - stream.closeLater(ErrorCode.PROTOCOL_ERROR); - removeStream(streamId); - return; - } - - // Update an existing stream. - stream.receiveHeaders(headerBlock, headersMode); - if (inFinished) stream.receiveFin(); - } - - @Override public void rstStream(int streamId, ErrorCode errorCode) { - if (pushedStream(streamId)) { - pushResetLater(streamId, errorCode); - return; - } - FramedStream rstStream = removeStream(streamId); - if (rstStream != null) { - rstStream.receiveRstStream(errorCode); - } - } - - @Override public void settings(boolean clearPrevious, Settings newSettings) { - long delta = 0; - FramedStream[] streamsToNotify = null; - synchronized (FramedConnection.this) { - int priorWriteWindowSize = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE); - if (clearPrevious) peerSettings.clear(); - peerSettings.merge(newSettings); - if (getProtocol() == Protocol.HTTP_2) { - ackSettingsLater(newSettings); - } - int peerInitialWindowSize = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE); - if (peerInitialWindowSize != -1 && peerInitialWindowSize != priorWriteWindowSize) { - delta = peerInitialWindowSize - priorWriteWindowSize; - if (!receivedInitialPeerSettings) { - addBytesToWriteWindow(delta); - receivedInitialPeerSettings = true; - } - if (!streams.isEmpty()) { - streamsToNotify = streams.values().toArray(new FramedStream[streams.size()]); - } - } - executor.execute(new NamedRunnable("OkHttp %s settings", hostname) { - @Override public void execute() { - listener.onSettings(FramedConnection.this); - } - }); - } - if (streamsToNotify != null && delta != 0) { - for (FramedStream stream : streamsToNotify) { - synchronized (stream) { - stream.addBytesToWriteWindow(delta); - } - } - } - } - - private void ackSettingsLater(final Settings peerSettings) { - executor.execute(new NamedRunnable("OkHttp %s ACK Settings", hostname) { - @Override public void execute() { - try { - frameWriter.ackSettings(peerSettings); - } catch (IOException ignored) { - } - } - }); - } - - @Override public void ackSettings() { - // TODO: If we don't get this callback after sending settings to the peer, SETTINGS_TIMEOUT. - } - - @Override public void ping(boolean reply, int payload1, int payload2) { - if (reply) { - Ping ping = removePing(payload1); - if (ping != null) { - ping.receive(); - } - } else { - // Send a reply to a client ping if this is a server and vice versa. - writePingLater(true, payload1, payload2, null); - } - } - - @Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) { - if (debugData.size() > 0) { // TODO: log the debugData - } - - // Copy the streams first. We don't want to hold a lock when we call receiveRstStream(). - FramedStream[] streamsCopy; - synchronized (FramedConnection.this) { - streamsCopy = streams.values().toArray(new FramedStream[streams.size()]); - shutdown = true; - } - - // Fail all streams created after the last good stream ID. - for (FramedStream framedStream : streamsCopy) { - if (framedStream.getId() > lastGoodStreamId && framedStream.isLocallyInitiated()) { - framedStream.receiveRstStream(ErrorCode.REFUSED_STREAM); - removeStream(framedStream.getId()); - } - } - } - - @Override public void windowUpdate(int streamId, long windowSizeIncrement) { - if (streamId == 0) { - synchronized (FramedConnection.this) { - bytesLeftInWriteWindow += windowSizeIncrement; - FramedConnection.this.notifyAll(); - } - } else { - FramedStream stream = getStream(streamId); - if (stream != null) { - synchronized (stream) { - stream.addBytesToWriteWindow(windowSizeIncrement); - } - } - } - } - - @Override public void priority(int streamId, int streamDependency, int weight, - boolean exclusive) { - // TODO: honor priority. - } - - @Override - public void pushPromise(int streamId, int promisedStreamId, List
requestHeaders) { - pushRequestLater(promisedStreamId, requestHeaders); - } - - @Override public void alternateService(int streamId, String origin, ByteString protocol, - String host, int port, long maxAge) { - // TODO: register alternate service. - } - } - - /** Even, positive numbered streams are pushed streams in HTTP/2. */ - private boolean pushedStream(int streamId) { - return protocol == Protocol.HTTP_2 && streamId != 0 && (streamId & 1) == 0; - } - - // Guarded by this. - private final Set currentPushRequests = new LinkedHashSet<>(); - - private void pushRequestLater(final int streamId, final List
requestHeaders) { - synchronized (this) { - if (currentPushRequests.contains(streamId)) { - writeSynResetLater(streamId, ErrorCode.PROTOCOL_ERROR); - return; - } - currentPushRequests.add(streamId); - } - pushExecutor.execute(new NamedRunnable("OkHttp %s Push Request[%s]", hostname, streamId) { - @Override public void execute() { - boolean cancel = pushObserver.onRequest(streamId, requestHeaders); - try { - if (cancel) { - frameWriter.rstStream(streamId, ErrorCode.CANCEL); - synchronized (FramedConnection.this) { - currentPushRequests.remove(streamId); - } - } - } catch (IOException ignored) { - } - } - }); - } - - private void pushHeadersLater(final int streamId, final List
requestHeaders, - final boolean inFinished) { - pushExecutor.execute(new NamedRunnable("OkHttp %s Push Headers[%s]", hostname, streamId) { - @Override public void execute() { - boolean cancel = pushObserver.onHeaders(streamId, requestHeaders, inFinished); - try { - if (cancel) frameWriter.rstStream(streamId, ErrorCode.CANCEL); - if (cancel || inFinished) { - synchronized (FramedConnection.this) { - currentPushRequests.remove(streamId); - } - } - } catch (IOException ignored) { - } - } - }); - } - - /** - * Eagerly reads {@code byteCount} bytes from the source before launching a background task to - * process the data. This avoids corrupting the stream. - */ - private void pushDataLater(final int streamId, final BufferedSource source, final int byteCount, - final boolean inFinished) throws IOException { - final Buffer buffer = new Buffer(); - source.require(byteCount); // Eagerly read the frame before firing client thread. - source.read(buffer, byteCount); - if (buffer.size() != byteCount) throw new IOException(buffer.size() + " != " + byteCount); - pushExecutor.execute(new NamedRunnable("OkHttp %s Push Data[%s]", hostname, streamId) { - @Override public void execute() { - try { - boolean cancel = pushObserver.onData(streamId, buffer, byteCount, inFinished); - if (cancel) frameWriter.rstStream(streamId, ErrorCode.CANCEL); - if (cancel || inFinished) { - synchronized (FramedConnection.this) { - currentPushRequests.remove(streamId); - } - } - } catch (IOException ignored) { - } - } - }); - } - - private void pushResetLater(final int streamId, final ErrorCode errorCode) { - pushExecutor.execute(new NamedRunnable("OkHttp %s Push Reset[%s]", hostname, streamId) { - @Override public void execute() { - pushObserver.onReset(streamId, errorCode); - synchronized (FramedConnection.this) { - currentPushRequests.remove(streamId); - } - } - }); - } - - /** Listener of streams and settings initiated by the peer. */ - public abstract static class Listener { - public static final Listener REFUSE_INCOMING_STREAMS = new Listener() { - @Override public void onStream(FramedStream stream) throws IOException { - stream.close(ErrorCode.REFUSED_STREAM); - } - }; - - /** - * Handle a new stream from this connection's peer. Implementations should respond by either - * {@linkplain FramedStream#reply replying to the stream} or {@linkplain FramedStream#close - * closing it}. This response does not need to be synchronous. - */ - public abstract void onStream(FramedStream stream) throws IOException; - - /** - * Notification that the connection's peer's settings may have changed. Implementations should - * take appropriate action to handle the updated settings. - * - *

It is the implementation's responsibility to handle concurrent calls to this method. A - * remote peer that sends multiple settings frames will trigger multiple calls to this method, - * and those calls are not necessarily serialized. - */ - public void onSettings(FramedConnection connection) { - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/FramedStream.java b/okhttp3/src/main/java/okhttp3/internal/framed/FramedStream.java deleted file mode 100644 index c94a8449e..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/FramedStream.java +++ /dev/null @@ -1,611 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -import java.io.EOFException; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.net.SocketTimeoutException; -import java.util.ArrayList; -import java.util.List; -import okio.AsyncTimeout; -import okio.Buffer; -import okio.BufferedSource; -import okio.Sink; -import okio.Source; -import okio.Timeout; - -import static okhttp3.internal.framed.Settings.DEFAULT_INITIAL_WINDOW_SIZE; - -/** A logical bidirectional stream. */ -public final class FramedStream { - // Internal state is guarded by this. No long-running or potentially - // blocking operations are performed while the lock is held. - - /** - * The total number of bytes consumed by the application (with {@link FramedDataSource#read}), but - * not yet acknowledged by sending a {@code WINDOW_UPDATE} frame on this stream. - */ - // Visible for testing - long unacknowledgedBytesRead = 0; - - /** - * Count of bytes that can be written on the stream before receiving a window update. Even if this - * is positive, writes will block until there available bytes in {@code - * connection.bytesLeftInWriteWindow}. - */ - // guarded by this - long bytesLeftInWriteWindow; - - private final int id; - private final FramedConnection connection; - - /** Headers sent by the stream initiator. Immutable and non null. */ - private final List

requestHeaders; - - /** Headers sent in the stream reply. Null if reply is either not sent or not sent yet. */ - private List
responseHeaders; - - private final FramedDataSource source; - final FramedDataSink sink; - private final StreamTimeout readTimeout = new StreamTimeout(); - private final StreamTimeout writeTimeout = new StreamTimeout(); - - /** - * The reason why this stream was abnormally closed. If there are multiple reasons to abnormally - * close this stream (such as both peers closing it near-simultaneously) then this is the first - * reason known to this peer. - */ - private ErrorCode errorCode = null; - - FramedStream(int id, FramedConnection connection, boolean outFinished, boolean inFinished, - List
requestHeaders) { - if (connection == null) throw new NullPointerException("connection == null"); - if (requestHeaders == null) throw new NullPointerException("requestHeaders == null"); - this.id = id; - this.connection = connection; - this.bytesLeftInWriteWindow = - connection.peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE); - this.source = new FramedDataSource( - connection.okHttpSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE)); - this.sink = new FramedDataSink(); - this.source.finished = inFinished; - this.sink.finished = outFinished; - this.requestHeaders = requestHeaders; - } - - public int getId() { - return id; - } - - /** - * Returns true if this stream is open. A stream is open until either: - * - *
    - *
  • A {@code SYN_RESET} frame abnormally terminates the stream. - *
  • Both input and output streams have transmitted all data and headers. - *
- * - *

Note that the input stream may continue to yield data even after a stream reports itself as - * not open. This is because input data is buffered. - */ - public synchronized boolean isOpen() { - if (errorCode != null) { - return false; - } - if ((source.finished || source.closed) - && (sink.finished || sink.closed) - && responseHeaders != null) { - return false; - } - return true; - } - - /** Returns true if this stream was created by this peer. */ - public boolean isLocallyInitiated() { - boolean streamIsClient = ((id & 1) == 1); - return connection.client == streamIsClient; - } - - public FramedConnection getConnection() { - return connection; - } - - public List

getRequestHeaders() { - return requestHeaders; - } - - /** - * Returns the stream's response headers, blocking if necessary if they have not been received - * yet. - */ - public synchronized List
getResponseHeaders() throws IOException { - readTimeout.enter(); - try { - while (responseHeaders == null && errorCode == null) { - waitForIo(); - } - } finally { - readTimeout.exitAndThrowIfTimedOut(); - } - if (responseHeaders != null) return responseHeaders; - throw new IOException("stream was reset: " + errorCode); - } - - /** - * Returns the reason why this stream was closed, or null if it closed normally or has not yet - * been closed. - */ - public synchronized ErrorCode getErrorCode() { - return errorCode; - } - - /** - * Sends a reply to an incoming stream. - * - * @param out true to create an output stream that we can use to send data to the remote peer. - * Corresponds to {@code FLAG_FIN}. - */ - public void reply(List
responseHeaders, boolean out) throws IOException { - assert (!Thread.holdsLock(FramedStream.this)); - boolean outFinished = false; - synchronized (this) { - if (responseHeaders == null) { - throw new NullPointerException("responseHeaders == null"); - } - if (this.responseHeaders != null) { - throw new IllegalStateException("reply already sent"); - } - this.responseHeaders = responseHeaders; - if (!out) { - this.sink.finished = true; - outFinished = true; - } - } - connection.writeSynReply(id, outFinished, responseHeaders); - - if (outFinished) { - connection.flush(); - } - } - - public Timeout readTimeout() { - return readTimeout; - } - - public Timeout writeTimeout() { - return writeTimeout; - } - - /** Returns a source that reads data from the peer. */ - public Source getSource() { - return source; - } - - /** - * Returns a sink that can be used to write data to the peer. - * - * @throws IllegalStateException if this stream was initiated by the peer and a {@link #reply} has - * not yet been sent. - */ - public Sink getSink() { - synchronized (this) { - if (responseHeaders == null && !isLocallyInitiated()) { - throw new IllegalStateException("reply before requesting the sink"); - } - } - return sink; - } - - /** - * Abnormally terminate this stream. This blocks until the {@code RST_STREAM} frame has been - * transmitted. - */ - public void close(ErrorCode rstStatusCode) throws IOException { - if (!closeInternal(rstStatusCode)) { - return; // Already closed. - } - connection.writeSynReset(id, rstStatusCode); - } - - /** - * Abnormally terminate this stream. This enqueues a {@code RST_STREAM} frame and returns - * immediately. - */ - public void closeLater(ErrorCode errorCode) { - if (!closeInternal(errorCode)) { - return; // Already closed. - } - connection.writeSynResetLater(id, errorCode); - } - - /** Returns true if this stream was closed. */ - private boolean closeInternal(ErrorCode errorCode) { - assert (!Thread.holdsLock(this)); - synchronized (this) { - if (this.errorCode != null) { - return false; - } - if (source.finished && sink.finished) { - return false; - } - this.errorCode = errorCode; - notifyAll(); - } - connection.removeStream(id); - return true; - } - - void receiveHeaders(List
headers, HeadersMode headersMode) { - assert (!Thread.holdsLock(FramedStream.this)); - ErrorCode errorCode = null; - boolean open = true; - synchronized (this) { - if (responseHeaders == null) { - if (headersMode.failIfHeadersAbsent()) { - errorCode = ErrorCode.PROTOCOL_ERROR; - } else { - responseHeaders = headers; - open = isOpen(); - notifyAll(); - } - } else { - if (headersMode.failIfHeadersPresent()) { - errorCode = ErrorCode.STREAM_IN_USE; - } else { - List
newHeaders = new ArrayList<>(); - newHeaders.addAll(responseHeaders); - newHeaders.addAll(headers); - this.responseHeaders = newHeaders; - } - } - } - if (errorCode != null) { - closeLater(errorCode); - } else if (!open) { - connection.removeStream(id); - } - } - - void receiveData(BufferedSource in, int length) throws IOException { - assert (!Thread.holdsLock(FramedStream.this)); - this.source.receive(in, length); - } - - void receiveFin() { - assert (!Thread.holdsLock(FramedStream.this)); - boolean open; - synchronized (this) { - this.source.finished = true; - open = isOpen(); - notifyAll(); - } - if (!open) { - connection.removeStream(id); - } - } - - synchronized void receiveRstStream(ErrorCode errorCode) { - if (this.errorCode == null) { - this.errorCode = errorCode; - notifyAll(); - } - } - - /** - * A source that reads the incoming data frames of a stream. Although this class uses - * synchronization to safely receive incoming data frames, it is not intended for use by multiple - * readers. - */ - private final class FramedDataSource implements Source { - /** Buffer to receive data from the network into. Only accessed by the reader thread. */ - private final Buffer receiveBuffer = new Buffer(); - - /** Buffer with readable data. Guarded by FramedStream.this. */ - private final Buffer readBuffer = new Buffer(); - - /** Maximum number of bytes to buffer before reporting a flow control error. */ - private final long maxByteCount; - - /** True if the caller has closed this stream. */ - private boolean closed; - - /** - * True if either side has cleanly shut down this stream. We will receive no more bytes beyond - * those already in the buffer. - */ - private boolean finished; - - private FramedDataSource(long maxByteCount) { - this.maxByteCount = maxByteCount; - } - - @Override public long read(Buffer sink, long byteCount) - throws IOException { - if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); - - long read; - synchronized (FramedStream.this) { - waitUntilReadable(); - checkNotClosed(); - if (readBuffer.size() == 0) return -1; // This source is exhausted. - - // Move bytes from the read buffer into the caller's buffer. - read = readBuffer.read(sink, Math.min(byteCount, readBuffer.size())); - - // Flow control: notify the peer that we're ready for more data! - unacknowledgedBytesRead += read; - if (unacknowledgedBytesRead - >= connection.okHttpSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE) / 2) { - connection.writeWindowUpdateLater(id, unacknowledgedBytesRead); - unacknowledgedBytesRead = 0; - } - } - - // Update connection.unacknowledgedBytesRead outside the stream lock. - synchronized (connection) { // Multiple application threads may hit this section. - connection.unacknowledgedBytesRead += read; - if (connection.unacknowledgedBytesRead - >= connection.okHttpSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE) / 2) { - connection.writeWindowUpdateLater(0, connection.unacknowledgedBytesRead); - connection.unacknowledgedBytesRead = 0; - } - } - - return read; - } - - /** Returns once the source is either readable or finished. */ - private void waitUntilReadable() throws IOException { - readTimeout.enter(); - try { - while (readBuffer.size() == 0 && !finished && !closed && errorCode == null) { - waitForIo(); - } - } finally { - readTimeout.exitAndThrowIfTimedOut(); - } - } - - void receive(BufferedSource in, long byteCount) throws IOException { - assert (!Thread.holdsLock(FramedStream.this)); - - while (byteCount > 0) { - boolean finished; - boolean flowControlError; - synchronized (FramedStream.this) { - finished = this.finished; - flowControlError = byteCount + readBuffer.size() > maxByteCount; - } - - // If the peer sends more data than we can handle, discard it and close the connection. - if (flowControlError) { - in.skip(byteCount); - closeLater(ErrorCode.FLOW_CONTROL_ERROR); - return; - } - - // Discard data received after the stream is finished. It's probably a benign race. - if (finished) { - in.skip(byteCount); - return; - } - - // Fill the receive buffer without holding any locks. - long read = in.read(receiveBuffer, byteCount); - if (read == -1) throw new EOFException(); - byteCount -= read; - - // Move the received data to the read buffer to the reader can read it. - synchronized (FramedStream.this) { - boolean wasEmpty = readBuffer.size() == 0; - readBuffer.writeAll(receiveBuffer); - if (wasEmpty) { - FramedStream.this.notifyAll(); - } - } - } - } - - @Override public Timeout timeout() { - return readTimeout; - } - - @Override public void close() throws IOException { - synchronized (FramedStream.this) { - closed = true; - readBuffer.clear(); - FramedStream.this.notifyAll(); - } - cancelStreamIfNecessary(); - } - - private void checkNotClosed() throws IOException { - if (closed) { - throw new IOException("stream closed"); - } - if (errorCode != null) { - throw new IOException("stream was reset: " + errorCode); - } - } - } - - private void cancelStreamIfNecessary() throws IOException { - assert (!Thread.holdsLock(FramedStream.this)); - boolean open; - boolean cancel; - synchronized (this) { - cancel = !source.finished && source.closed && (sink.finished || sink.closed); - open = isOpen(); - } - if (cancel) { - // RST this stream to prevent additional data from being sent. This - // is safe because the input stream is closed (we won't use any - // further bytes) and the output stream is either finished or closed - // (so RSTing both streams doesn't cause harm). - FramedStream.this.close(ErrorCode.CANCEL); - } else if (!open) { - connection.removeStream(id); - } - } - - /** - * A sink that writes outgoing data frames of a stream. This class is not thread safe. - */ - final class FramedDataSink implements Sink { - private static final long EMIT_BUFFER_SIZE = 16384; - - /** - * Buffer of outgoing data. This batches writes of small writes into this sink as larges frames - * written to the outgoing connection. Batching saves the (small) framing overhead. - */ - private final Buffer sendBuffer = new Buffer(); - - private boolean closed; - - /** - * True if either side has cleanly shut down this stream. We shall send no more bytes. - */ - private boolean finished; - - @Override public void write(Buffer source, long byteCount) throws IOException { - assert (!Thread.holdsLock(FramedStream.this)); - sendBuffer.write(source, byteCount); - while (sendBuffer.size() >= EMIT_BUFFER_SIZE) { - emitDataFrame(false); - } - } - - /** - * Emit a single data frame to the connection. The frame's size be limited by this stream's - * write window. This method will block until the write window is nonempty. - */ - private void emitDataFrame(boolean outFinished) throws IOException { - long toWrite; - synchronized (FramedStream.this) { - writeTimeout.enter(); - try { - while (bytesLeftInWriteWindow <= 0 && !finished && !closed && errorCode == null) { - waitForIo(); // Wait until we receive a WINDOW_UPDATE for this stream. - } - } finally { - writeTimeout.exitAndThrowIfTimedOut(); - } - - checkOutNotClosed(); // Kick out if the stream was reset or closed while waiting. - toWrite = Math.min(bytesLeftInWriteWindow, sendBuffer.size()); - bytesLeftInWriteWindow -= toWrite; - } - - writeTimeout.enter(); - try { - connection.writeData(id, outFinished && toWrite == sendBuffer.size(), sendBuffer, toWrite); - } finally { - writeTimeout.exitAndThrowIfTimedOut(); - } - } - - @Override public void flush() throws IOException { - assert (!Thread.holdsLock(FramedStream.this)); - synchronized (FramedStream.this) { - checkOutNotClosed(); - } - while (sendBuffer.size() > 0) { - emitDataFrame(false); - connection.flush(); - } - } - - @Override public Timeout timeout() { - return writeTimeout; - } - - @Override public void close() throws IOException { - assert (!Thread.holdsLock(FramedStream.this)); - synchronized (FramedStream.this) { - if (closed) return; - } - if (!sink.finished) { - // Emit the remaining data, setting the END_STREAM flag on the last frame. - if (sendBuffer.size() > 0) { - while (sendBuffer.size() > 0) { - emitDataFrame(true); - } - } else { - // Send an empty frame just so we can set the END_STREAM flag. - connection.writeData(id, true, null, 0); - } - } - synchronized (FramedStream.this) { - closed = true; - } - connection.flush(); - cancelStreamIfNecessary(); - } - } - - /** - * {@code delta} will be negative if a settings frame initial window is smaller than the last. - */ - void addBytesToWriteWindow(long delta) { - bytesLeftInWriteWindow += delta; - if (delta > 0) FramedStream.this.notifyAll(); - } - - private void checkOutNotClosed() throws IOException { - if (sink.closed) { - throw new IOException("stream closed"); - } else if (sink.finished) { - throw new IOException("stream finished"); - } else if (errorCode != null) { - throw new IOException("stream was reset: " + errorCode); - } - } - - /** - * Like {@link #wait}, but throws an {@code InterruptedIOException} when interrupted instead of - * the more awkward {@link InterruptedException}. - */ - private void waitForIo() throws InterruptedIOException { - try { - wait(); - } catch (InterruptedException e) { - throw new InterruptedIOException(); - } - } - - /** - * The Okio timeout watchdog will call {@link #timedOut} if the timeout is reached. In that case - * we close the stream (asynchronously) which will notify the waiting thread. - */ - class StreamTimeout extends AsyncTimeout { - @Override protected void timedOut() { - closeLater(ErrorCode.CANCEL); - } - - @Override protected IOException newTimeoutException(IOException cause) { - SocketTimeoutException socketTimeoutException = new SocketTimeoutException("timeout"); - if (cause != null) { - socketTimeoutException.initCause(cause); - } - return socketTimeoutException; - } - - public void exitAndThrowIfTimedOut() throws IOException { - if (exit()) throw newTimeoutException(null /* cause */); - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/Header.java b/okhttp3/src/main/java/okhttp3/internal/framed/Header.java deleted file mode 100644 index 953671eb3..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/Header.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -import okio.ByteString; - -/** HTTP header: the name is an ASCII string, but the value can be UTF-8. */ -public final class Header { - // Special header names defined in the SPDY and HTTP/2 specs. - public static final ByteString RESPONSE_STATUS = ByteString.encodeUtf8(":status"); - public static final ByteString TARGET_METHOD = ByteString.encodeUtf8(":method"); - public static final ByteString TARGET_PATH = ByteString.encodeUtf8(":path"); - public static final ByteString TARGET_SCHEME = ByteString.encodeUtf8(":scheme"); - public static final ByteString TARGET_AUTHORITY = ByteString.encodeUtf8(":authority"); // HTTP/2 - public static final ByteString TARGET_HOST = ByteString.encodeUtf8(":host"); // spdy/3 - public static final ByteString VERSION = ByteString.encodeUtf8(":version"); // spdy/3 - - /** Name in case-insensitive ASCII encoding. */ - public final ByteString name; - /** Value in UTF-8 encoding. */ - public final ByteString value; - final int hpackSize; - - // TODO: search for toLowerCase and consider moving logic here. - public Header(String name, String value) { - this(ByteString.encodeUtf8(name), ByteString.encodeUtf8(value)); - } - - public Header(ByteString name, String value) { - this(name, ByteString.encodeUtf8(value)); - } - - public Header(ByteString name, ByteString value) { - this.name = name; - this.value = value; - this.hpackSize = 32 + name.size() + value.size(); - } - - @Override public boolean equals(Object other) { - if (other instanceof Header) { - Header that = (Header) other; - return this.name.equals(that.name) - && this.value.equals(that.value); - } - return false; - } - - @Override public int hashCode() { - int result = 17; - result = 31 * result + name.hashCode(); - result = 31 * result + value.hashCode(); - return result; - } - - @Override public String toString() { - return String.format("%s: %s", name.utf8(), value.utf8()); - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/HeadersMode.java b/okhttp3/src/main/java/okhttp3/internal/framed/HeadersMode.java deleted file mode 100644 index bc851ed78..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/HeadersMode.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -public enum HeadersMode { - SPDY_SYN_STREAM, - SPDY_REPLY, - SPDY_HEADERS, - HTTP_20_HEADERS; - - /** Returns true if it is an error these headers to create a new stream. */ - public boolean failIfStreamAbsent() { - return this == SPDY_REPLY || this == SPDY_HEADERS; - } - - /** Returns true if it is an error these headers to update an existing stream. */ - public boolean failIfStreamPresent() { - return this == SPDY_SYN_STREAM; - } - - /** - * Returns true if it is an error these headers to be the initial headers of a response. - */ - public boolean failIfHeadersAbsent() { - return this == SPDY_HEADERS; - } - - /** - * Returns true if it is an error these headers to be update existing headers of a response. - */ - public boolean failIfHeadersPresent() { - return this == SPDY_REPLY; - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/Hpack.java b/okhttp3/src/main/java/okhttp3/internal/framed/Hpack.java deleted file mode 100644 index 2b49784e9..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/Hpack.java +++ /dev/null @@ -1,435 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import okio.Buffer; -import okio.BufferedSource; -import okio.ByteString; -import okio.Okio; -import okio.Source; - -/** - * Read and write HPACK v10. - * - * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12 - * - * This implementation uses an array for the dynamic table and a list for indexed entries. Dynamic - * entries are added to the array, starting in the last position moving forward. When the array - * fills, it is doubled. - */ -final class Hpack { - private static final int PREFIX_4_BITS = 0x0f; - private static final int PREFIX_5_BITS = 0x1f; - private static final int PREFIX_6_BITS = 0x3f; - private static final int PREFIX_7_BITS = 0x7f; - - private static final Header[] STATIC_HEADER_TABLE = new Header[] { - new Header(Header.TARGET_AUTHORITY, ""), - new Header(Header.TARGET_METHOD, "GET"), - new Header(Header.TARGET_METHOD, "POST"), - new Header(Header.TARGET_PATH, "/"), - new Header(Header.TARGET_PATH, "/index.html"), - new Header(Header.TARGET_SCHEME, "http"), - new Header(Header.TARGET_SCHEME, "https"), - new Header(Header.RESPONSE_STATUS, "200"), - new Header(Header.RESPONSE_STATUS, "204"), - new Header(Header.RESPONSE_STATUS, "206"), - new Header(Header.RESPONSE_STATUS, "304"), - new Header(Header.RESPONSE_STATUS, "400"), - new Header(Header.RESPONSE_STATUS, "404"), - new Header(Header.RESPONSE_STATUS, "500"), - new Header("accept-charset", ""), - new Header("accept-encoding", "gzip, deflate"), - new Header("accept-language", ""), - new Header("accept-ranges", ""), - new Header("accept", ""), - new Header("access-control-allow-origin", ""), - new Header("age", ""), - new Header("allow", ""), - new Header("authorization", ""), - new Header("cache-control", ""), - new Header("content-disposition", ""), - new Header("content-encoding", ""), - new Header("content-language", ""), - new Header("content-length", ""), - new Header("content-location", ""), - new Header("content-range", ""), - new Header("content-type", ""), - new Header("cookie", ""), - new Header("date", ""), - new Header("etag", ""), - new Header("expect", ""), - new Header("expires", ""), - new Header("from", ""), - new Header("host", ""), - new Header("if-match", ""), - new Header("if-modified-since", ""), - new Header("if-none-match", ""), - new Header("if-range", ""), - new Header("if-unmodified-since", ""), - new Header("last-modified", ""), - new Header("link", ""), - new Header("location", ""), - new Header("max-forwards", ""), - new Header("proxy-authenticate", ""), - new Header("proxy-authorization", ""), - new Header("range", ""), - new Header("referer", ""), - new Header("refresh", ""), - new Header("retry-after", ""), - new Header("server", ""), - new Header("set-cookie", ""), - new Header("strict-transport-security", ""), - new Header("transfer-encoding", ""), - new Header("user-agent", ""), - new Header("vary", ""), - new Header("via", ""), - new Header("www-authenticate", "") - }; - - private Hpack() { - } - - // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-3.1 - static final class Reader { - - private final List
headerList = new ArrayList<>(); - private final BufferedSource source; - - private int headerTableSizeSetting; - private int maxDynamicTableByteCount; - // Visible for testing. - Header[] dynamicTable = new Header[8]; - // Array is populated back to front, so new entries always have lowest index. - int nextHeaderIndex = dynamicTable.length - 1; - int headerCount = 0; - int dynamicTableByteCount = 0; - - Reader(int headerTableSizeSetting, Source source) { - this.headerTableSizeSetting = headerTableSizeSetting; - this.maxDynamicTableByteCount = headerTableSizeSetting; - this.source = Okio.buffer(source); - } - - int maxDynamicTableByteCount() { - return maxDynamicTableByteCount; - } - - /** - * Called by the reader when the peer sent {@link Settings#HEADER_TABLE_SIZE}. While this - * establishes the maximum dynamic table size, the {@link #maxDynamicTableByteCount} set during - * processing may limit the table size to a smaller amount. - * - *

Evicts entries or clears the table as needed. - */ - void headerTableSizeSetting(int headerTableSizeSetting) { - this.headerTableSizeSetting = headerTableSizeSetting; - this.maxDynamicTableByteCount = headerTableSizeSetting; - adjustDynamicTableByteCount(); - } - - private void adjustDynamicTableByteCount() { - if (maxDynamicTableByteCount < dynamicTableByteCount) { - if (maxDynamicTableByteCount == 0) { - clearDynamicTable(); - } else { - evictToRecoverBytes(dynamicTableByteCount - maxDynamicTableByteCount); - } - } - } - - private void clearDynamicTable() { - headerList.clear(); - Arrays.fill(dynamicTable, null); - nextHeaderIndex = dynamicTable.length - 1; - headerCount = 0; - dynamicTableByteCount = 0; - } - - /** Returns the count of entries evicted. */ - private int evictToRecoverBytes(int bytesToRecover) { - int entriesToEvict = 0; - if (bytesToRecover > 0) { - // determine how many headers need to be evicted. - for (int j = dynamicTable.length - 1; j >= nextHeaderIndex && bytesToRecover > 0; j--) { - bytesToRecover -= dynamicTable[j].hpackSize; - dynamicTableByteCount -= dynamicTable[j].hpackSize; - headerCount--; - entriesToEvict++; - } - System.arraycopy(dynamicTable, nextHeaderIndex + 1, dynamicTable, - nextHeaderIndex + 1 + entriesToEvict, headerCount); - nextHeaderIndex += entriesToEvict; - } - return entriesToEvict; - } - - /** - * Read {@code byteCount} bytes of headers from the source stream. This implementation does not - * propagate the never indexed flag of a header. - */ - void readHeaders() throws IOException { - while (!source.exhausted()) { - int b = source.readByte() & 0xff; - if (b == 0x80) { // 10000000 - throw new IOException("index == 0"); - } else if ((b & 0x80) == 0x80) { // 1NNNNNNN - int index = readInt(b, PREFIX_7_BITS); - readIndexedHeader(index - 1); - } else if (b == 0x40) { // 01000000 - readLiteralHeaderWithIncrementalIndexingNewName(); - } else if ((b & 0x40) == 0x40) { // 01NNNNNN - int index = readInt(b, PREFIX_6_BITS); - readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1); - } else if ((b & 0x20) == 0x20) { // 001NNNNN - maxDynamicTableByteCount = readInt(b, PREFIX_5_BITS); - if (maxDynamicTableByteCount < 0 - || maxDynamicTableByteCount > headerTableSizeSetting) { - throw new IOException("Invalid dynamic table size update " + maxDynamicTableByteCount); - } - adjustDynamicTableByteCount(); - } else if (b == 0x10 || b == 0) { // 000?0000 - Ignore never indexed bit. - readLiteralHeaderWithoutIndexingNewName(); - } else { // 000?NNNN - Ignore never indexed bit. - int index = readInt(b, PREFIX_4_BITS); - readLiteralHeaderWithoutIndexingIndexedName(index - 1); - } - } - } - - public List

getAndResetHeaderList() { - List
result = new ArrayList<>(headerList); - headerList.clear(); - return result; - } - - private void readIndexedHeader(int index) throws IOException { - if (isStaticHeader(index)) { - Header staticEntry = STATIC_HEADER_TABLE[index]; - headerList.add(staticEntry); - } else { - int dynamicTableIndex = dynamicTableIndex(index - STATIC_HEADER_TABLE.length); - if (dynamicTableIndex < 0 || dynamicTableIndex > dynamicTable.length - 1) { - throw new IOException("Header index too large " + (index + 1)); - } - headerList.add(dynamicTable[dynamicTableIndex]); - } - } - - // referencedHeaders is relative to nextHeaderIndex + 1. - private int dynamicTableIndex(int index) { - return nextHeaderIndex + 1 + index; - } - - private void readLiteralHeaderWithoutIndexingIndexedName(int index) throws IOException { - ByteString name = getName(index); - ByteString value = readByteString(); - headerList.add(new Header(name, value)); - } - - private void readLiteralHeaderWithoutIndexingNewName() throws IOException { - ByteString name = checkLowercase(readByteString()); - ByteString value = readByteString(); - headerList.add(new Header(name, value)); - } - - private void readLiteralHeaderWithIncrementalIndexingIndexedName(int nameIndex) - throws IOException { - ByteString name = getName(nameIndex); - ByteString value = readByteString(); - insertIntoDynamicTable(-1, new Header(name, value)); - } - - private void readLiteralHeaderWithIncrementalIndexingNewName() throws IOException { - ByteString name = checkLowercase(readByteString()); - ByteString value = readByteString(); - insertIntoDynamicTable(-1, new Header(name, value)); - } - - private ByteString getName(int index) { - if (isStaticHeader(index)) { - return STATIC_HEADER_TABLE[index].name; - } else { - return dynamicTable[dynamicTableIndex(index - STATIC_HEADER_TABLE.length)].name; - } - } - - private boolean isStaticHeader(int index) { - return index >= 0 && index <= STATIC_HEADER_TABLE.length - 1; - } - - /** index == -1 when new. */ - private void insertIntoDynamicTable(int index, Header entry) { - headerList.add(entry); - - int delta = entry.hpackSize; - if (index != -1) { // Index -1 == new header. - delta -= dynamicTable[dynamicTableIndex(index)].hpackSize; - } - - // if the new or replacement header is too big, drop all entries. - if (delta > maxDynamicTableByteCount) { - clearDynamicTable(); - return; - } - - // Evict headers to the required length. - int bytesToRecover = (dynamicTableByteCount + delta) - maxDynamicTableByteCount; - int entriesEvicted = evictToRecoverBytes(bytesToRecover); - - if (index == -1) { // Adding a value to the dynamic table. - if (headerCount + 1 > dynamicTable.length) { // Need to grow the dynamic table. - Header[] doubled = new Header[dynamicTable.length * 2]; - System.arraycopy(dynamicTable, 0, doubled, dynamicTable.length, dynamicTable.length); - nextHeaderIndex = dynamicTable.length - 1; - dynamicTable = doubled; - } - index = nextHeaderIndex--; - dynamicTable[index] = entry; - headerCount++; - } else { // Replace value at same position. - index += dynamicTableIndex(index) + entriesEvicted; - dynamicTable[index] = entry; - } - dynamicTableByteCount += delta; - } - - private int readByte() throws IOException { - return source.readByte() & 0xff; - } - - int readInt(int firstByte, int prefixMask) throws IOException { - int prefix = firstByte & prefixMask; - if (prefix < prefixMask) { - return prefix; // This was a single byte value. - } - - // This is a multibyte value. Read 7 bits at a time. - int result = prefixMask; - int shift = 0; - while (true) { - int b = readByte(); - if ((b & 0x80) != 0) { // Equivalent to (b >= 128) since b is in [0..255]. - result += (b & 0x7f) << shift; - shift += 7; - } else { - result += b << shift; // Last byte. - break; - } - } - return result; - } - - /** Reads a potentially Huffman encoded byte string. */ - ByteString readByteString() throws IOException { - int firstByte = readByte(); - boolean huffmanDecode = (firstByte & 0x80) == 0x80; // 1NNNNNNN - int length = readInt(firstByte, PREFIX_7_BITS); - - if (huffmanDecode) { - return ByteString.of(Huffman.get().decode(source.readByteArray(length))); - } else { - return source.readByteString(length); - } - } - } - - private static final Map NAME_TO_FIRST_INDEX = nameToFirstIndex(); - - private static Map nameToFirstIndex() { - Map result = new LinkedHashMap<>(STATIC_HEADER_TABLE.length); - for (int i = 0; i < STATIC_HEADER_TABLE.length; i++) { - if (!result.containsKey(STATIC_HEADER_TABLE[i].name)) { - result.put(STATIC_HEADER_TABLE[i].name, i); - } - } - return Collections.unmodifiableMap(result); - } - - static final class Writer { - private final Buffer out; - - Writer(Buffer out) { - this.out = out; - } - - /** This does not use "never indexed" semantics for sensitive headers. */ - // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-6.2.3 - void writeHeaders(List
headerBlock) throws IOException { - // TODO: implement index tracking - for (int i = 0, size = headerBlock.size(); i < size; i++) { - ByteString name = headerBlock.get(i).name.toAsciiLowercase(); - Integer staticIndex = NAME_TO_FIRST_INDEX.get(name); - if (staticIndex != null) { - // Literal Header Field without Indexing - Indexed Name. - writeInt(staticIndex + 1, PREFIX_4_BITS, 0); - writeByteString(headerBlock.get(i).value); - } else { - out.writeByte(0x00); // Literal Header without Indexing - New Name. - writeByteString(name); - writeByteString(headerBlock.get(i).value); - } - } - } - - // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-4.1.1 - void writeInt(int value, int prefixMask, int bits) throws IOException { - // Write the raw value for a single byte value. - if (value < prefixMask) { - out.writeByte(bits | value); - return; - } - - // Write the mask to start a multibyte value. - out.writeByte(bits | prefixMask); - value -= prefixMask; - - // Write 7 bits at a time 'til we're done. - while (value >= 0x80) { - int b = value & 0x7f; - out.writeByte(b | 0x80); - value >>>= 7; - } - out.writeByte(value); - } - - void writeByteString(ByteString data) throws IOException { - writeInt(data.size(), PREFIX_7_BITS, 0); - out.write(data); - } - } - - /** - * An HTTP/2 response cannot contain uppercase header characters and must be treated as - * malformed. - */ - private static ByteString checkLowercase(ByteString name) throws IOException { - for (int i = 0, length = name.size(); i < length; i++) { - byte c = name.getByte(i); - if (c >= 'A' && c <= 'Z') { - throw new IOException("PROTOCOL_ERROR response malformed: mixed case name: " + name.utf8()); - } - } - return name; - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/Http2.java b/okhttp3/src/main/java/okhttp3/internal/framed/Http2.java deleted file mode 100644 index a6c177cc6..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/Http2.java +++ /dev/null @@ -1,770 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -import java.io.IOException; -import java.util.List; -import java.util.logging.Logger; -import okhttp3.Protocol; -import okio.Buffer; -import okio.BufferedSink; -import okio.BufferedSource; -import okio.ByteString; -import okio.Source; -import okio.Timeout; - -import static java.lang.String.format; -import static java.util.logging.Level.FINE; -import static okhttp3.internal.framed.Http2.FrameLogger.formatHeader; -import static okio.ByteString.EMPTY; - -/** - * Read and write HTTP/2 frames. - * - *

This implementation assumes we do not send an increased {@link Settings#getMaxFrameSize frame - * size setting} to the peer. Hence, we expect all frames to have a max length of {@link - * #INITIAL_MAX_FRAME_SIZE}.

http://tools.ietf.org/html/draft-ietf-httpbis-http2-17 - */ -public final class Http2 implements Variant { - private static final Logger logger = Logger.getLogger(FrameLogger.class.getName()); - - @Override public Protocol getProtocol() { - return Protocol.HTTP_2; - } - - private static final ByteString CONNECTION_PREFACE - = ByteString.encodeUtf8("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"); - - /** The initial max frame size, applied independently writing to, or reading from the peer. */ - static final int INITIAL_MAX_FRAME_SIZE = 0x4000; // 16384 - - static final byte TYPE_DATA = 0x0; - static final byte TYPE_HEADERS = 0x1; - static final byte TYPE_PRIORITY = 0x2; - static final byte TYPE_RST_STREAM = 0x3; - static final byte TYPE_SETTINGS = 0x4; - static final byte TYPE_PUSH_PROMISE = 0x5; - static final byte TYPE_PING = 0x6; - static final byte TYPE_GOAWAY = 0x7; - static final byte TYPE_WINDOW_UPDATE = 0x8; - static final byte TYPE_CONTINUATION = 0x9; - - static final byte FLAG_NONE = 0x0; - static final byte FLAG_ACK = 0x1; // Used for settings and ping. - static final byte FLAG_END_STREAM = 0x1; // Used for headers and data. - static final byte FLAG_END_HEADERS = 0x4; // Used for headers and continuation. - static final byte FLAG_END_PUSH_PROMISE = 0x4; - static final byte FLAG_PADDED = 0x8; // Used for headers and data. - static final byte FLAG_PRIORITY = 0x20; // Used for headers. - static final byte FLAG_COMPRESSED = 0x20; // Used for data. - - /** - * Creates a frame reader with max header table size of 4096 and data frame compression disabled. - */ - @Override public FrameReader newReader(BufferedSource source, boolean client) { - return new Reader(source, 4096, client); - } - - @Override public FrameWriter newWriter(BufferedSink sink, boolean client) { - return new Writer(sink, client); - } - - static final class Reader implements FrameReader { - private final BufferedSource source; - private final ContinuationSource continuation; - private final boolean client; - - // Visible for testing. - final Hpack.Reader hpackReader; - - Reader(BufferedSource source, int headerTableSize, boolean client) { - this.source = source; - this.client = client; - this.continuation = new ContinuationSource(this.source); - this.hpackReader = new Hpack.Reader(headerTableSize, continuation); - } - - @Override public void readConnectionPreface() throws IOException { - if (client) return; // Nothing to read; servers doesn't send a connection preface! - ByteString connectionPreface = source.readByteString(CONNECTION_PREFACE.size()); - if (logger.isLoggable(FINE)) logger.fine(format("<< CONNECTION %s", connectionPreface.hex())); - if (!CONNECTION_PREFACE.equals(connectionPreface)) { - throw ioException("Expected a connection header but was %s", connectionPreface.utf8()); - } - } - - @Override public boolean nextFrame(Handler handler) throws IOException { - try { - source.require(9); // Frame header size - } catch (IOException e) { - return false; // This might be a normal socket close. - } - - /* 0 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Length (24) | - * +---------------+---------------+---------------+ - * | Type (8) | Flags (8) | - * +-+-+-----------+---------------+-------------------------------+ - * |R| Stream Identifier (31) | - * +=+=============================================================+ - * | Frame Payload (0...) ... - * +---------------------------------------------------------------+ - */ - int length = readMedium(source); - if (length < 0 || length > INITIAL_MAX_FRAME_SIZE) { - throw ioException("FRAME_SIZE_ERROR: %s", length); - } - byte type = (byte) (source.readByte() & 0xff); - byte flags = (byte) (source.readByte() & 0xff); - int streamId = (source.readInt() & 0x7fffffff); // Ignore reserved bit. - if (logger.isLoggable(FINE)) logger.fine(formatHeader(true, streamId, length, type, flags)); - - switch (type) { - case TYPE_DATA: - readData(handler, length, flags, streamId); - break; - - case TYPE_HEADERS: - readHeaders(handler, length, flags, streamId); - break; - - case TYPE_PRIORITY: - readPriority(handler, length, flags, streamId); - break; - - case TYPE_RST_STREAM: - readRstStream(handler, length, flags, streamId); - break; - - case TYPE_SETTINGS: - readSettings(handler, length, flags, streamId); - break; - - case TYPE_PUSH_PROMISE: - readPushPromise(handler, length, flags, streamId); - break; - - case TYPE_PING: - readPing(handler, length, flags, streamId); - break; - - case TYPE_GOAWAY: - readGoAway(handler, length, flags, streamId); - break; - - case TYPE_WINDOW_UPDATE: - readWindowUpdate(handler, length, flags, streamId); - break; - - default: - // Implementations MUST discard frames that have unknown or unsupported types. - source.skip(length); - } - return true; - } - - private void readHeaders(Handler handler, int length, byte flags, int streamId) - throws IOException { - if (streamId == 0) throw ioException("PROTOCOL_ERROR: TYPE_HEADERS streamId == 0"); - - boolean endStream = (flags & FLAG_END_STREAM) != 0; - - short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0; - - if ((flags & FLAG_PRIORITY) != 0) { - readPriority(handler, streamId); - length -= 5; // account for above read. - } - - length = lengthWithoutPadding(length, flags, padding); - - List

headerBlock = readHeaderBlock(length, padding, flags, streamId); - - handler.headers(false, endStream, streamId, -1, headerBlock, HeadersMode.HTTP_20_HEADERS); - } - - private List
readHeaderBlock(int length, short padding, byte flags, int streamId) - throws IOException { - continuation.length = continuation.left = length; - continuation.padding = padding; - continuation.flags = flags; - continuation.streamId = streamId; - - // TODO: Concat multi-value headers with 0x0, except COOKIE, which uses 0x3B, 0x20. - // http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-8.1.2.5 - hpackReader.readHeaders(); - return hpackReader.getAndResetHeaderList(); - } - - private void readData(Handler handler, int length, byte flags, int streamId) - throws IOException { - // TODO: checkState open or half-closed (local) or raise STREAM_CLOSED - boolean inFinished = (flags & FLAG_END_STREAM) != 0; - boolean gzipped = (flags & FLAG_COMPRESSED) != 0; - if (gzipped) { - throw ioException("PROTOCOL_ERROR: FLAG_COMPRESSED without SETTINGS_COMPRESS_DATA"); - } - - short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0; - length = lengthWithoutPadding(length, flags, padding); - - handler.data(inFinished, streamId, source, length); - source.skip(padding); - } - - private void readPriority(Handler handler, int length, byte flags, int streamId) - throws IOException { - if (length != 5) throw ioException("TYPE_PRIORITY length: %d != 5", length); - if (streamId == 0) throw ioException("TYPE_PRIORITY streamId == 0"); - readPriority(handler, streamId); - } - - private void readPriority(Handler handler, int streamId) throws IOException { - int w1 = source.readInt(); - boolean exclusive = (w1 & 0x80000000) != 0; - int streamDependency = (w1 & 0x7fffffff); - int weight = (source.readByte() & 0xff) + 1; - handler.priority(streamId, streamDependency, weight, exclusive); - } - - private void readRstStream(Handler handler, int length, byte flags, int streamId) - throws IOException { - if (length != 4) throw ioException("TYPE_RST_STREAM length: %d != 4", length); - if (streamId == 0) throw ioException("TYPE_RST_STREAM streamId == 0"); - int errorCodeInt = source.readInt(); - ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt); - if (errorCode == null) { - throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt); - } - handler.rstStream(streamId, errorCode); - } - - private void readSettings(Handler handler, int length, byte flags, int streamId) - throws IOException { - if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0"); - if ((flags & FLAG_ACK) != 0) { - if (length != 0) throw ioException("FRAME_SIZE_ERROR ack frame should be empty!"); - handler.ackSettings(); - return; - } - - if (length % 6 != 0) throw ioException("TYPE_SETTINGS length %% 6 != 0: %s", length); - Settings settings = new Settings(); - for (int i = 0; i < length; i += 6) { - short id = source.readShort(); - int value = source.readInt(); - - switch (id) { - case 1: // SETTINGS_HEADER_TABLE_SIZE - break; - case 2: // SETTINGS_ENABLE_PUSH - if (value != 0 && value != 1) { - throw ioException("PROTOCOL_ERROR SETTINGS_ENABLE_PUSH != 0 or 1"); - } - break; - case 3: // SETTINGS_MAX_CONCURRENT_STREAMS - id = 4; // Renumbered in draft 10. - break; - case 4: // SETTINGS_INITIAL_WINDOW_SIZE - id = 7; // Renumbered in draft 10. - if (value < 0) { - throw ioException("PROTOCOL_ERROR SETTINGS_INITIAL_WINDOW_SIZE > 2^31 - 1"); - } - break; - case 5: // SETTINGS_MAX_FRAME_SIZE - if (value < INITIAL_MAX_FRAME_SIZE || value > 16777215) { - throw ioException("PROTOCOL_ERROR SETTINGS_MAX_FRAME_SIZE: %s", value); - } - break; - case 6: // SETTINGS_MAX_HEADER_LIST_SIZE - break; // Advisory only, so ignored. - default: - break; // Must ignore setting with unknown id. - } - settings.set(id, 0, value); - } - handler.settings(false, settings); - if (settings.getHeaderTableSize() >= 0) { - hpackReader.headerTableSizeSetting(settings.getHeaderTableSize()); - } - } - - private void readPushPromise(Handler handler, int length, byte flags, int streamId) - throws IOException { - if (streamId == 0) { - throw ioException("PROTOCOL_ERROR: TYPE_PUSH_PROMISE streamId == 0"); - } - short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0; - int promisedStreamId = source.readInt() & 0x7fffffff; - length -= 4; // account for above read. - length = lengthWithoutPadding(length, flags, padding); - List
headerBlock = readHeaderBlock(length, padding, flags, streamId); - handler.pushPromise(streamId, promisedStreamId, headerBlock); - } - - private void readPing(Handler handler, int length, byte flags, int streamId) - throws IOException { - if (length != 8) throw ioException("TYPE_PING length != 8: %s", length); - if (streamId != 0) throw ioException("TYPE_PING streamId != 0"); - int payload1 = source.readInt(); - int payload2 = source.readInt(); - boolean ack = (flags & FLAG_ACK) != 0; - handler.ping(ack, payload1, payload2); - } - - private void readGoAway(Handler handler, int length, byte flags, int streamId) - throws IOException { - if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length); - if (streamId != 0) throw ioException("TYPE_GOAWAY streamId != 0"); - int lastStreamId = source.readInt(); - int errorCodeInt = source.readInt(); - int opaqueDataLength = length - 8; - ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt); - if (errorCode == null) { - throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt); - } - ByteString debugData = EMPTY; - if (opaqueDataLength > 0) { // Must read debug data in order to not corrupt the connection. - debugData = source.readByteString(opaqueDataLength); - } - handler.goAway(lastStreamId, errorCode, debugData); - } - - private void readWindowUpdate(Handler handler, int length, byte flags, int streamId) - throws IOException { - if (length != 4) throw ioException("TYPE_WINDOW_UPDATE length !=4: %s", length); - long increment = (source.readInt() & 0x7fffffffL); - if (increment == 0) throw ioException("windowSizeIncrement was 0", increment); - handler.windowUpdate(streamId, increment); - } - - @Override public void close() throws IOException { - source.close(); - } - } - - static final class Writer implements FrameWriter { - private final BufferedSink sink; - private final boolean client; - private final Buffer hpackBuffer; - private final Hpack.Writer hpackWriter; - private int maxFrameSize; - private boolean closed; - - Writer(BufferedSink sink, boolean client) { - this.sink = sink; - this.client = client; - this.hpackBuffer = new Buffer(); - this.hpackWriter = new Hpack.Writer(hpackBuffer); - this.maxFrameSize = INITIAL_MAX_FRAME_SIZE; - } - - @Override public synchronized void flush() throws IOException { - if (closed) throw new IOException("closed"); - sink.flush(); - } - - @Override public synchronized void ackSettings(Settings peerSettings) throws IOException { - if (closed) throw new IOException("closed"); - this.maxFrameSize = peerSettings.getMaxFrameSize(maxFrameSize); - int length = 0; - byte type = TYPE_SETTINGS; - byte flags = FLAG_ACK; - int streamId = 0; - frameHeader(streamId, length, type, flags); - sink.flush(); - } - - @Override public synchronized void connectionPreface() throws IOException { - if (closed) throw new IOException("closed"); - if (!client) return; // Nothing to write; servers don't send connection headers! - if (logger.isLoggable(FINE)) { - logger.fine(format(">> CONNECTION %s", CONNECTION_PREFACE.hex())); - } - sink.write(CONNECTION_PREFACE.toByteArray()); - sink.flush(); - } - - @Override public synchronized void synStream(boolean outFinished, boolean inFinished, - int streamId, int associatedStreamId, List
headerBlock) - throws IOException { - if (inFinished) throw new UnsupportedOperationException(); - if (closed) throw new IOException("closed"); - headers(outFinished, streamId, headerBlock); - } - - @Override public synchronized void synReply(boolean outFinished, int streamId, - List
headerBlock) throws IOException { - if (closed) throw new IOException("closed"); - headers(outFinished, streamId, headerBlock); - } - - @Override public synchronized void headers(int streamId, List
headerBlock) - throws IOException { - if (closed) throw new IOException("closed"); - headers(false, streamId, headerBlock); - } - - @Override public synchronized void pushPromise(int streamId, int promisedStreamId, - List
requestHeaders) throws IOException { - if (closed) throw new IOException("closed"); - hpackWriter.writeHeaders(requestHeaders); - - long byteCount = hpackBuffer.size(); - int length = (int) Math.min(maxFrameSize - 4, byteCount); - byte type = TYPE_PUSH_PROMISE; - byte flags = byteCount == length ? FLAG_END_HEADERS : 0; - frameHeader(streamId, length + 4, type, flags); - sink.writeInt(promisedStreamId & 0x7fffffff); - sink.write(hpackBuffer, length); - - if (byteCount > length) writeContinuationFrames(streamId, byteCount - length); - } - - void headers(boolean outFinished, int streamId, List
headerBlock) throws IOException { - if (closed) throw new IOException("closed"); - hpackWriter.writeHeaders(headerBlock); - - long byteCount = hpackBuffer.size(); - int length = (int) Math.min(maxFrameSize, byteCount); - byte type = TYPE_HEADERS; - byte flags = byteCount == length ? FLAG_END_HEADERS : 0; - if (outFinished) flags |= FLAG_END_STREAM; - frameHeader(streamId, length, type, flags); - sink.write(hpackBuffer, length); - - if (byteCount > length) writeContinuationFrames(streamId, byteCount - length); - } - - private void writeContinuationFrames(int streamId, long byteCount) throws IOException { - while (byteCount > 0) { - int length = (int) Math.min(maxFrameSize, byteCount); - byteCount -= length; - frameHeader(streamId, length, TYPE_CONTINUATION, byteCount == 0 ? FLAG_END_HEADERS : 0); - sink.write(hpackBuffer, length); - } - } - - @Override public synchronized void rstStream(int streamId, ErrorCode errorCode) - throws IOException { - if (closed) throw new IOException("closed"); - if (errorCode.httpCode == -1) throw new IllegalArgumentException(); - - int length = 4; - byte type = TYPE_RST_STREAM; - byte flags = FLAG_NONE; - frameHeader(streamId, length, type, flags); - sink.writeInt(errorCode.httpCode); - sink.flush(); - } - - @Override public int maxDataLength() { - return maxFrameSize; - } - - @Override public synchronized void data(boolean outFinished, int streamId, Buffer source, - int byteCount) throws IOException { - if (closed) throw new IOException("closed"); - byte flags = FLAG_NONE; - if (outFinished) flags |= FLAG_END_STREAM; - dataFrame(streamId, flags, source, byteCount); - } - - void dataFrame(int streamId, byte flags, Buffer buffer, int byteCount) throws IOException { - byte type = TYPE_DATA; - frameHeader(streamId, byteCount, type, flags); - if (byteCount > 0) { - sink.write(buffer, byteCount); - } - } - - @Override public synchronized void settings(Settings settings) throws IOException { - if (closed) throw new IOException("closed"); - int length = settings.size() * 6; - byte type = TYPE_SETTINGS; - byte flags = FLAG_NONE; - int streamId = 0; - frameHeader(streamId, length, type, flags); - for (int i = 0; i < Settings.COUNT; i++) { - if (!settings.isSet(i)) continue; - int id = i; - if (id == 4) { - id = 3; // SETTINGS_MAX_CONCURRENT_STREAMS renumbered. - } else if (id == 7) { - id = 4; // SETTINGS_INITIAL_WINDOW_SIZE renumbered. - } - sink.writeShort(id); - sink.writeInt(settings.get(i)); - } - sink.flush(); - } - - @Override public synchronized void ping(boolean ack, int payload1, int payload2) - throws IOException { - if (closed) throw new IOException("closed"); - int length = 8; - byte type = TYPE_PING; - byte flags = ack ? FLAG_ACK : FLAG_NONE; - int streamId = 0; - frameHeader(streamId, length, type, flags); - sink.writeInt(payload1); - sink.writeInt(payload2); - sink.flush(); - } - - @Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode, - byte[] debugData) throws IOException { - if (closed) throw new IOException("closed"); - if (errorCode.httpCode == -1) throw illegalArgument("errorCode.httpCode == -1"); - int length = 8 + debugData.length; - byte type = TYPE_GOAWAY; - byte flags = FLAG_NONE; - int streamId = 0; - frameHeader(streamId, length, type, flags); - sink.writeInt(lastGoodStreamId); - sink.writeInt(errorCode.httpCode); - if (debugData.length > 0) { - sink.write(debugData); - } - sink.flush(); - } - - @Override public synchronized void windowUpdate(int streamId, long windowSizeIncrement) - throws IOException { - if (closed) throw new IOException("closed"); - if (windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL) { - throw illegalArgument("windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL: %s", - windowSizeIncrement); - } - int length = 4; - byte type = TYPE_WINDOW_UPDATE; - byte flags = FLAG_NONE; - frameHeader(streamId, length, type, flags); - sink.writeInt((int) windowSizeIncrement); - sink.flush(); - } - - @Override public synchronized void close() throws IOException { - closed = true; - sink.close(); - } - - void frameHeader(int streamId, int length, byte type, byte flags) throws IOException { - if (logger.isLoggable(FINE)) logger.fine(formatHeader(false, streamId, length, type, flags)); - if (length > maxFrameSize) { - throw illegalArgument("FRAME_SIZE_ERROR length > %d: %d", maxFrameSize, length); - } - if ((streamId & 0x80000000) != 0) throw illegalArgument("reserved bit set: %s", streamId); - writeMedium(sink, length); - sink.writeByte(type & 0xff); - sink.writeByte(flags & 0xff); - sink.writeInt(streamId & 0x7fffffff); - } - } - - private static IllegalArgumentException illegalArgument(String message, Object... args) { - throw new IllegalArgumentException(format(message, args)); - } - - private static IOException ioException(String message, Object... args) throws IOException { - throw new IOException(format(message, args)); - } - - /** - * Decompression of the header block occurs above the framing layer. This class lazily reads - * continuation frames as they are needed by {@link Hpack.Reader#readHeaders()}. - */ - static final class ContinuationSource implements Source { - private final BufferedSource source; - - int length; - byte flags; - int streamId; - - int left; - short padding; - - public ContinuationSource(BufferedSource source) { - this.source = source; - } - - @Override public long read(Buffer sink, long byteCount) throws IOException { - while (left == 0) { - source.skip(padding); - padding = 0; - if ((flags & FLAG_END_HEADERS) != 0) return -1; - readContinuationHeader(); - // TODO: test case for empty continuation header? - } - - long read = source.read(sink, Math.min(byteCount, left)); - if (read == -1) return -1; - left -= read; - return read; - } - - @Override public Timeout timeout() { - return source.timeout(); - } - - @Override public void close() throws IOException { - } - - private void readContinuationHeader() throws IOException { - int previousStreamId = streamId; - - length = left = readMedium(source); - byte type = (byte) (source.readByte() & 0xff); - flags = (byte) (source.readByte() & 0xff); - if (logger.isLoggable(FINE)) logger.fine(formatHeader(true, streamId, length, type, flags)); - streamId = (source.readInt() & 0x7fffffff); - if (type != TYPE_CONTINUATION) throw ioException("%s != TYPE_CONTINUATION", type); - if (streamId != previousStreamId) throw ioException("TYPE_CONTINUATION streamId changed"); - } - } - - private static int lengthWithoutPadding(int length, byte flags, short padding) - throws IOException { - if ((flags & FLAG_PADDED) != 0) length--; // Account for reading the padding length. - if (padding > length) { - throw ioException("PROTOCOL_ERROR padding %s > remaining length %s", padding, length); - } - return (short) (length - padding); - } - - /** - * Logs a human-readable representation of HTTP/2 frame headers. - * - *

The format is: - * - *

-   *   direction streamID length type flags
-   * 
- * Where direction is {@code <<} for inbound and {@code >>} for outbound. - * - *

For example, the following would indicate a HEAD request sent from the client. - *

-   * {@code
-   *   << 0x0000000f    12 HEADERS       END_HEADERS|END_STREAM
-   * }
-   * 
- */ - static final class FrameLogger { - - static String formatHeader(boolean inbound, int streamId, int length, byte type, byte flags) { - String formattedType = type < TYPES.length ? TYPES[type] : format("0x%02x", type); - String formattedFlags = formatFlags(type, flags); - return format("%s 0x%08x %5d %-13s %s", inbound ? "<<" : ">>", streamId, length, - formattedType, formattedFlags); - } - - /** - * Looks up valid string representing flags from the table. Invalid combinations are represented - * in binary. - */ - // Visible for testing. - static String formatFlags(byte type, byte flags) { - if (flags == 0) return ""; - switch (type) { // Special case types that have 0 or 1 flag. - case TYPE_SETTINGS: - case TYPE_PING: - return flags == FLAG_ACK ? "ACK" : BINARY[flags]; - case TYPE_PRIORITY: - case TYPE_RST_STREAM: - case TYPE_GOAWAY: - case TYPE_WINDOW_UPDATE: - return BINARY[flags]; - } - String result = flags < FLAGS.length ? FLAGS[flags] : BINARY[flags]; - // Special case types that have overlap flag values. - if (type == TYPE_PUSH_PROMISE && (flags & FLAG_END_PUSH_PROMISE) != 0) { - return result.replace("HEADERS", "PUSH_PROMISE"); // TODO: Avoid allocation. - } else if (type == TYPE_DATA && (flags & FLAG_COMPRESSED) != 0) { - return result.replace("PRIORITY", "COMPRESSED"); // TODO: Avoid allocation. - } - return result; - } - - /** Lookup table for valid frame types. */ - private static final String[] TYPES = new String[] { - "DATA", - "HEADERS", - "PRIORITY", - "RST_STREAM", - "SETTINGS", - "PUSH_PROMISE", - "PING", - "GOAWAY", - "WINDOW_UPDATE", - "CONTINUATION" - }; - - /** - * Lookup table for valid flags for DATA, HEADERS, CONTINUATION. Invalid combinations are - * represented in binary. - */ - private static final String[] FLAGS = new String[0x40]; // Highest bit flag is 0x20. - private static final String[] BINARY = new String[256]; - - static { - for (int i = 0; i < BINARY.length; i++) { - BINARY[i] = format("%8s", Integer.toBinaryString(i)).replace(' ', '0'); - } - - FLAGS[FLAG_NONE] = ""; - FLAGS[FLAG_END_STREAM] = "END_STREAM"; - - int[] prefixFlags = new int[] {FLAG_END_STREAM}; - - FLAGS[FLAG_PADDED] = "PADDED"; - for (int prefixFlag : prefixFlags) { - FLAGS[prefixFlag | FLAG_PADDED] = FLAGS[prefixFlag] + "|PADDED"; - } - - FLAGS[FLAG_END_HEADERS] = "END_HEADERS"; // Same as END_PUSH_PROMISE. - FLAGS[FLAG_PRIORITY] = "PRIORITY"; // Same as FLAG_COMPRESSED. - FLAGS[FLAG_END_HEADERS | FLAG_PRIORITY] = "END_HEADERS|PRIORITY"; // Only valid on HEADERS. - int[] frameFlags = - new int[] {FLAG_END_HEADERS, FLAG_PRIORITY, FLAG_END_HEADERS | FLAG_PRIORITY}; - - for (int frameFlag : frameFlags) { - for (int prefixFlag : prefixFlags) { - FLAGS[prefixFlag | frameFlag] = FLAGS[prefixFlag] + '|' + FLAGS[frameFlag]; - FLAGS[prefixFlag | frameFlag | FLAG_PADDED] = - FLAGS[prefixFlag] + '|' + FLAGS[frameFlag] + "|PADDED"; - } - } - - for (int i = 0; i < FLAGS.length; i++) { // Fill in holes with binary representation. - if (FLAGS[i] == null) FLAGS[i] = BINARY[i]; - } - } - } - - private static int readMedium(BufferedSource source) throws IOException { - return (source.readByte() & 0xff) << 16 - | (source.readByte() & 0xff) << 8 - | (source.readByte() & 0xff); - } - - private static void writeMedium(BufferedSink sink, int i) throws IOException { - sink.writeByte((i >>> 16) & 0xff); - sink.writeByte((i >>> 8) & 0xff); - sink.writeByte(i & 0xff); - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/Huffman.java b/okhttp3/src/main/java/okhttp3/internal/framed/Huffman.java deleted file mode 100644 index 0c7f3012a..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/Huffman.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * This class was originally composed from the following classes in Twitter Hpack. - * - *
    - *
  • {@code com.twitter.hpack.HuffmanEncoder}
  • - *
  • {@code com.twitter.hpack.HuffmanDecoder}
  • - *
  • {@code com.twitter.hpack.HpackUtil}
  • - *
- */ -class Huffman { - - // Appendix C: Huffman Codes - // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-B - private static final int[] CODES = { - 0x1ff8, 0x7fffd8, 0xfffffe2, 0xfffffe3, 0xfffffe4, 0xfffffe5, 0xfffffe6, 0xfffffe7, 0xfffffe8, - 0xffffea, 0x3ffffffc, 0xfffffe9, 0xfffffea, 0x3ffffffd, 0xfffffeb, 0xfffffec, 0xfffffed, - 0xfffffee, 0xfffffef, 0xffffff0, 0xffffff1, 0xffffff2, 0x3ffffffe, 0xffffff3, 0xffffff4, - 0xffffff5, 0xffffff6, 0xffffff7, 0xffffff8, 0xffffff9, 0xffffffa, 0xffffffb, 0x14, 0x3f8, - 0x3f9, 0xffa, 0x1ff9, 0x15, 0xf8, 0x7fa, 0x3fa, 0x3fb, 0xf9, 0x7fb, 0xfa, 0x16, 0x17, 0x18, - 0x0, 0x1, 0x2, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x5c, 0xfb, 0x7ffc, 0x20, 0xffb, - 0x3fc, 0x1ffa, 0x21, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, - 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0xfc, 0x73, 0xfd, 0x1ffb, 0x7fff0, - 0x1ffc, 0x3ffc, 0x22, 0x7ffd, 0x3, 0x23, 0x4, 0x24, 0x5, 0x25, 0x26, 0x27, 0x6, 0x74, 0x75, - 0x28, 0x29, 0x2a, 0x7, 0x2b, 0x76, 0x2c, 0x8, 0x9, 0x2d, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7ffe, - 0x7fc, 0x3ffd, 0x1ffd, 0xffffffc, 0xfffe6, 0x3fffd2, 0xfffe7, 0xfffe8, 0x3fffd3, 0x3fffd4, - 0x3fffd5, 0x7fffd9, 0x3fffd6, 0x7fffda, 0x7fffdb, 0x7fffdc, 0x7fffdd, 0x7fffde, 0xffffeb, - 0x7fffdf, 0xffffec, 0xffffed, 0x3fffd7, 0x7fffe0, 0xffffee, 0x7fffe1, 0x7fffe2, 0x7fffe3, - 0x7fffe4, 0x1fffdc, 0x3fffd8, 0x7fffe5, 0x3fffd9, 0x7fffe6, 0x7fffe7, 0xffffef, 0x3fffda, - 0x1fffdd, 0xfffe9, 0x3fffdb, 0x3fffdc, 0x7fffe8, 0x7fffe9, 0x1fffde, 0x7fffea, 0x3fffdd, - 0x3fffde, 0xfffff0, 0x1fffdf, 0x3fffdf, 0x7fffeb, 0x7fffec, 0x1fffe0, 0x1fffe1, 0x3fffe0, - 0x1fffe2, 0x7fffed, 0x3fffe1, 0x7fffee, 0x7fffef, 0xfffea, 0x3fffe2, 0x3fffe3, 0x3fffe4, - 0x7ffff0, 0x3fffe5, 0x3fffe6, 0x7ffff1, 0x3ffffe0, 0x3ffffe1, 0xfffeb, 0x7fff1, 0x3fffe7, - 0x7ffff2, 0x3fffe8, 0x1ffffec, 0x3ffffe2, 0x3ffffe3, 0x3ffffe4, 0x7ffffde, 0x7ffffdf, - 0x3ffffe5, 0xfffff1, 0x1ffffed, 0x7fff2, 0x1fffe3, 0x3ffffe6, 0x7ffffe0, 0x7ffffe1, 0x3ffffe7, - 0x7ffffe2, 0xfffff2, 0x1fffe4, 0x1fffe5, 0x3ffffe8, 0x3ffffe9, 0xffffffd, 0x7ffffe3, - 0x7ffffe4, 0x7ffffe5, 0xfffec, 0xfffff3, 0xfffed, 0x1fffe6, 0x3fffe9, 0x1fffe7, 0x1fffe8, - 0x7ffff3, 0x3fffea, 0x3fffeb, 0x1ffffee, 0x1ffffef, 0xfffff4, 0xfffff5, 0x3ffffea, 0x7ffff4, - 0x3ffffeb, 0x7ffffe6, 0x3ffffec, 0x3ffffed, 0x7ffffe7, 0x7ffffe8, 0x7ffffe9, 0x7ffffea, - 0x7ffffeb, 0xffffffe, 0x7ffffec, 0x7ffffed, 0x7ffffee, 0x7ffffef, 0x7fffff0, 0x3ffffee - }; - - private static final byte[] CODE_LENGTHS = { - 13, 23, 28, 28, 28, 28, 28, 28, 28, 24, 30, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 30, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 6, 10, 10, 12, 13, 6, 8, 11, 10, 10, 8, 11, 8, 6, 6, 6, 5, - 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 15, 6, 12, 10, 13, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 8, 13, 19, 13, 14, 6, 15, 5, 6, 5, 6, 5, 6, 6, 6, 5, 7, 7, 6, - 6, 6, 5, 6, 7, 6, 5, 5, 6, 7, 7, 7, 7, 7, 15, 11, 14, 13, 28, 20, 22, 20, 20, 22, 22, 22, 23, - 22, 23, 23, 23, 23, 23, 24, 23, 24, 24, 22, 23, 24, 23, 23, 23, 23, 21, 22, 23, 22, 23, 23, - 24, 22, 21, 20, 22, 22, 23, 23, 21, 23, 22, 22, 24, 21, 22, 23, 23, 21, 21, 22, 21, 23, 22, - 23, 23, 20, 22, 22, 22, 23, 22, 22, 23, 26, 26, 20, 19, 22, 23, 22, 25, 26, 26, 26, 27, 27, - 26, 24, 25, 19, 21, 26, 27, 27, 26, 27, 24, 21, 21, 26, 26, 28, 27, 27, 27, 20, 24, 20, 21, - 22, 21, 21, 23, 22, 22, 25, 25, 24, 24, 26, 23, 26, 27, 26, 26, 27, 27, 27, 27, 27, 28, 27, - 27, 27, 27, 27, 26 - }; - - private static final Huffman INSTANCE = new Huffman(); - - public static Huffman get() { - return INSTANCE; - } - - private final Node root = new Node(); - - private Huffman() { - buildTree(); - } - - void encode(byte[] data, OutputStream out) throws IOException { - long current = 0; - int n = 0; - - for (int i = 0; i < data.length; i++) { - int b = data[i] & 0xFF; - int code = CODES[b]; - int nbits = CODE_LENGTHS[b]; - - current <<= nbits; - current |= code; - n += nbits; - - while (n >= 8) { - n -= 8; - out.write(((int) (current >> n))); - } - } - - if (n > 0) { - current <<= (8 - n); - current |= (0xFF >>> n); - out.write((int) current); - } - } - - int encodedLength(byte[] bytes) { - long len = 0; - - for (int i = 0; i < bytes.length; i++) { - int b = bytes[i] & 0xFF; - len += CODE_LENGTHS[b]; - } - - return (int) ((len + 7) >> 3); - } - - byte[] decode(byte[] buf) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - Node node = root; - int current = 0; - int nbits = 0; - for (int i = 0; i < buf.length; i++) { - int b = buf[i] & 0xFF; - current = (current << 8) | b; - nbits += 8; - while (nbits >= 8) { - int c = (current >>> (nbits - 8)) & 0xFF; - node = node.children[c]; - if (node.children == null) { - // terminal node - baos.write(node.symbol); - nbits -= node.terminalBits; - node = root; - } else { - // non-terminal node - nbits -= 8; - } - } - } - - while (nbits > 0) { - int c = (current << (8 - nbits)) & 0xFF; - node = node.children[c]; - if (node.children != null || node.terminalBits > nbits) { - break; - } - baos.write(node.symbol); - nbits -= node.terminalBits; - node = root; - } - - return baos.toByteArray(); - } - - private void buildTree() { - for (int i = 0; i < CODE_LENGTHS.length; i++) { - addCode(i, CODES[i], CODE_LENGTHS[i]); - } - } - - private void addCode(int sym, int code, byte len) { - Node terminal = new Node(sym, len); - - Node current = root; - while (len > 8) { - len -= 8; - int i = ((code >>> len) & 0xFF); - if (current.children == null) { - throw new IllegalStateException("invalid dictionary: prefix not unique"); - } - if (current.children[i] == null) { - current.children[i] = new Node(); - } - current = current.children[i]; - } - - int shift = 8 - len; - int start = (code << shift) & 0xFF; - int end = 1 << shift; - for (int i = start; i < start + end; i++) { - current.children[i] = terminal; - } - } - - private static final class Node { - - // Null if terminal. - private final Node[] children; - - // Terminal nodes have a symbol. - private final int symbol; - - // Number of bits represented in the terminal node. - private final int terminalBits; - - /** Construct an internal node. */ - Node() { - this.children = new Node[256]; - this.symbol = 0; // Not read. - this.terminalBits = 0; // Not read. - } - - /** - * Construct a terminal node. - * - * @param symbol symbol the node represents - * @param bits length of Huffman code in bits - */ - Node(int symbol, int bits) { - this.children = null; - this.symbol = symbol; - int b = bits & 0x07; - this.terminalBits = b == 0 ? 8 : b; - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/NameValueBlockReader.java b/okhttp3/src/main/java/okhttp3/internal/framed/NameValueBlockReader.java deleted file mode 100644 index 75cd9594f..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/NameValueBlockReader.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; -import okio.Buffer; -import okio.BufferedSource; -import okio.ByteString; -import okio.ForwardingSource; -import okio.InflaterSource; -import okio.Okio; -import okio.Source; - -/** - * Reads a SPDY/3 Name/Value header block. This class is made complicated by the requirement that - * we're strict with which bytes we put in the compressed bytes buffer. We need to put all - * compressed bytes into that buffer -- but no other bytes. - */ -class NameValueBlockReader { - /** This source transforms compressed bytes into uncompressed bytes. */ - private final InflaterSource inflaterSource; - - /** - * How many compressed bytes must be read into inflaterSource before {@link #readNameValueBlock} - * returns. - */ - private int compressedLimit; - - /** This source holds inflated bytes. */ - private final BufferedSource source; - - public NameValueBlockReader(BufferedSource source) { - // Limit the inflater input stream to only those bytes in the Name/Value - // block. We cut the inflater off at its source because we can't predict the - // ratio of compressed bytes to uncompressed bytes. - Source throttleSource = new ForwardingSource(source) { - @Override public long read(Buffer sink, long byteCount) throws IOException { - if (compressedLimit == 0) return -1; // Out of data for the current block. - long read = super.read(sink, Math.min(byteCount, compressedLimit)); - if (read == -1) return -1; - compressedLimit -= read; - return read; - } - }; - - // Subclass inflater to install a dictionary when it's needed. - Inflater inflater = new Inflater() { - @Override public int inflate(byte[] buffer, int offset, int count) - throws DataFormatException { - int result = super.inflate(buffer, offset, count); - if (result == 0 && needsDictionary()) { - setDictionary(Spdy3.DICTIONARY); - result = super.inflate(buffer, offset, count); - } - return result; - } - }; - - this.inflaterSource = new InflaterSource(throttleSource, inflater); - this.source = Okio.buffer(inflaterSource); - } - - public List
readNameValueBlock(int length) throws IOException { - this.compressedLimit += length; - - int numberOfPairs = source.readInt(); - if (numberOfPairs < 0) throw new IOException("numberOfPairs < 0: " + numberOfPairs); - if (numberOfPairs > 1024) throw new IOException("numberOfPairs > 1024: " + numberOfPairs); - - List
entries = new ArrayList<>(numberOfPairs); - for (int i = 0; i < numberOfPairs; i++) { - ByteString name = readByteString().toAsciiLowercase(); - ByteString values = readByteString(); - if (name.size() == 0) throw new IOException("name.size == 0"); - entries.add(new Header(name, values)); - } - - doneReading(); - return entries; - } - - private ByteString readByteString() throws IOException { - int length = source.readInt(); - return source.readByteString(length); - } - - private void doneReading() throws IOException { - // Move any outstanding unread bytes into the inflater. One side-effect of - // deflate compression is that sometimes there are bytes remaining in the - // stream after we've consumed all of the content. - if (compressedLimit > 0) { - inflaterSource.refill(); - if (compressedLimit != 0) throw new IOException("compressedLimit > 0: " + compressedLimit); - } - } - - public void close() throws IOException { - source.close(); - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/Ping.java b/okhttp3/src/main/java/okhttp3/internal/framed/Ping.java deleted file mode 100644 index d3e0b428d..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/Ping.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2012 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * A locally-originated ping. - */ -public final class Ping { - private final CountDownLatch latch = new CountDownLatch(1); - private long sent = -1; - private long received = -1; - - Ping() { - } - - void send() { - if (sent != -1) throw new IllegalStateException(); - sent = System.nanoTime(); - } - - void receive() { - if (received != -1 || sent == -1) throw new IllegalStateException(); - received = System.nanoTime(); - latch.countDown(); - } - - void cancel() { - if (received != -1 || sent == -1) throw new IllegalStateException(); - received = sent - 1; - latch.countDown(); - } - - /** - * Returns the round trip time for this ping in nanoseconds, waiting for the response to arrive if - * necessary. Returns -1 if the response was canceled. - */ - public long roundTripTime() throws InterruptedException { - latch.await(); - return received - sent; - } - - /** - * Returns the round trip time for this ping in nanoseconds, or -1 if the response was canceled, - * or -2 if the timeout elapsed before the round trip completed. - */ - public long roundTripTime(long timeout, TimeUnit unit) throws InterruptedException { - if (latch.await(timeout, unit)) { - return received - sent; - } else { - return -2; - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/PushObserver.java b/okhttp3/src/main/java/okhttp3/internal/framed/PushObserver.java deleted file mode 100644 index 95dd41a5b..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/PushObserver.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -import java.io.IOException; -import java.util.List; -import okhttp3.Protocol; -import okio.BufferedSource; - -/** - * {@link Protocol#HTTP_2 HTTP/2} only. Processes server-initiated HTTP requests on the client. - * Implementations must quickly dispatch callbacks to avoid creating a bottleneck. - * - *

While {@link #onReset} may occur at any time, the following callbacks are expected in order, - * correlated by stream ID. - * - *

    - *
  • {@link #onRequest}
  • {@link #onHeaders} (unless canceled) - *
  • {@link #onData} (optional sequence of data frames) - *
- * - *

As a stream ID is scoped to a single HTTP/2 connection, implementations which target multiple - * connections should expect repetition of stream IDs. - * - *

Return true to request cancellation of a pushed stream. Note that this does not guarantee - * future frames won't arrive on the stream ID. - */ -public interface PushObserver { - /** - * Describes the request that the server intends to push a response for. - * - * @param streamId server-initiated stream ID: an even number. - * @param requestHeaders minimally includes {@code :method}, {@code :scheme}, {@code :authority}, - * and (@code :path}. - */ - boolean onRequest(int streamId, List

requestHeaders); - - /** - * The response headers corresponding to a pushed request. When {@code last} is true, there are - * no data frames to follow. - * - * @param streamId server-initiated stream ID: an even number. - * @param responseHeaders minimally includes {@code :status}. - * @param last when true, there is no response data. - */ - boolean onHeaders(int streamId, List
responseHeaders, boolean last); - - /** - * A chunk of response data corresponding to a pushed request. This data must either be read or - * skipped. - * - * @param streamId server-initiated stream ID: an even number. - * @param source location of data corresponding with this stream ID. - * @param byteCount number of bytes to read or skip from the source. - * @param last when true, there are no data frames to follow. - */ - boolean onData(int streamId, BufferedSource source, int byteCount, boolean last) - throws IOException; - - /** Indicates the reason why this stream was canceled. */ - void onReset(int streamId, ErrorCode errorCode); - - PushObserver CANCEL = new PushObserver() { - - @Override public boolean onRequest(int streamId, List
requestHeaders) { - return true; - } - - @Override public boolean onHeaders(int streamId, List
responseHeaders, boolean last) { - return true; - } - - @Override public boolean onData(int streamId, BufferedSource source, int byteCount, - boolean last) throws IOException { - source.skip(byteCount); - return true; - } - - @Override public void onReset(int streamId, ErrorCode errorCode) { - } - }; -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/Settings.java b/okhttp3/src/main/java/okhttp3/internal/framed/Settings.java deleted file mode 100644 index f4eab0173..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/Settings.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2012 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -import java.util.Arrays; - -/** - * Settings describe characteristics of the sending peer, which are used by the receiving peer. - * Settings are {@link FramedConnection connection} scoped. - */ -public final class Settings { - /** - * From the SPDY/3 and HTTP/2 specs, the default initial window size for all streams is 64 KiB. - * (Chrome 25 uses 10 MiB). - */ - static final int DEFAULT_INITIAL_WINDOW_SIZE = 64 * 1024; - - /** Peer request to clear durable settings. */ - static final int FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS = 0x1; - - /** Sent by servers only. The peer requests this setting persisted for future connections. */ - static final int PERSIST_VALUE = 0x1; - /** Sent by clients only. The client is reminding the server of a persisted value. */ - static final int PERSISTED = 0x2; - - /** spdy/3: Sender's estimate of max incoming kbps. */ - static final int UPLOAD_BANDWIDTH = 1; - /** HTTP/2: Size in bytes of the table used to decode the sender's header blocks. */ - static final int HEADER_TABLE_SIZE = 1; - /** spdy/3: Sender's estimate of max outgoing kbps. */ - static final int DOWNLOAD_BANDWIDTH = 2; - /** HTTP/2: The peer must not send a PUSH_PROMISE frame when this is 0. */ - static final int ENABLE_PUSH = 2; - /** spdy/3: Sender's estimate of millis between sending a request and receiving a response. */ - static final int ROUND_TRIP_TIME = 3; - /** Sender's maximum number of concurrent streams. */ - static final int MAX_CONCURRENT_STREAMS = 4; - /** spdy/3: Current CWND in Packets. */ - static final int CURRENT_CWND = 5; - /** HTTP/2: Size in bytes of the largest frame payload the sender will accept. */ - static final int MAX_FRAME_SIZE = 5; - /** spdy/3: Retransmission rate. Percentage */ - static final int DOWNLOAD_RETRANS_RATE = 6; - /** HTTP/2: Advisory only. Size in bytes of the largest header list the sender will accept. */ - static final int MAX_HEADER_LIST_SIZE = 6; - /** Window size in bytes. */ - static final int INITIAL_WINDOW_SIZE = 7; - /** spdy/3: Size of the client certificate vector. Unsupported. */ - static final int CLIENT_CERTIFICATE_VECTOR_SIZE = 8; - /** Flow control options. */ - static final int FLOW_CONTROL_OPTIONS = 10; - - /** Total number of settings. */ - static final int COUNT = 10; - - /** If set, flow control is disabled for streams directed to the sender of these settings. */ - static final int FLOW_CONTROL_OPTIONS_DISABLED = 0x1; - - /** Bitfield of which flags that values. */ - private int set; - - /** Bitfield of flags that have {@link #PERSIST_VALUE}. */ - private int persistValue; - - /** Bitfield of flags that have {@link #PERSISTED}. */ - private int persisted; - - /** Flag values. */ - private final int[] values = new int[COUNT]; - - void clear() { - set = persistValue = persisted = 0; - Arrays.fill(values, 0); - } - - Settings set(int id, int idFlags, int value) { - if (id >= values.length) { - return this; // Discard unknown settings. - } - - int bit = 1 << id; - set |= bit; - if ((idFlags & PERSIST_VALUE) != 0) { - persistValue |= bit; - } else { - persistValue &= ~bit; - } - if ((idFlags & PERSISTED) != 0) { - persisted |= bit; - } else { - persisted &= ~bit; - } - - values[id] = value; - return this; - } - - /** Returns true if a value has been assigned for the setting {@code id}. */ - boolean isSet(int id) { - int bit = 1 << id; - return (set & bit) != 0; - } - - /** Returns the value for the setting {@code id}, or 0 if unset. */ - int get(int id) { - return values[id]; - } - - /** Returns the flags for the setting {@code id}, or 0 if unset. */ - int flags(int id) { - int result = 0; - if (isPersisted(id)) result |= Settings.PERSISTED; - if (persistValue(id)) result |= Settings.PERSIST_VALUE; - return result; - } - - /** Returns the number of settings that have values assigned. */ - int size() { - return Integer.bitCount(set); - } - - /** spdy/3 only. */ - int getUploadBandwidth(int defaultValue) { - int bit = 1 << UPLOAD_BANDWIDTH; - return (bit & set) != 0 ? values[UPLOAD_BANDWIDTH] : defaultValue; - } - - /** HTTP/2 only. Returns -1 if unset. */ - int getHeaderTableSize() { - int bit = 1 << HEADER_TABLE_SIZE; - return (bit & set) != 0 ? values[HEADER_TABLE_SIZE] : -1; - } - - /** spdy/3 only. */ - int getDownloadBandwidth(int defaultValue) { - int bit = 1 << DOWNLOAD_BANDWIDTH; - return (bit & set) != 0 ? values[DOWNLOAD_BANDWIDTH] : defaultValue; - } - - /** HTTP/2 only. */ - // TODO: honor this setting in HTTP/2. - boolean getEnablePush(boolean defaultValue) { - int bit = 1 << ENABLE_PUSH; - return ((bit & set) != 0 ? values[ENABLE_PUSH] : defaultValue ? 1 : 0) == 1; - } - - /** spdy/3 only. */ - int getRoundTripTime(int defaultValue) { - int bit = 1 << ROUND_TRIP_TIME; - return (bit & set) != 0 ? values[ROUND_TRIP_TIME] : defaultValue; - } - - // TODO: honor this setting in spdy/3 and HTTP/2. - int getMaxConcurrentStreams(int defaultValue) { - int bit = 1 << MAX_CONCURRENT_STREAMS; - return (bit & set) != 0 ? values[MAX_CONCURRENT_STREAMS] : defaultValue; - } - - /** spdy/3 only. */ - int getCurrentCwnd(int defaultValue) { - int bit = 1 << CURRENT_CWND; - return (bit & set) != 0 ? values[CURRENT_CWND] : defaultValue; - } - - /** HTTP/2 only. */ - int getMaxFrameSize(int defaultValue) { - int bit = 1 << MAX_FRAME_SIZE; - return (bit & set) != 0 ? values[MAX_FRAME_SIZE] : defaultValue; - } - - /** spdy/3 only. */ - int getDownloadRetransRate(int defaultValue) { - int bit = 1 << DOWNLOAD_RETRANS_RATE; - return (bit & set) != 0 ? values[DOWNLOAD_RETRANS_RATE] : defaultValue; - } - - /** HTTP/2 only. */ - int getMaxHeaderListSize(int defaultValue) { - int bit = 1 << MAX_HEADER_LIST_SIZE; - return (bit & set) != 0 ? values[MAX_HEADER_LIST_SIZE] : defaultValue; - } - - int getInitialWindowSize(int defaultValue) { - int bit = 1 << INITIAL_WINDOW_SIZE; - return (bit & set) != 0 ? values[INITIAL_WINDOW_SIZE] : defaultValue; - } - - /** spdy/3 only. */ - int getClientCertificateVectorSize(int defaultValue) { - int bit = 1 << CLIENT_CERTIFICATE_VECTOR_SIZE; - return (bit & set) != 0 ? values[CLIENT_CERTIFICATE_VECTOR_SIZE] : defaultValue; - } - - // TODO: honor this setting in spdy/3 and HTTP/2. - boolean isFlowControlDisabled() { - int bit = 1 << FLOW_CONTROL_OPTIONS; - int value = (bit & set) != 0 ? values[FLOW_CONTROL_OPTIONS] : 0; - return (value & FLOW_CONTROL_OPTIONS_DISABLED) != 0; - } - - /** - * Returns true if this user agent should use this setting in future spdy/3 connections to the - * same host. - */ - boolean persistValue(int id) { - int bit = 1 << id; - return (persistValue & bit) != 0; - } - - /** Returns true if this setting was persisted. */ - boolean isPersisted(int id) { - int bit = 1 << id; - return (persisted & bit) != 0; - } - - /** - * Writes {@code other} into this. If any setting is populated by this and {@code other}, the - * value and flags from {@code other} will be kept. - */ - void merge(Settings other) { - for (int i = 0; i < COUNT; i++) { - if (!other.isSet(i)) continue; - set(i, other.flags(i), other.get(i)); - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/Spdy3.java b/okhttp3/src/main/java/okhttp3/internal/framed/Spdy3.java deleted file mode 100644 index 72053e006..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/Spdy3.java +++ /dev/null @@ -1,491 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.ProtocolException; -import java.util.List; -import java.util.zip.Deflater; -import okhttp3.Protocol; -import okhttp3.internal.Util; -import okio.Buffer; -import okio.BufferedSink; -import okio.BufferedSource; -import okio.ByteString; -import okio.DeflaterSink; -import okio.Okio; - -/** - * Read and write spdy/3.1 frames. http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1 - */ -public final class Spdy3 implements Variant { - - @Override public Protocol getProtocol() { - return Protocol.SPDY_3; - } - - static final int TYPE_DATA = 0x0; - static final int TYPE_SYN_STREAM = 0x1; - static final int TYPE_SYN_REPLY = 0x2; - static final int TYPE_RST_STREAM = 0x3; - static final int TYPE_SETTINGS = 0x4; - static final int TYPE_PING = 0x6; - static final int TYPE_GOAWAY = 0x7; - static final int TYPE_HEADERS = 0x8; - static final int TYPE_WINDOW_UPDATE = 0x9; - - static final int FLAG_FIN = 0x1; - static final int FLAG_UNIDIRECTIONAL = 0x2; - - static final int VERSION = 3; - - static final byte[] DICTIONARY; - - static { - try { - DICTIONARY = ("\u0000\u0000\u0000\u0007options\u0000\u0000\u0000\u0004hea" - + "d\u0000\u0000\u0000\u0004post\u0000\u0000\u0000\u0003put\u0000\u0000\u0000\u0006dele" - + "te\u0000\u0000\u0000\u0005trace\u0000\u0000\u0000\u0006accept\u0000\u0000\u0000" - + "\u000Eaccept-charset\u0000\u0000\u0000\u000Faccept-encoding\u0000\u0000\u0000\u000Fa" - + "ccept-language\u0000\u0000\u0000\raccept-ranges\u0000\u0000\u0000\u0003age\u0000" - + "\u0000\u0000\u0005allow\u0000\u0000\u0000\rauthorization\u0000\u0000\u0000\rcache-co" - + "ntrol\u0000\u0000\u0000\nconnection\u0000\u0000\u0000\fcontent-base\u0000\u0000" - + "\u0000\u0010content-encoding\u0000\u0000\u0000\u0010content-language\u0000\u0000" - + "\u0000\u000Econtent-length\u0000\u0000\u0000\u0010content-location\u0000\u0000\u0000" - + "\u000Bcontent-md5\u0000\u0000\u0000\rcontent-range\u0000\u0000\u0000\fcontent-type" - + "\u0000\u0000\u0000\u0004date\u0000\u0000\u0000\u0004etag\u0000\u0000\u0000\u0006expe" - + "ct\u0000\u0000\u0000\u0007expires\u0000\u0000\u0000\u0004from\u0000\u0000\u0000" - + "\u0004host\u0000\u0000\u0000\bif-match\u0000\u0000\u0000\u0011if-modified-since" - + "\u0000\u0000\u0000\rif-none-match\u0000\u0000\u0000\bif-range\u0000\u0000\u0000" - + "\u0013if-unmodified-since\u0000\u0000\u0000\rlast-modified\u0000\u0000\u0000\blocati" - + "on\u0000\u0000\u0000\fmax-forwards\u0000\u0000\u0000\u0006pragma\u0000\u0000\u0000" - + "\u0012proxy-authenticate\u0000\u0000\u0000\u0013proxy-authorization\u0000\u0000" - + "\u0000\u0005range\u0000\u0000\u0000\u0007referer\u0000\u0000\u0000\u000Bretry-after" - + "\u0000\u0000\u0000\u0006server\u0000\u0000\u0000\u0002te\u0000\u0000\u0000\u0007trai" - + "ler\u0000\u0000\u0000\u0011transfer-encoding\u0000\u0000\u0000\u0007upgrade\u0000" - + "\u0000\u0000\nuser-agent\u0000\u0000\u0000\u0004vary\u0000\u0000\u0000\u0003via" - + "\u0000\u0000\u0000\u0007warning\u0000\u0000\u0000\u0010www-authenticate\u0000\u0000" - + "\u0000\u0006method\u0000\u0000\u0000\u0003get\u0000\u0000\u0000\u0006status\u0000" - + "\u0000\u0000\u0006200 OK\u0000\u0000\u0000\u0007version\u0000\u0000\u0000\bHTTP/1.1" - + "\u0000\u0000\u0000\u0003url\u0000\u0000\u0000\u0006public\u0000\u0000\u0000\nset-coo" - + "kie\u0000\u0000\u0000\nkeep-alive\u0000\u0000\u0000\u0006origin100101201202205206300" - + "302303304305306307402405406407408409410411412413414415416417502504505203 Non-Authori" - + "tative Information204 No Content301 Moved Permanently400 Bad Request401 Unauthorized" - + "403 Forbidden404 Not Found500 Internal Server Error501 Not Implemented503 Service Un" - + "availableJan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec 00:00:00 Mon, Tue, Wed, Th" - + "u, Fri, Sat, Sun, GMTchunked,text/html,image/png,image/jpg,image/gif,application/xml" - + ",application/xhtml+xml,text/plain,text/javascript,publicprivatemax-age=gzip,deflate," - + "sdchcharset=utf-8charset=iso-8859-1,utf-,*,enq=0.").getBytes(Util.UTF_8.name()); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(); - } - } - - @Override public FrameReader newReader(BufferedSource source, boolean client) { - return new Reader(source, client); - } - - @Override public FrameWriter newWriter(BufferedSink sink, boolean client) { - return new Writer(sink, client); - } - - /** Read spdy/3 frames. */ - static final class Reader implements FrameReader { - private final BufferedSource source; - private final boolean client; - private final NameValueBlockReader headerBlockReader; - - Reader(BufferedSource source, boolean client) { - this.source = source; - this.headerBlockReader = new NameValueBlockReader(this.source); - this.client = client; - } - - @Override public void readConnectionPreface() { - } - - /** - * Send the next frame to {@code handler}. Returns true unless there are no more frames on the - * stream. - */ - @Override public boolean nextFrame(Handler handler) throws IOException { - int w1; - int w2; - try { - w1 = source.readInt(); - w2 = source.readInt(); - } catch (IOException e) { - return false; // This might be a normal socket close. - } - - boolean control = (w1 & 0x80000000) != 0; - int flags = (w2 & 0xff000000) >>> 24; - int length = (w2 & 0xffffff); - - if (control) { - int version = (w1 & 0x7fff0000) >>> 16; - int type = (w1 & 0xffff); - - if (version != 3) { - throw new ProtocolException("version != 3: " + version); - } - - switch (type) { - case TYPE_SYN_STREAM: - readSynStream(handler, flags, length); - return true; - - case TYPE_SYN_REPLY: - readSynReply(handler, flags, length); - return true; - - case TYPE_RST_STREAM: - readRstStream(handler, flags, length); - return true; - - case TYPE_SETTINGS: - readSettings(handler, flags, length); - return true; - - case TYPE_PING: - readPing(handler, flags, length); - return true; - - case TYPE_GOAWAY: - readGoAway(handler, flags, length); - return true; - - case TYPE_HEADERS: - readHeaders(handler, flags, length); - return true; - - case TYPE_WINDOW_UPDATE: - readWindowUpdate(handler, flags, length); - return true; - - default: - source.skip(length); - return true; - } - } else { - int streamId = w1 & 0x7fffffff; - boolean inFinished = (flags & FLAG_FIN) != 0; - handler.data(inFinished, streamId, source, length); - return true; - } - } - - private void readSynStream(Handler handler, int flags, int length) throws IOException { - int w1 = source.readInt(); - int w2 = source.readInt(); - int streamId = w1 & 0x7fffffff; - int associatedStreamId = w2 & 0x7fffffff; - source.readShort(); // int priority = (s3 & 0xe000) >>> 13; int slot = s3 & 0xff; - List
headerBlock = headerBlockReader.readNameValueBlock(length - 10); - - boolean inFinished = (flags & FLAG_FIN) != 0; - boolean outFinished = (flags & FLAG_UNIDIRECTIONAL) != 0; - handler.headers(outFinished, inFinished, streamId, associatedStreamId, headerBlock, - HeadersMode.SPDY_SYN_STREAM); - } - - private void readSynReply(Handler handler, int flags, int length) throws IOException { - int w1 = source.readInt(); - int streamId = w1 & 0x7fffffff; - List
headerBlock = headerBlockReader.readNameValueBlock(length - 4); - boolean inFinished = (flags & FLAG_FIN) != 0; - handler.headers(false, inFinished, streamId, -1, headerBlock, HeadersMode.SPDY_REPLY); - } - - private void readRstStream(Handler handler, int flags, int length) throws IOException { - if (length != 8) throw ioException("TYPE_RST_STREAM length: %d != 8", length); - int streamId = source.readInt() & 0x7fffffff; - int errorCodeInt = source.readInt(); - ErrorCode errorCode = ErrorCode.fromSpdy3Rst(errorCodeInt); - if (errorCode == null) { - throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt); - } - handler.rstStream(streamId, errorCode); - } - - private void readHeaders(Handler handler, int flags, int length) throws IOException { - int w1 = source.readInt(); - int streamId = w1 & 0x7fffffff; - List
headerBlock = headerBlockReader.readNameValueBlock(length - 4); - handler.headers(false, false, streamId, -1, headerBlock, HeadersMode.SPDY_HEADERS); - } - - private void readWindowUpdate(Handler handler, int flags, int length) throws IOException { - if (length != 8) throw ioException("TYPE_WINDOW_UPDATE length: %d != 8", length); - int w1 = source.readInt(); - int w2 = source.readInt(); - int streamId = w1 & 0x7fffffff; - long increment = w2 & 0x7fffffff; - if (increment == 0) throw ioException("windowSizeIncrement was 0", increment); - handler.windowUpdate(streamId, increment); - } - - private void readPing(Handler handler, int flags, int length) throws IOException { - if (length != 4) throw ioException("TYPE_PING length: %d != 4", length); - int id = source.readInt(); - boolean ack = client == ((id & 1) == 1); - handler.ping(ack, id, 0); - } - - private void readGoAway(Handler handler, int flags, int length) throws IOException { - if (length != 8) throw ioException("TYPE_GOAWAY length: %d != 8", length); - int lastGoodStreamId = source.readInt() & 0x7fffffff; - int errorCodeInt = source.readInt(); - ErrorCode errorCode = ErrorCode.fromSpdyGoAway(errorCodeInt); - if (errorCode == null) { - throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt); - } - handler.goAway(lastGoodStreamId, errorCode, ByteString.EMPTY); - } - - private void readSettings(Handler handler, int flags, int length) throws IOException { - int numberOfEntries = source.readInt(); - if (length != 4 + 8 * numberOfEntries) { - throw ioException("TYPE_SETTINGS length: %d != 4 + 8 * %d", length, numberOfEntries); - } - Settings settings = new Settings(); - for (int i = 0; i < numberOfEntries; i++) { - int w1 = source.readInt(); - int value = source.readInt(); - int idFlags = (w1 & 0xff000000) >>> 24; - int id = w1 & 0xffffff; - settings.set(id, idFlags, value); - } - boolean clearPrevious = (flags & Settings.FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) != 0; - handler.settings(clearPrevious, settings); - } - - private static IOException ioException(String message, Object... args) throws IOException { - throw new IOException(String.format(message, args)); - } - - @Override public void close() throws IOException { - headerBlockReader.close(); - } - } - - /** Write spdy/3 frames. */ - static final class Writer implements FrameWriter { - private final BufferedSink sink; - private final Buffer headerBlockBuffer; - private final BufferedSink headerBlockOut; - private final boolean client; - private boolean closed; - - Writer(BufferedSink sink, boolean client) { - this.sink = sink; - this.client = client; - - Deflater deflater = new Deflater(); - deflater.setDictionary(DICTIONARY); - headerBlockBuffer = new Buffer(); - headerBlockOut = Okio.buffer(new DeflaterSink(headerBlockBuffer, deflater)); - } - - @Override public void ackSettings(Settings peerSettings) { - // Do nothing: no ACK for SPDY/3 settings. - } - - @Override - public void pushPromise(int streamId, int promisedStreamId, List
requestHeaders) - throws IOException { - // Do nothing: no push promise for SPDY/3. - } - - @Override public synchronized void connectionPreface() { - // Do nothing: no connection preface for SPDY/3. - } - - @Override public synchronized void flush() throws IOException { - if (closed) throw new IOException("closed"); - sink.flush(); - } - - @Override public synchronized void synStream(boolean outFinished, boolean inFinished, - int streamId, int associatedStreamId, List
headerBlock) - throws IOException { - if (closed) throw new IOException("closed"); - writeNameValueBlockToBuffer(headerBlock); - int length = (int) (10 + headerBlockBuffer.size()); - int type = TYPE_SYN_STREAM; - int flags = (outFinished ? FLAG_FIN : 0) | (inFinished ? FLAG_UNIDIRECTIONAL : 0); - - int unused = 0; - sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff); - sink.writeInt((flags & 0xff) << 24 | length & 0xffffff); - sink.writeInt(streamId & 0x7fffffff); - sink.writeInt(associatedStreamId & 0x7fffffff); - sink.writeShort((unused & 0x7) << 13 | (unused & 0x1f) << 8 | (unused & 0xff)); - sink.writeAll(headerBlockBuffer); - sink.flush(); - } - - @Override public synchronized void synReply(boolean outFinished, int streamId, - List
headerBlock) throws IOException { - if (closed) throw new IOException("closed"); - writeNameValueBlockToBuffer(headerBlock); - int type = TYPE_SYN_REPLY; - int flags = (outFinished ? FLAG_FIN : 0); - int length = (int) (headerBlockBuffer.size() + 4); - - sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff); - sink.writeInt((flags & 0xff) << 24 | length & 0xffffff); - sink.writeInt(streamId & 0x7fffffff); - sink.writeAll(headerBlockBuffer); - sink.flush(); - } - - @Override public synchronized void headers(int streamId, List
headerBlock) - throws IOException { - if (closed) throw new IOException("closed"); - writeNameValueBlockToBuffer(headerBlock); - int flags = 0; - int type = TYPE_HEADERS; - int length = (int) (headerBlockBuffer.size() + 4); - - sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff); - sink.writeInt((flags & 0xff) << 24 | length & 0xffffff); - sink.writeInt(streamId & 0x7fffffff); - sink.writeAll(headerBlockBuffer); - } - - @Override public synchronized void rstStream(int streamId, ErrorCode errorCode) - throws IOException { - if (closed) throw new IOException("closed"); - if (errorCode.spdyRstCode == -1) throw new IllegalArgumentException(); - int flags = 0; - int type = TYPE_RST_STREAM; - int length = 8; - sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff); - sink.writeInt((flags & 0xff) << 24 | length & 0xffffff); - sink.writeInt(streamId & 0x7fffffff); - sink.writeInt(errorCode.spdyRstCode); - sink.flush(); - } - - @Override public int maxDataLength() { - return 16383; - } - - @Override public synchronized void data(boolean outFinished, int streamId, Buffer source, - int byteCount) throws IOException { - int flags = (outFinished ? FLAG_FIN : 0); - sendDataFrame(streamId, flags, source, byteCount); - } - - void sendDataFrame(int streamId, int flags, Buffer buffer, int byteCount) - throws IOException { - if (closed) throw new IOException("closed"); - if (byteCount > 0xffffffL) { - throw new IllegalArgumentException("FRAME_TOO_LARGE max size is 16Mib: " + byteCount); - } - sink.writeInt(streamId & 0x7fffffff); - sink.writeInt((flags & 0xff) << 24 | byteCount & 0xffffff); - if (byteCount > 0) { - sink.write(buffer, byteCount); - } - } - - private void writeNameValueBlockToBuffer(List
headerBlock) throws IOException { - headerBlockOut.writeInt(headerBlock.size()); - for (int i = 0, size = headerBlock.size(); i < size; i++) { - ByteString name = headerBlock.get(i).name; - headerBlockOut.writeInt(name.size()); - headerBlockOut.write(name); - ByteString value = headerBlock.get(i).value; - headerBlockOut.writeInt(value.size()); - headerBlockOut.write(value); - } - headerBlockOut.flush(); - } - - @Override public synchronized void settings(Settings settings) throws IOException { - if (closed) throw new IOException("closed"); - int type = TYPE_SETTINGS; - int flags = 0; - int size = settings.size(); - int length = 4 + size * 8; - sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff); - sink.writeInt((flags & 0xff) << 24 | length & 0xffffff); - sink.writeInt(size); - for (int i = 0; i <= Settings.COUNT; i++) { - if (!settings.isSet(i)) continue; - int settingsFlags = settings.flags(i); - sink.writeInt((settingsFlags & 0xff) << 24 | (i & 0xffffff)); - sink.writeInt(settings.get(i)); - } - sink.flush(); - } - - @Override public synchronized void ping(boolean reply, int payload1, int payload2) - throws IOException { - if (closed) throw new IOException("closed"); - boolean payloadIsReply = client != ((payload1 & 1) == 1); - if (reply != payloadIsReply) throw new IllegalArgumentException("payload != reply"); - int type = TYPE_PING; - int flags = 0; - int length = 4; - sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff); - sink.writeInt((flags & 0xff) << 24 | length & 0xffffff); - sink.writeInt(payload1); - sink.flush(); - } - - @Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode, - byte[] ignored) throws IOException { - if (closed) throw new IOException("closed"); - if (errorCode.spdyGoAwayCode == -1) { - throw new IllegalArgumentException("errorCode.spdyGoAwayCode == -1"); - } - int type = TYPE_GOAWAY; - int flags = 0; - int length = 8; - sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff); - sink.writeInt((flags & 0xff) << 24 | length & 0xffffff); - sink.writeInt(lastGoodStreamId); - sink.writeInt(errorCode.spdyGoAwayCode); - sink.flush(); - } - - @Override public synchronized void windowUpdate(int streamId, long increment) - throws IOException { - if (closed) throw new IOException("closed"); - if (increment == 0 || increment > 0x7fffffffL) { - throw new IllegalArgumentException( - "windowSizeIncrement must be between 1 and 0x7fffffff: " + increment); - } - int type = TYPE_WINDOW_UPDATE; - int flags = 0; - int length = 8; - sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff); - sink.writeInt((flags & 0xff) << 24 | length & 0xffffff); - sink.writeInt(streamId); - sink.writeInt((int) increment); - sink.flush(); - } - - @Override public synchronized void close() throws IOException { - closed = true; - Util.closeAll(sink, headerBlockOut); - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/framed/Variant.java b/okhttp3/src/main/java/okhttp3/internal/framed/Variant.java deleted file mode 100644 index 4ff379400..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/framed/Variant.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.framed; - -import okhttp3.Protocol; -import okio.BufferedSink; -import okio.BufferedSource; - -/** A version and dialect of the framed socket protocol. */ -public interface Variant { - - /** The protocol as selected using ALPN. */ - Protocol getProtocol(); - - /** - * @param client true if this is the HTTP client's reader, reading frames from a server. - */ - FrameReader newReader(BufferedSource source, boolean client); - - /** - * @param client true if this is the HTTP client's writer, writing frames to a server. - */ - FrameWriter newWriter(BufferedSink sink, boolean client); -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/CacheRequest.java b/okhttp3/src/main/java/okhttp3/internal/http/CacheRequest.java deleted file mode 100644 index 12c3134e8..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/CacheRequest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import java.io.IOException; -import okio.Sink; - -public interface CacheRequest { - Sink body() throws IOException; - - void abort(); -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/CacheStrategy.java b/okhttp3/src/main/java/okhttp3/internal/http/CacheStrategy.java deleted file mode 100644 index 940bd68b7..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/CacheStrategy.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import java.util.Date; -import okhttp3.CacheControl; -import okhttp3.Headers; -import okhttp3.Request; -import okhttp3.Response; - -import static java.net.HttpURLConnection.HTTP_BAD_METHOD; -import static java.net.HttpURLConnection.HTTP_GONE; -import static java.net.HttpURLConnection.HTTP_MOVED_PERM; -import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; -import static java.net.HttpURLConnection.HTTP_MULT_CHOICE; -import static java.net.HttpURLConnection.HTTP_NOT_AUTHORITATIVE; -import static java.net.HttpURLConnection.HTTP_NOT_FOUND; -import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED; -import static java.net.HttpURLConnection.HTTP_NO_CONTENT; -import static java.net.HttpURLConnection.HTTP_OK; -import static java.net.HttpURLConnection.HTTP_REQ_TOO_LONG; -import static java.util.concurrent.TimeUnit.SECONDS; - -/** - * Given a request and cached response, this figures out whether to use the network, the cache, or - * both. - * - *

Selecting a cache strategy may add conditions to the request (like the "If-Modified-Since" - * header for conditional GETs) or warnings to the cached response (if the cached data is - * potentially stale). - */ -public final class CacheStrategy { - /** The request to send on the network, or null if this call doesn't use the network. */ - public final Request networkRequest; - - /** The cached response to return or validate; or null if this call doesn't use a cache. */ - public final Response cacheResponse; - - private CacheStrategy(Request networkRequest, Response cacheResponse) { - this.networkRequest = networkRequest; - this.cacheResponse = cacheResponse; - } - - /** Returns true if {@code response} can be stored to later serve another request. */ - public static boolean isCacheable(Response response, Request request) { - // Always go to network for uncacheable response codes (RFC 7231 section 6.1), - // This implementation doesn't support caching partial content. - switch (response.code()) { - case HTTP_OK: - case HTTP_NOT_AUTHORITATIVE: - case HTTP_NO_CONTENT: - case HTTP_MULT_CHOICE: - case HTTP_MOVED_PERM: - case HTTP_NOT_FOUND: - case HTTP_BAD_METHOD: - case HTTP_GONE: - case HTTP_REQ_TOO_LONG: - case HTTP_NOT_IMPLEMENTED: - case StatusLine.HTTP_PERM_REDIRECT: - // These codes can be cached unless headers forbid it. - break; - - case HTTP_MOVED_TEMP: - case StatusLine.HTTP_TEMP_REDIRECT: - // These codes can only be cached with the right response headers. - // http://tools.ietf.org/html/rfc7234#section-3 - // s-maxage is not checked because OkHttp is a private cache that should ignore s-maxage. - if (response.header("Expires") != null - || response.cacheControl().maxAgeSeconds() != -1 - || response.cacheControl().isPublic() - || response.cacheControl().isPrivate()) { - break; - } - // Fall-through. - - default: - // All other codes cannot be cached. - return false; - } - - // A 'no-store' directive on request or response prevents the response from being cached. - return !response.cacheControl().noStore() && !request.cacheControl().noStore(); - } - - public static class Factory { - final long nowMillis; - final Request request; - final Response cacheResponse; - - /** The server's time when the cached response was served, if known. */ - private Date servedDate; - private String servedDateString; - - /** The last modified date of the cached response, if known. */ - private Date lastModified; - private String lastModifiedString; - - /** - * The expiration date of the cached response, if known. If both this field and the max age are - * set, the max age is preferred. - */ - private Date expires; - - /** - * Extension header set by OkHttp specifying the timestamp when the cached HTTP request was - * first initiated. - */ - private long sentRequestMillis; - - /** - * Extension header set by OkHttp specifying the timestamp when the cached HTTP response was - * first received. - */ - private long receivedResponseMillis; - - /** Etag of the cached response. */ - private String etag; - - /** Age of the cached response. */ - private int ageSeconds = -1; - - public Factory(long nowMillis, Request request, Response cacheResponse) { - this.nowMillis = nowMillis; - this.request = request; - this.cacheResponse = cacheResponse; - - if (cacheResponse != null) { - Headers headers = cacheResponse.headers(); - for (int i = 0, size = headers.size(); i < size; i++) { - String fieldName = headers.name(i); - String value = headers.value(i); - if ("Date".equalsIgnoreCase(fieldName)) { - servedDate = HttpDate.parse(value); - servedDateString = value; - } else if ("Expires".equalsIgnoreCase(fieldName)) { - expires = HttpDate.parse(value); - } else if ("Last-Modified".equalsIgnoreCase(fieldName)) { - lastModified = HttpDate.parse(value); - lastModifiedString = value; - } else if ("ETag".equalsIgnoreCase(fieldName)) { - etag = value; - } else if ("Age".equalsIgnoreCase(fieldName)) { - ageSeconds = HeaderParser.parseSeconds(value, -1); - } else if (OkHeaders.SENT_MILLIS.equalsIgnoreCase(fieldName)) { - sentRequestMillis = Long.parseLong(value); - } else if (OkHeaders.RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) { - receivedResponseMillis = Long.parseLong(value); - } - } - } - } - - /** - * Returns a strategy to satisfy {@code request} using the a cached response {@code response}. - */ - public CacheStrategy get() { - CacheStrategy candidate = getCandidate(); - - if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) { - // We're forbidden from using the network and the cache is insufficient. - return new CacheStrategy(null, null); - } - - return candidate; - } - - /** Returns a strategy to use assuming the request can use the network. */ - private CacheStrategy getCandidate() { - // No cached response. - if (cacheResponse == null) { - return new CacheStrategy(request, null); - } - - // Drop the cached response if it's missing a required handshake. - if (request.isHttps() && cacheResponse.handshake() == null) { - return new CacheStrategy(request, null); - } - - // If this response shouldn't have been stored, it should never be used - // as a response source. This check should be redundant as long as the - // persistence store is well-behaved and the rules are constant. - if (!isCacheable(cacheResponse, request)) { - return new CacheStrategy(request, null); - } - - CacheControl requestCaching = request.cacheControl(); - if (requestCaching.noCache() || hasConditions(request)) { - return new CacheStrategy(request, null); - } - - long ageMillis = cacheResponseAge(); - long freshMillis = computeFreshnessLifetime(); - - if (requestCaching.maxAgeSeconds() != -1) { - freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds())); - } - - long minFreshMillis = 0; - if (requestCaching.minFreshSeconds() != -1) { - minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds()); - } - - long maxStaleMillis = 0; - CacheControl responseCaching = cacheResponse.cacheControl(); - if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) { - maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds()); - } - - if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) { - Response.Builder builder = cacheResponse.newBuilder(); - if (ageMillis + minFreshMillis >= freshMillis) { - builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\""); - } - long oneDayMillis = 24 * 60 * 60 * 1000L; - if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) { - builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\""); - } - return new CacheStrategy(null, builder.build()); - } - - Request.Builder conditionalRequestBuilder = request.newBuilder(); - - if (etag != null) { - conditionalRequestBuilder.header("If-None-Match", etag); - } else if (lastModified != null) { - conditionalRequestBuilder.header("If-Modified-Since", lastModifiedString); - } else if (servedDate != null) { - conditionalRequestBuilder.header("If-Modified-Since", servedDateString); - } - - Request conditionalRequest = conditionalRequestBuilder.build(); - return hasConditions(conditionalRequest) - ? new CacheStrategy(conditionalRequest, cacheResponse) - : new CacheStrategy(conditionalRequest, null); - } - - /** - * Returns the number of milliseconds that the response was fresh for, starting from the served - * date. - */ - private long computeFreshnessLifetime() { - CacheControl responseCaching = cacheResponse.cacheControl(); - if (responseCaching.maxAgeSeconds() != -1) { - return SECONDS.toMillis(responseCaching.maxAgeSeconds()); - } else if (expires != null) { - long servedMillis = servedDate != null - ? servedDate.getTime() - : receivedResponseMillis; - long delta = expires.getTime() - servedMillis; - return delta > 0 ? delta : 0; - } else if (lastModified != null - && cacheResponse.request().url().query() == null) { - // As recommended by the HTTP RFC and implemented in Firefox, the - // max age of a document should be defaulted to 10% of the - // document's age at the time it was served. Default expiration - // dates aren't used for URIs containing a query. - long servedMillis = servedDate != null - ? servedDate.getTime() - : sentRequestMillis; - long delta = servedMillis - lastModified.getTime(); - return delta > 0 ? (delta / 10) : 0; - } - return 0; - } - - /** - * Returns the current age of the response, in milliseconds. The calculation is specified by RFC - * 2616, 13.2.3 Age Calculations. - */ - private long cacheResponseAge() { - long apparentReceivedAge = servedDate != null - ? Math.max(0, receivedResponseMillis - servedDate.getTime()) - : 0; - long receivedAge = ageSeconds != -1 - ? Math.max(apparentReceivedAge, SECONDS.toMillis(ageSeconds)) - : apparentReceivedAge; - long responseDuration = receivedResponseMillis - sentRequestMillis; - long residentDuration = nowMillis - receivedResponseMillis; - return receivedAge + responseDuration + residentDuration; - } - - /** - * Returns true if computeFreshnessLifetime used a heuristic. If we used a heuristic to serve a - * cached response older than 24 hours, we are required to attach a warning. - */ - private boolean isFreshnessLifetimeHeuristic() { - return cacheResponse.cacheControl().maxAgeSeconds() == -1 && expires == null; - } - - /** - * Returns true if the request contains conditions that save the server from sending a response - * that the client has locally. When a request is enqueued with its own conditions, the built-in - * response cache won't be used. - */ - private static boolean hasConditions(Request request) { - return request.header("If-Modified-Since") != null || request.header("If-None-Match") != null; - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/HeaderParser.java b/okhttp3/src/main/java/okhttp3/internal/http/HeaderParser.java deleted file mode 100644 index 1e27c988c..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/HeaderParser.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -public final class HeaderParser { - /** - * Returns the next index in {@code input} at or after {@code pos} that contains a character from - * {@code characters}. Returns the input length if none of the requested characters can be found. - */ - public static int skipUntil(String input, int pos, String characters) { - for (; pos < input.length(); pos++) { - if (characters.indexOf(input.charAt(pos)) != -1) { - break; - } - } - return pos; - } - - /** - * Returns the next non-whitespace character in {@code input} that is white space. Result is - * undefined if input contains newline characters. - */ - public static int skipWhitespace(String input, int pos) { - for (; pos < input.length(); pos++) { - char c = input.charAt(pos); - if (c != ' ' && c != '\t') { - break; - } - } - return pos; - } - - /** - * Returns {@code value} as a positive integer, or 0 if it is negative, or {@code defaultValue} if - * it cannot be parsed. - */ - public static int parseSeconds(String value, int defaultValue) { - try { - long seconds = Long.parseLong(value); - if (seconds > Integer.MAX_VALUE) { - return Integer.MAX_VALUE; - } else if (seconds < 0) { - return 0; - } else { - return (int) seconds; - } - } catch (NumberFormatException e) { - return defaultValue; - } - } - - private HeaderParser() { - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/Http1xStream.java b/okhttp3/src/main/java/okhttp3/internal/http/Http1xStream.java deleted file mode 100644 index 320788755..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/Http1xStream.java +++ /dev/null @@ -1,493 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import java.io.EOFException; -import java.io.IOException; -import java.net.ProtocolException; -import okhttp3.Headers; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.internal.Internal; -import okhttp3.internal.Util; -import okhttp3.internal.io.RealConnection; -import okio.Buffer; -import okio.BufferedSink; -import okio.BufferedSource; -import okio.ForwardingTimeout; -import okio.Okio; -import okio.Sink; -import okio.Source; -import okio.Timeout; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static okhttp3.internal.Util.checkOffsetAndCount; -import static okhttp3.internal.http.StatusLine.HTTP_CONTINUE; - -/** - * A socket connection that can be used to send HTTP/1.1 messages. This class strictly enforces the - * following lifecycle: - * - *

    - *
  1. {@linkplain #writeRequest Send request headers}. - *
  2. Open a sink to write the request body. Either {@linkplain #newFixedLengthSink - * fixed-length} or {@link #newChunkedSink chunked}. - *
  3. Write to and then close that sink. - *
  4. {@linkplain #readResponse Read response headers}. - *
  5. Open a source to read the response body. Either {@linkplain #newFixedLengthSource - * fixed-length}, {@linkplain #newChunkedSource chunked} or {@linkplain - * #newUnknownLengthSource unknown length}. - *
  6. Read from and close that source. - *
- * - *

Exchanges that do not have a request body may skip creating and closing the request body. - * Exchanges that do not have a response body can call {@link #newFixedLengthSource(long) - * newFixedLengthSource(0)} and may skip reading and closing that source. - */ -public final class Http1xStream implements HttpStream { - private static final int STATE_IDLE = 0; // Idle connections are ready to write request headers. - private static final int STATE_OPEN_REQUEST_BODY = 1; - private static final int STATE_WRITING_REQUEST_BODY = 2; - private static final int STATE_READ_RESPONSE_HEADERS = 3; - private static final int STATE_OPEN_RESPONSE_BODY = 4; - private static final int STATE_READING_RESPONSE_BODY = 5; - private static final int STATE_CLOSED = 6; - - /** The stream allocation that owns this stream. May be null for HTTPS proxy tunnels. */ - private final StreamAllocation streamAllocation; - private final BufferedSource source; - private final BufferedSink sink; - private HttpEngine httpEngine; - private int state = STATE_IDLE; - - public Http1xStream(StreamAllocation streamAllocation, BufferedSource source, BufferedSink sink) { - this.streamAllocation = streamAllocation; - this.source = source; - this.sink = sink; - } - - @Override public void setHttpEngine(HttpEngine httpEngine) { - this.httpEngine = httpEngine; - } - - @Override public Sink createRequestBody(Request request, long contentLength) throws IOException { - if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) { - // Stream a request body of unknown length. - return newChunkedSink(); - } - - if (contentLength != -1) { - // Stream a request body of a known length. - return newFixedLengthSink(contentLength); - } - - throw new IllegalStateException( - "Cannot stream a request body without chunked encoding or a known content length!"); - } - - @Override public void cancel() { - RealConnection connection = streamAllocation.connection(); - if (connection != null) connection.cancel(); - } - - /** - * Prepares the HTTP headers and sends them to the server. - * - *

For streaming requests with a body, headers must be prepared before the - * output stream has been written to. Otherwise the body would need to be buffered! - * - *

For non-streaming requests with a body, headers must be prepared after the - * output stream has been written to and closed. This ensures that the {@code Content-Length} - * header field receives the proper value. - */ - @Override public void writeRequestHeaders(Request request) throws IOException { - httpEngine.writingRequestHeaders(); - String requestLine = RequestLine.get( - request, httpEngine.getConnection().route().proxy().type()); - writeRequest(request.headers(), requestLine); - } - - @Override public Response.Builder readResponseHeaders() throws IOException { - return readResponse(); - } - - @Override public ResponseBody openResponseBody(Response response) throws IOException { - Source source = getTransferStream(response); - return new RealResponseBody(response.headers(), Okio.buffer(source)); - } - - private Source getTransferStream(Response response) throws IOException { - if (!HttpEngine.hasBody(response)) { - return newFixedLengthSource(0); - } - - if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) { - return newChunkedSource(httpEngine); - } - - long contentLength = OkHeaders.contentLength(response); - if (contentLength != -1) { - return newFixedLengthSource(contentLength); - } - - // Wrap the input stream from the connection (rather than just returning - // "socketIn" directly here), so that we can control its use after the - // reference escapes. - return newUnknownLengthSource(); - } - - /** Returns true if this connection is closed. */ - public boolean isClosed() { - return state == STATE_CLOSED; - } - - @Override public void finishRequest() throws IOException { - sink.flush(); - } - - /** Returns bytes of a request header for sending on an HTTP transport. */ - public void writeRequest(Headers headers, String requestLine) throws IOException { - if (state != STATE_IDLE) throw new IllegalStateException("state: " + state); - sink.writeUtf8(requestLine).writeUtf8("\r\n"); - for (int i = 0, size = headers.size(); i < size; i++) { - sink.writeUtf8(headers.name(i)) - .writeUtf8(": ") - .writeUtf8(headers.value(i)) - .writeUtf8("\r\n"); - } - sink.writeUtf8("\r\n"); - state = STATE_OPEN_REQUEST_BODY; - } - - /** Parses bytes of a response header from an HTTP transport. */ - public Response.Builder readResponse() throws IOException { - if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) { - throw new IllegalStateException("state: " + state); - } - - try { - while (true) { - StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict()); - - Response.Builder responseBuilder = new Response.Builder() - .protocol(statusLine.protocol) - .code(statusLine.code) - .message(statusLine.message) - .headers(readHeaders()); - - if (statusLine.code != HTTP_CONTINUE) { - state = STATE_OPEN_RESPONSE_BODY; - return responseBuilder; - } - } - } catch (EOFException e) { - // Provide more context if the server ends the stream before sending a response. - IOException exception = new IOException("unexpected end of stream on " + streamAllocation); - exception.initCause(e); - throw exception; - } - } - - /** Reads headers or trailers. */ - public Headers readHeaders() throws IOException { - Headers.Builder headers = new Headers.Builder(); - // parse the result headers until the first blank line - for (String line; (line = source.readUtf8LineStrict()).length() != 0; ) { - Internal.instance.addLenient(headers, line); - } - return headers.build(); - } - - public Sink newChunkedSink() { - if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state); - state = STATE_WRITING_REQUEST_BODY; - return new ChunkedSink(); - } - - public Sink newFixedLengthSink(long contentLength) { - if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state); - state = STATE_WRITING_REQUEST_BODY; - return new FixedLengthSink(contentLength); - } - - @Override public void writeRequestBody(RetryableSink requestBody) throws IOException { - if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state); - state = STATE_READ_RESPONSE_HEADERS; - requestBody.writeToSocket(sink); - } - - public Source newFixedLengthSource(long length) throws IOException { - if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state); - state = STATE_READING_RESPONSE_BODY; - return new FixedLengthSource(length); - } - - public Source newChunkedSource(HttpEngine httpEngine) throws IOException { - if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state); - state = STATE_READING_RESPONSE_BODY; - return new ChunkedSource(httpEngine); - } - - public Source newUnknownLengthSource() throws IOException { - if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state); - if (streamAllocation == null) throw new IllegalStateException("streamAllocation == null"); - state = STATE_READING_RESPONSE_BODY; - streamAllocation.noNewStreams(); - return new UnknownLengthSource(); - } - - /** - * Sets the delegate of {@code timeout} to {@link Timeout#NONE} and resets its underlying timeout - * to the default configuration. Use this to avoid unexpected sharing of timeouts between pooled - * connections. - */ - private void detachTimeout(ForwardingTimeout timeout) { - Timeout oldDelegate = timeout.delegate(); - timeout.setDelegate(Timeout.NONE); - oldDelegate.clearDeadline(); - oldDelegate.clearTimeout(); - } - - /** An HTTP body with a fixed length known in advance. */ - private final class FixedLengthSink implements Sink { - private final ForwardingTimeout timeout = new ForwardingTimeout(sink.timeout()); - private boolean closed; - private long bytesRemaining; - - private FixedLengthSink(long bytesRemaining) { - this.bytesRemaining = bytesRemaining; - } - - @Override public Timeout timeout() { - return timeout; - } - - @Override public void write(Buffer source, long byteCount) throws IOException { - if (closed) throw new IllegalStateException("closed"); - checkOffsetAndCount(source.size(), 0, byteCount); - if (byteCount > bytesRemaining) { - throw new ProtocolException("expected " + bytesRemaining - + " bytes but received " + byteCount); - } - sink.write(source, byteCount); - bytesRemaining -= byteCount; - } - - @Override public void flush() throws IOException { - if (closed) return; // Don't throw; this stream might have been closed on the caller's behalf. - sink.flush(); - } - - @Override public void close() throws IOException { - if (closed) return; - closed = true; - if (bytesRemaining > 0) throw new ProtocolException("unexpected end of stream"); - detachTimeout(timeout); - state = STATE_READ_RESPONSE_HEADERS; - } - } - - /** - * An HTTP body with alternating chunk sizes and chunk bodies. It is the caller's responsibility - * to buffer chunks; typically by using a buffered sink with this sink. - */ - private final class ChunkedSink implements Sink { - private final ForwardingTimeout timeout = new ForwardingTimeout(sink.timeout()); - private boolean closed; - - @Override public Timeout timeout() { - return timeout; - } - - @Override public void write(Buffer source, long byteCount) throws IOException { - if (closed) throw new IllegalStateException("closed"); - if (byteCount == 0) return; - - sink.writeHexadecimalUnsignedLong(byteCount); - sink.writeUtf8("\r\n"); - sink.write(source, byteCount); - sink.writeUtf8("\r\n"); - } - - @Override public synchronized void flush() throws IOException { - if (closed) return; // Don't throw; this stream might have been closed on the caller's behalf. - sink.flush(); - } - - @Override public synchronized void close() throws IOException { - if (closed) return; - closed = true; - sink.writeUtf8("0\r\n\r\n"); - detachTimeout(timeout); - state = STATE_READ_RESPONSE_HEADERS; - } - } - - private abstract class AbstractSource implements Source { - protected final ForwardingTimeout timeout = new ForwardingTimeout(source.timeout()); - protected boolean closed; - - @Override public Timeout timeout() { - return timeout; - } - - /** - * Closes the cache entry and makes the socket available for reuse. This should be invoked when - * the end of the body has been reached. - */ - protected final void endOfInput(boolean reuseConnection) throws IOException { - if (state == STATE_CLOSED) return; - if (state != STATE_READING_RESPONSE_BODY) throw new IllegalStateException("state: " + state); - - detachTimeout(timeout); - - state = STATE_CLOSED; - if (streamAllocation != null) { - streamAllocation.streamFinished(!reuseConnection, Http1xStream.this); - } - } - } - - /** An HTTP body with a fixed length specified in advance. */ - private class FixedLengthSource extends AbstractSource { - private long bytesRemaining; - - public FixedLengthSource(long length) throws IOException { - bytesRemaining = length; - if (bytesRemaining == 0) { - endOfInput(true); - } - } - - @Override public long read(Buffer sink, long byteCount) throws IOException { - if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); - if (closed) throw new IllegalStateException("closed"); - if (bytesRemaining == 0) return -1; - - long read = source.read(sink, Math.min(bytesRemaining, byteCount)); - if (read == -1) { - endOfInput(false); // The server didn't supply the promised content length. - throw new ProtocolException("unexpected end of stream"); - } - - bytesRemaining -= read; - if (bytesRemaining == 0) { - endOfInput(true); - } - return read; - } - - @Override public void close() throws IOException { - if (closed) return; - - if (bytesRemaining != 0 && !Util.discard(this, DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) { - endOfInput(false); - } - - closed = true; - } - } - - /** An HTTP body with alternating chunk sizes and chunk bodies. */ - private class ChunkedSource extends AbstractSource { - private static final long NO_CHUNK_YET = -1L; - private long bytesRemainingInChunk = NO_CHUNK_YET; - private boolean hasMoreChunks = true; - private final HttpEngine httpEngine; - - ChunkedSource(HttpEngine httpEngine) throws IOException { - this.httpEngine = httpEngine; - } - - @Override public long read(Buffer sink, long byteCount) throws IOException { - if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); - if (closed) throw new IllegalStateException("closed"); - if (!hasMoreChunks) return -1; - - if (bytesRemainingInChunk == 0 || bytesRemainingInChunk == NO_CHUNK_YET) { - readChunkSize(); - if (!hasMoreChunks) return -1; - } - - long read = source.read(sink, Math.min(byteCount, bytesRemainingInChunk)); - if (read == -1) { - endOfInput(false); // The server didn't supply the promised chunk length. - throw new ProtocolException("unexpected end of stream"); - } - bytesRemainingInChunk -= read; - return read; - } - - private void readChunkSize() throws IOException { - // Read the suffix of the previous chunk. - if (bytesRemainingInChunk != NO_CHUNK_YET) { - source.readUtf8LineStrict(); - } - try { - bytesRemainingInChunk = source.readHexadecimalUnsignedLong(); - String extensions = source.readUtf8LineStrict().trim(); - if (bytesRemainingInChunk < 0 || (!extensions.isEmpty() && !extensions.startsWith(";"))) { - throw new ProtocolException("expected chunk size and optional extensions but was \"" - + bytesRemainingInChunk + extensions + "\""); - } - } catch (NumberFormatException e) { - throw new ProtocolException(e.getMessage()); - } - if (bytesRemainingInChunk == 0L) { - hasMoreChunks = false; - httpEngine.receiveHeaders(readHeaders()); - endOfInput(true); - } - } - - @Override public void close() throws IOException { - if (closed) return; - if (hasMoreChunks && !Util.discard(this, DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) { - endOfInput(false); - } - closed = true; - } - } - - /** An HTTP message body terminated by the end of the underlying stream. */ - private class UnknownLengthSource extends AbstractSource { - private boolean inputExhausted; - - @Override public long read(Buffer sink, long byteCount) - throws IOException { - if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); - if (closed) throw new IllegalStateException("closed"); - if (inputExhausted) return -1; - - long read = source.read(sink, byteCount); - if (read == -1) { - inputExhausted = true; - endOfInput(true); - return -1; - } - return read; - } - - @Override public void close() throws IOException { - if (closed) return; - if (!inputExhausted) { - endOfInput(false); - } - closed = true; - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/Http2xStream.java b/okhttp3/src/main/java/okhttp3/internal/http/Http2xStream.java deleted file mode 100644 index 99151f837..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/Http2xStream.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import java.io.IOException; -import java.net.ProtocolException; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import okhttp3.Headers; -import okhttp3.Protocol; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.internal.Util; -import okhttp3.internal.framed.ErrorCode; -import okhttp3.internal.framed.FramedConnection; -import okhttp3.internal.framed.FramedStream; -import okhttp3.internal.framed.Header; -import okio.ByteString; -import okio.ForwardingSource; -import okio.Okio; -import okio.Sink; -import okio.Source; - -import static okhttp3.internal.framed.Header.RESPONSE_STATUS; -import static okhttp3.internal.framed.Header.TARGET_AUTHORITY; -import static okhttp3.internal.framed.Header.TARGET_HOST; -import static okhttp3.internal.framed.Header.TARGET_METHOD; -import static okhttp3.internal.framed.Header.TARGET_PATH; -import static okhttp3.internal.framed.Header.TARGET_SCHEME; -import static okhttp3.internal.framed.Header.VERSION; - -/** An HTTP stream for HTTP/2 and SPDY. */ -public final class Http2xStream implements HttpStream { - private static final ByteString CONNECTION = ByteString.encodeUtf8("connection"); - private static final ByteString HOST = ByteString.encodeUtf8("host"); - private static final ByteString KEEP_ALIVE = ByteString.encodeUtf8("keep-alive"); - private static final ByteString PROXY_CONNECTION = ByteString.encodeUtf8("proxy-connection"); - private static final ByteString TRANSFER_ENCODING = ByteString.encodeUtf8("transfer-encoding"); - private static final ByteString TE = ByteString.encodeUtf8("te"); - private static final ByteString ENCODING = ByteString.encodeUtf8("encoding"); - private static final ByteString UPGRADE = ByteString.encodeUtf8("upgrade"); - - /** See http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-3.2.1-Request. */ - private static final List SPDY_3_SKIPPED_REQUEST_HEADERS = Util.immutableList( - CONNECTION, - HOST, - KEEP_ALIVE, - PROXY_CONNECTION, - TRANSFER_ENCODING, - TARGET_METHOD, - TARGET_PATH, - TARGET_SCHEME, - TARGET_AUTHORITY, - TARGET_HOST, - VERSION); - private static final List SPDY_3_SKIPPED_RESPONSE_HEADERS = Util.immutableList( - CONNECTION, - HOST, - KEEP_ALIVE, - PROXY_CONNECTION, - TRANSFER_ENCODING); - - /** See http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-8.1.3. */ - private static final List HTTP_2_SKIPPED_REQUEST_HEADERS = Util.immutableList( - CONNECTION, - HOST, - KEEP_ALIVE, - PROXY_CONNECTION, - TE, - TRANSFER_ENCODING, - ENCODING, - UPGRADE, - TARGET_METHOD, - TARGET_PATH, - TARGET_SCHEME, - TARGET_AUTHORITY, - TARGET_HOST, - VERSION); - private static final List HTTP_2_SKIPPED_RESPONSE_HEADERS = Util.immutableList( - CONNECTION, - HOST, - KEEP_ALIVE, - PROXY_CONNECTION, - TE, - TRANSFER_ENCODING, - ENCODING, - UPGRADE); - - private final StreamAllocation streamAllocation; - private final FramedConnection framedConnection; - private HttpEngine httpEngine; - private FramedStream stream; - - public Http2xStream(StreamAllocation streamAllocation, FramedConnection framedConnection) { - this.streamAllocation = streamAllocation; - this.framedConnection = framedConnection; - } - - @Override public void setHttpEngine(HttpEngine httpEngine) { - this.httpEngine = httpEngine; - } - - @Override public Sink createRequestBody(Request request, long contentLength) throws IOException { - return stream.getSink(); - } - - @Override public void writeRequestHeaders(Request request) throws IOException { - if (stream != null) return; - - httpEngine.writingRequestHeaders(); - boolean permitsRequestBody = httpEngine.permitsRequestBody(request); - List

requestHeaders = framedConnection.getProtocol() == Protocol.HTTP_2 - ? http2HeadersList(request) - : spdy3HeadersList(request); - boolean hasResponseBody = true; - stream = framedConnection.newStream(requestHeaders, permitsRequestBody, hasResponseBody); - stream.readTimeout().timeout(httpEngine.client.readTimeoutMillis(), TimeUnit.MILLISECONDS); - stream.writeTimeout().timeout(httpEngine.client.writeTimeoutMillis(), TimeUnit.MILLISECONDS); - } - - @Override public void writeRequestBody(RetryableSink requestBody) throws IOException { - requestBody.writeToSocket(stream.getSink()); - } - - @Override public void finishRequest() throws IOException { - stream.getSink().close(); - } - - @Override public Response.Builder readResponseHeaders() throws IOException { - return framedConnection.getProtocol() == Protocol.HTTP_2 - ? readHttp2HeadersList(stream.getResponseHeaders()) - : readSpdy3HeadersList(stream.getResponseHeaders()); - } - - /** - * Returns a list of alternating names and values containing a SPDY request. Names are all - * lowercase. No names are repeated. If any name has multiple values, they are concatenated using - * "\0" as a delimiter. - */ - public static List
spdy3HeadersList(Request request) { - Headers headers = request.headers(); - List
result = new ArrayList<>(headers.size() + 5); - result.add(new Header(TARGET_METHOD, request.method())); - result.add(new Header(TARGET_PATH, RequestLine.requestPath(request.url()))); - result.add(new Header(VERSION, "HTTP/1.1")); - result.add(new Header(TARGET_HOST, Util.hostHeader(request.url(), false))); - result.add(new Header(TARGET_SCHEME, request.url().scheme())); - - Set names = new LinkedHashSet<>(); - for (int i = 0, size = headers.size(); i < size; i++) { - // header names must be lowercase. - ByteString name = ByteString.encodeUtf8(headers.name(i).toLowerCase(Locale.US)); - - // Drop headers that are forbidden when layering HTTP over SPDY. - if (SPDY_3_SKIPPED_REQUEST_HEADERS.contains(name)) continue; - - // If we haven't seen this name before, add the pair to the end of the list... - String value = headers.value(i); - if (names.add(name)) { - result.add(new Header(name, value)); - continue; - } - - // ...otherwise concatenate the existing values and this value. - for (int j = 0; j < result.size(); j++) { - if (result.get(j).name.equals(name)) { - String concatenated = joinOnNull(result.get(j).value.utf8(), value); - result.set(j, new Header(name, concatenated)); - break; - } - } - } - return result; - } - - private static String joinOnNull(String first, String second) { - return new StringBuilder(first).append('\0').append(second).toString(); - } - - public static List
http2HeadersList(Request request) { - Headers headers = request.headers(); - List
result = new ArrayList<>(headers.size() + 4); - result.add(new Header(TARGET_METHOD, request.method())); - result.add(new Header(TARGET_PATH, RequestLine.requestPath(request.url()))); - result.add(new Header(TARGET_AUTHORITY, Util.hostHeader(request.url(), false))); // Optional. - result.add(new Header(TARGET_SCHEME, request.url().scheme())); - - for (int i = 0, size = headers.size(); i < size; i++) { - // header names must be lowercase. - ByteString name = ByteString.encodeUtf8(headers.name(i).toLowerCase(Locale.US)); - if (!HTTP_2_SKIPPED_REQUEST_HEADERS.contains(name)) { - result.add(new Header(name, headers.value(i))); - } - } - return result; - } - - /** Returns headers for a name value block containing a SPDY response. */ - public static Response.Builder readSpdy3HeadersList(List
headerBlock) throws IOException { - String status = null; - String version = "HTTP/1.1"; - Headers.Builder headersBuilder = new Headers.Builder(); - for (int i = 0, size = headerBlock.size(); i < size; i++) { - ByteString name = headerBlock.get(i).name; - - String values = headerBlock.get(i).value.utf8(); - for (int start = 0; start < values.length(); ) { - int end = values.indexOf('\0', start); - if (end == -1) { - end = values.length(); - } - String value = values.substring(start, end); - if (name.equals(RESPONSE_STATUS)) { - status = value; - } else if (name.equals(VERSION)) { - version = value; - } else if (!SPDY_3_SKIPPED_RESPONSE_HEADERS.contains(name)) { - headersBuilder.add(name.utf8(), value); - } - start = end + 1; - } - } - if (status == null) throw new ProtocolException("Expected ':status' header not present"); - - StatusLine statusLine = StatusLine.parse(version + " " + status); - return new Response.Builder() - .protocol(Protocol.SPDY_3) - .code(statusLine.code) - .message(statusLine.message) - .headers(headersBuilder.build()); - } - - /** Returns headers for a name value block containing an HTTP/2 response. */ - public static Response.Builder readHttp2HeadersList(List
headerBlock) throws IOException { - String status = null; - - Headers.Builder headersBuilder = new Headers.Builder(); - for (int i = 0, size = headerBlock.size(); i < size; i++) { - ByteString name = headerBlock.get(i).name; - - String value = headerBlock.get(i).value.utf8(); - if (name.equals(RESPONSE_STATUS)) { - status = value; - } else if (!HTTP_2_SKIPPED_RESPONSE_HEADERS.contains(name)) { - headersBuilder.add(name.utf8(), value); - } - } - if (status == null) throw new ProtocolException("Expected ':status' header not present"); - - StatusLine statusLine = StatusLine.parse("HTTP/1.1 " + status); - return new Response.Builder() - .protocol(Protocol.HTTP_2) - .code(statusLine.code) - .message(statusLine.message) - .headers(headersBuilder.build()); - } - - @Override public ResponseBody openResponseBody(Response response) throws IOException { - Source source = new StreamFinishingSource(stream.getSource()); - return new RealResponseBody(response.headers(), Okio.buffer(source)); - } - - @Override public void cancel() { - if (stream != null) stream.closeLater(ErrorCode.CANCEL); - } - - class StreamFinishingSource extends ForwardingSource { - public StreamFinishingSource(Source delegate) { - super(delegate); - } - - @Override public void close() throws IOException { - streamAllocation.streamFinished(false, Http2xStream.this); - super.close(); - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/HttpDate.java b/okhttp3/src/main/java/okhttp3/internal/http/HttpDate.java deleted file mode 100644 index 2991dd7b5..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/HttpDate.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import java.text.DateFormat; -import java.text.ParsePosition; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -import static okhttp3.internal.Util.UTC; - -/** - * Best-effort parser for HTTP dates. - */ -public final class HttpDate { - /** The last four-digit year: "Fri, 31 Dec 9999 23:59:59 GMT". */ - public static final long MAX_DATE = 253402300799999L; - - /** - * Most websites serve cookies in the blessed format. Eagerly create the parser to ensure such - * cookies are on the fast path. - */ - private static final ThreadLocal STANDARD_DATE_FORMAT = - new ThreadLocal() { - @Override protected DateFormat initialValue() { - // RFC 2616 specified: RFC 822, updated by RFC 1123 format with fixed GMT. - DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US); - rfc1123.setLenient(false); - rfc1123.setTimeZone(UTC); - return rfc1123; - } - }; - - /** If we fail to parse a date in a non-standard format, try each of these formats in sequence. */ - private static final String[] BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS = new String[] { - // HTTP formats required by RFC2616 but with any timezone. - "EEE, dd MMM yyyy HH:mm:ss zzz", // RFC 822, updated by RFC 1123 with any TZ - "EEEE, dd-MMM-yy HH:mm:ss zzz", // RFC 850, obsoleted by RFC 1036 with any TZ. - "EEE MMM d HH:mm:ss yyyy", // ANSI C's asctime() format - // Alternative formats. - "EEE, dd-MMM-yyyy HH:mm:ss z", - "EEE, dd-MMM-yyyy HH-mm-ss z", - "EEE, dd MMM yy HH:mm:ss z", - "EEE dd-MMM-yyyy HH:mm:ss z", - "EEE dd MMM yyyy HH:mm:ss z", - "EEE dd-MMM-yyyy HH-mm-ss z", - "EEE dd-MMM-yy HH:mm:ss z", - "EEE dd MMM yy HH:mm:ss z", - "EEE,dd-MMM-yy HH:mm:ss z", - "EEE,dd-MMM-yyyy HH:mm:ss z", - "EEE, dd-MM-yyyy HH:mm:ss z", - - /* RI bug 6641315 claims a cookie of this format was once served by www.yahoo.com */ - "EEE MMM d yyyy HH:mm:ss z", - }; - - private static final DateFormat[] BROWSER_COMPATIBLE_DATE_FORMATS = - new DateFormat[BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS.length]; - - /** Returns the date for {@code value}. Returns null if the value couldn't be parsed. */ - public static Date parse(String value) { - if (value.length() == 0) { - return null; - } - - ParsePosition position = new ParsePosition(0); - Date result = STANDARD_DATE_FORMAT.get().parse(value, position); - if (position.getIndex() == value.length()) { - // STANDARD_DATE_FORMAT must match exactly; all text must be consumed, e.g. no ignored - // non-standard trailing "+01:00". Those cases are covered below. - return result; - } - synchronized (BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS) { - for (int i = 0, count = BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS.length; i < count; i++) { - DateFormat format = BROWSER_COMPATIBLE_DATE_FORMATS[i]; - if (format == null) { - format = new SimpleDateFormat(BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS[i], Locale.US); - // Set the timezone to use when interpreting formats that don't have a timezone. GMT is - // specified by RFC 2616. - format.setTimeZone(UTC); - BROWSER_COMPATIBLE_DATE_FORMATS[i] = format; - } - position.setIndex(0); - result = format.parse(value, position); - if (position.getIndex() != 0) { - // Something was parsed. It's possible the entire string was not consumed but we ignore - // that. If any of the BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS ended in "'GMT'" we'd have - // to also check that position.getIndex() == value.length() otherwise parsing might have - // terminated early, ignoring things like "+01:00". Leaving this as != 0 means that any - // trailing junk is ignored. - return result; - } - } - } - return null; - } - - /** Returns the string for {@code value}. */ - public static String format(Date value) { - return STANDARD_DATE_FORMAT.get().format(value); - } - - private HttpDate() { - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/HttpEngine.java b/okhttp3/src/main/java/okhttp3/internal/http/HttpEngine.java deleted file mode 100644 index 7bd65d983..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/HttpEngine.java +++ /dev/null @@ -1,985 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import java.io.IOException; -import java.net.ProtocolException; -import java.net.Proxy; -import java.util.Date; -import java.util.List; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSocketFactory; -import okhttp3.Address; -import okhttp3.CertificatePinner; -import okhttp3.Connection; -import okhttp3.Cookie; -import okhttp3.CookieJar; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.Interceptor; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Protocol; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.Route; -import okhttp3.internal.Internal; -import okhttp3.internal.InternalCache; -import okhttp3.internal.Version; -import okio.Buffer; -import okio.BufferedSink; -import okio.BufferedSource; -import okio.GzipSource; -import okio.Okio; -import okio.Sink; -import okio.Source; -import okio.Timeout; - -import static java.net.HttpURLConnection.HTTP_CLIENT_TIMEOUT; -import static java.net.HttpURLConnection.HTTP_MOVED_PERM; -import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; -import static java.net.HttpURLConnection.HTTP_MULT_CHOICE; -import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED; -import static java.net.HttpURLConnection.HTTP_NO_CONTENT; -import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; -import static java.net.HttpURLConnection.HTTP_SEE_OTHER; -import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static okhttp3.internal.Util.closeQuietly; -import static okhttp3.internal.Util.discard; -import static okhttp3.internal.Util.hostHeader; -import static okhttp3.internal.http.StatusLine.HTTP_CONTINUE; -import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT; -import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT; - -/** - * Handles a single HTTP request/response pair. Each HTTP engine follows this lifecycle:
  1. It - * is created.
  2. The HTTP request message is sent with sendRequest(). Once the request is sent it - * is an error to modify the request headers. After sendRequest() has been called the request body - * can be written to if it exists.
  3. The HTTP response message is read with readResponse(). After - * the response has been read the response headers and body can be read. All responses have a - * response body input stream, though in some instances this stream is empty.
- * - *

The request and response may be served by the HTTP response cache, by the network, or by both - * in the event of a conditional GET. - */ -public final class HttpEngine { - /** - * How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox, - * curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5. - */ - public static final int MAX_FOLLOW_UPS = 20; - - private static final ResponseBody EMPTY_BODY = new ResponseBody() { - @Override public MediaType contentType() { - return null; - } - - @Override public long contentLength() { - return 0; - } - - @Override public BufferedSource source() { - return new Buffer(); - } - }; - - final OkHttpClient client; - - public final StreamAllocation streamAllocation; - private final Response priorResponse; - private HttpStream httpStream; - - /** The time when the request headers were written, or -1 if they haven't been written yet. */ - long sentRequestMillis = -1; - - /** - * True if this client added an "Accept-Encoding: gzip" header field and is therefore responsible - * for also decompressing the transfer stream. - */ - private boolean transparentGzip; - - /** - * True if the request body must be completely buffered before transmission; false if it can be - * streamed. Buffering has two advantages: we don't need the content-length in advance and we can - * retransmit if necessary. The upside of streaming is that we can save memory. - */ - public final boolean bufferRequestBody; - - /** - * The original application-provided request. Never modified by OkHttp. When follow-up requests - * are necessary, they are derived from this request. - */ - private final Request userRequest; - - /** - * The request to send on the network, or null for no network request. This is derived from the - * user request, and customized to support OkHttp features like compression and caching. - */ - private Request networkRequest; - - /** - * The cached response, or null if the cache doesn't exist or cannot be used for this request. - * Conditional caching means this may be non-null even when the network request is non-null. Never - * modified by OkHttp. - */ - private Response cacheResponse; - - /** - * The user-visible response. This is derived from either the network response, cache response, or - * both. It is customized to support OkHttp features like compression and caching. - */ - private Response userResponse; - - private Sink requestBodyOut; - private BufferedSink bufferedRequestBody; - private final boolean callerWritesRequestBody; - private final boolean forWebSocket; - - /** The cache request currently being populated from a network response. */ - private CacheRequest storeRequest; - private CacheStrategy cacheStrategy; - - /** - * @param request the HTTP request without a body. The body must be written via the engine's - * request body stream. - * @param callerWritesRequestBody true for the {@code HttpURLConnection}-style interaction model - * where control flow is returned to the calling application to write the request body before the - * response body is readable. - */ - public HttpEngine(OkHttpClient client, Request request, boolean bufferRequestBody, - boolean callerWritesRequestBody, boolean forWebSocket, StreamAllocation streamAllocation, - RetryableSink requestBodyOut, Response priorResponse) { - this.client = client; - this.userRequest = request; - this.bufferRequestBody = bufferRequestBody; - this.callerWritesRequestBody = callerWritesRequestBody; - this.forWebSocket = forWebSocket; - this.streamAllocation = streamAllocation != null - ? streamAllocation - : new StreamAllocation(client.connectionPool(), createAddress(client, request)); - this.requestBodyOut = requestBodyOut; - this.priorResponse = priorResponse; - } - - /** - * Figures out what the response source will be, and opens a socket to that source if necessary. - * Prepares the request headers and gets ready to start writing the request body if it exists. - * - * @throws RequestException if there was a problem with request setup. Unrecoverable. - * @throws RouteException if the was a problem during connection via a specific route. Sometimes - * recoverable. See {@link #recover}. - * @throws IOException if there was a problem while making a request. Sometimes recoverable. See - * {@link #recover(IOException)}. - */ - public void sendRequest() throws RequestException, RouteException, IOException { - if (cacheStrategy != null) return; // Already sent. - if (httpStream != null) throw new IllegalStateException(); - - Request request = networkRequest(userRequest); - - InternalCache responseCache = Internal.instance.internalCache(client); - Response cacheCandidate = responseCache != null - ? responseCache.get(request) - : null; - - long now = System.currentTimeMillis(); - cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get(); - networkRequest = cacheStrategy.networkRequest; - cacheResponse = cacheStrategy.cacheResponse; - - if (responseCache != null) { - responseCache.trackResponse(cacheStrategy); - } - - if (cacheCandidate != null && cacheResponse == null) { - closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. - } - - // If we're forbidden from using the network and the cache is insufficient, fail. - if (networkRequest == null && cacheResponse == null) { - userResponse = new Response.Builder() - .request(userRequest) - .priorResponse(stripBody(priorResponse)) - .protocol(Protocol.HTTP_1_1) - .code(504) - .message("Unsatisfiable Request (only-if-cached)") - .body(EMPTY_BODY) - .build(); - return; - } - - // If we don't need the network, we're done. - if (networkRequest == null) { - userResponse = cacheResponse.newBuilder() - .request(userRequest) - .priorResponse(stripBody(priorResponse)) - .cacheResponse(stripBody(cacheResponse)) - .build(); - userResponse = unzip(userResponse); - return; - } - - // We need the network to satisfy this request. Possibly for validating a conditional GET. - boolean success = false; - try { - httpStream = connect(); - httpStream.setHttpEngine(this); - - if (writeRequestHeadersEagerly()) { - long contentLength = OkHeaders.contentLength(request); - if (bufferRequestBody) { - if (contentLength > Integer.MAX_VALUE) { - throw new IllegalStateException("Use setFixedLengthStreamingMode() or " - + "setChunkedStreamingMode() for requests larger than 2 GiB."); - } - - if (contentLength != -1) { - // Buffer a request body of a known length. - httpStream.writeRequestHeaders(networkRequest); - requestBodyOut = new RetryableSink((int) contentLength); - } else { - // Buffer a request body of an unknown length. Don't write request headers until the - // entire body is ready; otherwise we can't set the Content-Length header correctly. - requestBodyOut = new RetryableSink(); - } - } else { - httpStream.writeRequestHeaders(networkRequest); - requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength); - } - } - success = true; - } finally { - // If we're crashing on I/O or otherwise, don't leak the cache body. - if (!success && cacheCandidate != null) { - closeQuietly(cacheCandidate.body()); - } - } - } - - /** - * If the caller's control flow writes the request body, we need to create that stream - * immediately. And that means we need to immediately write the request headers, so we can - * start streaming the request body. (We may already have a request body if we're retrying a - * failed POST.) - */ - private boolean writeRequestHeadersEagerly() { - return callerWritesRequestBody - && permitsRequestBody(networkRequest) - && requestBodyOut == null; - } - - private HttpStream connect() throws RouteException, RequestException, IOException { - boolean doExtensiveHealthChecks = !networkRequest.method().equals("GET"); - return streamAllocation.newStream(client.connectTimeoutMillis(), - client.readTimeoutMillis(), client.writeTimeoutMillis(), - client.retryOnConnectionFailure(), doExtensiveHealthChecks); - } - - private static Response stripBody(Response response) { - return response != null && response.body() != null - ? response.newBuilder().body(null).build() - : response; - } - - /** - * Called immediately before the transport transmits HTTP request headers. This is used to observe - * the sent time should the request be cached. - */ - public void writingRequestHeaders() { - if (sentRequestMillis != -1) throw new IllegalStateException(); - sentRequestMillis = System.currentTimeMillis(); - } - - boolean permitsRequestBody(Request request) { - return HttpMethod.permitsRequestBody(request.method()); - } - - /** Returns the request body or null if this request doesn't have a body. */ - public Sink getRequestBody() { - if (cacheStrategy == null) throw new IllegalStateException(); - return requestBodyOut; - } - - public BufferedSink getBufferedRequestBody() { - BufferedSink result = bufferedRequestBody; - if (result != null) return result; - Sink requestBody = getRequestBody(); - return requestBody != null - ? (bufferedRequestBody = Okio.buffer(requestBody)) - : null; - } - - public boolean hasResponse() { - return userResponse != null; - } - - public Request getRequest() { - return userRequest; - } - - /** Returns the engine's response. */ - // TODO: the returned body will always be null. - public Response getResponse() { - if (userResponse == null) throw new IllegalStateException(); - return userResponse; - } - - public Connection getConnection() { - return streamAllocation.connection(); - } - - /** - * Report and attempt to recover from a failure to communicate with a server. Returns a new HTTP - * engine that should be used for the retry if {@code e} is recoverable, or null if the failure is - * permanent. Requests with a body can only be recovered if the body is buffered. - */ - public HttpEngine recover(IOException e, Sink requestBodyOut) { - if (!streamAllocation.recover(e, requestBodyOut)) { - return null; - } - - if (!client.retryOnConnectionFailure()) { - return null; - } - - StreamAllocation streamAllocation = close(); - - // For failure recovery, use the same route selector with a new connection. - return new HttpEngine(client, userRequest, bufferRequestBody, callerWritesRequestBody, - forWebSocket, streamAllocation, (RetryableSink) requestBodyOut, priorResponse); - } - - public HttpEngine recover(IOException e) { - return recover(e, requestBodyOut); - } - - private void maybeCache() throws IOException { - InternalCache responseCache = Internal.instance.internalCache(client); - if (responseCache == null) return; - - // Should we cache this response for this request? - if (!CacheStrategy.isCacheable(userResponse, networkRequest)) { - if (HttpMethod.invalidatesCache(networkRequest.method())) { - try { - responseCache.remove(networkRequest); - } catch (IOException ignored) { - // The cache cannot be written. - } - } - return; - } - - // Offer this request to the cache. - storeRequest = responseCache.put(stripBody(userResponse)); - } - - /** - * Configure the socket connection to be either pooled or closed when it is either exhausted or - * closed. If it is unneeded when this is called, it will be released immediately. - */ - public void releaseStreamAllocation() throws IOException { - streamAllocation.release(); - } - - /** - * Immediately closes the socket connection if it's currently held by this engine. Use this to - * interrupt an in-flight request from any thread. It's the caller's responsibility to close the - * request body and response body streams; otherwise resources may be leaked. - * - *

This method is safe to be called concurrently, but provides limited guarantees. If a - * transport layer connection has been established (such as a HTTP/2 stream) that is terminated. - * Otherwise if a socket connection is being established, that is terminated. - */ - public void cancel() { - streamAllocation.cancel(); - } - - /** - * Release any resources held by this engine. Returns the stream allocation held by this engine, - * which itself must be used or released. - */ - public StreamAllocation close() { - if (bufferedRequestBody != null) { - // This also closes the wrapped requestBodyOut. - closeQuietly(bufferedRequestBody); - } else if (requestBodyOut != null) { - closeQuietly(requestBodyOut); - } - - if (userResponse != null) { - closeQuietly(userResponse.body()); - } else { - // If this engine never achieved a response body, its stream allocation is dead. - streamAllocation.connectionFailed(null); - } - - return streamAllocation; - } - - /** - * Returns a new response that does gzip decompression on {@code response}, if transparent gzip - * was both offered by OkHttp and used by the origin server. - * - *

In addition to decompression, this will also strip the corresponding headers. We strip the - * Content-Encoding header to prevent the application from attempting to double decompress. We - * strip the Content-Length header because it is the length of the compressed content, but the - * application is only interested in the length of the uncompressed content. - * - *

This method should only be used for non-empty response bodies. Response codes like "304 Not - * Modified" can include "Content-Encoding: gzip" without a response body and we will crash if we - * attempt to decompress the zero-byte source. - */ - private Response unzip(final Response response) throws IOException { - if (!transparentGzip || !"gzip".equalsIgnoreCase(userResponse.header("Content-Encoding"))) { - return response; - } - - if (response.body() == null) { - return response; - } - - GzipSource responseBody = new GzipSource(response.body().source()); - Headers strippedHeaders = response.headers().newBuilder() - .removeAll("Content-Encoding") - .removeAll("Content-Length") - .build(); - return response.newBuilder() - .headers(strippedHeaders) - .body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody))) - .build(); - } - - /** - * Returns true if the response must have a (possibly 0-length) body. See RFC 2616 section 4.3. - */ - public static boolean hasBody(Response response) { - // HEAD requests never yield a body regardless of the response headers. - if (response.request().method().equals("HEAD")) { - return false; - } - - int responseCode = response.code(); - if ((responseCode < HTTP_CONTINUE || responseCode >= 200) - && responseCode != HTTP_NO_CONTENT - && responseCode != HTTP_NOT_MODIFIED) { - return true; - } - - // If the Content-Length or Transfer-Encoding headers disagree with the - // response code, the response is malformed. For best compatibility, we - // honor the headers. - if (OkHeaders.contentLength(response) != -1 - || "chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) { - return true; - } - - return false; - } - - /** - * Populates request with defaults and cookies. - * - *

This client doesn't specify a default {@code Accept} header because it doesn't know what - * content types the application is interested in. - */ - private Request networkRequest(Request request) throws IOException { - Request.Builder result = request.newBuilder(); - - if (request.header("Host") == null) { - result.header("Host", hostHeader(request.url(), false)); - } - - if (request.header("Connection") == null) { - result.header("Connection", "Keep-Alive"); - } - - if (request.header("Accept-Encoding") == null) { - transparentGzip = true; - result.header("Accept-Encoding", "gzip"); - } - - List cookies = client.cookieJar().loadForRequest(request.url(), request); - if (!cookies.isEmpty()) { - result.header("Cookie", cookieHeader(cookies)); - } - - if (request.header("User-Agent") == null) { - result.header("User-Agent", Version.userAgent()); - } - - return result.build(); - } - - /** Returns a 'Cookie' HTTP request header with all cookies, like {@code a=b; c=d}. */ - private String cookieHeader(List cookies) { - StringBuilder cookieHeader = new StringBuilder(); - for (int i = 0, size = cookies.size(); i < size; i++) { - if (i > 0) { - cookieHeader.append("; "); - } - Cookie cookie = cookies.get(i); - cookieHeader.append(cookie.name()).append('=').append(cookie.value()); - } - return cookieHeader.toString(); - } - - /** - * Flushes the remaining request header and body, parses the HTTP response headers and starts - * reading the HTTP response body if it exists. - */ - public void readResponse() throws IOException { - if (userResponse != null) { - return; // Already ready. - } - if (networkRequest == null && cacheResponse == null) { - throw new IllegalStateException("call sendRequest() first!"); - } - if (networkRequest == null) { - return; // No network response to read. - } - - Response networkResponse; - - if (forWebSocket) { - httpStream.writeRequestHeaders(networkRequest); - networkResponse = readNetworkResponse(); - } else if (!callerWritesRequestBody) { - networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest); - } else { - // Emit the request body's buffer so that everything is in requestBodyOut. - if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) { - bufferedRequestBody.emit(); - } - - // Emit the request headers if we haven't yet. We might have just learned the Content-Length. - if (sentRequestMillis == -1) { - if (OkHeaders.contentLength(networkRequest) == -1 - && requestBodyOut instanceof RetryableSink) { - long contentLength = ((RetryableSink) requestBodyOut).contentLength(); - networkRequest = networkRequest.newBuilder() - .header("Content-Length", Long.toString(contentLength)) - .build(); - } - httpStream.writeRequestHeaders(networkRequest); - } - - // Write the request body to the socket. - if (requestBodyOut != null) { - if (bufferedRequestBody != null) { - // This also closes the wrapped requestBodyOut. - bufferedRequestBody.close(); - } else { - requestBodyOut.close(); - } - if (requestBodyOut instanceof RetryableSink) { - httpStream.writeRequestBody((RetryableSink) requestBodyOut); - } - } - - networkResponse = readNetworkResponse(); - } - - receiveHeaders(networkResponse.headers()); - - // If we have a cache response too, then we're doing a conditional get. - if (cacheResponse != null) { - if (validate(cacheResponse, networkResponse)) { - userResponse = cacheResponse.newBuilder() - .request(userRequest) - .priorResponse(stripBody(priorResponse)) - .headers(combine(cacheResponse.headers(), networkResponse.headers())) - .cacheResponse(stripBody(cacheResponse)) - .networkResponse(stripBody(networkResponse)) - .build(); - networkResponse.body().close(); - releaseStreamAllocation(); - - // Update the cache after combining headers but before stripping the - // Content-Encoding header (as performed by initContentStream()). - InternalCache responseCache = Internal.instance.internalCache(client); - responseCache.trackConditionalCacheHit(); - responseCache.update(cacheResponse, stripBody(userResponse)); - userResponse = unzip(userResponse); - return; - } else { - closeQuietly(cacheResponse.body()); - } - } - - userResponse = networkResponse.newBuilder() - .request(userRequest) - .priorResponse(stripBody(priorResponse)) - .cacheResponse(stripBody(cacheResponse)) - .networkResponse(stripBody(networkResponse)) - .build(); - - if (hasBody(userResponse)) { - maybeCache(); - userResponse = unzip(cacheWritingResponse(storeRequest, userResponse)); - } - } - - class NetworkInterceptorChain implements Interceptor.Chain { - private final int index; - private final Request request; - private int calls; - - NetworkInterceptorChain(int index, Request request) { - this.index = index; - this.request = request; - } - - @Override public Connection connection() { - return streamAllocation.connection(); - } - - @Override public Request request() { - return request; - } - - @Override public Response proceed(Request request) throws IOException { - calls++; - - if (index > 0) { - Interceptor caller = client.networkInterceptors().get(index - 1); - Address address = connection().route().address(); - - // Confirm that the interceptor uses the connection we've already prepared. - if (!request.url().host().equals(address.url().host()) - || request.url().port() != address.url().port()) { - throw new IllegalStateException("network interceptor " + caller - + " must retain the same host and port"); - } - - // Confirm that this is the interceptor's first call to chain.proceed(). - if (calls > 1) { - throw new IllegalStateException("network interceptor " + caller - + " must call proceed() exactly once"); - } - } - - if (index < client.networkInterceptors().size()) { - // There's another interceptor in the chain. Call that. - NetworkInterceptorChain chain = new NetworkInterceptorChain(index + 1, request); - Interceptor interceptor = client.networkInterceptors().get(index); - Response interceptedResponse = interceptor.intercept(chain); - - // Confirm that the interceptor made the required call to chain.proceed(). - if (chain.calls != 1) { - throw new IllegalStateException("network interceptor " + interceptor - + " must call proceed() exactly once"); - } - if (interceptedResponse == null) { - throw new NullPointerException("network interceptor " + interceptor - + " returned null"); - } - - return interceptedResponse; - } - - httpStream.writeRequestHeaders(request); - - //Update the networkRequest with the possibly updated interceptor request. - networkRequest = request; - - if (permitsRequestBody(request) && request.body() != null) { - Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength()); - BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); - request.body().writeTo(bufferedRequestBody); - bufferedRequestBody.close(); - } - - Response response = readNetworkResponse(); - - int code = response.code(); - if ((code == 204 || code == 205) && response.body().contentLength() > 0) { - throw new ProtocolException( - "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength()); - } - - return response; - } - } - - private Response readNetworkResponse() throws IOException { - httpStream.finishRequest(); - - Response networkResponse = httpStream.readResponseHeaders() - .request(networkRequest) - .handshake(streamAllocation.connection().handshake()) - .header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis)) - .header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis())) - .build(); - - if (!forWebSocket) { - networkResponse = networkResponse.newBuilder() - .body(httpStream.openResponseBody(networkResponse)) - .build(); - } - - if ("close".equalsIgnoreCase(networkResponse.request().header("Connection")) - || "close".equalsIgnoreCase(networkResponse.header("Connection"))) { - streamAllocation.noNewStreams(); - } - - return networkResponse; - } - - /** - * Returns a new source that writes bytes to {@code cacheRequest} as they are read by the source - * consumer. This is careful to discard bytes left over when the stream is closed; otherwise we - * may never exhaust the source stream and therefore not complete the cached response. - */ - private Response cacheWritingResponse(final CacheRequest cacheRequest, Response response) - throws IOException { - // Some apps return a null body; for compatibility we treat that like a null cache request. - if (cacheRequest == null) return response; - Sink cacheBodyUnbuffered = cacheRequest.body(); - if (cacheBodyUnbuffered == null) return response; - - final BufferedSource source = response.body().source(); - final BufferedSink cacheBody = Okio.buffer(cacheBodyUnbuffered); - - Source cacheWritingSource = new Source() { - boolean cacheRequestClosed; - - @Override public long read(Buffer sink, long byteCount) throws IOException { - long bytesRead; - try { - bytesRead = source.read(sink, byteCount); - } catch (IOException e) { - if (!cacheRequestClosed) { - cacheRequestClosed = true; - cacheRequest.abort(); // Failed to write a complete cache response. - } - throw e; - } - - if (bytesRead == -1) { - if (!cacheRequestClosed) { - cacheRequestClosed = true; - cacheBody.close(); // The cache response is complete! - } - return -1; - } - - sink.copyTo(cacheBody.buffer(), sink.size() - bytesRead, bytesRead); - cacheBody.emitCompleteSegments(); - return bytesRead; - } - - @Override public Timeout timeout() { - return source.timeout(); - } - - @Override public void close() throws IOException { - if (!cacheRequestClosed - && !discard(this, HttpStream.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) { - cacheRequestClosed = true; - cacheRequest.abort(); - } - source.close(); - } - }; - - return response.newBuilder() - .body(new RealResponseBody(response.headers(), Okio.buffer(cacheWritingSource))) - .build(); - } - - /** - * Returns true if {@code cached} should be used; false if {@code network} response should be - * used. - */ - private static boolean validate(Response cached, Response network) { - if (network.code() == HTTP_NOT_MODIFIED) { - return true; - } - - // The HTTP spec says that if the network's response is older than our - // cached response, we may return the cache's response. Like Chrome (but - // unlike Firefox), this client prefers to return the newer response. - Date lastModified = cached.headers().getDate("Last-Modified"); - if (lastModified != null) { - Date networkLastModified = network.headers().getDate("Last-Modified"); - if (networkLastModified != null - && networkLastModified.getTime() < lastModified.getTime()) { - return true; - } - } - - return false; - } - - /** - * Combines cached headers with a network headers as defined by RFC 2616, 13.5.3. - */ - private static Headers combine(Headers cachedHeaders, Headers networkHeaders) throws IOException { - Headers.Builder result = new Headers.Builder(); - - for (int i = 0, size = cachedHeaders.size(); i < size; i++) { - String fieldName = cachedHeaders.name(i); - String value = cachedHeaders.value(i); - if ("Warning".equalsIgnoreCase(fieldName) && value.startsWith("1")) { - continue; // Drop 100-level freshness warnings. - } - if (!OkHeaders.isEndToEnd(fieldName) || networkHeaders.get(fieldName) == null) { - result.add(fieldName, value); - } - } - - for (int i = 0, size = networkHeaders.size(); i < size; i++) { - String fieldName = networkHeaders.name(i); - if ("Content-Length".equalsIgnoreCase(fieldName)) { - continue; // Ignore content-length headers of validating responses. - } - if (OkHeaders.isEndToEnd(fieldName)) { - result.add(fieldName, networkHeaders.value(i)); - } - } - - return result.build(); - } - - public void receiveHeaders(Headers headers) throws IOException { - if (client.cookieJar() == CookieJar.NO_COOKIES) return; - - List cookies = Cookie.parseAll(userRequest.url(), headers); - if (cookies.isEmpty()) return; - - client.cookieJar().saveFromResponse(userRequest.url(), cookies); - } - - /** - * Figures out the HTTP request to make in response to receiving this engine's response. This will - * either add authentication headers, follow redirects or handle a client request timeout. If a - * follow-up is either unnecessary or not applicable, this returns null. - */ - public Request followUpRequest() throws IOException { - if (userResponse == null) throw new IllegalStateException(); - Connection connection = streamAllocation.connection(); - Route route = connection != null - ? connection.route() - : null; - int responseCode = userResponse.code(); - - final String method = userRequest.method(); - switch (responseCode) { - case HTTP_PROXY_AUTH: - Proxy selectedProxy = route != null - ? route.proxy() - : client.proxy(); - if (selectedProxy.type() != Proxy.Type.HTTP) { - throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy"); - } - // fall-through - case HTTP_UNAUTHORIZED: - return client.authenticator().authenticate(route, userResponse); - - case HTTP_PERM_REDIRECT: - case HTTP_TEMP_REDIRECT: - // "If the 307 or 308 status code is received in response to a request other than GET - // or HEAD, the user agent MUST NOT automatically redirect the request" - if (!method.equals("GET") && !method.equals("HEAD")) { - return null; - } - // fall-through - case HTTP_MULT_CHOICE: - case HTTP_MOVED_PERM: - case HTTP_MOVED_TEMP: - case HTTP_SEE_OTHER: - // Does the client allow redirects? - if (!client.followRedirects()) return null; - - String location = userResponse.header("Location"); - if (location == null) return null; - HttpUrl url = userRequest.url().resolve(location); - - // Don't follow redirects to unsupported protocols. - if (url == null) return null; - - // If configured, don't follow redirects between SSL and non-SSL. - boolean sameScheme = url.scheme().equals(userRequest.url().scheme()); - if (!sameScheme && !client.followSslRedirects()) return null; - - // Redirects don't include a request body. - Request.Builder requestBuilder = userRequest.newBuilder(); - if (HttpMethod.permitsRequestBody(method)) { - if (HttpMethod.redirectsToGet(method)) { - requestBuilder.method("GET", null); - } else { - requestBuilder.method(method, null); - } - requestBuilder.removeHeader("Transfer-Encoding"); - requestBuilder.removeHeader("Content-Length"); - requestBuilder.removeHeader("Content-Type"); - } - - // When redirecting across hosts, drop all authentication headers. This - // is potentially annoying to the application layer since they have no - // way to retain them. - if (!sameConnection(url)) { - requestBuilder.removeHeader("Authorization"); - } - - return requestBuilder.url(url).build(); - - case HTTP_CLIENT_TIMEOUT: - // 408's are rare in practice, but some servers like HAProxy use this response code. The - // spec says that we may repeat the request without modifications. Modern browsers also - // repeat the request (even non-idempotent ones.) - boolean retryableBody = requestBodyOut == null || requestBodyOut instanceof RetryableSink; - if (callerWritesRequestBody && !retryableBody) { - return null; - } - - return userRequest; - - default: - return null; - } - } - - /** - * Returns true if an HTTP request for {@code followUp} can reuse the connection used by this - * engine. - */ - public boolean sameConnection(HttpUrl followUp) { - HttpUrl url = userRequest.url(); - return url.host().equals(followUp.host()) - && url.port() == followUp.port() - && url.scheme().equals(followUp.scheme()); - } - - private static Address createAddress(OkHttpClient client, Request request) { - SSLSocketFactory sslSocketFactory = null; - HostnameVerifier hostnameVerifier = null; - CertificatePinner certificatePinner = null; - if (request.isHttps()) { - sslSocketFactory = client.sslSocketFactory(); - hostnameVerifier = client.hostnameVerifier(); - certificatePinner = client.certificatePinner(); - } - - return new Address(request.url().host(), request.url().port(), client.dns(), - client.socketFactory(), sslSocketFactory, hostnameVerifier, certificatePinner, - client.proxyAuthenticator(), client.proxy(), client.protocols(), - client.connectionSpecs(), client.proxySelector()); - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/HttpMethod.java b/okhttp3/src/main/java/okhttp3/internal/http/HttpMethod.java deleted file mode 100644 index 3629810c0..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/HttpMethod.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -public final class HttpMethod { - public static boolean invalidatesCache(String method) { - return method.equals("POST") - || method.equals("PATCH") - || method.equals("PUT") - || method.equals("DELETE") - || method.equals("MOVE"); // WebDAV - } - - public static boolean requiresRequestBody(String method) { - return method.equals("POST") - || method.equals("PUT") - || method.equals("PATCH") - || method.equals("PROPPATCH") // WebDAV - || method.equals("REPORT"); // CalDAV/CardDAV (defined in WebDAV Versioning) - } - - public static boolean permitsRequestBody(String method) { - return requiresRequestBody(method) - || method.equals("OPTIONS") - || method.equals("DELETE") // Permitted as spec is ambiguous. - || method.equals("PROPFIND") // (WebDAV) without body: request - || method.equals("MKCOL") // (WebDAV) may contain a body, but behaviour is unspecified - || method.equals("LOCK"); // (WebDAV) body: create lock, without body: refresh lock - } - - public static boolean redirectsToGet(String method) { - // All requests but PROPFIND should redirect to a GET request. - return !method.equals("PROPFIND"); - } - - private HttpMethod() { - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/HttpStream.java b/okhttp3/src/main/java/okhttp3/internal/http/HttpStream.java deleted file mode 100644 index 34c471345..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/HttpStream.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import java.io.IOException; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okio.Sink; - -public interface HttpStream { - /** - * The timeout to use while discarding a stream of input data. Since this is used for connection - * reuse, this timeout should be significantly less than the time it takes to establish a new - * connection. - */ - int DISCARD_STREAM_TIMEOUT_MILLIS = 100; - - /** Returns an output stream where the request body can be streamed. */ - Sink createRequestBody(Request request, long contentLength) throws IOException; - - /** This should update the HTTP engine's sentRequestMillis field. */ - void writeRequestHeaders(Request request) throws IOException; - - /** - * Sends the request body returned by {@link #createRequestBody} to the remote peer. - */ - void writeRequestBody(RetryableSink requestBody) throws IOException; - - /** Flush the request to the underlying socket. */ - void finishRequest() throws IOException; - - /** Read and return response headers. */ - Response.Builder readResponseHeaders() throws IOException; - - /** Returns a stream that reads the response body. */ - ResponseBody openResponseBody(Response response) throws IOException; - - void setHttpEngine(HttpEngine httpEngine); - - /** - * Cancel this stream. Resources held by this stream will be cleaned up, though not synchronously. - * That may happen later by the connection pool thread. - */ - void cancel(); -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/OkHeaders.java b/okhttp3/src/main/java/okhttp3/internal/http/OkHeaders.java deleted file mode 100644 index db8011c54..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/OkHeaders.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import okhttp3.Challenge; -import okhttp3.Headers; -import okhttp3.Protocol; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.internal.Platform; - -import static okhttp3.internal.Util.equal; - -/** Headers and utilities for internal use by OkHttp. */ -public final class OkHeaders { - - static final String PREFIX = Platform.get().getPrefix(); - - /** - * Synthetic response header: the local time when the request was sent. - */ - public static final String SENT_MILLIS = PREFIX + "-Sent-Millis"; - - /** - * Synthetic response header: the local time when the response was received. - */ - public static final String RECEIVED_MILLIS = PREFIX + "-Received-Millis"; - - /** - * Synthetic response header: the selected {@link Protocol protocol} ("spdy/3.1", "http/1.1", - * etc). - */ - public static final String SELECTED_PROTOCOL = PREFIX + "-Selected-Protocol"; - - /** Synthetic response header: the location from which the response was loaded. */ - public static final String RESPONSE_SOURCE = PREFIX + "-Response-Source"; - - private OkHeaders() { - } - - public static long contentLength(Request request) { - return contentLength(request.headers()); - } - - public static long contentLength(Response response) { - return contentLength(response.headers()); - } - - public static long contentLength(Headers headers) { - return stringToLong(headers.get("Content-Length")); - } - - private static long stringToLong(String s) { - if (s == null) return -1; - try { - return Long.parseLong(s); - } catch (NumberFormatException e) { - return -1; - } - } - - /** - * Returns true if none of the Vary headers have changed between {@code cachedRequest} and {@code - * newRequest}. - */ - public static boolean varyMatches( - Response cachedResponse, Headers cachedRequest, Request newRequest) { - for (String field : varyFields(cachedResponse)) { - if (!equal(cachedRequest.values(field), newRequest.headers(field))) return false; - } - return true; - } - - /** - * Returns true if a Vary header contains an asterisk. Such responses cannot be cached. - */ - public static boolean hasVaryAll(Response response) { - return hasVaryAll(response.headers()); - } - - /** - * Returns true if a Vary header contains an asterisk. Such responses cannot be cached. - */ - public static boolean hasVaryAll(Headers responseHeaders) { - return varyFields(responseHeaders).contains("*"); - } - - private static Set varyFields(Response response) { - return varyFields(response.headers()); - } - - /** - * Returns the names of the request headers that need to be checked for equality when caching. - */ - public static Set varyFields(Headers responseHeaders) { - Set result = Collections.emptySet(); - for (int i = 0, size = responseHeaders.size(); i < size; i++) { - if (!"Vary".equalsIgnoreCase(responseHeaders.name(i))) continue; - - String value = responseHeaders.value(i); - if (result.isEmpty()) { - result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - } - for (String varyField : value.split(",")) { - result.add(varyField.trim()); - } - } - return result; - } - - /** - * Returns the subset of the headers in {@code response}'s request that impact the content of - * response's body. - */ - public static Headers varyHeaders(Response response) { - // Use the request headers sent over the network, since that's what the - // response varies on. Otherwise OkHttp-supplied headers like - // "Accept-Encoding: gzip" may be lost. - Headers requestHeaders = response.networkResponse().request().headers(); - Headers responseHeaders = response.headers(); - return varyHeaders(requestHeaders, responseHeaders); - } - - /** - * Returns the subset of the headers in {@code requestHeaders} that impact the content of - * response's body. - */ - public static Headers varyHeaders(Headers requestHeaders, Headers responseHeaders) { - Set varyFields = varyFields(responseHeaders); - if (varyFields.isEmpty()) return new Headers.Builder().build(); - - Headers.Builder result = new Headers.Builder(); - for (int i = 0, size = requestHeaders.size(); i < size; i++) { - String fieldName = requestHeaders.name(i); - if (varyFields.contains(fieldName)) { - result.add(fieldName, requestHeaders.value(i)); - } - } - return result.build(); - } - - /** - * Returns true if {@code fieldName} is an end-to-end HTTP header, as defined by RFC 2616, - * 13.5.1. - */ - static boolean isEndToEnd(String fieldName) { - return !"Connection".equalsIgnoreCase(fieldName) - && !"Keep-Alive".equalsIgnoreCase(fieldName) - && !"Proxy-Authenticate".equalsIgnoreCase(fieldName) - && !"Proxy-Authorization".equalsIgnoreCase(fieldName) - && !"TE".equalsIgnoreCase(fieldName) - && !"Trailers".equalsIgnoreCase(fieldName) - && !"Transfer-Encoding".equalsIgnoreCase(fieldName) - && !"Upgrade".equalsIgnoreCase(fieldName); - } - - /** - * Parse RFC 2617 challenges. This API is only interested in the scheme name and realm. - */ - public static List parseChallenges(Headers responseHeaders, String challengeHeader) { - // auth-scheme = token - // auth-param = token "=" ( token | quoted-string ) - // challenge = auth-scheme 1*SP 1#auth-param - // realm = "realm" "=" realm-value - // realm-value = quoted-string - List result = new ArrayList<>(); - for (int i = 0, size = responseHeaders.size(); i < size; i++) { - if (!challengeHeader.equalsIgnoreCase(responseHeaders.name(i))) { - continue; - } - String value = responseHeaders.value(i); - int pos = 0; - while (pos < value.length()) { - int tokenStart = pos; - pos = HeaderParser.skipUntil(value, pos, " "); - - String scheme = value.substring(tokenStart, pos).trim(); - pos = HeaderParser.skipWhitespace(value, pos); - - // TODO: This currently only handles schemes with a 'realm' parameter; - // It needs to be fixed to handle any scheme and any parameters - // http://code.google.com/p/android/issues/detail?id=11140 - - if (!value.regionMatches(true, pos, "realm=\"", 0, "realm=\"".length())) { - break; // Unexpected challenge parameter; give up! - } - - pos += "realm=\"".length(); - int realmStart = pos; - pos = HeaderParser.skipUntil(value, pos, "\""); - String realm = value.substring(realmStart, pos); - pos++; // Consume '"' close quote. - pos = HeaderParser.skipUntil(value, pos, ","); - pos++; // Consume ',' comma. - pos = HeaderParser.skipWhitespace(value, pos); - result.add(new Challenge(scheme, realm)); - } - } - return result; - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/RealResponseBody.java b/okhttp3/src/main/java/okhttp3/internal/http/RealResponseBody.java deleted file mode 100644 index d66fed988..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/RealResponseBody.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import okhttp3.Headers; -import okhttp3.MediaType; -import okhttp3.ResponseBody; -import okio.BufferedSource; - -public final class RealResponseBody extends ResponseBody { - private final Headers headers; - private final BufferedSource source; - - public RealResponseBody(Headers headers, BufferedSource source) { - this.headers = headers; - this.source = source; - } - - @Override public MediaType contentType() { - String contentType = headers.get("Content-Type"); - return contentType != null ? MediaType.parse(contentType) : null; - } - - @Override public long contentLength() { - return OkHeaders.contentLength(headers); - } - - @Override public BufferedSource source() { - return source; - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/RequestException.java b/okhttp3/src/main/java/okhttp3/internal/http/RequestException.java deleted file mode 100644 index 56884c811..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/RequestException.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2015 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import java.io.IOException; - -/** - * Indicates a problem with interpreting a request. It may indicate there was a problem with the - * request itself, or the environment being used to interpret the request (network failure, etc.). - */ -public final class RequestException extends Exception { - - public RequestException(IOException cause) { - super(cause); - } - - @Override - public IOException getCause() { - return (IOException) super.getCause(); - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/RequestLine.java b/okhttp3/src/main/java/okhttp3/internal/http/RequestLine.java deleted file mode 100644 index c70a86934..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/RequestLine.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import java.net.HttpURLConnection; -import java.net.Proxy; -import okhttp3.HttpUrl; -import okhttp3.Request; - -public final class RequestLine { - private RequestLine() { - } - - /** - * Returns the request status line, like "GET / HTTP/1.1". This is exposed to the application by - * {@link HttpURLConnection#getHeaderFields}, so it needs to be set even if the transport is - * SPDY. - */ - static String get(Request request, Proxy.Type proxyType) { - StringBuilder result = new StringBuilder(); - result.append(request.method()); - result.append(' '); - - if (includeAuthorityInRequestLine(request, proxyType)) { - result.append(request.url()); - } else { - result.append(requestPath(request.url())); - } - - result.append(" HTTP/1.1"); - return result.toString(); - } - - /** - * Returns true if the request line should contain the full URL with host and port (like "GET - * http://android.com/foo HTTP/1.1") or only the path (like "GET /foo HTTP/1.1"). - */ - private static boolean includeAuthorityInRequestLine(Request request, Proxy.Type proxyType) { - return !request.isHttps() && proxyType == Proxy.Type.HTTP; - } - - /** - * Returns the path to request, like the '/' in 'GET / HTTP/1.1'. Never empty, even if the request - * URL is. Includes the query component if it exists. - */ - public static String requestPath(HttpUrl url) { - String path = url.encodedPath(); - String query = url.encodedQuery(); - return query != null ? (path + '?' + query) : path; - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/RetryableSink.java b/okhttp3/src/main/java/okhttp3/internal/http/RetryableSink.java deleted file mode 100644 index c0010d189..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/RetryableSink.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import java.io.IOException; -import java.net.ProtocolException; -import okio.Buffer; -import okio.Sink; -import okio.Timeout; - -import static okhttp3.internal.Util.checkOffsetAndCount; - -/** - * An HTTP request body that's completely buffered in memory. This allows the post body to be - * transparently re-sent if the HTTP request must be sent multiple times. - */ -public final class RetryableSink implements Sink { - private boolean closed; - private final int limit; - private final Buffer content = new Buffer(); - - public RetryableSink(int limit) { - this.limit = limit; - } - - public RetryableSink() { - this(-1); - } - - @Override public void close() throws IOException { - if (closed) return; - closed = true; - if (content.size() < limit) { - throw new ProtocolException( - "content-length promised " + limit + " bytes, but received " + content.size()); - } - } - - @Override public void write(Buffer source, long byteCount) throws IOException { - if (closed) throw new IllegalStateException("closed"); - checkOffsetAndCount(source.size(), 0, byteCount); - if (limit != -1 && content.size() > limit - byteCount) { - throw new ProtocolException("exceeded content-length limit of " + limit + " bytes"); - } - content.write(source, byteCount); - } - - @Override public void flush() throws IOException { - } - - @Override public Timeout timeout() { - return Timeout.NONE; - } - - public long contentLength() throws IOException { - return content.size(); - } - - public void writeToSocket(Sink socketOut) throws IOException { - // Copy the content; otherwise we won't have data to retry. - Buffer buffer = new Buffer(); - content.copyTo(buffer, 0, content.size()); - socketOut.write(buffer, buffer.size()); - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/RouteException.java b/okhttp3/src/main/java/okhttp3/internal/http/RouteException.java deleted file mode 100644 index 030b346a5..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/RouteException.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2015 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -/** - * An exception thrown to indicate a problem connecting via a single Route. Multiple attempts may - * have been made with alternative protocols, none of which were successful. - */ -public final class RouteException extends Exception { - private static final Method addSuppressedExceptionMethod; - - static { - Method m; - try { - m = Throwable.class.getDeclaredMethod("addSuppressed", Throwable.class); - } catch (Exception e) { - m = null; - } - addSuppressedExceptionMethod = m; - } - - private IOException lastException; - - public RouteException(IOException cause) { - super(cause); - lastException = cause; - } - - public IOException getLastConnectException() { - return lastException; - } - - public void addConnectException(IOException e) { - addSuppressedIfPossible(e, lastException); - lastException = e; - } - - private void addSuppressedIfPossible(IOException e, IOException suppressed) { - if (addSuppressedExceptionMethod != null) { - try { - addSuppressedExceptionMethod.invoke(e, suppressed); - } catch (InvocationTargetException | IllegalAccessException ignored) { - } - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/RouteSelector.java b/okhttp3/src/main/java/okhttp3/internal/http/RouteSelector.java deleted file mode 100644 index 3b5725cbe..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/RouteSelector.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2012 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.SocketAddress; -import java.net.SocketException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.NoSuchElementException; -import okhttp3.Address; -import okhttp3.HttpUrl; -import okhttp3.Route; -import okhttp3.internal.RouteDatabase; - -/** - * Selects routes to connect to an origin server. Each connection requires a choice of proxy server, - * IP address, and TLS mode. Connections may also be recycled. - */ -public final class RouteSelector { - private final Address address; - private final RouteDatabase routeDatabase; - - /* The most recently attempted route. */ - private Proxy lastProxy; - private InetSocketAddress lastInetSocketAddress; - - /* State for negotiating the next proxy to use. */ - private List proxies = Collections.emptyList(); - private int nextProxyIndex; - - /* State for negotiating the next socket address to use. */ - private List inetSocketAddresses = Collections.emptyList(); - private int nextInetSocketAddressIndex; - - /* State for negotiating failed routes */ - private final List postponedRoutes = new ArrayList<>(); - - public RouteSelector(Address address, RouteDatabase routeDatabase) { - this.address = address; - this.routeDatabase = routeDatabase; - - resetNextProxy(address.url(), address.proxy()); - } - - /** - * Returns true if there's another route to attempt. Every address has at least one route. - */ - public boolean hasNext() { - return hasNextInetSocketAddress() - || hasNextProxy() - || hasNextPostponed(); - } - - public Route next() throws IOException { - // Compute the next route to attempt. - if (!hasNextInetSocketAddress()) { - if (!hasNextProxy()) { - if (!hasNextPostponed()) { - throw new NoSuchElementException(); - } - return nextPostponed(); - } - lastProxy = nextProxy(); - } - lastInetSocketAddress = nextInetSocketAddress(); - - Route route = new Route(address, lastProxy, lastInetSocketAddress); - if (routeDatabase.shouldPostpone(route)) { - postponedRoutes.add(route); - // We will only recurse in order to skip previously failed routes. They will be tried last. - return next(); - } - - return route; - } - - /** - * Clients should invoke this method when they encounter a connectivity failure on a connection - * returned by this route selector. - */ - public void connectFailed(Route failedRoute, IOException failure) { - if (failedRoute.proxy().type() != Proxy.Type.DIRECT && address.proxySelector() != null) { - // Tell the proxy selector when we fail to connect on a fresh connection. - address.proxySelector().connectFailed( - address.url().uri(), failedRoute.proxy().address(), failure); - } - - routeDatabase.failed(failedRoute); - } - - /** Prepares the proxy servers to try. */ - private void resetNextProxy(HttpUrl url, Proxy proxy) { - if (proxy != null) { - // If the user specifies a proxy, try that and only that. - proxies = Collections.singletonList(proxy); - } else { - // Try each of the ProxySelector choices until one connection succeeds. If none succeed - // then we'll try a direct connection below. - proxies = new ArrayList<>(); - List selectedProxies = address.proxySelector().select(url.uri()); - if (selectedProxies != null) proxies.addAll(selectedProxies); - // Finally try a direct connection. We only try it once! - proxies.removeAll(Collections.singleton(Proxy.NO_PROXY)); - proxies.add(Proxy.NO_PROXY); - } - nextProxyIndex = 0; - } - - /** Returns true if there's another proxy to try. */ - private boolean hasNextProxy() { - return nextProxyIndex < proxies.size(); - } - - /** Returns the next proxy to try. May be PROXY.NO_PROXY but never null. */ - private Proxy nextProxy() throws IOException { - if (!hasNextProxy()) { - throw new SocketException("No route to " + address.url().host() - + "; exhausted proxy configurations: " + proxies); - } - Proxy result = proxies.get(nextProxyIndex++); - resetNextInetSocketAddress(result); - return result; - } - - /** Prepares the socket addresses to attempt for the current proxy or host. */ - private void resetNextInetSocketAddress(Proxy proxy) throws IOException { - // Clear the addresses. Necessary if getAllByName() below throws! - inetSocketAddresses = new ArrayList<>(); - - String socketHost; - int socketPort; - if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) { - socketHost = address.url().host(); - socketPort = address.url().port(); - } else { - SocketAddress proxyAddress = proxy.address(); - if (!(proxyAddress instanceof InetSocketAddress)) { - throw new IllegalArgumentException( - "Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass()); - } - InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress; - socketHost = getHostString(proxySocketAddress); - socketPort = proxySocketAddress.getPort(); - } - - if (socketPort < 1 || socketPort > 65535) { - throw new SocketException("No route to " + socketHost + ":" + socketPort - + "; port is out of range"); - } - - if (proxy.type() == Proxy.Type.SOCKS) { - inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort)); - } else { - // Try each address for best behavior in mixed IPv4/IPv6 environments. - List addresses = address.dns().lookup(socketHost); - for (int i = 0, size = addresses.size(); i < size; i++) { - InetAddress inetAddress = addresses.get(i); - inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort)); - } - } - - nextInetSocketAddressIndex = 0; - } - - /** - * Obtain a "host" from an {@link InetSocketAddress}. This returns a string containing either an - * actual host name or a numeric IP address. - */ - // Visible for testing - static String getHostString(InetSocketAddress socketAddress) { - InetAddress address = socketAddress.getAddress(); - if (address == null) { - // The InetSocketAddress was specified with a string (either a numeric IP or a host name). If - // it is a name, all IPs for that name should be tried. If it is an IP address, only that IP - // address should be tried. - return socketAddress.getHostName(); - } - // The InetSocketAddress has a specific address: we should only try that address. Therefore we - // return the address and ignore any host name that may be available. - return address.getHostAddress(); - } - - /** Returns true if there's another socket address to try. */ - private boolean hasNextInetSocketAddress() { - return nextInetSocketAddressIndex < inetSocketAddresses.size(); - } - - /** Returns the next socket address to try. */ - private InetSocketAddress nextInetSocketAddress() throws IOException { - if (!hasNextInetSocketAddress()) { - throw new SocketException("No route to " + address.url().host() - + "; exhausted inet socket addresses: " + inetSocketAddresses); - } - return inetSocketAddresses.get(nextInetSocketAddressIndex++); - } - - /** Returns true if there is another postponed route to try. */ - private boolean hasNextPostponed() { - return !postponedRoutes.isEmpty(); - } - - /** Returns the next postponed route to try. */ - private Route nextPostponed() { - return postponedRoutes.remove(0); - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/StatusLine.java b/okhttp3/src/main/java/okhttp3/internal/http/StatusLine.java deleted file mode 100644 index f3591a6bf..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/StatusLine.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2013 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import java.io.IOException; -import java.net.ProtocolException; -import okhttp3.Protocol; -import okhttp3.Response; - -/** An HTTP response status line like "HTTP/1.1 200 OK". */ -public final class StatusLine { - /** Numeric status code, 307: Temporary Redirect. */ - public static final int HTTP_TEMP_REDIRECT = 307; - public static final int HTTP_PERM_REDIRECT = 308; - public static final int HTTP_CONTINUE = 100; - - public final Protocol protocol; - public final int code; - public final String message; - - public StatusLine(Protocol protocol, int code, String message) { - this.protocol = protocol; - this.code = code; - this.message = message; - } - - public static StatusLine get(Response response) { - return new StatusLine(response.protocol(), response.code(), response.message()); - } - - public static StatusLine parse(String statusLine) throws IOException { - // H T T P / 1 . 1 2 0 0 T e m p o r a r y R e d i r e c t - // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 - - // Parse protocol like "HTTP/1.1" followed by a space. - int codeStart; - Protocol protocol; - if (statusLine.startsWith("HTTP/1.")) { - if (statusLine.length() < 9 || statusLine.charAt(8) != ' ') { - throw new ProtocolException("Unexpected status line: " + statusLine); - } - int httpMinorVersion = statusLine.charAt(7) - '0'; - codeStart = 9; - if (httpMinorVersion == 0) { - protocol = Protocol.HTTP_1_0; - } else if (httpMinorVersion == 1) { - protocol = Protocol.HTTP_1_1; - } else { - throw new ProtocolException("Unexpected status line: " + statusLine); - } - } else if (statusLine.startsWith("ICY ")) { - // Shoutcast uses ICY instead of "HTTP/1.0". - protocol = Protocol.HTTP_1_0; - codeStart = 4; - } else { - throw new ProtocolException("Unexpected status line: " + statusLine); - } - - // Parse response code like "200". Always 3 digits. - if (statusLine.length() < codeStart + 3) { - throw new ProtocolException("Unexpected status line: " + statusLine); - } - int code; - try { - code = Integer.parseInt(statusLine.substring(codeStart, codeStart + 3)); - } catch (NumberFormatException e) { - throw new ProtocolException("Unexpected status line: " + statusLine); - } - - // Parse an optional response message like "OK" or "Not Modified". If it - // exists, it is separated from the response code by a space. - String message = ""; - if (statusLine.length() > codeStart + 3) { - if (statusLine.charAt(codeStart + 3) != ' ') { - throw new ProtocolException("Unexpected status line: " + statusLine); - } - message = statusLine.substring(codeStart + 4); - } - - return new StatusLine(protocol, code, message); - } - - @Override public String toString() { - StringBuilder result = new StringBuilder(); - result.append(protocol == Protocol.HTTP_1_0 ? "HTTP/1.0" : "HTTP/1.1"); - result.append(' ').append(code); - if (message != null) { - result.append(' ').append(message); - } - return result.toString(); - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/http/StreamAllocation.java b/okhttp3/src/main/java/okhttp3/internal/http/StreamAllocation.java deleted file mode 100644 index bd9abecaa..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/http/StreamAllocation.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright (C) 2015 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.http; - -import java.io.IOException; -import java.io.InterruptedIOException; -import java.lang.ref.Reference; -import java.lang.ref.WeakReference; -import java.net.ProtocolException; -import java.net.SocketTimeoutException; -import java.security.cert.CertificateException; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLPeerUnverifiedException; -import okhttp3.Address; -import okhttp3.ConnectionPool; -import okhttp3.Route; -import okhttp3.internal.Internal; -import okhttp3.internal.RouteDatabase; -import okhttp3.internal.Util; -import okhttp3.internal.io.RealConnection; -import okio.Sink; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -/** - * This class coordinates the relationship between three entities: - * - *

    - *
  • Connections: physical socket connections to remote servers. These are - * potentially slow to establish so it is necessary to be able to cancel a connection - * currently being connected. - *
  • Streams: logical HTTP request/response pairs that are layered on - * connections. Each connection has its own allocation limit, which defines how many - * concurrent streams that connection can carry. HTTP/1.x connections can carry 1 stream - * at a time, SPDY and HTTP/2 typically carry multiple. - *
  • Calls: a logical sequence of streams, typically an initial request and - * its follow up requests. We prefer to keep all streams of a single call on the same - * connection for better behavior and locality. - *
- * - *

Instances of this class act on behalf of the call, using one or more streams over one or more - * connections. This class has APIs to release each of the above resources: - * - *

    - *
  • {@link #noNewStreams()} prevents the connection from being used for new streams in the - * future. Use this after a {@code Connection: close} header, or when the connection may be - * inconsistent. - *
  • {@link #streamFinished streamFinished()} releases the active stream from this allocation. - * Note that only one stream may be active at a given time, so it is necessary to call - * {@link #streamFinished streamFinished()} before creating a subsequent stream with {@link - * #newStream newStream()}. - *
  • {@link #release()} removes the call's hold on the connection. Note that this won't - * immediately free the connection if there is a stream still lingering. That happens when a - * call is complete but its response body has yet to be fully consumed. - *
- * - *

This class supports {@linkplain #cancel asynchronous canceling}. This is intended to have the - * smallest blast radius possible. If an HTTP/2 stream is active, canceling will cancel that stream - * but not the other streams sharing its connection. But if the TLS handshake is still in progress - * then canceling may break the entire connection. - */ -public final class StreamAllocation { - public final Address address; - private Route route; - private final ConnectionPool connectionPool; - - // State guarded by connectionPool. - private RouteSelector routeSelector; - private RealConnection connection; - private boolean released; - private boolean canceled; - private HttpStream stream; - - public StreamAllocation(ConnectionPool connectionPool, Address address) { - this.connectionPool = connectionPool; - this.address = address; - this.routeSelector = new RouteSelector(address, routeDatabase()); - } - - public HttpStream newStream(int connectTimeout, int readTimeout, int writeTimeout, - boolean connectionRetryEnabled, boolean doExtensiveHealthChecks) - throws RouteException, IOException { - try { - RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout, - writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks); - - HttpStream resultStream; - if (resultConnection.framedConnection != null) { - resultStream = new Http2xStream(this, resultConnection.framedConnection); - } else { - resultConnection.socket().setSoTimeout(readTimeout); - resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS); - resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS); - resultStream = new Http1xStream(this, resultConnection.source, resultConnection.sink); - } - - synchronized (connectionPool) { - stream = resultStream; - return resultStream; - } - } catch (IOException e) { - throw new RouteException(e); - } - } - - /** - * Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated - * until a healthy connection is found. - */ - private RealConnection findHealthyConnection(int connectTimeout, int readTimeout, - int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks) - throws IOException, RouteException { - while (true) { - RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout, - connectionRetryEnabled); - - // If this is a brand new connection, we can skip the extensive health checks. - synchronized (connectionPool) { - if (candidate.successCount == 0) { - return candidate; - } - } - - // Otherwise do a potentially-slow check to confirm that the pooled connection is still good. - if (candidate.isHealthy(doExtensiveHealthChecks)) { - return candidate; - } - - connectionFailed(new IOException()); - } - } - - /** - * Returns a connection to host a new stream. This prefers the existing connection if it exists, - * then the pool, finally building a new connection. - */ - private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, - boolean connectionRetryEnabled) throws IOException, RouteException { - Route selectedRoute; - synchronized (connectionPool) { - if (released) throw new IllegalStateException("released"); - if (stream != null) throw new IllegalStateException("stream != null"); - if (canceled) throw new IOException("Canceled"); - - RealConnection allocatedConnection = this.connection; - if (allocatedConnection != null && !allocatedConnection.noNewStreams) { - return allocatedConnection; - } - - // Attempt to get a connection from the pool. - RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this); - if (pooledConnection != null) { - this.connection = pooledConnection; - return pooledConnection; - } - - selectedRoute = route; - } - - if (selectedRoute == null) { - selectedRoute = routeSelector.next(); - synchronized (connectionPool) { - route = selectedRoute; - } - } - RealConnection newConnection = new RealConnection(selectedRoute); - acquire(newConnection); - - synchronized (connectionPool) { - Internal.instance.put(connectionPool, newConnection); - this.connection = newConnection; - if (canceled) throw new IOException("Canceled"); - } - - newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(), - connectionRetryEnabled); - routeDatabase().connected(newConnection.route()); - - return newConnection; - } - - public void streamFinished(boolean noNewStreams, HttpStream stream) { - synchronized (connectionPool) { - if (stream == null || stream != this.stream) { - throw new IllegalStateException("expected " + this.stream + " but was " + stream); - } - if (!noNewStreams) { - connection.successCount++; - } - } - deallocate(noNewStreams, false, true); - } - - public HttpStream stream() { - synchronized (connectionPool) { - return stream; - } - } - - private RouteDatabase routeDatabase() { - return Internal.instance.routeDatabase(connectionPool); - } - - public synchronized RealConnection connection() { - return connection; - } - - public void release() { - deallocate(false, true, false); - } - - /** Forbid new streams from being created on the connection that hosts this allocation. */ - public void noNewStreams() { - deallocate(true, false, false); - } - - /** - * Releases resources held by this allocation. If sufficient resources are allocated, the - * connection will be detached or closed. - */ - private void deallocate(boolean noNewStreams, boolean released, boolean streamFinished) { - RealConnection connectionToClose = null; - synchronized (connectionPool) { - if (streamFinished) { - this.stream = null; - } - if (released) { - this.released = true; - } - if (connection != null) { - if (noNewStreams) { - connection.noNewStreams = true; - } - if (this.stream == null && (this.released || connection.noNewStreams)) { - release(connection); - if (connection.allocations.isEmpty()) { - connection.idleAtNanos = System.nanoTime(); - if (Internal.instance.connectionBecameIdle(connectionPool, connection)) { - connectionToClose = connection; - } - } - connection = null; - } - } - } - if (connectionToClose != null) { - Util.closeQuietly(connectionToClose.socket()); - } - } - - public void cancel() { - HttpStream streamToCancel; - RealConnection connectionToCancel; - synchronized (connectionPool) { - canceled = true; - streamToCancel = stream; - connectionToCancel = connection; - } - if (streamToCancel != null) { - streamToCancel.cancel(); - } else if (connectionToCancel != null) { - connectionToCancel.cancel(); - } - } - - public void connectionFailed(IOException e) { - synchronized (connectionPool) { - // Avoid this route if it's never seen a successful call. - if (connection != null && connection.successCount == 0) { - if (route != null && e != null) { - routeSelector.connectFailed(route, e); - } - route = null; - } - } - deallocate(true, false, true); - } - - /** - * Use this allocation to hold {@code connection}. Each call to this must be paired with a call to - * {@link #release} on the same connection. - */ - public void acquire(RealConnection connection) { - connection.allocations.add(new WeakReference<>(this)); - } - - /** Remove this allocation from the connection's list of allocations. */ - private void release(RealConnection connection) { - for (int i = 0, size = connection.allocations.size(); i < size; i++) { - Reference reference = connection.allocations.get(i); - if (reference.get() == this) { - connection.allocations.remove(i); - return; - } - } - throw new IllegalStateException(); - } - - public boolean recover(IOException e, Sink requestBodyOut) { - if (connection != null) { - connectionFailed(e); - } - - boolean canRetryRequestBody = requestBodyOut == null || requestBodyOut instanceof RetryableSink; - if ((routeSelector != null && !routeSelector.hasNext()) // No more routes to attempt. - || !isRecoverable(e) - || !canRetryRequestBody) { - return false; - } - - return true; - } - - private boolean isRecoverable(IOException e) { - // If there was a protocol problem, don't recover. - if (e instanceof ProtocolException) { - return false; - } - - // If there was an interruption don't recover, but if there was a timeout - // we should try the next route (if there is one). - if (e instanceof InterruptedIOException) { - return e instanceof SocketTimeoutException; - } - - // Look for known client-side or negotiation errors that are unlikely to be fixed by trying - // again with a different route. - if (e instanceof SSLHandshakeException) { - // If the problem was a CertificateException from the X509TrustManager, - // do not retry. - if (e.getCause() instanceof CertificateException) { - return false; - } - } - if (e instanceof SSLPeerUnverifiedException) { - // e.g. a certificate pinning error. - return false; - } - - // An example of one we might want to retry with a different route is a problem connecting to a - // proxy and would manifest as a standard IOException. Unless it is one we know we should not - // retry, we return true and try a new route. - return true; - } - - @Override public String toString() { - return address.toString(); - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/io/FileSystem.java b/okhttp3/src/main/java/okhttp3/internal/io/FileSystem.java deleted file mode 100644 index 0b1b1cfbc..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/io/FileSystem.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2015 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.io; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import okio.Okio; -import okio.Sink; -import okio.Source; - -/** - * Access to read and write files on a hierarchical data store. Most callers should use the {@link - * #SYSTEM} implementation, which uses the host machine's local file system. Alternate - * implementations may be used to inject faults (for testing) or to transform stored data (to add - * encryption, for example). - * - *

All operations on a file system are racy. For example, guarding a call to {@link #source} with - * {@link #exists} does not guarantee that {@link FileNotFoundException} will not be thrown. The - * file may be moved between the two calls! - * - *

This interface is less ambitious than {@link java.nio.file.FileSystem} introduced in Java 7. - * It lacks important features like file watching, metadata, permissions, and disk space - * information. In exchange for these limitations, this interface is easier to implement and works - * on all versions of Java and Android. - */ -public interface FileSystem { - /** The host machine's local file system. */ - FileSystem SYSTEM = new FileSystem() { - @Override public Source source(File file) throws FileNotFoundException { - return Okio.source(file); - } - - @Override public Sink sink(File file) throws FileNotFoundException { - try { - return Okio.sink(file); - } catch (FileNotFoundException e) { - // Maybe the parent directory doesn't exist? Try creating it first. - file.getParentFile().mkdirs(); - return Okio.sink(file); - } - } - - @Override public Sink appendingSink(File file) throws FileNotFoundException { - try { - return Okio.appendingSink(file); - } catch (FileNotFoundException e) { - // Maybe the parent directory doesn't exist? Try creating it first. - file.getParentFile().mkdirs(); - return Okio.appendingSink(file); - } - } - - @Override public void delete(File file) throws IOException { - // If delete() fails, make sure it's because the file didn't exist! - if (!file.delete() && file.exists()) { - throw new IOException("failed to delete " + file); - } - } - - @Override public boolean exists(File file) { - return file.exists(); - } - - @Override public long size(File file) { - return file.length(); - } - - @Override public void rename(File from, File to) throws IOException { - delete(to); - if (!from.renameTo(to)) { - throw new IOException("failed to rename " + from + " to " + to); - } - } - - @Override public void deleteContents(File directory) throws IOException { - File[] files = directory.listFiles(); - if (files == null) { - throw new IOException("not a readable directory: " + directory); - } - for (File file : files) { - if (file.isDirectory()) { - deleteContents(file); - } - if (!file.delete()) { - throw new IOException("failed to delete " + file); - } - } - } - }; - - /** Reads from {@code file}. */ - Source source(File file) throws FileNotFoundException; - - /** - * Writes to {@code file}, discarding any data already present. Creates parent directories if - * necessary. - */ - Sink sink(File file) throws FileNotFoundException; - - /** - * Writes to {@code file}, appending if data is already present. Creates parent directories if - * necessary. - */ - Sink appendingSink(File file) throws FileNotFoundException; - - /** Deletes {@code file} if it exists. Throws if the file exists and cannot be deleted. */ - void delete(File file) throws IOException; - - /** Returns true if {@code file} exists on the file system. */ - boolean exists(File file); - - /** Returns the number of bytes stored in {@code file}, or 0 if it does not exist. */ - long size(File file); - - /** Renames {@code from} to {@code to}. Throws if the file cannot be renamed. */ - void rename(File from, File to) throws IOException; - - /** - * Recursively delete the contents of {@code directory}. Throws an IOException if any file could - * not be deleted, or if {@code dir} is not a readable directory. - */ - void deleteContents(File directory) throws IOException; -} diff --git a/okhttp3/src/main/java/okhttp3/internal/io/RealConnection.java b/okhttp3/src/main/java/okhttp3/internal/io/RealConnection.java deleted file mode 100644 index ed8019e2f..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/io/RealConnection.java +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.io; - -import java.io.IOException; -import java.lang.ref.Reference; -import java.net.ConnectException; -import java.net.Proxy; -import java.net.Socket; -import java.net.SocketTimeoutException; -import java.net.UnknownServiceException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import okhttp3.Address; -import okhttp3.CertificatePinner; -import okhttp3.Connection; -import okhttp3.ConnectionSpec; -import okhttp3.Handshake; -import okhttp3.HttpUrl; -import okhttp3.Protocol; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.Route; -import okhttp3.internal.ConnectionSpecSelector; -import okhttp3.internal.Platform; -import okhttp3.internal.Util; -import okhttp3.internal.Version; -import okhttp3.internal.framed.ErrorCode; -import okhttp3.internal.framed.FramedConnection; -import okhttp3.internal.framed.FramedStream; -import okhttp3.internal.http.Http1xStream; -import okhttp3.internal.http.OkHeaders; -import okhttp3.internal.http.RouteException; -import okhttp3.internal.http.StreamAllocation; -import okhttp3.internal.tls.OkHostnameVerifier; -import okio.BufferedSink; -import okio.BufferedSource; -import okio.Okio; -import okio.Source; - -import static java.net.HttpURLConnection.HTTP_OK; -import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static okhttp3.internal.Util.closeQuietly; - -public final class RealConnection extends FramedConnection.Listener implements Connection { - private final Route route; - - /** The low-level TCP socket. */ - private Socket rawSocket; - - /** - * The application layer socket. Either an {@link SSLSocket} layered over {@link #rawSocket}, or - * {@link #rawSocket} itself if this connection does not use SSL. - */ - public Socket socket; - private Handshake handshake; - private Protocol protocol; - public volatile FramedConnection framedConnection; - public int successCount; - public BufferedSource source; - public BufferedSink sink; - public int allocationLimit; - public final List> allocations = new ArrayList<>(); - public boolean noNewStreams; - public long idleAtNanos = Long.MAX_VALUE; - - public RealConnection(Route route) { - this.route = route; - } - - public void connect(int connectTimeout, int readTimeout, int writeTimeout, - List connectionSpecs, boolean connectionRetryEnabled) throws RouteException { - if (protocol != null) throw new IllegalStateException("already connected"); - - RouteException routeException = null; - ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs); - Proxy proxy = route.proxy(); - Address address = route.address(); - - if (route.address().sslSocketFactory() == null - && !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) { - throw new RouteException(new UnknownServiceException( - "CLEARTEXT communication not supported: " + connectionSpecs)); - } - - while (protocol == null) { - try { - rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP - ? address.socketFactory().createSocket() - : new Socket(proxy); - connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector); - } catch (IOException e) { - closeQuietly(socket); - closeQuietly(rawSocket); - socket = null; - rawSocket = null; - source = null; - sink = null; - handshake = null; - protocol = null; - - if (routeException == null) { - routeException = new RouteException(e); - } else { - routeException.addConnectException(e); - } - - if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) { - throw routeException; - } - } - } - } - - /** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */ - private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout, - ConnectionSpecSelector connectionSpecSelector) throws IOException { - rawSocket.setSoTimeout(readTimeout); - try { - Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout); - } catch (ConnectException e) { - throw new ConnectException("Failed to connect to " + route.socketAddress()); - } - source = Okio.buffer(Okio.source(rawSocket)); - sink = Okio.buffer(Okio.sink(rawSocket)); - - if (route.address().sslSocketFactory() != null) { - connectTls(readTimeout, writeTimeout, connectionSpecSelector); - } else { - protocol = Protocol.HTTP_1_1; - socket = rawSocket; - } - - if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) { - socket.setSoTimeout(0); // Framed connection timeouts are set per-stream. - - FramedConnection framedConnection = new FramedConnection.Builder(true) - .socket(socket, route.address().url().host(), source, sink) - .protocol(protocol) - .listener(this) - .build(); - framedConnection.sendConnectionPreface(); - - // Only assign the framed connection once the preface has been sent successfully. - this.allocationLimit = framedConnection.maxConcurrentStreams(); - this.framedConnection = framedConnection; - } else { - this.allocationLimit = 1; - } - } - - private void connectTls(int readTimeout, int writeTimeout, - ConnectionSpecSelector connectionSpecSelector) throws IOException { - if (route.requiresTunnel()) { - createTunnel(readTimeout, writeTimeout); - } - - Address address = route.address(); - SSLSocketFactory sslSocketFactory = address.sslSocketFactory(); - boolean success = false; - SSLSocket sslSocket = null; - try { - // Create the wrapper over the connected socket. - sslSocket = (SSLSocket) sslSocketFactory.createSocket( - rawSocket, address.url().host(), address.url().port(), true /* autoClose */); - - // Configure the socket's ciphers, TLS versions, and extensions. - ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket); - if (connectionSpec.supportsTlsExtensions()) { - Platform.get().configureTlsExtensions( - sslSocket, address.url().host(), address.protocols()); - } - - // Force handshake. This can throw! - sslSocket.startHandshake(); - Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession()); - - // Verify that the socket's certificates are acceptable for the target host. - if (!address.hostnameVerifier().verify(address.url().host(), sslSocket.getSession())) { - X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0); - throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:" - + "\n certificate: " + CertificatePinner.pin(cert) - + "\n DN: " + cert.getSubjectDN().getName() - + "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert)); - } - - // Check that the certificate pinner is satisfied by the certificates presented. - address.certificatePinner().check(address.url().host(), - unverifiedHandshake.peerCertificates()); - - // Success! Save the handshake and the ALPN protocol. - String maybeProtocol = connectionSpec.supportsTlsExtensions() - ? Platform.get().getSelectedProtocol(sslSocket) - : null; - socket = sslSocket; - source = Okio.buffer(Okio.source(socket)); - sink = Okio.buffer(Okio.sink(socket)); - handshake = unverifiedHandshake; - protocol = maybeProtocol != null - ? Protocol.get(maybeProtocol) - : Protocol.HTTP_1_1; - success = true; - } catch (AssertionError e) { - if (Util.isAndroidGetsocknameError(e)) throw new IOException(e); - throw e; - } finally { - if (sslSocket != null) { - Platform.get().afterHandshake(sslSocket); - } - if (!success) { - closeQuietly(sslSocket); - } - } - } - - /** - * To make an HTTPS connection over an HTTP proxy, send an unencrypted CONNECT request to create - * the proxy connection. This may need to be retried if the proxy requires authorization. - */ - private void createTunnel(int readTimeout, int writeTimeout) throws IOException { - // Make an SSL Tunnel on the first message pair of each SSL + proxy connection. - Request tunnelRequest = createTunnelRequest(); - HttpUrl url = tunnelRequest.url(); - String requestLine = "CONNECT " + Util.hostHeader(url, true) + " HTTP/1.1"; - while (true) { - Http1xStream tunnelConnection = new Http1xStream(null, source, sink); - source.timeout().timeout(readTimeout, MILLISECONDS); - sink.timeout().timeout(writeTimeout, MILLISECONDS); - tunnelConnection.writeRequest(tunnelRequest.headers(), requestLine); - tunnelConnection.finishRequest(); - Response response = tunnelConnection.readResponse().request(tunnelRequest).build(); - // The response body from a CONNECT should be empty, but if it is not then we should consume - // it before proceeding. - long contentLength = OkHeaders.contentLength(response); - if (contentLength == -1L) { - contentLength = 0L; - } - Source body = tunnelConnection.newFixedLengthSource(contentLength); - Util.skipAll(body, Integer.MAX_VALUE, TimeUnit.MILLISECONDS); - body.close(); - - switch (response.code()) { - case HTTP_OK: - // Assume the server won't send a TLS ServerHello until we send a TLS ClientHello. If - // that happens, then we will have buffered bytes that are needed by the SSLSocket! - // This check is imperfect: it doesn't tell us whether a handshake will succeed, just - // that it will almost certainly fail because the proxy has sent unexpected data. - if (!source.buffer().exhausted() || !sink.buffer().exhausted()) { - throw new IOException("TLS tunnel buffered too many bytes!"); - } - return; - - case HTTP_PROXY_AUTH: - tunnelRequest = route.address().proxyAuthenticator().authenticate(route, response); - if (tunnelRequest != null) continue; - throw new IOException("Failed to authenticate with proxy"); - - default: - throw new IOException( - "Unexpected response code for CONNECT: " + response.code()); - } - } - } - - /** - * Returns a request that creates a TLS tunnel via an HTTP proxy, or null if no tunnel is - * necessary. Everything in the tunnel request is sent unencrypted to the proxy server, so tunnels - * include only the minimum set of headers. This avoids sending potentially sensitive data like - * HTTP cookies to the proxy unencrypted. - */ - private Request createTunnelRequest() throws IOException { - return new Request.Builder() - .url(route.address().url()) - .header("Host", Util.hostHeader(route.address().url(), true)) - .header("Proxy-Connection", "Keep-Alive") - .header("User-Agent", Version.userAgent()) // For HTTP/1.0 proxies like Squid. - .build(); - } - - /** Returns true if {@link #connect} has been attempted on this connection. */ - boolean isConnected() { - return protocol != null; - } - - @Override public Route route() { - return route; - } - - public void cancel() { - // Close the raw socket so we don't end up doing synchronous I/O. - closeQuietly(rawSocket); - } - - @Override public Socket socket() { - return socket; - } - - /** Returns true if this connection is ready to host new streams. */ - public boolean isHealthy(boolean doExtensiveChecks) { - if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) { - return false; - } - - if (framedConnection != null) { - return true; // TODO: check framedConnection.shutdown. - } - - if (doExtensiveChecks) { - try { - int readTimeout = socket.getSoTimeout(); - try { - socket.setSoTimeout(1); - if (source.exhausted()) { - return false; // Stream is exhausted; socket is closed. - } - return true; - } finally { - socket.setSoTimeout(readTimeout); - } - } catch (SocketTimeoutException ignored) { - // Read timed out; socket is good. - } catch (IOException e) { - return false; // Couldn't read; socket is closed. - } - } - - return true; - } - - /** Refuse incoming streams. */ - @Override public void onStream(FramedStream stream) throws IOException { - stream.close(ErrorCode.REFUSED_STREAM); - } - - /** When settings are received, adjust the allocation limit. */ - @Override public void onSettings(FramedConnection connection) { - allocationLimit = connection.maxConcurrentStreams(); - } - - @Override public Handshake handshake() { - return handshake; - } - - /** - * Returns true if this is a SPDY connection. Such connections can be used in multiple HTTP - * requests simultaneously. - */ - public boolean isMultiplexed() { - return framedConnection != null; - } - - @Override public Protocol protocol() { - if (framedConnection == null) { - return protocol != null ? protocol : Protocol.HTTP_1_1; - } else { - return framedConnection.getProtocol(); - } - } - - @Override public String toString() { - return "Connection{" - + route.address().url().host() + ":" + route.address().url().port() - + ", proxy=" - + route.proxy() - + " hostAddress=" - + route.socketAddress() - + " cipherSuite=" - + (handshake != null ? handshake.cipherSuite() : "none") - + " protocol=" - + protocol - + '}'; - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/tls/AndroidTrustRootIndex.java b/okhttp3/src/main/java/okhttp3/internal/tls/AndroidTrustRootIndex.java deleted file mode 100644 index 3074a1e98..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/tls/AndroidTrustRootIndex.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2016 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.tls; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.security.cert.TrustAnchor; -import java.security.cert.X509Certificate; -import javax.net.ssl.X509TrustManager; - -/** - * A index of trusted root certificates that exploits knowledge of Android implementation details. - * This class is potentially much faster to initialize than {@link RealTrustRootIndex} because - * it doesn't need to load and index trusted CA certificates. - */ -public final class AndroidTrustRootIndex implements TrustRootIndex { - private final X509TrustManager trustManager; - private final Method findByIssuerAndSignatureMethod; - - public AndroidTrustRootIndex( - X509TrustManager trustManager, Method findByIssuerAndSignatureMethod) { - this.findByIssuerAndSignatureMethod = findByIssuerAndSignatureMethod; - this.trustManager = trustManager; - } - - @Override public X509Certificate findByIssuerAndSignature(X509Certificate cert) { - try { - TrustAnchor trustAnchor = (TrustAnchor) findByIssuerAndSignatureMethod.invoke( - trustManager, cert); - return trustAnchor != null - ? trustAnchor.getTrustedCert() - : null; - } catch (IllegalAccessException e) { - throw new AssertionError(); - } catch (InvocationTargetException e) { - return null; - } - } - - public static TrustRootIndex get(X509TrustManager trustManager) { - // From org.conscrypt.TrustManagerImpl, we want the method with this signature: - // private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert); - try { - Method method = trustManager.getClass().getDeclaredMethod( - "findTrustAnchorByIssuerAndSignature", X509Certificate.class); - method.setAccessible(true); - return new AndroidTrustRootIndex(trustManager, method); - } catch (NoSuchMethodException e) { - return null; - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/tls/CertificateChainCleaner.java b/okhttp3/src/main/java/okhttp3/internal/tls/CertificateChainCleaner.java deleted file mode 100644 index d887df615..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/tls/CertificateChainCleaner.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.tls; - -import java.security.GeneralSecurityException; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.Iterator; -import java.util.List; -import javax.net.ssl.SSLPeerUnverifiedException; - -/** - * Computes the effective certificate chain from the raw array returned by Java's built in TLS APIs. - * Cleaning a chain returns a list of certificates where the first element is {@code chain[0]}, each - * certificate is signed by the certificate that follows, and the last certificate is a trusted CA - * certificate. - * - *

Use of the chain cleaner is necessary to omit unexpected certificates that aren't relevant to - * the TLS handshake and to extract the trusted CA certificate for the benefit of certificate - * pinning. - * - *

This class includes code from Conscrypt's {@code - * TrustManagerImpl} and {@code TrustedCertificateIndex}. - */ -public final class CertificateChainCleaner { - /** The maximum number of signers in a chain. We use 9 for consistency with OpenSSL. */ - private static final int MAX_SIGNERS = 9; - - private final TrustRootIndex trustRootIndex; - - public CertificateChainCleaner(TrustRootIndex trustRootIndex) { - this.trustRootIndex = trustRootIndex; - } - - /** - * Returns a cleaned chain for {@code chain}. - * - *

This method throws if the complete chain to a trusted CA certificate cannot be constructed. - * This is unexpected unless the trust root index in this class has a different trust manager than - * what was used to establish {@code chain}. - */ - public List clean(List chain) throws SSLPeerUnverifiedException { - Deque queue = new ArrayDeque<>(chain); - List result = new ArrayList<>(); - result.add(queue.removeFirst()); - boolean foundTrustedCertificate = false; - - followIssuerChain: - for (int c = 0; c < MAX_SIGNERS; c++) { - X509Certificate toVerify = (X509Certificate) result.get(result.size() - 1); - - // If this cert has been signed by a trusted cert, use that. Add the trusted certificate to - // the end of the chain unless it's already present. (That would happen if the first - // certificate in the chain is itself a self-signed and trusted CA certificate.) - X509Certificate trustedCert = trustRootIndex.findByIssuerAndSignature(toVerify); - if (trustedCert != null) { - if (result.size() > 1 || !toVerify.equals(trustedCert)) { - result.add(trustedCert); - } - if (verifySignature(trustedCert, trustedCert)) { - return result; // The self-signed cert is a root CA. We're done. - } - foundTrustedCertificate = true; - continue; - } - - // Search for the certificate in the chain that signed this certificate. This is typically the - // next element in the chain, but it could be any element. - for (Iterator i = queue.iterator(); i.hasNext(); ) { - X509Certificate signingCert = (X509Certificate) i.next(); - if (verifySignature(toVerify, signingCert)) { - i.remove(); - result.add(signingCert); - continue followIssuerChain; - } - } - - // We've reached the end of the chain. If any cert in the chain is trusted, we're done. - if (foundTrustedCertificate) { - return result; - } - - // The last link isn't trusted. Fail. - throw new SSLPeerUnverifiedException("Failed to find a trusted cert that signed " + toVerify); - } - - throw new SSLPeerUnverifiedException("Certificate chain too long: " + result); - } - - /** Returns true if {@code toVerify} was signed by {@code signingCert}'s public key. */ - private boolean verifySignature(X509Certificate toVerify, X509Certificate signingCert) { - if (!toVerify.getIssuerDN().equals(signingCert.getSubjectDN())) return false; - try { - toVerify.verify(signingCert.getPublicKey()); - return true; - } catch (GeneralSecurityException verifyFailed) { - return false; - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/tls/DistinguishedNameParser.java b/okhttp3/src/main/java/okhttp3/internal/tls/DistinguishedNameParser.java deleted file mode 100644 index 616bca534..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/tls/DistinguishedNameParser.java +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.tls; - -import javax.security.auth.x500.X500Principal; - -/** - * A distinguished name (DN) parser. This parser only supports extracting a string value from a DN. - * It doesn't support values in the hex-string style. - */ -final class DistinguishedNameParser { - private final String dn; - private final int length; - private int pos; - private int beg; - private int end; - - /** Temporary variable to store positions of the currently parsed item. */ - private int cur; - - /** Distinguished name characters. */ - private char[] chars; - - public DistinguishedNameParser(X500Principal principal) { - // RFC2253 is used to ensure we get attributes in the reverse - // order of the underlying ASN.1 encoding, so that the most - // significant values of repeated attributes occur first. - this.dn = principal.getName(X500Principal.RFC2253); - this.length = this.dn.length(); - } - - // gets next attribute type: (ALPHA 1*keychar) / oid - private String nextAT() { - // skip preceding space chars, they can present after - // comma or semicolon (compatibility with RFC 1779) - for (; pos < length && chars[pos] == ' '; pos++) { - } - if (pos == length) { - return null; // reached the end of DN - } - - // mark the beginning of attribute type - beg = pos; - - // attribute type chars - pos++; - for (; pos < length && chars[pos] != '=' && chars[pos] != ' '; pos++) { - // we don't follow exact BNF syntax here: - // accept any char except space and '=' - } - if (pos >= length) { - throw new IllegalStateException("Unexpected end of DN: " + dn); - } - - // mark the end of attribute type - end = pos; - - // skip trailing space chars between attribute type and '=' - // (compatibility with RFC 1779) - if (chars[pos] == ' ') { - for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) { - } - - if (chars[pos] != '=' || pos == length) { - throw new IllegalStateException("Unexpected end of DN: " + dn); - } - } - - pos++; //skip '=' char - - // skip space chars between '=' and attribute value - // (compatibility with RFC 1779) - for (; pos < length && chars[pos] == ' '; pos++) { - } - - // in case of oid attribute type skip its prefix: "oid." or "OID." - // (compatibility with RFC 1779) - if ((end - beg > 4) && (chars[beg + 3] == '.') - && (chars[beg] == 'O' || chars[beg] == 'o') - && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i') - && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) { - beg += 4; - } - - return new String(chars, beg, end - beg); - } - - // gets quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION - private String quotedAV() { - pos++; - beg = pos; - end = beg; - while (true) { - - if (pos == length) { - throw new IllegalStateException("Unexpected end of DN: " + dn); - } - - if (chars[pos] == '"') { - // enclosing quotation was found - pos++; - break; - } else if (chars[pos] == '\\') { - chars[end] = getEscaped(); - } else { - // shift char: required for string with escaped chars - chars[end] = chars[pos]; - } - pos++; - end++; - } - - // skip trailing space chars before comma or semicolon. - // (compatibility with RFC 1779) - for (; pos < length && chars[pos] == ' '; pos++) { - } - - return new String(chars, beg, end - beg); - } - - // gets hex string attribute value: "#" hexstring - private String hexAV() { - if (pos + 4 >= length) { - // encoded byte array must be not less then 4 c - throw new IllegalStateException("Unexpected end of DN: " + dn); - } - - beg = pos; // store '#' position - pos++; - while (true) { - - // check for end of attribute value - // looks for space and component separators - if (pos == length || chars[pos] == '+' || chars[pos] == ',' - || chars[pos] == ';') { - end = pos; - break; - } - - if (chars[pos] == ' ') { - end = pos; - pos++; - // skip trailing space chars before comma or semicolon. - // (compatibility with RFC 1779) - for (; pos < length && chars[pos] == ' '; pos++) { - } - break; - } else if (chars[pos] >= 'A' && chars[pos] <= 'F') { - chars[pos] += 32; //to low case - } - - pos++; - } - - // verify length of hex string - // encoded byte array must be not less then 4 and must be even number - int hexLen = end - beg; // skip first '#' char - if (hexLen < 5 || (hexLen & 1) == 0) { - throw new IllegalStateException("Unexpected end of DN: " + dn); - } - - // get byte encoding from string representation - byte[] encoded = new byte[hexLen / 2]; - for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) { - encoded[i] = (byte) getByte(p); - } - - return new String(chars, beg, hexLen); - } - - // gets string attribute value: *( stringchar / pair ) - private String escapedAV() { - beg = pos; - end = pos; - while (true) { - if (pos >= length) { - // the end of DN has been found - return new String(chars, beg, end - beg); - } - - switch (chars[pos]) { - case '+': - case ',': - case ';': - // separator char has been found - return new String(chars, beg, end - beg); - case '\\': - // escaped char - chars[end++] = getEscaped(); - pos++; - break; - case ' ': - // need to figure out whether space defines - // the end of attribute value or not - cur = end; - - pos++; - chars[end++] = ' '; - - for (; pos < length && chars[pos] == ' '; pos++) { - chars[end++] = ' '; - } - if (pos == length || chars[pos] == ',' || chars[pos] == '+' - || chars[pos] == ';') { - // separator char or the end of DN has been found - return new String(chars, beg, cur - beg); - } - break; - default: - chars[end++] = chars[pos]; - pos++; - } - } - } - - // returns escaped char - private char getEscaped() { - pos++; - if (pos == length) { - throw new IllegalStateException("Unexpected end of DN: " + dn); - } - - switch (chars[pos]) { - case '"': - case '\\': - case ',': - case '=': - case '+': - case '<': - case '>': - case '#': - case ';': - case ' ': - case '*': - case '%': - case '_': - //FIXME: escaping is allowed only for leading or trailing space char - return chars[pos]; - default: - // RFC doesn't explicitly say that escaped hex pair is - // interpreted as UTF-8 char. It only contains an example of such DN. - return getUTF8(); - } - } - - // decodes UTF-8 char - // see http://www.unicode.org for UTF-8 bit distribution table - private char getUTF8() { - int res = getByte(pos); - pos++; //FIXME tmp - - if (res < 128) { // one byte: 0-7F - return (char) res; - } else if (res >= 192 && res <= 247) { - - int count; - if (res <= 223) { // two bytes: C0-DF - count = 1; - res = res & 0x1F; - } else if (res <= 239) { // three bytes: E0-EF - count = 2; - res = res & 0x0F; - } else { // four bytes: F0-F7 - count = 3; - res = res & 0x07; - } - - int b; - for (int i = 0; i < count; i++) { - pos++; - if (pos == length || chars[pos] != '\\') { - return 0x3F; //FIXME failed to decode UTF-8 char - return '?' - } - pos++; - - b = getByte(pos); - pos++; //FIXME tmp - if ((b & 0xC0) != 0x80) { - return 0x3F; //FIXME failed to decode UTF-8 char - return '?' - } - - res = (res << 6) + (b & 0x3F); - } - return (char) res; - } else { - return 0x3F; //FIXME failed to decode UTF-8 char - return '?' - } - } - - // Returns byte representation of a char pair - // The char pair is composed of DN char in - // specified 'position' and the next char - // According to BNF syntax: - // hexchar = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" - // / "a" / "b" / "c" / "d" / "e" / "f" - private int getByte(int position) { - if (position + 1 >= length) { - throw new IllegalStateException("Malformed DN: " + dn); - } - - int b1, b2; - - b1 = chars[position]; - if (b1 >= '0' && b1 <= '9') { - b1 = b1 - '0'; - } else if (b1 >= 'a' && b1 <= 'f') { - b1 = b1 - 87; // 87 = 'a' - 10 - } else if (b1 >= 'A' && b1 <= 'F') { - b1 = b1 - 55; // 55 = 'A' - 10 - } else { - throw new IllegalStateException("Malformed DN: " + dn); - } - - b2 = chars[position + 1]; - if (b2 >= '0' && b2 <= '9') { - b2 = b2 - '0'; - } else if (b2 >= 'a' && b2 <= 'f') { - b2 = b2 - 87; // 87 = 'a' - 10 - } else if (b2 >= 'A' && b2 <= 'F') { - b2 = b2 - 55; // 55 = 'A' - 10 - } else { - throw new IllegalStateException("Malformed DN: " + dn); - } - - return (b1 << 4) + b2; - } - - /** - * Parses the DN and returns the most significant attribute value for an attribute type, or null - * if none found. - * - * @param attributeType attribute type to look for (e.g. "ca") - */ - public String findMostSpecific(String attributeType) { - // Initialize internal state. - pos = 0; - beg = 0; - end = 0; - cur = 0; - chars = dn.toCharArray(); - - String attType = nextAT(); - if (attType == null) { - return null; - } - while (true) { - String attValue = ""; - - if (pos == length) { - return null; - } - - switch (chars[pos]) { - case '"': - attValue = quotedAV(); - break; - case '#': - attValue = hexAV(); - break; - case '+': - case ',': - case ';': // compatibility with RFC 1779: semicolon can separate RDNs - //empty attribute value - break; - default: - attValue = escapedAV(); - } - - // Values are ordered from most specific to least specific - // due to the RFC2253 formatting. So take the first match - // we see. - if (attributeType.equalsIgnoreCase(attType)) { - return attValue; - } - - if (pos >= length) { - return null; - } - - if (chars[pos] == ',' || chars[pos] == ';') { - } else if (chars[pos] != '+') { - throw new IllegalStateException("Malformed DN: " + dn); - } - - pos++; - attType = nextAT(); - if (attType == null) { - throw new IllegalStateException("Malformed DN: " + dn); - } - } - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/tls/OkHostnameVerifier.java b/okhttp3/src/main/java/okhttp3/internal/tls/OkHostnameVerifier.java deleted file mode 100644 index a85df784d..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/tls/OkHostnameVerifier.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.tls; - -import java.security.cert.Certificate; -import java.security.cert.CertificateParsingException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSession; -import javax.security.auth.x500.X500Principal; - -import static okhttp3.internal.Util.verifyAsIpAddress; - -/** - * A HostnameVerifier consistent with RFC 2818. - */ -public final class OkHostnameVerifier implements HostnameVerifier { - public static final OkHostnameVerifier INSTANCE = new OkHostnameVerifier(); - - private static final int ALT_DNS_NAME = 2; - private static final int ALT_IPA_NAME = 7; - - private OkHostnameVerifier() { - } - - @Override - public boolean verify(String host, SSLSession session) { - try { - Certificate[] certificates = session.getPeerCertificates(); - return verify(host, (X509Certificate) certificates[0]); - } catch (SSLException e) { - return false; - } - } - - public boolean verify(String host, X509Certificate certificate) { - return verifyAsIpAddress(host) - ? verifyIpAddress(host, certificate) - : verifyHostname(host, certificate); - } - - /** Returns true if {@code certificate} matches {@code ipAddress}. */ - private boolean verifyIpAddress(String ipAddress, X509Certificate certificate) { - List altNames = getSubjectAltNames(certificate, ALT_IPA_NAME); - for (int i = 0, size = altNames.size(); i < size; i++) { - if (ipAddress.equalsIgnoreCase(altNames.get(i))) { - return true; - } - } - return false; - } - - /** Returns true if {@code certificate} matches {@code hostname}. */ - private boolean verifyHostname(String hostname, X509Certificate certificate) { - hostname = hostname.toLowerCase(Locale.US); - boolean hasDns = false; - List altNames = getSubjectAltNames(certificate, ALT_DNS_NAME); - for (int i = 0, size = altNames.size(); i < size; i++) { - hasDns = true; - if (verifyHostname(hostname, altNames.get(i))) { - return true; - } - } - - if (!hasDns) { - X500Principal principal = certificate.getSubjectX500Principal(); - // RFC 2818 advises using the most specific name for matching. - String cn = new DistinguishedNameParser(principal).findMostSpecific("cn"); - if (cn != null) { - return verifyHostname(hostname, cn); - } - } - - return false; - } - - public static List allSubjectAltNames(X509Certificate certificate) { - List altIpaNames = getSubjectAltNames(certificate, ALT_IPA_NAME); - List altDnsNames = getSubjectAltNames(certificate, ALT_DNS_NAME); - List result = new ArrayList<>(altIpaNames.size() + altDnsNames.size()); - result.addAll(altIpaNames); - result.addAll(altDnsNames); - return result; - } - - private static List getSubjectAltNames(X509Certificate certificate, int type) { - List result = new ArrayList<>(); - try { - Collection subjectAltNames = certificate.getSubjectAlternativeNames(); - if (subjectAltNames == null) { - return Collections.emptyList(); - } - for (Object subjectAltName : subjectAltNames) { - List entry = (List) subjectAltName; - if (entry == null || entry.size() < 2) { - continue; - } - Integer altNameType = (Integer) entry.get(0); - if (altNameType == null) { - continue; - } - if (altNameType == type) { - String altName = (String) entry.get(1); - if (altName != null) { - result.add(altName); - } - } - } - return result; - } catch (CertificateParsingException e) { - return Collections.emptyList(); - } - } - - /** - * Returns {@code true} iff {@code hostname} matches the domain name {@code pattern}. - * - * @param hostname lower-case host name. - * @param pattern domain name pattern from certificate. May be a wildcard pattern such as {@code - * *.android.com}. - */ - private boolean verifyHostname(String hostname, String pattern) { - // Basic sanity checks - // Check length == 0 instead of .isEmpty() to support Java 5. - if ((hostname == null) || (hostname.length() == 0) || (hostname.startsWith(".")) - || (hostname.endsWith(".."))) { - // Invalid domain name - return false; - } - if ((pattern == null) || (pattern.length() == 0) || (pattern.startsWith(".")) - || (pattern.endsWith(".."))) { - // Invalid pattern/domain name - return false; - } - - // Normalize hostname and pattern by turning them into absolute domain names if they are not - // yet absolute. This is needed because server certificates do not normally contain absolute - // names or patterns, but they should be treated as absolute. At the same time, any hostname - // presented to this method should also be treated as absolute for the purposes of matching - // to the server certificate. - // www.android.com matches www.android.com - // www.android.com matches www.android.com. - // www.android.com. matches www.android.com. - // www.android.com. matches www.android.com - if (!hostname.endsWith(".")) { - hostname += '.'; - } - if (!pattern.endsWith(".")) { - pattern += '.'; - } - // hostname and pattern are now absolute domain names. - - pattern = pattern.toLowerCase(Locale.US); - // hostname and pattern are now in lower case -- domain names are case-insensitive. - - if (!pattern.contains("*")) { - // Not a wildcard pattern -- hostname and pattern must match exactly. - return hostname.equals(pattern); - } - // Wildcard pattern - - // WILDCARD PATTERN RULES: - // 1. Asterisk (*) is only permitted in the left-most domain name label and must be the - // only character in that label (i.e., must match the whole left-most label). - // For example, *.example.com is permitted, while *a.example.com, a*.example.com, - // a*b.example.com, a.*.example.com are not permitted. - // 2. Asterisk (*) cannot match across domain name labels. - // For example, *.example.com matches test.example.com but does not match - // sub.test.example.com. - // 3. Wildcard patterns for single-label domain names are not permitted. - - if ((!pattern.startsWith("*.")) || (pattern.indexOf('*', 1) != -1)) { - // Asterisk (*) is only permitted in the left-most domain name label and must be the only - // character in that label - return false; - } - - // Optimization: check whether hostname is too short to match the pattern. hostName must be at - // least as long as the pattern because asterisk must match the whole left-most label and - // hostname starts with a non-empty label. Thus, asterisk has to match one or more characters. - if (hostname.length() < pattern.length()) { - // hostname too short to match the pattern. - return false; - } - - if ("*.".equals(pattern)) { - // Wildcard pattern for single-label domain name -- not permitted. - return false; - } - - // hostname must end with the region of pattern following the asterisk. - String suffix = pattern.substring(1); - if (!hostname.endsWith(suffix)) { - // hostname does not end with the suffix - return false; - } - - // Check that asterisk did not match across domain name labels. - int suffixStartIndexInHostname = hostname.length() - suffix.length(); - if ((suffixStartIndexInHostname > 0) - && (hostname.lastIndexOf('.', suffixStartIndexInHostname - 1) != -1)) { - // Asterisk is matching across domain name labels -- not permitted. - return false; - } - - // hostname matches pattern - return true; - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/tls/RealTrustRootIndex.java b/okhttp3/src/main/java/okhttp3/internal/tls/RealTrustRootIndex.java deleted file mode 100644 index fb688328f..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/tls/RealTrustRootIndex.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2016 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.tls; - -import java.security.PublicKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import javax.security.auth.x500.X500Principal; - -public final class RealTrustRootIndex implements TrustRootIndex { - private final Map> subjectToCaCerts; - - public RealTrustRootIndex(X509Certificate... caCerts) { - subjectToCaCerts = new LinkedHashMap<>(); - for (X509Certificate caCert : caCerts) { - X500Principal subject = caCert.getSubjectX500Principal(); - List subjectCaCerts = subjectToCaCerts.get(subject); - if (subjectCaCerts == null) { - subjectCaCerts = new ArrayList<>(1); - subjectToCaCerts.put(subject, subjectCaCerts); - } - subjectCaCerts.add(caCert); - } - } - - @Override public X509Certificate findByIssuerAndSignature(X509Certificate cert) { - X500Principal issuer = cert.getIssuerX500Principal(); - List subjectCaCerts = subjectToCaCerts.get(issuer); - if (subjectCaCerts == null) return null; - - for (X509Certificate caCert : subjectCaCerts) { - PublicKey publicKey = caCert.getPublicKey(); - try { - cert.verify(publicKey); - return caCert; - } catch (Exception ignored) { - } - } - - return null; - } -} diff --git a/okhttp3/src/main/java/okhttp3/internal/tls/TrustRootIndex.java b/okhttp3/src/main/java/okhttp3/internal/tls/TrustRootIndex.java deleted file mode 100644 index 499f120e3..000000000 --- a/okhttp3/src/main/java/okhttp3/internal/tls/TrustRootIndex.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2016 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.internal.tls; - -import java.security.cert.X509Certificate; - -public interface TrustRootIndex { - /** Returns the trusted CA certificate that signed {@code cert}. */ - X509Certificate findByIssuerAndSignature(X509Certificate cert); -} diff --git a/okhttp3/src/test/java/com/squareup/okhttp3/ExampleUnitTest.java b/okhttp3/src/test/java/com/squareup/okhttp3/ExampleUnitTest.java deleted file mode 100644 index d6ba9a7ea..000000000 --- a/okhttp3/src/test/java/com/squareup/okhttp3/ExampleUnitTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2016 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.squareup.okhttp3; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * To work on unit tests, switch the Test Artifact in the Build Variants view. - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 667c8e809..2b0e21a81 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', 'beerbelly', 'conaco', ':daogenerator', ':easyrecyclerview', ':okhttp3', 'yorozuya' +include ':app', ':daogenerator' diff --git a/yorozuya b/yorozuya deleted file mode 160000 index 2fbe76c05..000000000 --- a/yorozuya +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2fbe76c0537f322616583ff4b4d1e5a64168f498