diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95776c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.gradle/ +.idea/ +*.iml +build/ +local.properties diff --git a/.travis.yml b/.travis.yml index bb298ee..e4328a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,14 +15,14 @@ android: - android-sdk-license-.+ - '.+' before_script: - - wget http://services.gradle.org/distributions/gradle-4.4-all.zip - - unzip gradle-4.4-all.zip - - export GRADLE_HOME=$PWD/gradle-4.4 - - export PATH=$GRADLE_HOME/bin:$PATH - - mkdir "$ANDROID_HOME/licenses" || true - - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license" - - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license" - - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a - - emulator -avd test -no-audio -no-window & - - android-wait-for-emulator - - adb shell input keyevent 82 & + - wget http://services.gradle.org/distributions/gradle-4.4-all.zip + - unzip gradle-4.4-all.zip + - export GRADLE_HOME=$PWD/gradle-4.4 + - export PATH=$GRADLE_HOME/bin:$PATH + - mkdir "$ANDROID_HOME/licenses" || true + - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license" + - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license" + - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a + - emulator -avd test -no-audio -no-window & + - android-wait-for-emulator + - adb shell input keyevent 82 & diff --git a/README.md b/README.md index 8460f7f..34a3b6f 100644 --- a/README.md +++ b/README.md @@ -59,11 +59,6 @@ schema的协议格式为:jdhttpmonitor://webview?param={'url'='http://www.dark 1. 信任所有的服务器证书不做校验
~~2. 开启返回包注入功能后,https返回的部分页面存在 err_CONTENT_LENGTH_MISMATCH 错误
~~(看起来似乎是解决了,待用户反馈) -#### 如果觉得工具好用的话请多多star以及Pull requests
支持我喝杯咖啡请扫描下面的二维码,谢谢(ง •̀_•́)ง
-![image](http://h5.darkal.cn/har/guide/img/code.jpg)

- -#### 相关技术交流可以加入QQ群:816839175
-![image](http://h5.darkal.cn/har/guide/img/qq.jpg)

### 致谢
AndroidHttpCapture基于Netty、browsermob-proxy来实现核心抓包的功能
diff --git a/app/app.iml b/app/app.iml deleted file mode 100644 index 16c1323..0000000 --- a/app/app.iml +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 73adbe9..fa755a3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -58,11 +58,26 @@ android { } productFlavors { } + aaptOptions { + cruncherEnabled = false + useNewCruncher = false + } lintOptions { + checkReleaseBuilds false abortOnError false + warningsAsErrors false + disable "UnusedResources" + textOutput "stdout" + textReport true } } +tasks.withType(Javadoc) { + options.addStringOption('Xdoclint:none', '-quiet') + options.addStringOption('encoding', 'UTF-8') + options.addStringOption('charSet', 'UTF-8') +} + dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') // Bugly上报 diff --git a/app/doc/MCCMNCs v3.xlsx b/app/doc/MCCMNCs v3.xlsx new file mode 100644 index 0000000..abca7a7 Binary files /dev/null and b/app/doc/MCCMNCs v3.xlsx differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 857a7a5..48f1f0d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,28 +1,35 @@ - + - + - + @@ -104,10 +111,8 @@ - - + + diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Activity/AppCompatPreferenceActivity.java b/app/src/main/java/cn/darkal/networkdiagnosis/Activity/AbstractAppCompatPreferenceActivity.java similarity index 96% rename from app/src/main/java/cn/darkal/networkdiagnosis/Activity/AppCompatPreferenceActivity.java rename to app/src/main/java/cn/darkal/networkdiagnosis/Activity/AbstractAppCompatPreferenceActivity.java index a797260..ead622a 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Activity/AppCompatPreferenceActivity.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Activity/AbstractAppCompatPreferenceActivity.java @@ -16,7 +16,7 @@ * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls * to be used with AppCompat. */ -public abstract class AppCompatPreferenceActivity extends PreferenceActivity { +public abstract class AbstractAppCompatPreferenceActivity extends PreferenceActivity { private AppCompatDelegate mDelegate; @@ -96,6 +96,7 @@ protected void onDestroy() { getDelegate().onDestroy(); } + @Override public void invalidateOptionsMenu() { getDelegate().invalidateOptionsMenu(); } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Activity/ChangeFilterActivity.java b/app/src/main/java/cn/darkal/networkdiagnosis/Activity/ChangeFilterActivity.java index 5c75f26..24a9e1b 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Activity/ChangeFilterActivity.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Activity/ChangeFilterActivity.java @@ -1,11 +1,11 @@ package cn.darkal.networkdiagnosis.Activity; import android.content.DialogInterface; +import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; -import android.os.Bundle; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -14,7 +14,6 @@ import android.widget.RelativeLayout; import java.util.ArrayList; -import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; @@ -22,7 +21,6 @@ import cn.darkal.networkdiagnosis.Bean.ResponseFilterRule; import cn.darkal.networkdiagnosis.R; import cn.darkal.networkdiagnosis.SysApplication; -import cn.darkal.networkdiagnosis.Utils.DeviceUtils; import cn.darkal.networkdiagnosis.Utils.SharedPreferenceUtils; public class ChangeFilterActivity extends AppCompatActivity { @@ -44,10 +42,10 @@ protected void onCreate(Bundle savedInstanceState) { ButterKnife.bind(this); setupActionBar(); - if(((SysApplication)getApplication()).ruleList == null){ - contentFilterAdapter = new ContentFilterAdapter(this,new ArrayList()); - }else{ - contentFilterAdapter = new ContentFilterAdapter(this,((SysApplication)getApplication()).ruleList); + if (((SysApplication) getApplication()).ruleList == null) { + contentFilterAdapter = new ContentFilterAdapter(this, new ArrayList()); + } else { + contentFilterAdapter = new ContentFilterAdapter(this, ((SysApplication) getApplication()).ruleList); } listView.setAdapter(contentFilterAdapter); @@ -72,19 +70,19 @@ private void setupActionBar() { } } - public void showDialog(final ResponseFilterRule responseFilterRule){ + public void showDialog(final ResponseFilterRule responseFilterRule) { AlertDialog.Builder builder = new AlertDialog.Builder(ChangeFilterActivity.this); View textEntryView = LayoutInflater.from(ChangeFilterActivity.this).inflate(R.layout.alert_resp_filter, null); final EditText urlEditText = (EditText) textEntryView.findViewById(R.id.et_origin_url); final EditText regexEditText = (EditText) textEntryView.findViewById(R.id.et_regex); final EditText contentEditText = (EditText) textEntryView.findViewById(R.id.et_replace_result); - if(responseFilterRule!=null){ + if (responseFilterRule != null) { urlEditText.setText(responseFilterRule.getUrl()); regexEditText.setText(responseFilterRule.getReplaceRegex()); contentEditText.setText(responseFilterRule.getReplaceContent()); builder.setTitle("修改注入项"); - }else{ + } else { builder.setTitle("新增注入项"); } @@ -93,13 +91,13 @@ public void showDialog(final ResponseFilterRule responseFilterRule){ builder.setPositiveButton("确认", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - if(responseFilterRule!=null){ + if (responseFilterRule != null) { responseFilterRule.setUrl(urlEditText.getText().toString()); responseFilterRule.setReplaceRegex(regexEditText.getText().toString()); responseFilterRule.setReplaceContent(contentEditText.getText().toString()); - }else { - if(urlEditText.getText().length()>0 && regexEditText.getText().length()>0 - && contentEditText.getText().length()>0) { + } else { + if (urlEditText.getText().length() > 0 && regexEditText.getText().length() > 0 + && contentEditText.getText().length() > 0) { ResponseFilterRule responseFilterRule = new ResponseFilterRule(); responseFilterRule.setUrl(urlEditText.getText().toString()); responseFilterRule.setReplaceRegex(regexEditText.getText().toString()); @@ -110,14 +108,14 @@ public void onClick(DialogInterface dialog, int which) { contentFilterAdapter.notifyDataSetChanged(); } }); - builder.setNegativeButton("取消",null); + builder.setNegativeButton("取消", null); builder.show(); } @Override protected void onStop() { SharedPreferenceUtils.save(getApplicationContext(), - "response_filter",((SysApplication) getApplication()).ruleList); + "response_filter", ((SysApplication) getApplication()).ruleList); super.onStop(); } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Activity/HarDetailActivity.java b/app/src/main/java/cn/darkal/networkdiagnosis/Activity/HarDetailActivity.java index ee96669..1652249 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Activity/HarDetailActivity.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Activity/HarDetailActivity.java @@ -5,7 +5,6 @@ import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.MenuItem; @@ -76,7 +75,7 @@ public void initHarLog(int pos) { addItem("Request Header"); for (HarNameValuePair pair : harRequest.getHeaders()) { // 不显示cookie - if (!pair.getName().equals("Cookie")) { + if (!"Cookie".equals(pair.getName())) { addItem(pair.getName(), pair.getDecodeValue()); } } @@ -89,14 +88,14 @@ public void initHarLog(int pos) { } if (harRequest.getPostData() != null) { - if(harRequest.getPostData().getText()!= null - && harRequest.getPostData().getText().length()>0) { + if (harRequest.getPostData().getText() != null + && harRequest.getPostData().getText().length() > 0) { addItem("Request Content"); addItem("PostData", harRequest.getPostData().getText()); } - if(harRequest.getPostData().getParams()!= null - && harRequest.getPostData().getParams().size()>0){ + if (harRequest.getPostData().getParams() != null + && harRequest.getPostData().getParams().size() > 0) { addItem("Request PostData"); for (HarPostDataParam pair : harRequest.getPostData().getParams()) { @@ -107,7 +106,7 @@ public void initHarLog(int pos) { addItem("Response Header"); for (HarNameValuePair pair : harResponse.getHeaders()) { - if (!pair.getName().equals("Cookie")) { + if (!"Cookie".equals(pair.getName())) { addItem(pair.getName(), pair.getDecodeValue()); } } @@ -145,7 +144,7 @@ public void addContentItem(String title, final String value, final int pos) { valueTextView.setText(value.substring(0, value.length() > 50 ? 50 : value.length())); } - if (title.equals("Content")) { + if ("Content".equals(title)) { view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Activity/JsonPreviewActivity.java b/app/src/main/java/cn/darkal/networkdiagnosis/Activity/JsonPreviewActivity.java index bda5e8c..b8d56cb 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Activity/JsonPreviewActivity.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Activity/JsonPreviewActivity.java @@ -6,7 +6,6 @@ import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.widget.TextView; @@ -39,6 +38,7 @@ public class JsonPreviewActivity extends AppCompatActivity { private Handler mHandler = new Handler(); private String content; private int selectedEncode = 0; + private String[] encodeItem = new String[]{"UTF-8", "ISO-8859-1", "GBK"}; @Override protected void onCreate(Bundle savedInstanceState) { @@ -52,13 +52,13 @@ protected void onCreate(Bundle savedInstanceState) { setupActionBar(); try { - int pos = getIntent().getIntExtra("pos",-1); - if(pos > -1){ + int pos = getIntent().getIntExtra("pos", -1); + if (pos > -1) { HarLog harLog = ((SysApplication) getApplication()).proxy.getHar().getLog(); HarEntry harEntry = harLog.getEntries().get(pos); content = harEntry.getResponse().getContent().getText(); initViewDelay(content); - }else{ + } else { finish(); } } catch (Exception e) { @@ -120,8 +120,6 @@ public String jsonFormatter(String uglyJSONString) throws Exception { return gson.toJson(je); } - private String[] encodeItem = new String[]{"UTF-8", "ISO-8859-1", "GBK"}; - @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.json_menu, menu); @@ -131,9 +129,9 @@ public boolean onCreateOptionsMenu(Menu menu) { public boolean onMenuItemClick(MenuItem item) { DialogInterface.OnClickListener listener = new ButtonOnClick(); AlertDialog.Builder builder = new AlertDialog.Builder(JsonPreviewActivity.this); - builder.setNegativeButton("取消",null); + builder.setNegativeButton("取消", null); builder.setPositiveButton("确认", listener); - builder.setSingleChoiceItems(encodeItem,selectedEncode,listener); + builder.setSingleChoiceItems(encodeItem, selectedEncode, listener); builder.create().show(); return true; } @@ -143,6 +141,24 @@ public boolean onMenuItemClick(MenuItem item) { return super.onCreateOptionsMenu(menu); } + public void changeEncode(int pos) { + switch (pos) { + case 0: + initViewDelay(content); + break; + case 1: + initViewDelay(new String(content.getBytes(Charset.forName("ISO-8859-1")), Charset.forName("UTF-8"))); + break; + case 2: + initViewDelay(new String(content.getBytes(Charset.forName("GBK")), Charset.forName("UTF-8"))); + break; + default: + initViewDelay(content); + break; + } + + } + private class ButtonOnClick implements DialogInterface.OnClickListener { private int index = -1; // 表示选项的索引 @@ -160,22 +176,4 @@ public void onClick(DialogInterface dialog, int which) { } } } - - public void changeEncode(int pos){ - switch (pos){ - case 0: - initViewDelay(content); - break; - case 1: - initViewDelay(new String(content.getBytes(Charset.forName("ISO-8859-1")), Charset.forName("UTF-8"))); - break; - case 2: - initViewDelay(new String(content.getBytes(Charset.forName("GBK")), Charset.forName("UTF-8"))); - break; - default: - initViewDelay(content); - break; - } - - } } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Activity/MainActivity.java b/app/src/main/java/cn/darkal/networkdiagnosis/Activity/MainActivity.java index b6ff32c..9138823 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Activity/MainActivity.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Activity/MainActivity.java @@ -75,8 +75,8 @@ import butterknife.ButterKnife; import cn.darkal.networkdiagnosis.Adapter.PageFilterAdapter; import cn.darkal.networkdiagnosis.Bean.PageBean; -import cn.darkal.networkdiagnosis.Fragment.BaseFragment; import cn.darkal.networkdiagnosis.Fragment.BackHandledInterface; +import cn.darkal.networkdiagnosis.Fragment.BaseFragment; import cn.darkal.networkdiagnosis.Fragment.NetworkFragment; import cn.darkal.networkdiagnosis.Fragment.PreviewFragment; import cn.darkal.networkdiagnosis.Fragment.WebViewFragment; @@ -101,51 +101,99 @@ public class MainActivity extends AppCompatActivity implements BackHandledInterf public final static int TYPE_NONE = 0; public final static int TYPE_SHARE = 1; public final static int TYPE_UPLOAD = 2; - - private int mLastHeightOfContainer; // 记录容器上一次的高度,用于检测高度变化 - private int mHeightOfVisibility; - Boolean isKeyboardOpen = false; - Boolean shouldExitSearchView = false; - - private BaseFragment mBackHandedFragment; - private long exitTime = 0; - - private Receiver receiver; - @BindView(R.id.fl_contain) public View rootView; - @BindView(R.id.nav_view) public NavigationView navigationView; - @BindView(R.id.fab) public FloatingActionMenu fam; - @BindView(R.id.fab_share) public FloatingActionButton shareFab; - @BindView(R.id.fab_upload) public FloatingActionButton uploadFab; - @BindView(R.id.fab_preview) public FloatingActionButton previewFab; - @BindView(R.id.fab_clear) public FloatingActionButton clearFab; - -// int lastX, lastY; -// Boolean isMove = false; - public SearchView searchView; public MenuItem homeItem; public MenuItem searchItem; public MenuItem filterMenuItem; - public Set disablePages = new HashSet<>(); public StringBuffer consoleLog = new StringBuffer(); - public SharedPreferences shp; + // int lastX, lastY; +// Boolean isMove = false; + Boolean isKeyboardOpen = false; + Boolean shouldExitSearchView = false; + private int mLastHeightOfContainer; // 记录容器上一次的高度,用于检测高度变化 + private int mHeightOfVisibility; + private BaseFragment mBackHandedFragment; + private long exitTime = 0; + private Receiver receiver; + private LoadingDialog loadingDialog; + private String[] uaItem = new String[]{"手机浏览器", "微信环境", "手Q环境"}; + public NavigationView.OnNavigationItemSelectedListener navigationItemListener = new NavigationView.OnNavigationItemSelectedListener() { + @Override + public boolean onNavigationItemSelected(MenuItem item) { + // Handle navigation view item clicks here. + int id = item.getItemId(); + + if (!SysApplication.isInitProxy) { + Toast.makeText(MainActivity.this, "请等待程序初始化完成", Toast.LENGTH_LONG).show(); + return true; + } + + switch (id) { + case R.id.nav_camera: { + Intent intent = new Intent(MainActivity.this, QrCodeScanActivity.class); + startActivity(intent); + break; + } + case R.id.nav_gallery: + switchContent(WebViewFragment.getInstance()); + break; + case R.id.nav_preview: + switchContent(PreviewFragment.getInstance()); + break; + case R.id.nav_slideshow: + switchContent(NetworkFragment.getInstance()); + break; + case R.id.nav_manage: { + Intent intent = new Intent(MainActivity.this, SettingsActivity.class); + startActivity(intent); + break; + } + case R.id.nav_ua: + showUaDialog(); + break; + case R.id.nav_modify: + if (shp.getBoolean("enable_filter", false)) { + Intent intent = new Intent(MainActivity.this, ChangeFilterActivity.class); + startActivity(intent); + } else { + Toast.makeText(MainActivity.this, "请前往设置启用注入功能", Toast.LENGTH_LONG).show(); + } + break; + case R.id.nav_cosole: + showLogDialog(); + break; + case R.id.nav_host: + showHostDialog(); + break; + case R.id.nav_page: + createPage(); + break; + default: + break; + } + + DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); + drawer.closeDrawer(GravityCompat.START); + return true; + } + }; @Override protected void onCreate(Bundle savedInstanceState) { @@ -339,13 +387,13 @@ public void changeStateBar(Fragment fragment) { public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); - if(id == R.id.action_home){ + if (id == R.id.action_home) { WebViewFragment webViewFragment = WebViewFragment.getInstance(); webViewFragment.loadUrl(HOME_URL); switchContent(webViewFragment); return true; } - if(id == R.id.action_guide){ + if (id == R.id.action_guide) { WebViewFragment webViewFragment = WebViewFragment.getInstance(); webViewFragment.loadUrl(GUIDE_URL); switchContent(webViewFragment); @@ -365,65 +413,6 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } - public NavigationView.OnNavigationItemSelectedListener navigationItemListener = new NavigationView.OnNavigationItemSelectedListener() { - @Override - public boolean onNavigationItemSelected(MenuItem item) { - // Handle navigation view item clicks here. - int id = item.getItemId(); - - if (!SysApplication.isInitProxy) { - Toast.makeText(MainActivity.this, "请等待程序初始化完成", Toast.LENGTH_LONG).show(); - return true; - } - - switch (id) { - case R.id.nav_camera: { - Intent intent = new Intent(MainActivity.this, QrCodeScanActivity.class); - startActivity(intent); - break; - } - case R.id.nav_gallery: - switchContent(WebViewFragment.getInstance()); - break; - case R.id.nav_preview: - switchContent(PreviewFragment.getInstance()); - break; - case R.id.nav_slideshow: - switchContent(NetworkFragment.getInstance()); - break; - case R.id.nav_manage: { - Intent intent = new Intent(MainActivity.this, SettingsActivity.class); - startActivity(intent); - break; - } - case R.id.nav_ua: - showUaDialog(); - break; - case R.id.nav_modify: - if (shp.getBoolean("enable_filter", false)) { - Intent intent = new Intent(MainActivity.this, ChangeFilterActivity.class); - startActivity(intent); - } else { - Toast.makeText(MainActivity.this, "请前往设置启用注入功能", Toast.LENGTH_LONG).show(); - } - break; - case R.id.nav_cosole: - showLogDialog(); - break; - case R.id.nav_host: - showHostDialog(); - break; - case R.id.nav_page: - createPage(); - break; - } - - DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); - drawer.closeDrawer(GravityCompat.START); - return true; - } - }; - /** * 修改显示的内容 不会重新加载 **/ @@ -500,7 +489,7 @@ public void run() { if (!isInstallCert) { Toast.makeText(this, "必须安装证书才可实现HTTPS抓包", Toast.LENGTH_LONG).show(); - FileUtil.checkPermission(this,runnable); + FileUtil.checkPermission(this, runnable); } } @@ -508,7 +497,7 @@ public void run() { public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == 3) { if (resultCode == Activity.RESULT_OK) { - SharedPreferenceUtils.putBoolean(this,"isInstallNewCert", true); + SharedPreferenceUtils.putBoolean(this, "isInstallNewCert", true); Toast.makeText(this, "安装成功", Toast.LENGTH_LONG).show(); } else { @@ -558,42 +547,16 @@ public boolean isShouldHideInput(View view, MotionEvent event) { return true; } - private class OnGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener { - private View mView; - - public OnGlobalLayoutListener(View view) { - mView = view; - } - - @Override - public void onGlobalLayout() { - int currentHeight = mView.getHeight(); - if (currentHeight < mLastHeightOfContainer) { // 软键盘打开 - if (mHeightOfVisibility == 0) { - mHeightOfVisibility = currentHeight; - } - isKeyboardOpen = true; - } else if (currentHeight > mLastHeightOfContainer && mLastHeightOfContainer != 0) { // 软键盘关闭 - isKeyboardOpen = false; - // 隐藏搜索框 - if (shouldExitSearchView) { - searchItem.collapseActionView(); - } - } - mLastHeightOfContainer = currentHeight; - } - } - @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); try { setIntent(intent); handleUriStartupParams(); - if (intent.getAction().equals("android.intent.action.SEARCH")) { + if ("android.intent.action.SEARCH".equals(intent.getAction())) { switchContent(PreviewFragment.getInstance()); } - }catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); } } @@ -612,14 +575,6 @@ protected void onStop() { super.onStop(); } - public class Receiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - installCert(); - Log.i("~~~~", "Receiver installCert"); - } - } - /** * 启动的时候根据bundle参数决定切换到哪个tab */ @@ -702,7 +657,7 @@ public void run() { } }; - FileUtil.checkPermission(this,runnable); + FileUtil.checkPermission(this, runnable); } public void shareZip() { @@ -725,46 +680,6 @@ public void uploadZip() { showUploadDialog(this); } - public class MyUploadDelegate implements UploadStatusDelegate { - @Override - public void onProgress(Context context, UploadInfo uploadInfo) { - Log.e("~~~~", uploadInfo.getProgressPercent() + ""); - } - - @Override - public void onError(Context context, UploadInfo uploadInfo, ServerResponse serverResponse, Exception exception) { - dismissLoading(); - Snackbar.make(rootView, "上传失败!", Snackbar.LENGTH_LONG).setAction("Action", null).show(); - exception.printStackTrace(); - CrashReport.postCatchedException(exception); - } - - @Override - public void onCompleted(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) { - try { - JSONObject jsonObject = new JSONObject(serverResponse.getBodyAsString()); - if (jsonObject.getInt("errId") == 0) { - Snackbar.make(rootView, "上传成功!", Snackbar.LENGTH_LONG).setAction("Action", null).show(); - } else if (jsonObject.getInt("errId") == 2 || jsonObject.getInt("errId") == 11004) { - Snackbar.make(rootView, "验证码错误!", Snackbar.LENGTH_LONG).setAction("Action", null).show(); - showUploadDialog(MainActivity.this); - } else { - Snackbar.make(rootView, "上传失败!", Snackbar.LENGTH_LONG).setAction("Action", null).show(); - } - } catch (Exception e) { - Snackbar.make(rootView, "上传失败!", Snackbar.LENGTH_LONG).setAction("Action", null).show(); - } - dismissLoading(); - } - - @Override - public void onCancelled(Context context, UploadInfo uploadInfo) { - dismissLoading(); - } - } - - private LoadingDialog loadingDialog; - public void showLoading(String text) { try { if (loadingDialog == null) { @@ -808,6 +723,7 @@ public void onClick(View view) { builder.setTitle("请输入验证码"); builder.setView(textEntryView); builder.setPositiveButton("确认", new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(edtInput.getWindowToken(), 0); @@ -824,6 +740,7 @@ public void run() { }); builder.setNegativeButton("取消", new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(edtInput.getWindowToken(), 0); @@ -934,20 +851,6 @@ public Har getFiltedHar() { return proxy.getHar(getPageSet()); } -// @Override -// protected void onSaveInstanceState(Bundle outState) { -// int tab; -// if (mBackHandedFragment instanceof PreviewFragment) { -// tab = 3; -// } else if (mBackHandedFragment instanceof NetworkFragment) { -// tab = 2; -// } else { -// tab = 1; -// } -// outState.putInt("tab", tab); -// super.onSaveInstanceState(outState); -// } - public void initFloatingActionMenu() { fam.setClosedOnTouchOutside(true); AnimatorSet set = new AnimatorSet(); @@ -969,10 +872,10 @@ public void initFloatingActionMenu() { public void onAnimationStart(Animator animation) { fam.getMenuIconView().setImageResource(fam.isOpened() ? R.drawable.ic_file_upload_white_24dp : R.drawable.ic_close_white_24dp); - if(mBackHandedFragment instanceof PreviewFragment){ - if(fam.isOpened()){ + if (mBackHandedFragment instanceof PreviewFragment) { + if (fam.isOpened()) { clearFab.show(true); - }else { + } else { clearFab.hide(true); } } @@ -988,7 +891,7 @@ public void onAnimationStart(Animator animation) { shareFab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - showFilter(MainActivity.this,TYPE_SHARE); + showFilter(MainActivity.this, TYPE_SHARE); fam.close(true); } }); @@ -996,7 +899,7 @@ public void onClick(View v) { uploadFab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - showFilter(MainActivity.this,TYPE_UPLOAD); + showFilter(MainActivity.this, TYPE_UPLOAD); fam.close(true); } }); @@ -1015,12 +918,14 @@ public void onClick(View v) { AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setTitle("请确认是否清除所有请求?"); builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { - ((SysApplication)getApplication()).proxy.getHar().getLog().clearAllEntries(); + ((SysApplication) getApplication()).proxy.getHar().getLog().clearAllEntries(); PreviewFragment.getInstance().notifyHarChange(); } }); builder.setNegativeButton("取消", new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { } }); @@ -1029,8 +934,6 @@ public void onClick(DialogInterface dialog, int whichButton) { }); } - private String[] uaItem = new String[]{"手机浏览器", "微信环境", "手Q环境"}; - public void showUaDialog() { DialogInterface.OnClickListener buttonListener = new ButtonOnClick(); AlertDialog.Builder builder = new AlertDialog.Builder(this); @@ -1049,25 +952,21 @@ public void showUaDialog() { builder.create().show(); } - private class ButtonOnClick implements DialogInterface.OnClickListener { - - private int index = -1; // 表示选项的索引 - - @Override - public void onClick(DialogInterface dialog, int which) { - if (which >= 0) { - index = which; - } else { - //用户单击的是【确定】按钮 - if (which == DialogInterface.BUTTON_POSITIVE) { - SharedPreferenceUtils.putString(MainActivity.this, "select_ua", index + ""); - WebViewFragment.getInstance().setUserAgent(); - } - } - } - } +// @Override +// protected void onSaveInstanceState(Bundle outState) { +// int tab; +// if (mBackHandedFragment instanceof PreviewFragment) { +// tab = 3; +// } else if (mBackHandedFragment instanceof NetworkFragment) { +// tab = 2; +// } else { +// tab = 1; +// } +// outState.putInt("tab", tab); +// super.onSaveInstanceState(outState); +// } - public void showLogDialog(){ + public void showLogDialog() { View textEntryView = LayoutInflater.from(this).inflate(R.layout.alert_textview, null); TextView edtInput = (TextView) textEntryView.findViewById(R.id.tv_content); edtInput.setText(consoleLog); @@ -1086,7 +985,7 @@ public void onClick(DialogInterface dialog, int which) { builder.show(); } - public void showHostDialog(){ + public void showHostDialog() { View textEntryView = LayoutInflater.from(this).inflate(R.layout.alert_edittext, null); final EditText editText = (EditText) textEntryView.findViewById(R.id.et_content); @@ -1103,8 +1002,8 @@ public void showHostDialog(){ public void onClick(DialogInterface dialog, int which) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); - SharedPreferenceUtils.putString(MainActivity.this, "system_host", editText.getText()+""); - DeviceUtils.changeHost(((SysApplication)getApplication()).proxy,editText.getText()+""); + SharedPreferenceUtils.putString(MainActivity.this, "system_host", editText.getText() + ""); + DeviceUtils.changeHost(((SysApplication) getApplication()).proxy, editText.getText() + ""); } }); builder.setNegativeButton("清空", new DialogInterface.OnClickListener() { @@ -1113,9 +1012,99 @@ public void onClick(DialogInterface dialog, int which) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); SharedPreferenceUtils.putString(MainActivity.this, "system_host", ""); - DeviceUtils.changeHost(((SysApplication)getApplication()).proxy,editText.getText()+""); + DeviceUtils.changeHost(((SysApplication) getApplication()).proxy, editText.getText() + ""); } }); builder.show(); } + + private class OnGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener { + private View mView; + + public OnGlobalLayoutListener(View view) { + mView = view; + } + + @Override + public void onGlobalLayout() { + int currentHeight = mView.getHeight(); + if (currentHeight < mLastHeightOfContainer) { // 软键盘打开 + if (mHeightOfVisibility == 0) { + mHeightOfVisibility = currentHeight; + } + isKeyboardOpen = true; + } else if (currentHeight > mLastHeightOfContainer && mLastHeightOfContainer != 0) { // 软键盘关闭 + isKeyboardOpen = false; + // 隐藏搜索框 + if (shouldExitSearchView) { + searchItem.collapseActionView(); + } + } + mLastHeightOfContainer = currentHeight; + } + } + + public class Receiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + installCert(); + Log.i("~~~~", "Receiver installCert"); + } + } + + public class MyUploadDelegate implements UploadStatusDelegate { + @Override + public void onProgress(Context context, UploadInfo uploadInfo) { + Log.e("~~~~", uploadInfo.getProgressPercent() + ""); + } + + @Override + public void onError(Context context, UploadInfo uploadInfo, ServerResponse serverResponse, Exception exception) { + dismissLoading(); + Snackbar.make(rootView, "上传失败!", Snackbar.LENGTH_LONG).setAction("Action", null).show(); + exception.printStackTrace(); + CrashReport.postCatchedException(exception); + } + + @Override + public void onCompleted(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) { + try { + JSONObject jsonObject = new JSONObject(serverResponse.getBodyAsString()); + if (jsonObject.getInt("errId") == 0) { + Snackbar.make(rootView, "上传成功!", Snackbar.LENGTH_LONG).setAction("Action", null).show(); + } else if (jsonObject.getInt("errId") == 2 || jsonObject.getInt("errId") == 11004) { + Snackbar.make(rootView, "验证码错误!", Snackbar.LENGTH_LONG).setAction("Action", null).show(); + showUploadDialog(MainActivity.this); + } else { + Snackbar.make(rootView, "上传失败!", Snackbar.LENGTH_LONG).setAction("Action", null).show(); + } + } catch (Exception e) { + Snackbar.make(rootView, "上传失败!", Snackbar.LENGTH_LONG).setAction("Action", null).show(); + } + dismissLoading(); + } + + @Override + public void onCancelled(Context context, UploadInfo uploadInfo) { + dismissLoading(); + } + } + + private class ButtonOnClick implements DialogInterface.OnClickListener { + + private int index = -1; // 表示选项的索引 + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which >= 0) { + index = which; + } else { + //用户单击的是【确定】按钮 + if (which == DialogInterface.BUTTON_POSITIVE) { + SharedPreferenceUtils.putString(MainActivity.this, "select_ua", index + ""); + WebViewFragment.getInstance().setUserAgent(); + } + } + } + } } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Activity/SettingsActivity.java b/app/src/main/java/cn/darkal/networkdiagnosis/Activity/SettingsActivity.java index e038f6a..234ee8a 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Activity/SettingsActivity.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Activity/SettingsActivity.java @@ -43,10 +43,11 @@ * href="http://developer.android.com/guide/topics/ui/settings.html">Settings * API Guide for more information on developing a Settings UI. */ -public class SettingsActivity extends AppCompatPreferenceActivity implements Preference.OnPreferenceChangeListener { +public class SettingsActivity extends AbstractAppCompatPreferenceActivity implements Preference.OnPreferenceChangeListener { ListPreference lp;//创建一个ListPreference对象 Preference hostPreference; + private LoadingDialog loadingDialog; /** * Helper method to determine if the device has an extra-large screen. For @@ -127,7 +128,6 @@ public boolean onIsMultiPane() { return isXLargeTablet(this); } - //让所选择的项显示出来,获取变化并显示出来 @Override public boolean onPreferenceChange(Preference preference, Object newValue) { @@ -143,13 +143,13 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } // 设置hosts - if (preference.getKey().equals("system_host")) { - DeviceUtils.changeHost(((SysApplication)getApplication()).proxy,newValue.toString()); + if ("system_host".equals(preference.getKey())) { + DeviceUtils.changeHost(((SysApplication) getApplication()).proxy, newValue.toString()); hostPreference.setSummary(getHost()); } // 重启抓包进程 - if (preference.getKey().equals("enable_filter")) { + if ("enable_filter".equals(preference.getKey())) { Toast.makeText(this, "重启程序后生效", Toast.LENGTH_SHORT).show(); // ((SysApplication)getApplication()).stopProxy(); // ((SysApplication)getApplication()).startProxy(); @@ -158,8 +158,6 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { return true; } - - public void installCert() { final String CERTIFICATE_RESOURCE = Environment.getExternalStorageDirectory() + "/har/littleproxy-mitm.pem"; Toast.makeText(this, "必须安装证书才可实现HTTPS抓包", Toast.LENGTH_LONG).show(); @@ -188,11 +186,9 @@ public void run() { } }; - FileUtil.checkPermission(this,runnable); + FileUtil.checkPermission(this, runnable); } - private LoadingDialog loadingDialog; - public void showLoading(final String text) { runOnUiThread(new Runnable() { @Override diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Adapter/ContentFilterAdapter.java b/app/src/main/java/cn/darkal/networkdiagnosis/Adapter/ContentFilterAdapter.java index 2ca0355..3cd1c39 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Adapter/ContentFilterAdapter.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Adapter/ContentFilterAdapter.java @@ -12,29 +12,23 @@ import java.util.List; import cn.darkal.networkdiagnosis.Activity.ChangeFilterActivity; -import cn.darkal.networkdiagnosis.Activity.MainActivity; import cn.darkal.networkdiagnosis.BR; -import cn.darkal.networkdiagnosis.Bean.PageBean; import cn.darkal.networkdiagnosis.Bean.ResponseFilterRule; -import cn.darkal.networkdiagnosis.Fragment.PreviewFragment; import cn.darkal.networkdiagnosis.R; -import cn.darkal.networkdiagnosis.SysApplication; -import cn.darkal.networkdiagnosis.Utils.DeviceUtils; /** * Created by Darkal on 2016/9/5. */ -public class ContentFilterAdapter extends BaseAdapter{ +public class ContentFilterAdapter extends BaseAdapter { ChangeFilterActivity changeFilterActivity; + private List ruleList; - public ContentFilterAdapter(ChangeFilterActivity changeFilterActivity,List ruleList){ + public ContentFilterAdapter(ChangeFilterActivity changeFilterActivity, List ruleList) { this.ruleList = ruleList; this.changeFilterActivity = changeFilterActivity; } - private List ruleList; - @Override public int getCount() { return ruleList.size(); @@ -60,7 +54,7 @@ public View getView(final int position, View convertView, ViewGroup parent) { convertView = listItemBinding.getRoot(); convertView.setTag(listItemBinding); } - listItemBinding.setVariable(BR.pages,ruleList.get(position)); + listItemBinding.setVariable(BR.pages, ruleList.get(position)); listItemBinding.executePendingBindings(); // listItemBinding.cli(new ButtonClick(MainActivity.this,position)); convertView.setOnClickListener(new View.OnClickListener() { @@ -75,12 +69,14 @@ public boolean onLongClick(View v) { AlertDialog.Builder builder = new AlertDialog.Builder(changeFilterActivity); builder.setTitle("请确认是否清除该注入项?"); builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { ruleList.remove(ruleList.get(position)); ContentFilterAdapter.this.notifyDataSetChanged(); } }); builder.setNegativeButton("取消", new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { } }); diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Adapter/PageFilterAdapter.java b/app/src/main/java/cn/darkal/networkdiagnosis/Adapter/PageFilterAdapter.java index 275a3e9..dc00d4d 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Adapter/PageFilterAdapter.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Adapter/PageFilterAdapter.java @@ -9,22 +9,22 @@ import java.util.List; -import cn.darkal.networkdiagnosis.Bean.PageBean; import cn.darkal.networkdiagnosis.BR; +import cn.darkal.networkdiagnosis.Bean.PageBean; import cn.darkal.networkdiagnosis.R; /** * Created by Darkal on 2016/9/5. */ -public class PageFilterAdapter extends BaseAdapter{ +public class PageFilterAdapter extends BaseAdapter { + + private List pageBeenList; - public PageFilterAdapter(List pageBeenList){ + public PageFilterAdapter(List pageBeenList) { this.pageBeenList = pageBeenList; } - private List pageBeenList; - @Override public int getCount() { return pageBeenList.size(); @@ -50,7 +50,7 @@ public View getView(int position, View convertView, ViewGroup parent) { convertView = listItemBinding.getRoot(); convertView.setTag(listItemBinding); } - listItemBinding.setVariable(BR.pages,pageBeenList.get(position)); + listItemBinding.setVariable(BR.pages, pageBeenList.get(position)); listItemBinding.executePendingBindings(); // listItemBinding.setButtonclick(new ButtonClick(MainActivity.this,position)); return convertView; diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Bean/PageBean.java b/app/src/main/java/cn/darkal/networkdiagnosis/Bean/PageBean.java index 1a01f51..e081de1 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Bean/PageBean.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Bean/PageBean.java @@ -28,21 +28,21 @@ public void setName(String name) { } public String getCount() { - return count+"请求"; + return count + "请求"; + } + + public void setCount(String count) { + this.count = count; } public Integer getCountInt() { - try{ + try { return Integer.parseInt(count); - }catch (Exception e){ + } catch (Exception e) { return 0; } } - public void setCount(String count) { - this.count = count; - } - public Boolean getSelected() { return isSelected; } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Bean/ResponseFilterRule.java b/app/src/main/java/cn/darkal/networkdiagnosis/Bean/ResponseFilterRule.java index 7605dc0..b987fc4 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Bean/ResponseFilterRule.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Bean/ResponseFilterRule.java @@ -8,14 +8,8 @@ * Created by darkal on 2017/5/31. */ -public class ResponseFilterRule implements Serializable{ - enum RULE_TYPE{ - STRING_REPLACE, - BEGIN_INSERT, - END_INSERT - } - - private RULE_TYPE ruleType = STRING_REPLACE; +public class ResponseFilterRule implements Serializable { + private RULE_TYPE ruleType = STRING_REPLACE; private String url; private String replaceRegex; private String replaceContent; @@ -60,4 +54,10 @@ public Boolean getEnable() { public void setEnable(Boolean enable) { isEnable = enable; } + + enum RULE_TYPE { + STRING_REPLACE, + BEGIN_INSERT, + END_INSERT + } } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/BaseFragment.java b/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/BaseFragment.java index d4f8850..92620ab 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/BaseFragment.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/BaseFragment.java @@ -23,10 +23,10 @@ public abstract class BaseFragment extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if(!(getActivity() instanceof BackHandledInterface)){ + if (!(getActivity() instanceof BackHandledInterface)) { throw new ClassCastException("Hosting Activity must implement BackHandledInterface"); - }else{ - this.mBackHandledInterface = (BackHandledInterface)getActivity(); + } else { + this.mBackHandledInterface = (BackHandledInterface) getActivity(); } } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/NetworkFragment.java b/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/NetworkFragment.java index 6686f4f..08d52f4 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/NetworkFragment.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/NetworkFragment.java @@ -18,28 +18,22 @@ public class NetworkFragment extends BaseFragment { + static NetworkFragment networkFragment; @BindView(R.id.bt_ping) Button pingButton; - @BindView(R.id.bt_dns) Button dnsButton; - @BindView(R.id.bt_trace) Button traceButton; - @BindView(R.id.bt_info) Button infoButton; - @BindView(R.id.tv_result) TextView resultTextView; - @BindView(R.id.et_url) EditText urlEditText; - static NetworkFragment networkFragment; - public static NetworkFragment getInstance() { - if(networkFragment == null){ + if (networkFragment == null) { networkFragment = new NetworkFragment(); } return networkFragment; @@ -64,7 +58,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, pingButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - PingTask pingTask = new PingTask(urlEditText.getText()+"",resultTextView); + PingTask pingTask = new PingTask(urlEditText.getText() + "", resultTextView); pingTask.doTask(); } }); @@ -72,7 +66,7 @@ public void onClick(View v) { dnsButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - DnsTask pingTask = new DnsTask(urlEditText.getText()+"",resultTextView); + DnsTask pingTask = new DnsTask(urlEditText.getText() + "", resultTextView); pingTask.doTask(); } }); @@ -80,7 +74,7 @@ public void onClick(View v) { traceButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - TraceTask pingTask = new TraceTask(getActivity(),urlEditText.getText()+"",resultTextView); + TraceTask pingTask = new TraceTask(getActivity(), urlEditText.getText() + "", resultTextView); pingTask.doTask(); } }); @@ -88,7 +82,7 @@ public void onClick(View v) { infoButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - InfoTask pingTask = new InfoTask(urlEditText.getText()+"",resultTextView); + InfoTask pingTask = new InfoTask(urlEditText.getText() + "", resultTextView); pingTask.doTask(); } }); diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/PreviewFragment.java b/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/PreviewFragment.java index f2ca02f..aa050e0 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/PreviewFragment.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/PreviewFragment.java @@ -28,18 +28,14 @@ public class PreviewFragment extends BaseFragment { + static PreviewFragment previewFragment; @BindView(R.id.rv_preview) RecyclerView recyclerView; - HarLog harLog; List harEntryList = new ArrayList<>(); - PreviewAdapter previewAdapter; - Boolean isHiddenHID = false; - static PreviewFragment previewFragment; - public static PreviewFragment getInstance() { if (previewFragment == null) { previewFragment = new PreviewFragment(); @@ -59,7 +55,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, View view = inflater.inflate(R.layout.fragment_preview, container, false); ButterKnife.bind(this, view); - if(SysApplication.isInitProxy) { + if (SysApplication.isInitProxy) { harLog = ((SysApplication) getActivity().getApplication()).proxy.getHar().getLog(); harEntryList.addAll(harLog.getEntries()); } @@ -67,13 +63,54 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); recyclerView.setAdapter(previewAdapter = new PreviewAdapter()); - if(((MainActivity) getActivity()).searchView!=null){ + if (((MainActivity) getActivity()).searchView != null) { ((MainActivity) getActivity()).searchView.setVisibility(View.VISIBLE); } return view; } + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (isVisibleToUser) { +// ((MainActivity) getActivity()).navigationView.setCheckedItem(R.id.nav_preview); + notifyHarChange(); + } + } + + public void notifyHarChange() { + if (previewAdapter != null) { + harLog = ((MainActivity) getActivity()).getFiltedHar().getLog(); + harEntryList.clear(); + harEntryList.addAll(harLog.getEntries()); + previewAdapter.notifyDataSetChanged(); + } + } + + @Override + public boolean onBackPressed() { + return false; + } + + public void filterItem(CharSequence s) { + if (previewAdapter != null) { + previewAdapter.getFilter().filter(s); + } + } + + @Override + public void onResume() { + super.onResume(); + + // 这里为了解决返回后焦点在搜索栏的bug + if (recyclerView != null) { + recyclerView.requestFocus(); + if (((MainActivity) getActivity()).searchView != null) { + filterItem(((MainActivity) getActivity()).searchView.getQuery()); + } + } + } private class PreviewAdapter extends RecyclerView.Adapter implements Filterable { @@ -87,13 +124,13 @@ public void onBindViewHolder(MyViewHolder holder, int position) { HarEntry harEntry = harEntryList.get(position); holder.rootView.setOnClickListener(new ClickListner(harEntry)); holder.tv.setText(harEntry.getRequest().getUrl()); - if(harEntry.getResponse().getStatus()>400){ + if (harEntry.getResponse().getStatus() > 400) { holder.iconView.setImageDrawable(getResources().getDrawable(R.drawable.ic_error_black_24dp)); - }else if(harEntry.getResponse().getStatus()>300){ + } else if (harEntry.getResponse().getStatus() > 300) { holder.iconView.setImageDrawable(getResources().getDrawable(R.drawable.ic_directions_black_24dp)); - }else if(harEntry.getResponse().getContent().getMimeType().contains("image")) { + } else if (harEntry.getResponse().getContent().getMimeType().contains("image")) { holder.iconView.setImageDrawable(getResources().getDrawable(R.drawable.ic_photo_black_24dp)); - }else{ + } else { holder.iconView.setImageDrawable(getResources().getDrawable(R.drawable.ic_description_black_24dp)); } holder.detailTextView.setText("Status:" + harEntry.getResponse().getStatus() + @@ -106,23 +143,6 @@ public int getItemCount() { return harEntryList.size(); } - - public class MyViewHolder extends RecyclerView.ViewHolder { - - TextView tv; - TextView detailTextView; - View rootView; - ImageView iconView; - - public MyViewHolder(View view) { - super(view); - tv = (TextView) view.findViewById(R.id.tv_url); - detailTextView = (TextView) view.findViewById(R.id.tv_detail); - rootView = view; - iconView = (ImageView) view.findViewById(R.id.iv_icon); - } - } - @Override public Filter getFilter() { return new Filter() { @@ -172,7 +192,7 @@ protected FilterResults performFiltering(CharSequence constraint) { @Override protected void publishResults(CharSequence constraint, FilterResults results) { harEntryList.clear();//清除原始数据 - if(results.values instanceof List){ + if (results.values instanceof List) { harEntryList.addAll((List) results.values);//将过滤结果添加到这个对象 } if (results.count > 0) { @@ -190,36 +210,34 @@ protected void publishResults(CharSequence constraint, FilterResults results) { } }; } - } - @Override - public void setUserVisibleHint(boolean isVisibleToUser) { - super.setUserVisibleHint(isVisibleToUser); - if (isVisibleToUser) { -// ((MainActivity) getActivity()).navigationView.setCheckedItem(R.id.nav_preview); - notifyHarChange(); - } - } + public class MyViewHolder extends RecyclerView.ViewHolder { - public void notifyHarChange(){ - if (previewAdapter != null) { - harLog = ((MainActivity) getActivity()).getFiltedHar().getLog(); - harEntryList.clear(); - harEntryList.addAll(harLog.getEntries()); - previewAdapter.notifyDataSetChanged(); + TextView tv; + TextView detailTextView; + View rootView; + ImageView iconView; + + public MyViewHolder(View view) { + super(view); + tv = (TextView) view.findViewById(R.id.tv_url); + detailTextView = (TextView) view.findViewById(R.id.tv_detail); + rootView = view; + iconView = (ImageView) view.findViewById(R.id.iv_icon); + } } } public class ClickListner implements View.OnClickListener { HarEntry harEntry; - public ClickListner(HarEntry harEntry){ + public ClickListner(HarEntry harEntry) { this.harEntry = harEntry; } @Override public void onClick(View view) { - if(harLog.getEntries().indexOf(harEntry)>=0) { + if (harLog.getEntries().indexOf(harEntry) >= 0) { isHiddenHID = true; Intent intent = new Intent(getContext(), HarDetailActivity.class); intent.putExtra("pos", ((SysApplication) getActivity().getApplication()).proxy. @@ -228,28 +246,4 @@ public void onClick(View view) { } } } - - @Override - public boolean onBackPressed() { - return false; - } - - public void filterItem(CharSequence s){ - if(previewAdapter!=null) { - previewAdapter.getFilter().filter(s); - } - } - - @Override - public void onResume() { - super.onResume(); - - // 这里为了解决返回后焦点在搜索栏的bug - if(recyclerView!=null) { - recyclerView.requestFocus(); - if(((MainActivity)getActivity()).searchView!=null) { - filterItem(((MainActivity) getActivity()).searchView.getQuery()); - } - } - } } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/WebViewFragment.java b/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/WebViewFragment.java index 11a10a9..a0f889d 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/WebViewFragment.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Fragment/WebViewFragment.java @@ -1,18 +1,14 @@ package cn.darkal.networkdiagnosis.Fragment; -import android.annotation.TargetApi; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.SharedPreferences; import android.graphics.Bitmap; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.preference.PreferenceManager; import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; import android.support.v4.widget.SwipeRefreshLayout; import android.util.Log; import android.view.KeyEvent; @@ -24,7 +20,6 @@ import android.webkit.ConsoleMessage; import android.webkit.DownloadListener; import android.webkit.WebChromeClient; -import android.webkit.WebResourceRequest; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; @@ -44,31 +39,22 @@ import cn.darkal.networkdiagnosis.Utils.SharedPreferenceUtils; public class WebViewFragment extends BaseFragment { + private final static WebViewFragment webViewFragment = new WebViewFragment(); + public Boolean isSetProxy = false; + public String baseUserAgentString = "Mozilla/5.0 (Linux; Android 5.0.2) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/37.0.0.0"; + public String userAgentString = baseUserAgentString; @BindView(R.id.fl_webview) WebView webView; - @BindView(R.id.bt_jump) Button jumpButton; - @BindView(R.id.et_url) EditText urlText; - @BindView(R.id.pb_progress) ProgressBar progressBar; - @BindView(R.id.swipe_container) SwipeRefreshLayout swipeRefreshLayout; - Receiver receiver; - public Boolean isSetProxy = false; - - public String baseUserAgentString = "Mozilla/5.0 (Linux; Android 5.0.2) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/37.0.0.0"; - - public String userAgentString = baseUserAgentString; - - private final static WebViewFragment webViewFragment = new WebViewFragment(); - public static WebViewFragment getInstance() { return webViewFragment; } @@ -108,7 +94,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, webSettings.setDomStorageEnabled(true); webSettings.setGeolocationEnabled(true); - baseUserAgentString = webSettings.getUserAgentString()+" jdhttpmonitor/" + DeviceUtils.getVersion(getContext()); + baseUserAgentString = webSettings.getUserAgentString() + " jdhttpmonitor/" + DeviceUtils.getVersion(getContext()); webSettings.setUserAgentString(userAgentString); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { @@ -130,7 +116,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { - if(url.startsWith("jdhttpmonitor://webview")) { + if (url.startsWith("jdhttpmonitor://webview")) { Intent intent = new Intent("android.intent.action.VIEW"); intent.setData(Uri.parse(url)); startActivity(intent); @@ -160,10 +146,11 @@ public void onPageStarted(WebView view, String url, Bitmap favicon) { @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { - ((MainActivity)getActivity()).consoleLog.append(consoleMessage.message()).append("\n").append("\n"); + ((MainActivity) getActivity()).consoleLog.append(consoleMessage.message()).append("\n").append("\n"); return super.onConsoleMessage(consoleMessage); } + @Override public void onProgressChanged(WebView view, int progress) { progressBar.setProgress(progress); if (progress == 100) { @@ -209,7 +196,7 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { } }); - swipeRefreshLayout.setColorSchemeResources(R.color.colorAccentDark,R.color.colorAccent); + swipeRefreshLayout.setColorSchemeResources(R.color.colorAccentDark, R.color.colorAccent); swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override @@ -251,26 +238,18 @@ public void initProxyWebView() { webView.post(new Runnable() { @Override public void run() { - if(ProxyUtils.setProxy(webView, "127.0.0.1", SysApplication.proxyPort)){ + if (ProxyUtils.setProxy(webView, "127.0.0.1", SysApplication.proxyPort)) { Log.e("~~~~", "initProxyWebView()"); webView.loadUrl(urlText.getText() + ""); isSetProxy = true; - }else{ - Toast.makeText(webView.getContext(),"Set proxy fail!",Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(webView.getContext(), "Set proxy fail!", Toast.LENGTH_LONG).show(); } } }); } } - public class Receiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - initProxyWebView(); - Log.i("~~~~", "Receiver initProxyWebView"); - } - } - public void loadUrl(String url) { if (webView != null) { if (!isSetProxy) { @@ -299,10 +278,10 @@ public void onSaveInstanceState(Bundle outState) { } - public void setUserAgent(){ + public void setUserAgent() { String originUA = userAgentString; - switch (SharedPreferenceUtils.getString(getContext(),"select_ua", "0")) { + switch (SharedPreferenceUtils.getString(getContext(), "select_ua", "0")) { case "0": userAgentString = baseUserAgentString; break; @@ -312,21 +291,31 @@ public void setUserAgent(){ case "2": userAgentString = baseUserAgentString + " MQQBrowser/6.2 TBS/036524 V1_AND_SQ_6.0.0_300_YYB_D QQ/6.0.0.2605 NetType/WIFI WebP/0.3.0 Pixel/1440"; break; + default: + break; } WebSettings webSettings = webView.getSettings(); webSettings.setUserAgentString(userAgentString); - if(!originUA.equals(userAgentString) && webView!=null){ + if (!originUA.equals(userAgentString) && webView != null) { reload(); } } - public void reload(){ - if(webView!=null && webView.getUrl()!=null) { + public void reload() { + if (webView != null && webView.getUrl() != null) { webView.reload(); } } + public class Receiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + initProxyWebView(); + Log.i("~~~~", "Receiver initProxyWebView"); + } + } + private class MyWebViewDownLoadListener implements DownloadListener { @Override public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/MyVpnService.java b/app/src/main/java/cn/darkal/networkdiagnosis/MyVpnService.java index 3ab2c31..f643ceb 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/MyVpnService.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/MyVpnService.java @@ -36,7 +36,7 @@ public MyVpnService() { private void setupVpn() { ParcelFileDescriptor parcelFileDescriptor; - Log.i("~~~","VpnService: try to setup VPN."); + Log.i("~~~", "VpnService: try to setup VPN."); Builder builder = new Builder(); builder.setSession("firewall"); builder.addAddress("10.0.8.1", 32); @@ -134,10 +134,11 @@ private void setupVpn() { } } - }catch(final IllegalStateException e) { - Log.i("~~~","VpnService: builder.establish() failed."); - if(mHandler != null) { + } catch (final IllegalStateException e) { + Log.i("~~~", "VpnService: builder.establish() failed."); + if (mHandler != null) { mHandler.post(new Runnable() { + @Override public void run() { Toast.makeText(MyVpnService.this, "Cannot establish VPN (" + e.toString() + ")", Toast.LENGTH_LONG).show(); @@ -364,6 +365,8 @@ private void configure(String parameters) throws Exception { case 's': builder.addSearchDomain(fields[1]); break; + default: + break; } } catch (Exception e) { throw new IllegalArgumentException("Bad parameter: " + parameter); diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/SysApplication.java b/app/src/main/java/cn/darkal/networkdiagnosis/SysApplication.java index 88eff04..aecef2b 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/SysApplication.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/SysApplication.java @@ -6,7 +6,9 @@ import android.preference.PreferenceManager; import android.support.multidex.MultiDexApplication; import android.util.Log; + import com.tencent.bugly.Bugly; + import net.gotev.uploadservice.UploadService; import net.lightbody.bmp.BrowserMobProxy; import net.lightbody.bmp.BrowserMobProxyServer; @@ -75,13 +77,13 @@ public void onTerminate() { new Thread(new Runnable() { @Override public void run() { - Log.e("~~~","onTerminate"); + Log.e("~~~", "onTerminate"); proxy.stop(); } }).start(); } - public void startProxy(){ + public void startProxy() { try { proxy = new BrowserMobProxyServer(); proxy.setTrustAllServers(true); @@ -106,18 +108,18 @@ public void startProxy(){ SharedPreferences shp = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - if(shp.getBoolean("enable_filter", false)) { + if (shp.getBoolean("enable_filter", false)) { Log.e("~~~enable_filter", ""); initResponseFilter(); } // 设置hosts - if(shp.getString("system_host", "").length()>0){ + if (shp.getString("system_host", "").length() > 0) { AdvancedHostResolver advancedHostResolver = proxy.getHostNameResolver(); - for (String temp : shp.getString("system_host", "").split("\\n")){ - if(temp.split(" ").length==2) { - advancedHostResolver.remapHost(temp.split(" ")[1],temp.split(" ")[0]); - Log.e("~~~~remapHost ",temp.split(" ")[1] +" " + temp.split(" ")[0]); + for (String temp : shp.getString("system_host", "").split("\\n")) { + if (temp.split(" ").length == 2) { + advancedHostResolver.remapHost(temp.split(" ")[1], temp.split(" ")[0]); + Log.e("~~~~remapHost ", temp.split(" ")[1] + " " + temp.split(" ")[0]); } } proxy.setHostNameResolver(advancedHostResolver); @@ -135,15 +137,15 @@ public void startProxy(){ isInitProxy = true; } - public void stopProxy(){ - if(proxy!=null){ + public void stopProxy() { + if (proxy != null) { proxy.stop(); } } - private void initResponseFilter(){ + private void initResponseFilter() { try { - if(ruleList == null){ + if (ruleList == null) { ResponseFilterRule rule = new ResponseFilterRule(); rule.setUrl("xw.qq.com/index.htm"); rule.setReplaceRegex(""); @@ -153,8 +155,8 @@ private void initResponseFilter(){ ruleList.add(rule); } - DeviceUtils.changeResponseFilter(this,ruleList); - }catch (Exception e){ + DeviceUtils.changeResponseFilter(this, ruleList); + } catch (Exception e) { e.printStackTrace(); } } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Task/BaseTask.java b/app/src/main/java/cn/darkal/networkdiagnosis/Task/BaseTask.java index 6ed5ab1..1517a7c 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Task/BaseTask.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Task/BaseTask.java @@ -17,33 +17,33 @@ public BaseTask(String url, TextView resultTextView) { this.resultTextView = resultTextView; } - public void doTask(){ + public void doTask() { resultTextView.setText(""); - tag = System.currentTimeMillis()+""; + tag = System.currentTimeMillis() + ""; resultTextView.setTag(tag); // TraceTask运行于主线程 - if(this instanceof TraceTask){ + if (this instanceof TraceTask) { getExecRunnable().run(); - }else { + } else { new Thread(getExecRunnable()).start(); } } - public class updateResultRunnable implements Runnable{ + public abstract Runnable getExecRunnable(); + + public class updateResultRunnable implements Runnable { String resultString; - public updateResultRunnable(String resultString){ + public updateResultRunnable(String resultString) { this.resultString = resultString; } @Override public void run() { - if(resultTextView!=null && resultTextView.getTag().equals(tag)) { + if (resultTextView != null && resultTextView.getTag().equals(tag)) { resultTextView.append(resultString); resultTextView.requestFocus(); } } } - - public abstract Runnable getExecRunnable(); } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Task/DnsTask.java b/app/src/main/java/cn/darkal/networkdiagnosis/Task/DnsTask.java index 7973c72..9a64f9f 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Task/DnsTask.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Task/DnsTask.java @@ -1,6 +1,7 @@ package cn.darkal.networkdiagnosis.Task; import android.widget.TextView; + import java.net.InetAddress; /** @@ -9,35 +10,32 @@ public class DnsTask extends BaseTask { String url; TextView resultTextView; - - public DnsTask(String url, TextView resultTextView) { - super(url, resultTextView); - this.url = url; - this.resultTextView = resultTextView; - } - - @Override - public Runnable getExecRunnable() { - return execRunnable; - } - public Runnable execRunnable = new Runnable() { @Override public void run() { StringBuilder sb = new StringBuilder(); - try{ + try { InetAddress aaa = InetAddress.getByName(url); InetAddress[] addrs = InetAddress.getAllByName(url); sb.append("Begin: \n" + aaa.toString() + "\nEnd\n"); - for (InetAddress adr : addrs) - { + for (InetAddress adr : addrs) { sb.append(adr.toString() + "\n"); resultTextView.post(new updateResultRunnable(adr.toString() + "\n")); } - } - catch (Exception e){ + } catch (Exception e) { resultTextView.post(new updateResultRunnable(e.toString() + "\n")); } } }; + + public DnsTask(String url, TextView resultTextView) { + super(url, resultTextView); + this.url = url; + this.resultTextView = resultTextView; + } + + @Override + public Runnable getExecRunnable() { + return execRunnable; + } } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Task/InfoTask.java b/app/src/main/java/cn/darkal/networkdiagnosis/Task/InfoTask.java index a03f03b..03d5d34 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Task/InfoTask.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Task/InfoTask.java @@ -2,7 +2,6 @@ import android.widget.TextView; - import cn.darkal.networkdiagnosis.Utils.NetInfo.NetBasicInfo; import cn.darkal.networkdiagnosis.Utils.NetInfo.SystemBasicInfo; @@ -13,18 +12,6 @@ public class InfoTask extends BaseTask { String url; TextView resultTextView; - - public InfoTask(String url, TextView resultTextView) { - super(url, resultTextView); - this.url = url; - this.resultTextView = resultTextView; - } - - @Override - public Runnable getExecRunnable() { - return execRunnable; - } - public Runnable execRunnable = new Runnable() { @Override public void run() { @@ -38,4 +25,15 @@ public void run() { + "\n")); } }; + + public InfoTask(String url, TextView resultTextView) { + super(url, resultTextView); + this.url = url; + this.resultTextView = resultTextView; + } + + @Override + public Runnable getExecRunnable() { + return execRunnable; + } } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Task/PingTask.java b/app/src/main/java/cn/darkal/networkdiagnosis/Task/PingTask.java index c8aca57..4f7ce66 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Task/PingTask.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Task/PingTask.java @@ -13,18 +13,6 @@ public class PingTask extends BaseTask { String url; TextView resultTextView; - - public PingTask(String url, TextView resultTextView) { - super(url, resultTextView); - this.url = url; - this.resultTextView = resultTextView; - } - - @Override - public Runnable getExecRunnable() { - return execRunnable; - } - public Runnable execRunnable = new Runnable() { @Override public void run() { @@ -48,14 +36,25 @@ public void run() { } catch (IOException e) { resultTextView.post(new updateResultRunnable(e.toString() + "\n")); } finally { - try{ + try { if (in != null) { in.close(); } - }catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); } } } }; + + public PingTask(String url, TextView resultTextView) { + super(url, resultTextView); + this.url = url; + this.resultTextView = resultTextView; + } + + @Override + public Runnable getExecRunnable() { + return execRunnable; + } } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Task/TraceRouteContainer.java b/app/src/main/java/cn/darkal/networkdiagnosis/Task/TraceRouteContainer.java index ffcf93c..416bddc 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Task/TraceRouteContainer.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Task/TraceRouteContainer.java @@ -40,49 +40,40 @@ public TraceRouteContainer(String hostname, String ip, float ms, boolean isSucce this.isSuccessful = isSuccessful; } - public String getHostname() - { + public String getHostname() { return hostname; } - public void setHostname(String hostname) - { + public void setHostname(String hostname) { this.hostname = hostname; } - public String getIp() - { + public String getIp() { return ip; } - public void setIp(String ip) - { + public void setIp(String ip) { this.ip = ip; } - public float getMs() - { + public float getMs() { return ms; } - public void setMs(float ms) - { + public void setMs(float ms) { this.ms = ms; } - public boolean isSuccessful() - { + public boolean isSuccessful() { return isSuccessful; } - public void setSuccessful(boolean isSuccessful) - { + public void setSuccessful(boolean isSuccessful) { this.isSuccessful = isSuccessful; } @Override - public String toString() - { + public String toString() { return "Traceroute : \nHostname : " + hostname + "\nip : " + ip + "\nMilliseconds : " + ms; } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Task/TraceRouteWithPing.java b/app/src/main/java/cn/darkal/networkdiagnosis/Task/TraceRouteWithPing.java index 86cc5e1..d05acef 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Task/TraceRouteWithPing.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Task/TraceRouteWithPing.java @@ -22,6 +22,7 @@ import android.annotation.SuppressLint; import android.os.AsyncTask; import android.os.Handler; +import android.text.TextUtils; import android.util.Log; import java.io.BufferedReader; @@ -88,7 +89,7 @@ private String parseIpFromPing(String ping) { // Get ip when ttl exceeded int index = ping.indexOf(FROM_PING); - if(index==0){ + if (index == 0) { index = ping.indexOf(SMALL_FROM_PING); } @@ -300,7 +301,7 @@ private String launchPing(String url) throws Exception { p.destroy(); - if (res.equals("")) { + if (TextUtils.isEmpty(res) || TextUtils.isEmpty(res.trim())) { throw new IllegalArgumentException(); } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Task/TraceTask.java b/app/src/main/java/cn/darkal/networkdiagnosis/Task/TraceTask.java index fcc82e1..2fd0c44 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Task/TraceTask.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Task/TraceTask.java @@ -2,7 +2,6 @@ import android.Manifest; import android.app.Activity; -import android.content.Context; import android.content.pm.PackageManager; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; @@ -20,27 +19,14 @@ /** * Created by xuzhou on 2016/8/1. */ -public class TraceTask extends BaseTask implements LDNetDiagnoListener { +public class TraceTask extends BaseTask implements LDNetDiagnoListener { String url; TextView resultTextView; Activity context; - - public TraceTask(Activity context , String url, TextView resultTextView) { - super(url, resultTextView); - this.context = context; - this.url = url; - this.resultTextView = resultTextView; - } - - @Override - public Runnable getExecRunnable() { - return execRunnable; - } - public Runnable execRunnable = new Runnable() { @Override public void run() { - try{ + try { int permissionCheck = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE); if (permissionCheck != PackageManager.PERMISSION_GRANTED) { @@ -48,25 +34,36 @@ public void run() { } else { // TraceRouteWithPing traceRouteWithPing = new TraceRouteWithPing(url, TraceTask.this); // traceRouteWithPing.executeTraceRoute(); - LDNetDiagnoService _netDiagnoService = new LDNetDiagnoService(context.getApplicationContext(), + LDNetDiagnoService netDiagnoService = new LDNetDiagnoService(context.getApplicationContext(), "NetworkDiagnosis", "网络诊断应用", DeviceUtils.getVersion(context), "", "", url, "", "", "", "", TraceTask.this); // 设置是否使用JNIC 完成traceroute - _netDiagnoService.setIfUseJNICTrace(true); - _netDiagnoService.execute(); + netDiagnoService.setIfUseJNICTrace(true); + netDiagnoService.execute(); } - } - catch (Exception e){ + } catch (Exception e) { resultTextView.post(new updateResultRunnable(e.toString() + "\n")); } } }; + private Pattern pattern = Pattern.compile("(?<=rom )[\\w\\W]+(?=\\n\\n)"); + + public TraceTask(Activity context, String url, TextView resultTextView) { + super(url, resultTextView); + this.context = context; + this.url = url; + this.resultTextView = resultTextView; + } + + @Override + public Runnable getExecRunnable() { + return execRunnable; + } - public void setResult(String result){ - Pattern pattern = Pattern.compile("(?<=rom )[\\w\\W]+(?=\\n\\n)"); + public void setResult(String result) { Matcher matcher = pattern.matcher(result); - if(matcher.find()){ + if (matcher.find()) { resultTextView.post(new updateResultRunnable(matcher.group(0) + "\n")); } } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Utils/X509ExtendedTrustManager.java b/app/src/main/java/cn/darkal/networkdiagnosis/Utils/AbstractX509ExtendedTrustManager.java similarity index 98% rename from app/src/main/java/cn/darkal/networkdiagnosis/Utils/X509ExtendedTrustManager.java rename to app/src/main/java/cn/darkal/networkdiagnosis/Utils/AbstractX509ExtendedTrustManager.java index 4f407b6..8355100 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Utils/X509ExtendedTrustManager.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Utils/AbstractX509ExtendedTrustManager.java @@ -33,7 +33,7 @@ * @see SSLParameters#setEndpointIdentificationAlgorithm(String) * @since 1.7 */ -public abstract class X509ExtendedTrustManager implements X509TrustManager { +public abstract class AbstractX509ExtendedTrustManager implements X509TrustManager { /** * Checks whether the specified certificate chain (partial or complete) can * be validated and is trusted for client authentication for the specified diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Utils/DatatypeConverter.java b/app/src/main/java/cn/darkal/networkdiagnosis/Utils/DatatypeConverter.java index a8b5947..c3c154f 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Utils/DatatypeConverter.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Utils/DatatypeConverter.java @@ -8,162 +8,179 @@ */ public class DatatypeConverter { - public static String doFormat( String format, Calendar cal ) throws IllegalArgumentException { - int fidx = 0; - int flen = format.length(); - StringBuilder buf = new StringBuilder(); + private static final byte PADDING = 127; + private static final byte[] decodeMap = initDecodeMap(); - while(fidx= 0) - buf.append('+'); - else { - buf.append('-'); - offset *= -1; - } + if (tz == null) { + return; + } - offset /= 60 * 1000; // offset is in milli-seconds + // otherwise print out normally. + int offset; + if (tz.inDaylightTime(cal.getTime())) { + offset = tz.getRawOffset() + (tz.useDaylightTime() ? 3600000 : 0); + } else { + offset = tz.getRawOffset(); + } - formatTwoDigits(offset / 60, buf); - buf.append(':'); - formatTwoDigits(offset % 60, buf); + if (offset == 0) { + buf.append('Z'); + return; } - /** formats Integer into two-character-wide string. */ - private static void formatTwoDigits(int n,StringBuilder buf) { - // n is always non-negative. - if (n < 10) buf.append('0'); - buf.append(n); + if (offset >= 0) { + buf.append('+'); + } else { + buf.append('-'); + offset *= -1; } + offset /= 60 * 1000; // offset is in milli-seconds - // base64 decoder -//==================================== + formatTwoDigits(offset / 60, buf); + buf.append(':'); + formatTwoDigits(offset % 60, buf); + } - private static final byte[] decodeMap = initDecodeMap(); - private static final byte PADDING = 127; + /** + * formats Integer into two-character-wide string. + */ + private static void formatTwoDigits(int n, StringBuilder buf) { + // n is always non-negative. + if (n < 10) { + buf.append('0'); + } + buf.append(n); + } private static byte[] initDecodeMap() { byte[] map = new byte[128]; int i; - for( i=0; i<128; i++ ) map[i] = -1; + for (i = 0; i < 128; i++) { + map[i] = -1; + } - for( i='A'; i<='Z'; i++ ) map[i] = (byte)(i-'A'); - for( i='a'; i<='z'; i++ ) map[i] = (byte)(i-'a'+26); - for( i='0'; i<='9'; i++ ) map[i] = (byte)(i-'0'+52); + for (i = 'A'; i <= 'Z'; i++) { + map[i] = (byte) (i - 'A'); + } + for (i = 'a'; i <= 'z'; i++) { + map[i] = (byte) (i - 'a' + 26); + } + for (i = '0'; i <= '9'; i++) { + map[i] = (byte) (i - '0' + 52); + } map['+'] = 62; map['/'] = 63; map['='] = PADDING; @@ -171,68 +188,75 @@ private static byte[] initDecodeMap() { return map; } - private static int guessLength( String text ) { + private static int guessLength(String text) { final int len = text.length(); // compute the tail '=' chars - int j=len-1; - for(; j>=0; j-- ) { + int j = len - 1; + for (; j >= 0; j--) { byte code = decodeMap[text.charAt(j)]; - if(code==PADDING) + if (code == PADDING) { continue; - if(code==-1) + } + if (code == -1) { // most likely this base64 text is indented. go with the upper bound - return text.length()/4*3; + return text.length() / 4 * 3; + } break; } j++; // text.charAt(j) is now at some base64 char, so +1 to make it the size - int padSize = len-j; - if(padSize >2) // something is wrong with base64. be safe and go with the upper bound - return text.length()/4*3; + int padSize = len - j; + if (padSize > 2) { + // something is wrong with base64. be safe and go with the upper bound + return text.length() / 4 * 3; + } // so far this base64 looks like it's unindented tightly packed base64. // take a chance and create an array with the expected size - return text.length()/4*3-padSize; + return text.length() / 4 * 3 - padSize; } public static byte[] parseBase64Binary(String text) { final int buflen = guessLength(text); final byte[] out = new byte[buflen]; - int o=0; + int o = 0; final int len = text.length(); int i; final byte[] quadruplet = new byte[4]; - int q=0; + int q = 0; // convert each quadruplet to three bytes. - for( i=0; i>4)); - if( quadruplet[2]!=PADDING ) - out[o++] = (byte)((quadruplet[1]<<4)|(quadruplet[2]>>2)); - if( quadruplet[3]!=PADDING ) - out[o++] = (byte)((quadruplet[2]<<6)|(quadruplet[3])); - q=0; + out[o++] = (byte) ((quadruplet[0] << 2) | (quadruplet[1] >> 4)); + if (quadruplet[2] != PADDING) { + out[o++] = (byte) ((quadruplet[1] << 4) | (quadruplet[2] >> 2)); + } + if (quadruplet[3] != PADDING) { + out[o++] = (byte) ((quadruplet[2] << 6) | (quadruplet[3])); + } + q = 0; } } - if(buflen==o) // speculation worked out to be OK + if (buflen == o) { + // speculation worked out to be OK return out; - + } // we overestimated, so need to create a new buffer byte[] nb = new byte[o]; - System.arraycopy(out,0,nb,0,o); + System.arraycopy(out, 0, nb, 0, o); return nb; } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Utils/DeviceUtils.java b/app/src/main/java/cn/darkal/networkdiagnosis/Utils/DeviceUtils.java index 199c9df..77be075 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Utils/DeviceUtils.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Utils/DeviceUtils.java @@ -6,23 +6,15 @@ import android.util.Log; import net.lightbody.bmp.BrowserMobProxy; -import net.lightbody.bmp.filters.RequestFilter; import net.lightbody.bmp.filters.ResponseFilter; import net.lightbody.bmp.proxy.dns.AdvancedHostResolver; import net.lightbody.bmp.util.HttpMessageContents; import net.lightbody.bmp.util.HttpMessageInfo; -import org.littleshoot.proxy.HttpFilters; -import org.littleshoot.proxy.HttpFiltersSource; - -import java.net.InetSocketAddress; import java.util.List; import cn.darkal.networkdiagnosis.Bean.ResponseFilterRule; import cn.darkal.networkdiagnosis.SysApplication; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; /** @@ -67,11 +59,11 @@ public static int dip2px(Context context, int dp) { //dp和px的转换关系 float density = context.getResources().getDisplayMetrics().density; //2*1.5+0.5 2*0.75 = 1.5+0.5 - return (int)(dp*density+0.5); + return (int) (dp * density + 0.5); } - public static void changeHost(BrowserMobProxy browserMobProxy,String newValue){ + public static void changeHost(BrowserMobProxy browserMobProxy, String newValue) { AdvancedHostResolver advancedHostResolver = browserMobProxy.getHostNameResolver(); advancedHostResolver.clearHostRemappings(); for (String temp : newValue.split("\\n")) { @@ -85,17 +77,17 @@ public static void changeHost(BrowserMobProxy browserMobProxy,String newValue){ browserMobProxy.setHostNameResolver(advancedHostResolver); } - public static void changeResponseFilter(SysApplication sysApplication,final List ruleList){ - if(ruleList == null){ - Log.e("~~~~","changeResponseFilter ruleList == null!"); + public static void changeResponseFilter(SysApplication sysApplication, final List ruleList) { + if (ruleList == null) { + Log.e("~~~~", "changeResponseFilter ruleList == null!"); return; } sysApplication.proxy.addResponseFilter(new ResponseFilter() { @Override public void filterResponse(HttpResponse response, HttpMessageContents contents, HttpMessageInfo messageInfo) { - for (ResponseFilterRule rule: ruleList) { - if(rule.getEnable()) { + for (ResponseFilterRule rule : ruleList) { + if (rule.getEnable()) { if (contents.isText() && messageInfo.getUrl().contains(rule.getUrl())) { String originContent = contents.getTextContents(); if (originContent != null) { diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Utils/FileUtil.java b/app/src/main/java/cn/darkal/networkdiagnosis/Utils/FileUtil.java index b8a4b70..97d0ed5 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Utils/FileUtil.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Utils/FileUtil.java @@ -9,7 +9,6 @@ import android.os.Environment; import android.provider.MediaStore; import android.support.v4.app.ActivityCompat; -import android.util.Log; import android.widget.Toast; import net.gotev.uploadservice.MultipartUploadRequest; @@ -428,7 +427,7 @@ public static void saveImageToGallery(Context context, String filePath) { } //递归删除文件夹下面的文件 - public static void deleteFiles(File file){ + public static void deleteFiles(File file) { try { if (file.exists()) { if (file.isDirectory()) { @@ -440,10 +439,11 @@ public static void deleteFiles(File file){ file.delete(); } } - }catch (Exception e){} + } catch (Exception e) { + } } - public static void checkPermission(Activity activity,Runnable runnable) { + public static void checkPermission(Activity activity, Runnable runnable) { //检查权限(NEED_PERMISSION)是否被授权 PackageManager.PERMISSION_GRANTED表示同意授权 if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Utils/NetInfo/NetBasicInfo.java b/app/src/main/java/cn/darkal/networkdiagnosis/Utils/NetInfo/NetBasicInfo.java index 9e094cf..e8ee8a4 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Utils/NetInfo/NetBasicInfo.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Utils/NetInfo/NetBasicInfo.java @@ -5,6 +5,8 @@ import android.net.NetworkInfo; import android.telephony.TelephonyManager; +import com.netease.LDNetDiagnoUtils.LDNetUtil; + import org.apache.http.conn.util.InetAddressUtils; import java.io.BufferedReader; @@ -191,24 +193,16 @@ public String getMacAddress(String netInterface) { e.printStackTrace(); } - return strMacAddr; } + public String getApnInfo() { TelephonyManager tel = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); String opCode = tel.getSimOperator(); - String operatorName; - if (opCode.startsWith("46000") || opCode.startsWith("46002")) { - operatorName = "中国移动"; - } else if (opCode.equals("46001")) { - operatorName = "中国联通"; - } else if (opCode.equals("46003")) { - operatorName = "中国电信"; - } else { - operatorName = "未知"; - } + String operatorName = LDNetUtil.getMobileOperator(mContext); + ConnectivityManager mag = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo mobInfo = mag.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); @@ -291,12 +285,12 @@ public String GetIp(Boolean isV4) { for (Enumeration ipAddr = intf.getInetAddresses(); ipAddr.hasMoreElements(); ) { InetAddress inetAddress = ipAddr.nextElement(); - if(isV4) { + if (isV4) { // ipv4地址 if (!inetAddress.isLoopbackAddress() && InetAddressUtils.isIPv4Address(inetAddress.getHostAddress())) { return inetAddress.getHostAddress(); } - }else{ + } else { // ipv6地址 if (!inetAddress.isLoopbackAddress() && InetAddressUtils.isIPv6Address(inetAddress.getHostAddress())) { return inetAddress.getHostAddress(); @@ -310,7 +304,7 @@ public String GetIp(Boolean isV4) { return ""; } - private String getLocalDNS(){ + private String getLocalDNS() { Process cmdProcess = null; BufferedReader reader = null; String dnsIP = ""; @@ -321,7 +315,7 @@ private String getLocalDNS(){ return dnsIP; } catch (IOException e) { return null; - } finally{ + } finally { try { if (reader != null) { reader.close(); diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Utils/NetInfo/SystemBasicInfo.java b/app/src/main/java/cn/darkal/networkdiagnosis/Utils/NetInfo/SystemBasicInfo.java index 76fed93..5de9a59 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Utils/NetInfo/SystemBasicInfo.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Utils/NetInfo/SystemBasicInfo.java @@ -37,7 +37,7 @@ public static String getBuildInfo() { public static String getUUID(Context context) { try { String androidId = getAndroidId(context); - String deviceId = getDeviceId(context)==null? "null":getDeviceId(context); + String deviceId = getDeviceId(context) == null ? "null" : getDeviceId(context); UUID uuid = new UUID(androidId.hashCode(), ((long) deviceId.hashCode() << 32) | deviceId.hashCode()); return uuid.toString(); } catch (Exception e) { diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/Utils/SharedPreferenceUtils.java b/app/src/main/java/cn/darkal/networkdiagnosis/Utils/SharedPreferenceUtils.java index 1627148..d8a8d26 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/Utils/SharedPreferenceUtils.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/Utils/SharedPreferenceUtils.java @@ -1,6 +1,5 @@ package cn.darkal.networkdiagnosis.Utils; -import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; @@ -166,7 +165,7 @@ public static void save(Context context, String key, Object saveObject) { /** * 获取SharedPreference保存的对象 * - * @param context context + * @param context context * @param key 储存对象的key * @return object 返回根据key得到的对象 */ diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/View/ClearTextView.java b/app/src/main/java/cn/darkal/networkdiagnosis/View/ClearTextView.java index 63634a1..5c1824b 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/View/ClearTextView.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/View/ClearTextView.java @@ -33,26 +33,12 @@ public class ClearTextView extends EditText { private int mButtonPadding = dp2px(3); - - - - /** - * 按钮显示方式 - * NEVER 不显示清空按钮 - * ALWAYS 始终显示清空按钮 - * WHILEEDITING 输入框内容不为空且有获得焦点 - * UNLESSEDITING 输入框内容不为空且没有获得焦点 - * */ - public enum ClearButtonMode { - NEVER, ALWAYS, WHILEEDITING, UNLESSEDITING - } - - public ClearTextView(Context context) { super(context); init(context, null); } + public ClearTextView(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); @@ -63,10 +49,6 @@ public ClearTextView(Context context, AttributeSet attrs, int defStyleAttr) { init(context, attrs); } - - - - /** * 初始化 */ @@ -101,10 +83,9 @@ private void init(Context context, AttributeSet attributeSet) { mInitPaddingRight = getPaddingRight(); } - - /** * 按钮状态管理 + * * @param canvas onDraw的Canvas */ private void buttonManager(Canvas canvas) { @@ -123,11 +104,10 @@ private void buttonManager(Canvas canvas) { } } - - /** * 设置输入框的内边距 - * @param isShow 是否显示按钮 + * + * @param isShow 是否显示按钮 */ private void setPadding(boolean isShow) { int paddingRight = mInitPaddingRight + (isShow ? mClearButton.getWidth() + mButtonPadding + mButtonPadding : 0); @@ -135,19 +115,18 @@ private void setPadding(boolean isShow) { setPadding(getPaddingLeft(), getPaddingTop(), paddingRight, getPaddingBottom()); } - - /** * 取得显示按钮与不显示按钮时的Rect - * @param isShow 是否显示按钮 + * + * @param isShow 是否显示按钮 */ private Rect getRect(boolean isShow) { int left, top, right, bottom; - right = isShow ? getMeasuredWidth() + getScrollX() - mButtonPadding - mButtonPadding : 0; - left = isShow ? right - mClearButton.getWidth() : 0; - top = isShow ? (getMeasuredHeight() - mClearButton.getHeight())/2 : 0; - bottom = isShow ? top + mClearButton.getHeight() : 0; + right = isShow ? getMeasuredWidth() + getScrollX() - mButtonPadding - mButtonPadding : 0; + left = isShow ? right - mClearButton.getWidth() : 0; + top = isShow ? (getMeasuredHeight() - mClearButton.getHeight()) / 2 : 0; + bottom = isShow ? top + mClearButton.getHeight() : 0; //更新输入框内边距 @@ -157,10 +136,9 @@ private Rect getRect(boolean isShow) { return new Rect(left, top, right, bottom); } - - /** * 绘制按钮图片 + * * @param canvas onDraw的Canvas * @param rect 图片位置 */ @@ -170,9 +148,6 @@ private void drawBitmap(Canvas canvas, Rect rect) { } } - - - @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); @@ -194,19 +169,17 @@ public boolean onTouchEvent(MotionEvent event) { this.setText(""); } break; + default: + break; } return super.onTouchEvent(event); } - - - - - /** * 获取Drawable - * @param resourseId 资源ID + * + * @param resourseId 资源ID */ private Drawable getDrawableCompat(int resourseId) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { @@ -218,6 +191,7 @@ private Drawable getDrawableCompat(int resourseId) { /** * 设置按钮左右内边距 + * * @param buttonPadding 单位为dp */ public void setButtonPadding(int buttonPadding) { @@ -226,6 +200,7 @@ public void setButtonPadding(int buttonPadding) { /** * 设置按钮显示方式 + * * @param clearButtonMode 显示方式 */ public void setClearButtonMode(ClearButtonMode clearButtonMode) { @@ -240,4 +215,15 @@ public int dp2px(float dipValue) { final float scale = getResources().getDisplayMetrics().density; return (int) (dipValue * scale + 0.5f); } + + /** + * 按钮显示方式 + * NEVER 不显示清空按钮 + * ALWAYS 始终显示清空按钮 + * WHILEEDITING 输入框内容不为空且有获得焦点 + * UNLESSEDITING 输入框内容不为空且没有获得焦点 + */ + public enum ClearButtonMode { + NEVER, ALWAYS, WHILEEDITING, UNLESSEDITING + } } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/View/LoadingDialog.java b/app/src/main/java/cn/darkal/networkdiagnosis/View/LoadingDialog.java index 492dfde..cdd22c4 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/View/LoadingDialog.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/View/LoadingDialog.java @@ -23,6 +23,7 @@ public class LoadingDialog extends ProgressDialog { private boolean mSingleLine; private Context mContext; private ProgressWheel mProgressWheel; + public LoadingDialog(Context context) { super(context, R.style.JzAlertDialogWhite); } @@ -52,7 +53,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_loading2_dialog); TextView mMessage = (TextView) findViewById(R.id.loading_dialog_message); - if ((mText == null) || (mText.equals(""))) { + if (TextUtils.isEmpty(mText)) { mMessage.setVisibility(View.GONE); } else { mMessage.setVisibility(View.VISIBLE); @@ -96,7 +97,7 @@ public void show() { @Override public void onAttachedToWindow() { super.onAttachedToWindow(); - if(mProgressWheel != null) { + if (mProgressWheel != null) { mProgressWheel.spin(); } } @@ -104,7 +105,7 @@ public void onAttachedToWindow() { @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); - if(mProgressWheel != null) { + if (mProgressWheel != null) { mProgressWheel.stopSpinning(); } } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/View/ProgressWheel.java b/app/src/main/java/cn/darkal/networkdiagnosis/View/ProgressWheel.java index 83b4586..3e9282e 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/View/ProgressWheel.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/View/ProgressWheel.java @@ -17,138 +17,135 @@ /** * An indicator of progress, similar to Android's ProgressBar. * Can be used in 'spin mode' or 'increment mode' - * @author Todd Davies * + * @author Todd Davies + *

* Licensed under the Creative Commons Attribution 3.0 license see: * http://creativecommons.org/licenses/by/3.0/ */ public class ProgressWheel extends View { - - //Sizes (with defaults) - private int fullRadius = 100; - private int circleRadius = 80; - private int barLength = 60; - private int barWidth = 20; //内部圆弧的宽度 - private int rimWidth = 20; - private int textSize = 20; + + int progress = 0; + boolean isSpinning = false; + //Sizes (with defaults) + private int fullRadius = 100; + private int circleRadius = 80; + private int barLength = 60; + private int barWidth = 20; //内部圆弧的宽度 + private int rimWidth = 20; + private int textSize = 20; private int barDegree = 60; - private float arcR = barWidth/2; //内部圆弧的半径 - - //Padding (with defaults) - private int paddingTop = 5; - private int paddingBottom = 5; - private int paddingLeft = 5; - private int paddingRight = 5; - - //Colors (with defaults) - private int barColor = 0xAA000000; - private int circleColor = 0x0000ffff; - private int rimColor = 0xAADDDDDD; + private float arcR = barWidth / 2; //内部圆弧的半径 + //Padding (with defaults) + private int paddingTop = 5; + private int paddingBottom = 5; + private int paddingLeft = 5; + private int paddingRight = 5; + //Colors (with defaults) + private int barColor = 0xAA000000; + private int circleColor = 0x0000ffff; + private int rimColor = 0xAADDDDDD; private int spinRimColor = 0xAADDDDDD; - private int textColor = 0xFF000000; - private int spinCircleColor = 0x00000000; - - //Paints - private Paint barPaint = new Paint(); //圆画笔 - private Paint circlePaint = new Paint(); //内部填充圆画笔 - private Paint barCirclePaint = new Paint(); //圆弧上两端点圆的画笔 - private Paint rimPaint = new Paint(); //底部圈画笔 - private Paint textPaint = new Paint(); + private int textColor = 0xFF000000; + private int spinCircleColor = 0x00000000; + //Paints + private Paint barPaint = new Paint(); //圆画笔 + private Paint circlePaint = new Paint(); //内部填充圆画笔 + private Paint barCirclePaint = new Paint(); //圆弧上两端点圆的画笔 + private Paint rimPaint = new Paint(); //底部圈画笔 + private Paint textPaint = new Paint(); private Paint spinRimPaint = new Paint(); //旋转时,底部圈画笔,主要用于此时改变底部圈颜色 - private Paint spinCirclePaint = new Paint();//旋转时,圆的画笔 - - //Rectangles - @SuppressWarnings("unused") - private RectF rectBounds = new RectF(); - private RectF circleBounds = new RectF(); - - private int startDegree = -90; //圆弧的起始位置, -90 顶上 - private float startArcX = 0; - private float startArcY = 0; - - //Animation - //The amount of pixels to move the bar by on each draw - private int spinSpeed = 2; - //The number of milliseconds to wait inbetween each draw - private int delayMillis = 0; - private Handler spinHandler = new Handler() { - /** - * This is the code that will increment the progress variable - * and so spin the wheel - */ - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case 0: - invalidate(); - if (isSpinning) { - progress += spinSpeed; - if (progress > 360) { - progress = 0; - } - spinHandler.sendEmptyMessageDelayed(0, delayMillis); - } - break; - case 1: - spinHandler.removeMessages(0); - isSpinning = false; - invalidate(); - break; - } - } - }; - int progress = 0; - boolean isSpinning = false; - - //Other - private String text = ""; - private String[] splitText = {}; - - /** - * The constructor for the ProgressWheel - * @param context - * @param attrs - */ - public ProgressWheel(Context context, AttributeSet attrs) { - super(context, attrs); - - parseAttributes(context.obtainStyledAttributes(attrs, - R.styleable.ProgressWheel)); - } - - //---------------------------------- - //Setting up stuff - //---------------------------------- - - /** - * Now we know the dimensions of the view, setup the bounds and paints - */ - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - - setupBounds(); - setupPaints(); - invalidate(); - - } - - @Override - protected void onDetachedFromWindow() { - invalidate(); - super.onDetachedFromWindow(); - } - - /** - * Set the properties of the paints we're using to - * draw the progress wheel - */ - private void setupPaints() { - barPaint.setColor(barColor); + private Paint spinCirclePaint = new Paint();//旋转时,圆的画笔 + //Rectangles + @SuppressWarnings("unused") + private RectF rectBounds = new RectF(); + private RectF circleBounds = new RectF(); + private int startDegree = -90; //圆弧的起始位置, -90 顶上 + private float startArcX = 0; + private float startArcY = 0; + //Animation + //The amount of pixels to move the bar by on each draw + private int spinSpeed = 2; + //The number of milliseconds to wait inbetween each draw + private int delayMillis = 0; + private Handler spinHandler = new Handler() { + /** + * This is the code that will increment the progress variable + * and so spin the wheel + */ + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case 0: + invalidate(); + if (isSpinning) { + progress += spinSpeed; + if (progress > 360) { + progress = 0; + } + spinHandler.sendEmptyMessageDelayed(0, delayMillis); + } + break; + case 1: + spinHandler.removeMessages(0); + isSpinning = false; + invalidate(); + break; + default: + break; + } + } + }; + //Other + private String text = ""; + private String[] splitText = {}; + + /** + * The constructor for the ProgressWheel + * + * @param context + * @param attrs + */ + public ProgressWheel(Context context, AttributeSet attrs) { + super(context, attrs); + + parseAttributes(context.obtainStyledAttributes(attrs, + R.styleable.ProgressWheel)); + } + + //---------------------------------- + //Setting up stuff + //---------------------------------- + + /** + * Now we know the dimensions of the view, setup the bounds and paints + */ + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + setupBounds(); + setupPaints(); + invalidate(); + + } + + @Override + protected void onDetachedFromWindow() { + invalidate(); + super.onDetachedFromWindow(); + } + + /** + * Set the properties of the paints we're using to + * draw the progress wheel + */ + private void setupPaints() { + barPaint.setColor(barColor); barPaint.setAntiAlias(true); barPaint.setStyle(Style.STROKE); barPaint.setStrokeWidth(barWidth); - + rimPaint.setColor(rimColor); rimPaint.setAntiAlias(true); rimPaint.setStyle(Style.STROKE); @@ -159,331 +156,338 @@ private void setupPaints() { spinRimPaint.setStyle(Style.STROKE); spinRimPaint.setStrokeWidth(rimWidth); - spinCirclePaint.setColor(spinCircleColor); - spinCirclePaint.setAntiAlias(true); - spinCirclePaint.setStyle(Style.FILL); - spinCirclePaint.setShadowLayer(1, 2, 2, 0x40000000); - + spinCirclePaint.setColor(spinCircleColor); + spinCirclePaint.setAntiAlias(true); + spinCirclePaint.setStyle(Style.FILL); + spinCirclePaint.setShadowLayer(1, 2, 2, 0x40000000); + circlePaint.setColor(circleColor); circlePaint.setAntiAlias(true); circlePaint.setStyle(Style.FILL); - barCirclePaint.setColor(barColor); - barCirclePaint.setAntiAlias(true); - barCirclePaint.setStyle(Style.FILL); - barCirclePaint.setStrokeWidth(barWidth); - + barCirclePaint.setColor(barColor); + barCirclePaint.setAntiAlias(true); + barCirclePaint.setStyle(Style.FILL); + barCirclePaint.setStrokeWidth(barWidth); + textPaint.setColor(textColor); textPaint.setStyle(Style.FILL); textPaint.setAntiAlias(true); textPaint.setTextSize(textSize); - } - - /** - * Set the bounds of the component - */ - private void setupBounds() { - paddingTop = this.getPaddingTop(); - paddingBottom = this.getPaddingBottom(); - paddingLeft = this.getPaddingLeft(); - paddingRight = this.getPaddingRight(); - - rectBounds = new RectF(paddingLeft, - paddingTop, + } + + /** + * Set the bounds of the component + */ + private void setupBounds() { + paddingTop = this.getPaddingTop(); + paddingBottom = this.getPaddingBottom(); + paddingLeft = this.getPaddingLeft(); + paddingRight = this.getPaddingRight(); + + rectBounds = new RectF(paddingLeft, + paddingTop, this.getLayoutParams().width - paddingRight, this.getLayoutParams().height - paddingBottom); - - circleBounds = new RectF(paddingLeft + barWidth, - paddingTop + barWidth, + + circleBounds = new RectF(paddingLeft + barWidth, + paddingTop + barWidth, this.getLayoutParams().width - paddingRight - barWidth, this.getLayoutParams().height - paddingBottom - barWidth); - - fullRadius = (this.getLayoutParams().width - paddingRight - barWidth)/2; - circleRadius = (fullRadius - barWidth) + 1; //内部圆的半径 - - arcR = barWidth/2; //圆弧的半径 - startArcX = (float) (-circleBounds.width()/2* Math.sin( 2* Math.PI/360*(startDegree+270))+this.getLayoutParams().width/2); //计算时角度要换成弧度 - startArcY = (float) (circleBounds.height()/2* Math.cos(2 * Math.PI / 360 * (startDegree+270)) + this.getLayoutParams().height/2); - } - - /** - * Parse the attributes passed to the view from the XML - * @param a the attributes to parse - */ - private void parseAttributes(TypedArray a) { - barWidth = (int) a.getDimension(R.styleable.ProgressWheel_barWidth_progress, - barWidth); - - rimWidth = (int) a.getDimension(R.styleable.ProgressWheel_rimWidth_progress, - rimWidth); - - spinSpeed = (int) a.getInteger(R.styleable.ProgressWheel_spinSpeed_progress, - spinSpeed); - - delayMillis = (int) a.getInteger(R.styleable.ProgressWheel_delayMillis_progress, - delayMillis); - if(delayMillis<0) { - delayMillis = 0; - } - - barColor = a.getColor(R.styleable.ProgressWheel_barColor_progress, barColor); - - barLength = (int) a.getDimension(R.styleable.ProgressWheel_barLength_progress, - barLength); - - textSize = (int) a.getDimension(R.styleable.ProgressWheel_textSize_progress, - textSize); - - textColor = (int) a.getColor(R.styleable.ProgressWheel_textColor_progress, - textColor); - - setText(a.getString(R.styleable.ProgressWheel_text_progress)); - - rimColor = (int) a.getColor(R.styleable.ProgressWheel_rimColor_progress, - rimColor); + + fullRadius = (this.getLayoutParams().width - paddingRight - barWidth) / 2; + circleRadius = (fullRadius - barWidth) + 1; //内部圆的半径 + + arcR = barWidth / 2; //圆弧的半径 + startArcX = (float) (-circleBounds.width() / 2 * Math.sin(2 * Math.PI / 360 * (startDegree + 270)) + this.getLayoutParams().width / 2); //计算时角度要换成弧度 + startArcY = (float) (circleBounds.height() / 2 * Math.cos(2 * Math.PI / 360 * (startDegree + 270)) + this.getLayoutParams().height / 2); + } + + /** + * Parse the attributes passed to the view from the XML + * + * @param a the attributes to parse + */ + private void parseAttributes(TypedArray a) { + barWidth = (int) a.getDimension(R.styleable.ProgressWheel_barWidth_progress, + barWidth); + + rimWidth = (int) a.getDimension(R.styleable.ProgressWheel_rimWidth_progress, + rimWidth); + + spinSpeed = (int) a.getInteger(R.styleable.ProgressWheel_spinSpeed_progress, + spinSpeed); + + delayMillis = (int) a.getInteger(R.styleable.ProgressWheel_delayMillis_progress, + delayMillis); + if (delayMillis < 0) { + delayMillis = 0; + } + + barColor = a.getColor(R.styleable.ProgressWheel_barColor_progress, barColor); + + barLength = (int) a.getDimension(R.styleable.ProgressWheel_barLength_progress, + barLength); + + textSize = (int) a.getDimension(R.styleable.ProgressWheel_textSize_progress, + textSize); + + textColor = (int) a.getColor(R.styleable.ProgressWheel_textColor_progress, + textColor); + + setText(a.getString(R.styleable.ProgressWheel_text_progress)); + + rimColor = (int) a.getColor(R.styleable.ProgressWheel_rimColor_progress, + rimColor); spinRimColor = (int) a.getColor(R.styleable.ProgressWheel_spinRimColor_progress, spinRimColor); - - circleColor = (int) a.getColor(R.styleable.ProgressWheel_circleColor_progress, circleColor); - spinCircleColor = (int) a.getColor(R.styleable.ProgressWheel_spinCircleColor_progress, spinCircleColor); - - barDegree = (int) a.getInteger(R.styleable.ProgressWheel_barDegree_progress,-1); - } - - //---------------------------------- - //Animation stuff - //---------------------------------- - - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - //Draw the rim - if(isSpinning()){ + + circleColor = (int) a.getColor(R.styleable.ProgressWheel_circleColor_progress, circleColor); + spinCircleColor = (int) a.getColor(R.styleable.ProgressWheel_spinCircleColor_progress, spinCircleColor); + + barDegree = (int) a.getInteger(R.styleable.ProgressWheel_barDegree_progress, -1); + } + + //---------------------------------- + //Animation stuff + //---------------------------------- + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + //Draw the rim + if (isSpinning()) { canvas.drawArc(circleBounds, 360, 360, false, spinRimPaint); - }else { + } else { canvas.drawArc(circleBounds, 360, 360, false, rimPaint); } - //Draw the bar - if(isSpinning) { - if(barDegree != -1) { //按度数 + //Draw the bar + if (isSpinning) { + if (barDegree != -1) { //按度数 canvas.drawArc(circleBounds, progress - 90, barDegree, false, barPaint); - //结束度数 - double t = (progress - 90+barDegree + 270); - //起始点坐标 - float startX = (float) (-circleBounds.width()/2* Math.sin( 2* Math.PI/360*(progress - 90+270))+this.getLayoutParams().width/2); //计算时角度要换成弧度 - float startY = (float) (circleBounds.height()/2* Math.cos(2 * Math.PI / 360 * (progress - 90 + 270)) + this.getLayoutParams().height/2); - //结束点坐标 - float endX = (float) (-circleBounds.width() / 2 * Math.sin(2 * Math.PI / 360 * t) + this.getLayoutParams().width / 2); - float endY = (float) (circleBounds.height() / 2 * Math.cos(2 * Math.PI / 360 * t) + this.getLayoutParams().height / 2); - //计算两点间距离 - float tmpR = (float) Math.sqrt((endX-startX)*(endX-startX)+(endY-startY)*(endY-startY)); + //结束度数 + double t = (progress - 90 + barDegree + 270); + //起始点坐标 + float startX = (float) (-circleBounds.width() / 2 * Math.sin(2 * Math.PI / 360 * (progress - 90 + 270)) + this.getLayoutParams().width / 2); //计算时角度要换成弧度 + float startY = (float) (circleBounds.height() / 2 * Math.cos(2 * Math.PI / 360 * (progress - 90 + 270)) + this.getLayoutParams().height / 2); + //结束点坐标 + float endX = (float) (-circleBounds.width() / 2 * Math.sin(2 * Math.PI / 360 * t) + this.getLayoutParams().width / 2); + float endY = (float) (circleBounds.height() / 2 * Math.cos(2 * Math.PI / 360 * t) + this.getLayoutParams().height / 2); + //计算两点间距离 + float tmpR = (float) Math.sqrt((endX - startX) * (endX - startX) + (endY - startY) * (endY - startY)); // canvas.drawCircle(startX, startY, tmpR, barPaint); //画圆弧起始圆 // canvas.drawCircle(x, y, tmpR, barPaint); //画圆弧起始圆 - //确定圆心点 - double tmp = (progress - 90 + barDegree)+(360-barDegree)/2+270; - float x2 = (float) (-circleBounds.width() / 2 * Math.sin(2 * Math.PI / 360 * tmp) + this.getLayoutParams().width / 2); - float y2 = (float) (circleBounds.height() / 2 * Math.cos(2 * Math.PI / 360 * tmp) + this.getLayoutParams().height / 2); - canvas.drawCircle(x2, y2, tmpR, spinCirclePaint); //画圆 - }else{ //按长度 + //确定圆心点 + double tmp = (progress - 90 + barDegree) + (360 - barDegree) / 2 + 270; + float x2 = (float) (-circleBounds.width() / 2 * Math.sin(2 * Math.PI / 360 * tmp) + this.getLayoutParams().width / 2); + float y2 = (float) (circleBounds.height() / 2 * Math.cos(2 * Math.PI / 360 * tmp) + this.getLayoutParams().height / 2); + canvas.drawCircle(x2, y2, tmpR, spinCirclePaint); //画圆 + } else { //按长度 canvas.drawArc(circleBounds, progress - 90, barLength, false, barPaint); - //结束度数 - double t = (progress - 90+barLength + 270); - //起始点坐标 - float startX = (float) (-circleBounds.width()/2* Math.sin( 2* Math.PI/360*(progress - 90+270))+this.getLayoutParams().width/2); //计算时角度要换成弧度 - float startY = (float) (circleBounds.height()/2* Math.cos(2 * Math.PI / 360 * (progress - 90 + 270)) + this.getLayoutParams().height/2); - //结束点坐标 - float endX = (float) (-circleBounds.width() / 2 * Math.sin(2 * Math.PI / 360 * t) + this.getLayoutParams().width / 2); - float endY = (float) (circleBounds.height() / 2 * Math.cos(2 * Math.PI / 360 * t) + this.getLayoutParams().height / 2); - //计算两点间距离 - float tmpR = (float) Math.sqrt((endX-startX)*(endX-startX)+(endY-startY)*(endY-startY)); + //结束度数 + double t = (progress - 90 + barLength + 270); + //起始点坐标 + float startX = (float) (-circleBounds.width() / 2 * Math.sin(2 * Math.PI / 360 * (progress - 90 + 270)) + this.getLayoutParams().width / 2); //计算时角度要换成弧度 + float startY = (float) (circleBounds.height() / 2 * Math.cos(2 * Math.PI / 360 * (progress - 90 + 270)) + this.getLayoutParams().height / 2); + //结束点坐标 + float endX = (float) (-circleBounds.width() / 2 * Math.sin(2 * Math.PI / 360 * t) + this.getLayoutParams().width / 2); + float endY = (float) (circleBounds.height() / 2 * Math.cos(2 * Math.PI / 360 * t) + this.getLayoutParams().height / 2); + //计算两点间距离 + float tmpR = (float) Math.sqrt((endX - startX) * (endX - startX) + (endY - startY) * (endY - startY)); // canvas.drawCircle(startX, startY, tmpR, barPaint); //画圆弧起始圆 // canvas.drawCircle(x, y, tmpR, barPaint); //画圆弧起始圆 - //确定圆心点 - double tmp = (progress - 90 + barLength)+(360-barLength)/2+270; - float x2 = (float) (-circleBounds.width() / 2 * Math.sin(2 * Math.PI / 360 * tmp) + this.getLayoutParams().width / 2); - float y2 = (float) (circleBounds.height() / 2 * Math.cos(2 * Math.PI / 360 * tmp) + this.getLayoutParams().height / 2); - canvas.drawCircle(x2, y2, tmpR, spinCirclePaint); //画圆 - } - } else { - canvas.drawArc(circleBounds, startDegree, progress, false, barPaint); // -90 从顶上开始 - double t = progress+startDegree+270; - if(progress != 0) { - canvas.drawCircle(startArcX, startArcY, arcR, barCirclePaint); //画圆弧起始圆 - - float x = (float) (-circleBounds.width() / 2 * Math.sin(2 * Math.PI / 360 * t) + this.getLayoutParams().width / 2); - float y = (float) (circleBounds.height() / 2 * Math.cos(2 * Math.PI / 360 * t) + this.getLayoutParams().height / 2); - canvas.drawCircle(x, y, arcR, barCirclePaint); //画圆弧结束圆 - } - } - //Draw the inner circle - canvas.drawCircle((circleBounds.width()/2) + rimWidth + paddingLeft, - (circleBounds.height()/2) + rimWidth + paddingTop, - circleRadius, - circlePaint); - //Draw the text (attempts to center it horizontally and vertically) - int offsetNum = 0; - for(String s : splitText) { - float offset = textPaint.measureText(s) / 2; - canvas.drawText(s, this.getWidth() / 2 - offset, - this.getHeight() / 2 + (textSize*(offsetNum)) - - ((splitText.length-1)*(textSize/2)), textPaint); - offsetNum++; - } - } - - /** - * Reset the count (in increment mode) - */ - public void resetCount() { - progress = 0; - setText("0%"); - invalidate(); - } - - /** - * Turn off spin mode - */ - public void stopSpinning() { - spinHandler.sendEmptyMessageDelayed(1,200); - } - - - /** - * Puts the view on spin mode - */ - public void spin() { - isSpinning = true; - spinHandler.sendEmptyMessage(0); - } - - public boolean isSpinning(){ + //确定圆心点 + double tmp = (progress - 90 + barLength) + (360 - barLength) / 2 + 270; + float x2 = (float) (-circleBounds.width() / 2 * Math.sin(2 * Math.PI / 360 * tmp) + this.getLayoutParams().width / 2); + float y2 = (float) (circleBounds.height() / 2 * Math.cos(2 * Math.PI / 360 * tmp) + this.getLayoutParams().height / 2); + canvas.drawCircle(x2, y2, tmpR, spinCirclePaint); //画圆 + } + } else { + canvas.drawArc(circleBounds, startDegree, progress, false, barPaint); // -90 从顶上开始 + double t = progress + startDegree + 270; + if (progress != 0) { + canvas.drawCircle(startArcX, startArcY, arcR, barCirclePaint); //画圆弧起始圆 + + float x = (float) (-circleBounds.width() / 2 * Math.sin(2 * Math.PI / 360 * t) + this.getLayoutParams().width / 2); + float y = (float) (circleBounds.height() / 2 * Math.cos(2 * Math.PI / 360 * t) + this.getLayoutParams().height / 2); + canvas.drawCircle(x, y, arcR, barCirclePaint); //画圆弧结束圆 + } + } + //Draw the inner circle + canvas.drawCircle((circleBounds.width() / 2) + rimWidth + paddingLeft, + (circleBounds.height() / 2) + rimWidth + paddingTop, + circleRadius, + circlePaint); + //Draw the text (attempts to center it horizontally and vertically) + int offsetNum = 0; + for (String s : splitText) { + float offset = textPaint.measureText(s) / 2; + canvas.drawText(s, this.getWidth() / 2 - offset, + this.getHeight() / 2 + (textSize * (offsetNum)) + - ((splitText.length - 1) * (textSize / 2)), textPaint); + offsetNum++; + } + } + + /** + * Reset the count (in increment mode) + */ + public void resetCount() { + progress = 0; + setText("0%"); + invalidate(); + } + + /** + * Turn off spin mode + */ + public void stopSpinning() { + spinHandler.sendEmptyMessageDelayed(1, 200); + } + + + /** + * Puts the view on spin mode + */ + public void spin() { + isSpinning = true; + spinHandler.sendEmptyMessage(0); + } + + public boolean isSpinning() { return isSpinning; } - /** - * Increment the progress by 1 (of 360) - */ - public void incrementProgress() { - isSpinning = false; - progress++; - setText(Math.round(((float)progress/360)*100) + "%"); - spinHandler.sendEmptyMessage(0); - } - - /** - * Set the progress to a specific value - */ - public void setProgress(int i) { - isSpinning = false; - progress=i; - spinHandler.sendEmptyMessage(0); - } - - //---------------------------------- - //Getters + setters - //---------------------------------- - - /** - * Set the text in the progress bar - * Doesn't invalidate the view - * @param text the text to show ('\n' constitutes a new line) - */ - public void setText(String text) { - this.text = text; - splitText = this.text.split("\n"); - } - - public int getCircleRadius() { - return circleRadius; - } - - public void setCircleRadius(int circleRadius) { - this.circleRadius = circleRadius; - } - - public int getBarLength() { - return barLength; - } - - public void setBarLength(int barLength) { - this.barLength = barLength; - } - - public int getBarWidth() { - return barWidth; - } - - public void setBarWidth(int barWidth) { - this.barWidth = barWidth; - } - - public int getTextSize() { - return textSize; - } - - public void setTextSize(int textSize) { - this.textSize = textSize; - } - - public int getPaddingTop() { - return paddingTop; - } - - public void setPaddingTop(int paddingTop) { - this.paddingTop = paddingTop; - } - - public int getPaddingBottom() { - return paddingBottom; - } - - public void setPaddingBottom(int paddingBottom) { - this.paddingBottom = paddingBottom; - } - - public int getPaddingLeft() { - return paddingLeft; - } - - public void setPaddingLeft(int paddingLeft) { - this.paddingLeft = paddingLeft; - } - - public int getPaddingRight() { - return paddingRight; - } - - public void setPaddingRight(int paddingRight) { - this.paddingRight = paddingRight; - } - - public int getBarColor() { - return barColor; - } - - public void setBarColor(int barColor) { - this.barColor = barColor; - } - - public int getCircleColor() { - return circleColor; - } - - public void setCircleColor(int circleColor) { - this.circleColor = circleColor; - } - - public int getRimColor() { - return rimColor; - } - - public void setRimColor(int rimColor) { - this.rimColor = rimColor; - } + /** + * Increment the progress by 1 (of 360) + */ + public void incrementProgress() { + isSpinning = false; + progress++; + setText(Math.round(((float) progress / 360) * 100) + "%"); + spinHandler.sendEmptyMessage(0); + } + + /** + * Set the progress to a specific value + */ + public void setProgress(int i) { + isSpinning = false; + progress = i; + spinHandler.sendEmptyMessage(0); + } + + //---------------------------------- + //Getters + setters + //---------------------------------- + + /** + * Set the text in the progress bar + * Doesn't invalidate the view + * + * @param text the text to show ('\n' constitutes a new line) + */ + public void setText(String text) { + this.text = text; + splitText = this.text.split("\n"); + } + + public int getCircleRadius() { + return circleRadius; + } + + public void setCircleRadius(int circleRadius) { + this.circleRadius = circleRadius; + } + + public int getBarLength() { + return barLength; + } + + public void setBarLength(int barLength) { + this.barLength = barLength; + } + + public int getBarWidth() { + return barWidth; + } + + public void setBarWidth(int barWidth) { + this.barWidth = barWidth; + } + + public int getTextSize() { + return textSize; + } + + public void setTextSize(int textSize) { + this.textSize = textSize; + } + + @Override + public int getPaddingTop() { + return paddingTop; + } + + public void setPaddingTop(int paddingTop) { + this.paddingTop = paddingTop; + } + + @Override + public int getPaddingBottom() { + return paddingBottom; + } + + public void setPaddingBottom(int paddingBottom) { + this.paddingBottom = paddingBottom; + } + + @Override + public int getPaddingLeft() { + return paddingLeft; + } + + public void setPaddingLeft(int paddingLeft) { + this.paddingLeft = paddingLeft; + } + + @Override + public int getPaddingRight() { + return paddingRight; + } + + public void setPaddingRight(int paddingRight) { + this.paddingRight = paddingRight; + } + + public int getBarColor() { + return barColor; + } + + public void setBarColor(int barColor) { + this.barColor = barColor; + } + + public int getCircleColor() { + return circleColor; + } + + public void setCircleColor(int circleColor) { + this.circleColor = circleColor; + } + + public int getRimColor() { + return rimColor; + } + + public void setRimColor(int rimColor) { + this.rimColor = rimColor; + } public int getSpinRimColor() { return spinRimColor; @@ -493,43 +497,43 @@ public void setSpinRimColor(int spinRimColor) { this.spinRimColor = spinRimColor; } - public Shader getRimShader() { - return rimPaint.getShader(); - } - - public void setRimShader(Shader shader) { - this.rimPaint.setShader(shader); - } - - public int getTextColor() { - return textColor; - } - - public void setTextColor(int textColor) { - this.textColor = textColor; - } - - public int getSpinSpeed() { - return spinSpeed; - } - - public void setSpinSpeed(int spinSpeed) { - this.spinSpeed = spinSpeed; - } - - public int getRimWidth() { - return rimWidth; - } - - public void setRimWidth(int rimWidth) { - this.rimWidth = rimWidth; - } - - public int getDelayMillis() { - return delayMillis; - } - - public void setDelayMillis(int delayMillis) { - this.delayMillis = delayMillis; - } + public Shader getRimShader() { + return rimPaint.getShader(); + } + + public void setRimShader(Shader shader) { + this.rimPaint.setShader(shader); + } + + public int getTextColor() { + return textColor; + } + + public void setTextColor(int textColor) { + this.textColor = textColor; + } + + public int getSpinSpeed() { + return spinSpeed; + } + + public void setSpinSpeed(int spinSpeed) { + this.spinSpeed = spinSpeed; + } + + public int getRimWidth() { + return rimWidth; + } + + public void setRimWidth(int rimWidth) { + this.rimWidth = rimWidth; + } + + public int getDelayMillis() { + return delayMillis; + } + + public void setDelayMillis(int delayMillis) { + this.delayMillis = delayMillis; + } } diff --git a/app/src/main/java/cn/darkal/networkdiagnosis/View/RecycleViewDivider.java b/app/src/main/java/cn/darkal/networkdiagnosis/View/RecycleViewDivider.java index 61d4732..9ad08b8 100644 --- a/app/src/main/java/cn/darkal/networkdiagnosis/View/RecycleViewDivider.java +++ b/app/src/main/java/cn/darkal/networkdiagnosis/View/RecycleViewDivider.java @@ -14,14 +14,11 @@ */ public class RecycleViewDivider extends RecyclerView.ItemDecoration { + public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; + public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; - - public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; - - public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; - private Drawable mDivider; private int mOrientation; diff --git a/app/src/main/java/com/google/zxing/QrCodeScanActivity.java b/app/src/main/java/com/google/zxing/QrCodeScanActivity.java index c02d3da..b6fabdb 100644 --- a/app/src/main/java/com/google/zxing/QrCodeScanActivity.java +++ b/app/src/main/java/com/google/zxing/QrCodeScanActivity.java @@ -1,4 +1,3 @@ - package com.google.zxing; import android.app.Activity; @@ -45,6 +44,18 @@ */ public class QrCodeScanActivity extends Activity implements SurfaceHolder.Callback { + private static final float BEEP_VOLUME = 1.00f; + private static final int REQUEST_CODE_GALLERY = 0x0708; + private static final long VIBRATE_DURATION = 200L; + /** + * When the beep has finished playing, rewind to queue up another one. + */ + private final OnCompletionListener beepListener = new OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mediaPlayer) { + mediaPlayer.seekTo(0); + } + }; private CameraManager cameraManager; private CaptureActivityHandler handler; private ViewfinderView viewfinderView; @@ -54,9 +65,7 @@ public class QrCodeScanActivity extends Activity implements SurfaceHolder.Callba private InactivityTimer inactivityTimer; private MediaPlayer mediaPlayer; private boolean playBeep; - private static final float BEEP_VOLUME = 1.00f; private boolean vibrate; - private static final int REQUEST_CODE_GALLERY = 0x0708; public CameraManager getCameraManager() { return cameraManager; @@ -274,8 +283,6 @@ private void initBeepSound() { } } - private static final long VIBRATE_DURATION = 200L; - private void playBeepSoundAndVibrate() { if (playBeep && mediaPlayer != null) { mediaPlayer.start(); @@ -286,15 +293,6 @@ private void playBeepSoundAndVibrate() { } } - /** - * When the beep has finished playing, rewind to queue up another one. - */ - private final OnCompletionListener beepListener = new OnCompletionListener() { - public void onCompletion(MediaPlayer mediaPlayer) { - mediaPlayer.seekTo(0); - } - }; - public void onGalleryClick(View view) { // 设定action和miniType Intent intent = new Intent(); @@ -349,6 +347,8 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { } } break; + default: + break; } } } diff --git a/app/src/main/java/com/google/zxing/camera/AutoFocusManager.java b/app/src/main/java/com/google/zxing/camera/AutoFocusManager.java index f42adbb..34ec94a 100644 --- a/app/src/main/java/com/google/zxing/camera/AutoFocusManager.java +++ b/app/src/main/java/com/google/zxing/camera/AutoFocusManager.java @@ -40,10 +40,10 @@ final class AutoFocusManager implements Camera.AutoFocusCallback { FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_MACRO); } - private boolean stopped; - private boolean focusing; private final boolean useAutoFocus; private final Camera camera; + private boolean stopped; + private boolean focusing; private AsyncTask outstandingTask; AutoFocusManager(Context context, Camera camera) { diff --git a/app/src/main/java/com/google/zxing/camera/CameraManager.java b/app/src/main/java/com/google/zxing/camera/CameraManager.java index fb227c9..4ba94d1 100644 --- a/app/src/main/java/com/google/zxing/camera/CameraManager.java +++ b/app/src/main/java/com/google/zxing/camera/CameraManager.java @@ -48,6 +48,11 @@ public final class CameraManager { private final Context context; private final CameraConfigurationManager configManager; + /** + * Preview frames are delivered here, which we pass on to the registered handler. Make sure to + * clear the handler so it will only receive one message. + */ + private final PreviewCallback previewCallback; private Camera camera; private AutoFocusManager autoFocusManager; private Rect framingRect; @@ -57,11 +62,6 @@ public final class CameraManager { private int requestedCameraId = -1; private int requestedFramingRectWidth; private int requestedFramingRectHeight; - /** - * Preview frames are delivered here, which we pass on to the registered handler. Make sure to - * clear the handler so it will only receive one message. - */ - private final PreviewCallback previewCallback; public CameraManager(Context context) { this.context = context; @@ -69,6 +69,17 @@ public CameraManager(Context context) { previewCallback = new PreviewCallback(configManager); } + private static int findDesiredDimensionInRange(int resolution, int hardMin, int hardMax) { + int dim = 5 * resolution / 8; // Target 5/8 of each dimension + if (dim < hardMin) { + return hardMin; + } + if (dim > hardMax) { + return hardMax; + } + return dim; + } + /** * Opens the camera driver and initializes the hardware parameters. * @@ -172,8 +183,6 @@ public synchronized void stopPreview() { } /** - * - * * @param newSetting if {@code true}, light should be turned on if currently off. And vice versa. */ public synchronized void setTorch(boolean newSetting) { @@ -240,17 +249,6 @@ public synchronized Rect getFramingRect() { return framingRect; } - private static int findDesiredDimensionInRange(int resolution, int hardMin, int hardMax) { - int dim = 5 * resolution / 8; // Target 5/8 of each dimension - if (dim < hardMin) { - return hardMin; - } - if (dim > hardMax) { - return hardMax; - } - return dim; - } - /** * Like {@link #getFramingRect} but coordinates are in terms of the preview frame, * not UI / screen. diff --git a/app/src/main/java/com/google/zxing/decoding/CaptureActivityHandler.java b/app/src/main/java/com/google/zxing/decoding/CaptureActivityHandler.java index 66fbe7c..0b77e3c 100644 --- a/app/src/main/java/com/google/zxing/decoding/CaptureActivityHandler.java +++ b/app/src/main/java/com/google/zxing/decoding/CaptureActivityHandler.java @@ -53,14 +53,8 @@ public final class CaptureActivityHandler extends Handler { private final QrCodeScanActivity activity; private final DecodeThread decodeThread; - private State state; private final CameraManager cameraManager; - - private enum State { - PREVIEW, - SUCCESS, - DONE - } + private State state; public CaptureActivityHandler(QrCodeScanActivity activity, Collection decodeFormats, @@ -160,4 +154,10 @@ private void restartPreviewAndDecode() { } } + private enum State { + PREVIEW, + SUCCESS, + DONE + } + } diff --git a/app/src/main/java/com/google/zxing/decoding/DecodeFormatManager.java b/app/src/main/java/com/google/zxing/decoding/DecodeFormatManager.java index 20f8989..48d6c80 100644 --- a/app/src/main/java/com/google/zxing/decoding/DecodeFormatManager.java +++ b/app/src/main/java/com/google/zxing/decoding/DecodeFormatManager.java @@ -31,15 +31,15 @@ final class DecodeFormatManager { - private static final Pattern COMMA_PATTERN = Pattern.compile(","); - static final Set PRODUCT_FORMATS; static final Set INDUSTRIAL_FORMATS; - private static final Set ONE_D_FORMATS; static final Set QR_CODE_FORMATS = EnumSet.of(BarcodeFormat.QR_CODE); static final Set DATA_MATRIX_FORMATS = EnumSet.of(BarcodeFormat.DATA_MATRIX); static final Set AZTEC_FORMATS = EnumSet.of(BarcodeFormat.AZTEC); static final Set PDF417_FORMATS = EnumSet.of(BarcodeFormat.PDF_417); + private static final Pattern COMMA_PATTERN = Pattern.compile(","); + private static final Set ONE_D_FORMATS; + private static final Map> FORMATS_FOR_MODE; static { PRODUCT_FORMATS = EnumSet.of(BarcodeFormat.UPC_A, @@ -57,8 +57,6 @@ final class DecodeFormatManager { ONE_D_FORMATS.addAll(INDUSTRIAL_FORMATS); } - private static final Map> FORMATS_FOR_MODE; - static { FORMATS_FOR_MODE = new HashMap>(); FORMATS_FOR_MODE.put(Intents.Scan.ONE_D_MODE, ONE_D_FORMATS); diff --git a/app/src/main/java/com/google/zxing/decoding/DecodeHandler.java b/app/src/main/java/com/google/zxing/decoding/DecodeHandler.java index 5735af3..eb0f1d6 100644 --- a/app/src/main/java/com/google/zxing/decoding/DecodeHandler.java +++ b/app/src/main/java/com/google/zxing/decoding/DecodeHandler.java @@ -51,12 +51,23 @@ final class DecodeHandler extends Handler { this.activity = activity; } + private static void bundleThumbnail(PlanarYUVLuminanceSource source, Bundle bundle) { + int[] pixels = source.renderThumbnail(); + int width = source.getThumbnailWidth(); + int height = source.getThumbnailHeight(); + Bitmap bitmap = Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.JPEG, 50, out); + bundle.putByteArray(DecodeThread.BARCODE_BITMAP, out.toByteArray()); + bundle.putFloat(DecodeThread.BARCODE_SCALED_FACTOR, (float) width / source.getWidth()); + } + @Override public void handleMessage(Message message) { if (!running) { return; } - if (message.what == R.id.decode) { + if (message.what == R.id.decode) { decode((byte[]) message.obj, message.arg1, message.arg2); } else if (message.what == R.id.quit) { running = false; @@ -119,15 +130,4 @@ private void decode(byte[] data, int width, int height) { } } - private static void bundleThumbnail(PlanarYUVLuminanceSource source, Bundle bundle) { - int[] pixels = source.renderThumbnail(); - int width = source.getThumbnailWidth(); - int height = source.getThumbnailHeight(); - Bitmap bitmap = Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.JPEG, 50, out); - bundle.putByteArray(DecodeThread.BARCODE_BITMAP, out.toByteArray()); - bundle.putFloat(DecodeThread.BARCODE_SCALED_FACTOR, (float) width / source.getWidth()); - } - } diff --git a/app/src/main/java/com/google/zxing/decoding/DecodeThread.java b/app/src/main/java/com/google/zxing/decoding/DecodeThread.java index 420358a..a2386bb 100644 --- a/app/src/main/java/com/google/zxing/decoding/DecodeThread.java +++ b/app/src/main/java/com/google/zxing/decoding/DecodeThread.java @@ -45,8 +45,8 @@ final class DecodeThread extends Thread { private final QrCodeScanActivity activity; private final Map hints; - private Handler handler; private final CountDownLatch handlerInitLatch; + private Handler handler; DecodeThread(QrCodeScanActivity activity, Collection decodeFormats, diff --git a/app/src/main/java/com/google/zxing/view/ViewfinderView.java b/app/src/main/java/com/google/zxing/view/ViewfinderView.java index 03a9c8c..f173b27 100644 --- a/app/src/main/java/com/google/zxing/view/ViewfinderView.java +++ b/app/src/main/java/com/google/zxing/view/ViewfinderView.java @@ -51,12 +51,6 @@ public final class ViewfinderView extends View { private static final int CURRENT_POINT_OPACITY = 0xA0; private static final int MAX_RESULT_POINTS = 20; private static final int POINT_SIZE = 6; - - /** - * 四个绿色边角对应的长度 - */ - private int ScreenRate; - /** * 四个绿色边角对应的宽度 */ @@ -65,21 +59,14 @@ public final class ViewfinderView extends View { * 扫描框中的中间线的宽度 */ private static final float MIDDLE_LINE_WIDTH = 3; - /** * 扫描框中的中间线的与扫描框左右的间隙 */ private static final float MIDDLE_LINE_PADDING = 5; - /** * 中间那条线每次刷新移动的距离 */ private static final float SPEEN_DISTANCE = 4; - - /** - * 手机的屏幕密度 - */ - private static float density; /** * 字体大小 */ @@ -88,30 +75,32 @@ public final class ViewfinderView extends View { * 字体距离扫描框下面的距离 */ private static final int TEXT_PADDING_TOP = 24; - + /** + * 手机的屏幕密度 + */ + private static float density; + private final int maskColor; + private final int resultColor; + private final int resultPointColor; + boolean isFirst; + /** + * 四个绿色边角对应的长度 + */ + private int ScreenRate; /** * 画笔对象的引用 */ private Paint paint; - /** * 中间滑动线的最顶端位置 */ private float slideTop; - /** * 中间滑动线的最底端位置 */ private int slideBottom; - - - boolean isFirst; - private CameraManager cameraManager; private Bitmap resultBitmap; - private final int maskColor; - private final int resultColor; - private final int resultPointColor; private int scannerAlpha; private List possibleResultPoints; private List lastPossibleResultPoints; @@ -153,7 +142,7 @@ public void onDraw(Canvas canvas) { } //初始化中间线滑动的最上边和最下边 - if(!isFirst){ + if (!isFirst) { isFirst = true; slideTop = frame.top; slideBottom = frame.bottom; @@ -183,11 +172,11 @@ public void onDraw(Canvas canvas) { canvas.drawRect(frame.left - 1, frame.top - 1, frame.left - 1 + ScreenRate, frame.top - 1 + CORNER_WIDTH, paint); canvas.drawRect(frame.left - 1, frame.top - 1, frame.left - 1 + CORNER_WIDTH, frame.top - 1 + ScreenRate, paint); canvas.drawRect(frame.right + 2 - ScreenRate, frame.top - 1, frame.right + 2, frame.top - 1 + CORNER_WIDTH, paint); - canvas.drawRect(frame.right+2 - CORNER_WIDTH, frame.top-1, frame.right+2, frame.top-1 + ScreenRate, paint); - canvas.drawRect(frame.left-1, frame.bottom+2 - CORNER_WIDTH, frame.left-1 + ScreenRate, frame.bottom+2, paint); - canvas.drawRect(frame.left-1, frame.bottom+2 - ScreenRate, frame.left-1 + CORNER_WIDTH, frame.bottom+2, paint); - canvas.drawRect(frame.right+2 - ScreenRate, frame.bottom+2 - CORNER_WIDTH, frame.right+2, frame.bottom+2, paint); - canvas.drawRect(frame.right+2 - CORNER_WIDTH, frame.bottom+2 - ScreenRate, frame.right+2, frame.bottom+2, paint); + canvas.drawRect(frame.right + 2 - CORNER_WIDTH, frame.top - 1, frame.right + 2, frame.top - 1 + ScreenRate, paint); + canvas.drawRect(frame.left - 1, frame.bottom + 2 - CORNER_WIDTH, frame.left - 1 + ScreenRate, frame.bottom + 2, paint); + canvas.drawRect(frame.left - 1, frame.bottom + 2 - ScreenRate, frame.left - 1 + CORNER_WIDTH, frame.bottom + 2, paint); + canvas.drawRect(frame.right + 2 - ScreenRate, frame.bottom + 2 - CORNER_WIDTH, frame.right + 2, frame.bottom + 2, paint); + canvas.drawRect(frame.right + 2 - CORNER_WIDTH, frame.bottom + 2 - ScreenRate, frame.right + 2, frame.bottom + 2, paint); paint.setColor(Color.WHITE); canvas.drawLine(frame.left, frame.bottom, frame.left, frame.top, paint); @@ -197,15 +186,15 @@ public void onDraw(Canvas canvas) { //绘制中间的线,每次刷新界面,中间的线往下移动SPEEN_DISTANCE slideTop += SPEEN_DISTANCE; - if(slideTop >= frame.bottom) { + if (slideTop >= frame.bottom) { slideTop = frame.top; } Shader shaderNew = new LinearGradient(frame.left + MIDDLE_LINE_PADDING, 0, frame.right - MIDDLE_LINE_PADDING, 0, - new int[] { 0x00FFFFFF, 0xFF00FF00, 0xFF00FF00, 0x00FFFFFF }, null, Shader.TileMode.MIRROR); + new int[]{0x00FFFFFF, 0xFF00FF00, 0xFF00FF00, 0x00FFFFFF}, null, Shader.TileMode.MIRROR); Shader shaderOld = paint.getShader(); paint.setShader(shaderNew); - canvas.drawRect(frame.left + MIDDLE_LINE_PADDING, slideTop - MIDDLE_LINE_WIDTH/2, - frame.right - MIDDLE_LINE_PADDING, slideTop + MIDDLE_LINE_WIDTH/2, paint); + canvas.drawRect(frame.left + MIDDLE_LINE_PADDING, slideTop - MIDDLE_LINE_WIDTH / 2, + frame.right - MIDDLE_LINE_PADDING, slideTop + MIDDLE_LINE_WIDTH / 2, paint); paint.setShader(shaderOld); @@ -215,7 +204,7 @@ public void onDraw(Canvas canvas) { paint.setTextAlign(Paint.Align.CENTER); paint.setTypeface(Typeface.create("system", Typeface.NORMAL)); //paint.setTypeface(Typeface.SANS_SERIF); - canvas.drawText(getResources().getString(R.string.scan_text), frame.centerX(), (float) (frame.bottom + (float)TEXT_PADDING_TOP * density), paint); + canvas.drawText(getResources().getString(R.string.scan_text), frame.centerX(), (float) (frame.bottom + (float) TEXT_PADDING_TOP * density), paint); float scaleX = frame.width() / (float) previewFrame.width(); float scaleY = frame.height() / (float) previewFrame.height(); diff --git a/app/src/main/java/com/netease/LDNetDiagnoService/AbstractLDNetAsyncTaskEx.java b/app/src/main/java/com/netease/LDNetDiagnoService/AbstractLDNetAsyncTaskEx.java new file mode 100644 index 0000000..04695df --- /dev/null +++ b/app/src/main/java/com/netease/LDNetDiagnoService/AbstractLDNetAsyncTaskEx.java @@ -0,0 +1,222 @@ +package com.netease.LDNetDiagnoService; + +import android.os.AsyncTask; +import android.os.Handler; +import android.os.Message; + +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * @author liujie + * + *

+ * The most part is copied for {@link AsyncTask}. + *

+ * What's we do here is to control the executor and the core + *

+ * number of thread parallely. + * + *

+ * Since Starting with HONEYCOMB, tasks are executed on a single thread + *

+ * to avoid common application errors caused by parallel execution. + */ + +public abstract class AbstractLDNetAsyncTaskEx { + private static final int MESSAGE_POST_RESULT = 0x1; + private static final int MESSAGE_POST_PROGRESS = 0x2; + private static final int MESSAGE_POST_CANCEL = 0x3; + private static final LDNetInternalHandler sHandler = new LDNetInternalHandler(); + private final AbstractLDNetWorkerRunnable mWorker; + private final FutureTask mFuture; + private volatile Status mStatus = Status.PENDING; + + public AbstractLDNetAsyncTaskEx() { + mWorker = new AbstractLDNetWorkerRunnable() { + @Override + public Result call() throws Exception { + // Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + return doInBackground(mParams); + } + }; + + mFuture = new FutureTask(mWorker) { + @SuppressWarnings("unchecked") + @Override + protected void done() { + Message message; + Result result = null; + + try { + result = get(); + } catch (InterruptedException e) { + android.util.Log.w(this.getClass().getSimpleName(), e); + } catch (ExecutionException e) { + throw new RuntimeException( + "An error occured while executing doInBackground()", + e.getCause()); + } catch (CancellationException e) { + message = sHandler.obtainMessage(MESSAGE_POST_CANCEL, + new LDNetAsyncTaskResult(AbstractLDNetAsyncTaskEx.this, + (Result[]) null)); + message.sendToTarget(); + return; + } catch (Throwable t) { +// throw new RuntimeException( +// "An error occured while executing " +// + "doInBackground()", t); + } + + message = sHandler.obtainMessage(MESSAGE_POST_RESULT, + new LDNetAsyncTaskResult(AbstractLDNetAsyncTaskEx.this, result)); + message.sendToTarget(); + } + }; + } + + // protected Hashtable mTaskCache = new + // Hashtable(); + + public final Status getStatus() { + return mStatus; + } + + protected abstract Result doInBackground(Params... params); + + /** + * 后台线程准备运行阶段 + */ + protected void onPreExecute() { + } + + /** + * 后台运行阶段,当前运行已经结束 + * + * @param result + */ + protected void onPostExecute(Result result) { + } + + /** + * 进度更新阶段 + * + * @param values + */ + protected void onProgressUpdate(Progress... values) { + } + + /** + * 取消运行 + */ + protected void onCancelled() { + } + + public final boolean isCancelled() { + return mFuture.isCancelled(); + } + + public final boolean cancel(boolean mayInterruptIfRunning) { + return mFuture.cancel(mayInterruptIfRunning); + } + + /** + * 初始化运行阶段 + * + * @param params + * @return + */ + @SuppressWarnings("incomplete-switch") + public final AbstractLDNetAsyncTaskEx execute(Params... params) { + if (mStatus != Status.PENDING) { + switch (mStatus) { + case RUNNING: + throw new IllegalStateException("Cannot execute task:" + + " the task is already running."); + case FINISHED: + throw new IllegalStateException("Cannot execute task:" + + " the task has already been executed " + + "(a task can be executed only once)"); + default: + break; + } + } + + mStatus = Status.RUNNING; + + onPreExecute(); + + mWorker.mParams = params; + ThreadPoolExecutor sExecutor = getThreadPoolExecutor(); + // ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, + // MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, + // sThreadFactory); + if (sExecutor != null) { + sExecutor.execute(mFuture); + return this; + } else { + return null; + } + } + + protected abstract ThreadPoolExecutor getThreadPoolExecutor(); + + protected final void publishProgress(Progress... values) { + sHandler.obtainMessage(MESSAGE_POST_PROGRESS, + new LDNetAsyncTaskResult(this, values)).sendToTarget(); + } + + protected void finish(Result result) { + if (isCancelled()) { + result = null; + } + onPostExecute(result); + mStatus = Status.FINISHED; + } + + public enum Status { + PENDING, RUNNING, FINISHED, + } + + private static class LDNetInternalHandler extends Handler { + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public void handleMessage(Message msg) { + LDNetAsyncTaskResult result = (LDNetAsyncTaskResult) msg.obj; + switch (msg.what) { + case MESSAGE_POST_RESULT: + // There is only one result + result.mTask.finish(result.mData[0]); + break; + case MESSAGE_POST_PROGRESS: + result.mTask.onProgressUpdate(result.mData); + break; + case MESSAGE_POST_CANCEL: + result.mTask.onCancelled(); + break; + default: + break; + } + } + } + + private static abstract class AbstractLDNetWorkerRunnable implements + Callable { + Params[] mParams; + } + + private static class LDNetAsyncTaskResult { + @SuppressWarnings("rawtypes") + final AbstractLDNetAsyncTaskEx mTask; + final Data[] mData; + + LDNetAsyncTaskResult(@SuppressWarnings("rawtypes") AbstractLDNetAsyncTaskEx task, + Data... data) { + mTask = task; + mData = data; + } + } +} diff --git a/app/src/main/java/com/netease/LDNetDiagnoService/LDNetAsyncTaskEx.java b/app/src/main/java/com/netease/LDNetDiagnoService/LDNetAsyncTaskEx.java deleted file mode 100644 index 4deeb2a..0000000 --- a/app/src/main/java/com/netease/LDNetDiagnoService/LDNetAsyncTaskEx.java +++ /dev/null @@ -1,216 +0,0 @@ -package com.netease.LDNetDiagnoService; - -import java.util.concurrent.Callable; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; -import java.util.concurrent.ThreadPoolExecutor; - -import android.os.AsyncTask; -import android.os.Handler; -import android.os.Message; - -/** - * - * @author liujie - * - *

- * The most part is copied for {@link AsyncTask}. - * - * What's we do here is to control the executor and the core - * - * number of thread parallely. - * - *

- * Since Starting with HONEYCOMB, tasks are executed on a single thread - * - * to avoid common application errors caused by parallel execution. - */ - -public abstract class LDNetAsyncTaskEx { - private static final int MESSAGE_POST_RESULT = 0x1; - private static final int MESSAGE_POST_PROGRESS = 0x2; - private static final int MESSAGE_POST_CANCEL = 0x3; - - private volatile Status mStatus = Status.PENDING; - - public enum Status { - PENDING, RUNNING, FINISHED, - } - - private static final LDNetInternalHandler sHandler = new LDNetInternalHandler(); - private final LDNetWorkerRunnable mWorker; - private final FutureTask mFuture; - - // protected Hashtable mTaskCache = new - // Hashtable(); - - public LDNetAsyncTaskEx() { - mWorker = new LDNetWorkerRunnable() { - public Result call() throws Exception { - // Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - return doInBackground(mParams); - } - }; - - mFuture = new FutureTask(mWorker) { - @SuppressWarnings("unchecked") - @Override - protected void done() { - Message message; - Result result = null; - - try { - result = get(); - } catch (InterruptedException e) { - android.util.Log.w(this.getClass().getSimpleName(), e); - } catch (ExecutionException e) { - throw new RuntimeException( - "An error occured while executing doInBackground()", - e.getCause()); - } catch (CancellationException e) { - message = sHandler.obtainMessage(MESSAGE_POST_CANCEL, - new LDNetAsyncTaskResult(LDNetAsyncTaskEx.this, - (Result[]) null)); - message.sendToTarget(); - return; - } catch (Throwable t) { -// throw new RuntimeException( -// "An error occured while executing " -// + "doInBackground()", t); - } - - message = sHandler.obtainMessage(MESSAGE_POST_RESULT, - new LDNetAsyncTaskResult(LDNetAsyncTaskEx.this, result)); - message.sendToTarget(); - } - }; - } - - private static class LDNetInternalHandler extends Handler { - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public void handleMessage(Message msg) { - LDNetAsyncTaskResult result = (LDNetAsyncTaskResult) msg.obj; - switch (msg.what) { - case MESSAGE_POST_RESULT: - // There is only one result - result.mTask.finish(result.mData[0]); - break; - case MESSAGE_POST_PROGRESS: - result.mTask.onProgressUpdate(result.mData); - break; - case MESSAGE_POST_CANCEL: - result.mTask.onCancelled(); - break; - } - } - } - - public final Status getStatus() { - return mStatus; - } - - protected abstract Result doInBackground(Params... params); - /** - * 后台线程准备运行阶段 - */ - protected void onPreExecute() { - } - - /** - * 后台运行阶段,当前运行已经结束 - * @param result - */ - protected void onPostExecute(Result result) { - } - - /** - * 进度更新阶段 - * @param values - */ - protected void onProgressUpdate(Progress... values) { - } - - /** - * 取消运行 - */ - protected void onCancelled() { - } - - public final boolean isCancelled() { - return mFuture.isCancelled(); - } - - public final boolean cancel(boolean mayInterruptIfRunning) { - return mFuture.cancel(mayInterruptIfRunning); - } - - /** - * 初始化运行阶段 - * @param params - * @return - */ - @SuppressWarnings("incomplete-switch") - public final LDNetAsyncTaskEx execute(Params... params) { - if (mStatus != Status.PENDING) { - switch (mStatus) { - case RUNNING: - throw new IllegalStateException("Cannot execute task:" - + " the task is already running."); - case FINISHED: - throw new IllegalStateException("Cannot execute task:" - + " the task has already been executed " - + "(a task can be executed only once)"); - } - } - - mStatus = Status.RUNNING; - - onPreExecute(); - - mWorker.mParams = params; - ThreadPoolExecutor sExecutor = getThreadPoolExecutor(); - // ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, - // MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, - // sThreadFactory); - if (sExecutor != null) { - sExecutor.execute(mFuture); - return this; - } else { - return null; - } - } - - protected abstract ThreadPoolExecutor getThreadPoolExecutor(); - - protected final void publishProgress(Progress... values) { - sHandler.obtainMessage(MESSAGE_POST_PROGRESS, - new LDNetAsyncTaskResult(this, values)).sendToTarget(); - } - - protected void finish(Result result) { - if (isCancelled()) { - result = null; - } - onPostExecute(result); - mStatus = Status.FINISHED; - } - - private static abstract class LDNetWorkerRunnable implements - Callable { - Params[] mParams; - } - - private static class LDNetAsyncTaskResult { - @SuppressWarnings("rawtypes") - final LDNetAsyncTaskEx mTask; - final Data[] mData; - - LDNetAsyncTaskResult(@SuppressWarnings("rawtypes") LDNetAsyncTaskEx task, - Data... data) { - mTask = task; - mData = data; - } - } -} diff --git a/app/src/main/java/com/netease/LDNetDiagnoService/LDNetDiagnoListener.java b/app/src/main/java/com/netease/LDNetDiagnoService/LDNetDiagnoListener.java index 00e5344..0e77612 100644 --- a/app/src/main/java/com/netease/LDNetDiagnoService/LDNetDiagnoListener.java +++ b/app/src/main/java/com/netease/LDNetDiagnoService/LDNetDiagnoListener.java @@ -2,21 +2,23 @@ /** * 监控网络诊断的跟踪信息 - * @author panghui * + * @author panghui */ public interface LDNetDiagnoListener { - - /** - * 当结束之后返回日志 - * @param log - */ - public void OnNetDiagnoFinished(String log); - - /** - * 跟踪过程中更新日志 - * @param log - */ - public void OnNetDiagnoUpdated(String log); + /** + * 当结束之后返回日志 + * + * @param log + */ + public void OnNetDiagnoFinished(String log); + + + /** + * 跟踪过程中更新日志 + * + * @param log + */ + public void OnNetDiagnoUpdated(String log); } diff --git a/app/src/main/java/com/netease/LDNetDiagnoService/LDNetDiagnoService.java b/app/src/main/java/com/netease/LDNetDiagnoService/LDNetDiagnoService.java index 8824273..ff23b84 100644 --- a/app/src/main/java/com/netease/LDNetDiagnoService/LDNetDiagnoService.java +++ b/app/src/main/java/com/netease/LDNetDiagnoService/LDNetDiagnoService.java @@ -1,5 +1,14 @@ package com.netease.LDNetDiagnoService; +import android.content.Context; +import android.telephony.TelephonyManager; +import android.text.TextUtils; + +import com.netease.LDNetDiagnoService.LDNetPing.LDNetPingListener; +import com.netease.LDNetDiagnoService.LDNetSocket.LDNetSocketListener; +import com.netease.LDNetDiagnoService.LDNetTraceRoute.LDNetTraceRouteListener; +import com.netease.LDNetDiagnoUtils.LDNetUtil; + import java.io.IOException; import java.net.HttpURLConnection; import java.net.InetAddress; @@ -15,519 +24,508 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import android.content.Context; -import android.telephony.TelephonyManager; -import android.text.TextUtils; - -import com.netease.LDNetDiagnoService.LDNetPing.LDNetPingListener; -import com.netease.LDNetDiagnoService.LDNetSocket.LDNetSocketListener; -import com.netease.LDNetDiagnoService.LDNetTraceRoute.LDNetTraceRouteListener; -import com.netease.LDNetDiagnoUtils.LDNetUtil; - /** * 网络诊断服务 通过对制定域名进行ping诊断和traceroute诊断收集诊断日志 * * @author panghui - * */ public class LDNetDiagnoService extends - LDNetAsyncTaskEx implements LDNetPingListener, - LDNetTraceRouteListener, LDNetSocketListener { - private String _appCode; // 客户端标记 - private String _appName; - private String _appVersion; - private String _UID; // 用户ID - private String _deviceID; // 客户端机器ID,如果不传入会默认取API提供的机器ID - private String _dormain; // 接口域名 - private String _carrierName; - private String _ISOCountryCode; - private String _MobileCountryCode; - private String _MobileNetCode; - - private boolean _isNetConnected;// 当前是否联网 - private boolean _isDomainParseOk;// 域名解析是否成功 - private boolean _isSocketConnected;// conected是否成功 - private Context _context; - private String _netType; - private String _localIp; - private String _gateWay; - private String _dns1; - private String _dns2; - private InetAddress[] _remoteInet; - private List _remoteIpList; - private final StringBuilder _logInfo = new StringBuilder(256); - private LDNetSocket _netSocker;// 监控socket的连接时间 - private LDNetPing _netPinger; // 监控ping命令的执行时间 - private LDNetTraceRoute _traceRouter; // 监控ping模拟traceroute的执行过程 - private boolean _isRunning; - - private LDNetDiagnoListener _netDiagnolistener; // 将监控日志上报到前段页面 - private boolean _isUseJNICConn = false; - private boolean _isUseJNICTrace = true; - private TelephonyManager _telManager = null; // 用于获取网络基本信息 - - public LDNetDiagnoService() { - super(); - } - - /** - * 初始化网络诊断服务 - * - * @param theAppCode - * @param theDeviceID - * @param theUID - * @param theDormain - */ - public LDNetDiagnoService(Context context, String theAppCode, - String theAppName, String theAppVersion, String theUID, - String theDeviceID, String theDormain, String theCarrierName, - String theISOCountryCode, String theMobileCountryCode, - String theMobileNetCode, LDNetDiagnoListener theListener) { - super(); - this._context = context; - this._appCode = theAppCode; - this._appName = theAppName; - this._appVersion = theAppVersion; - this._UID = theUID; - this._deviceID = theDeviceID; - this._dormain = theDormain; - this._carrierName = theCarrierName; - this._ISOCountryCode = theISOCountryCode; - this._MobileCountryCode = theMobileCountryCode; - this._MobileNetCode = theMobileNetCode; - this._netDiagnolistener = theListener; - // - this._isRunning = false; - _remoteIpList = new ArrayList(); - _telManager = (TelephonyManager) context - .getSystemService(Context.TELEPHONY_SERVICE); - sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, - KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory); - - } - - @Override - protected String doInBackground(String... params) { - if (this.isCancelled()) - return null; - // TODO Auto-generated method stub - return this.startNetDiagnosis(); - } - - @Override - protected void onPostExecute(String result) { - if (this.isCancelled()) - return; - super.onPostExecute(result); - // 线程执行结束 - recordStepInfo("\n网络诊断结束\n"); - this.stopNetDialogsis(); - if (_netDiagnolistener != null) { - _netDiagnolistener.OnNetDiagnoFinished(_logInfo.toString()); + AbstractLDNetAsyncTaskEx implements LDNetPingListener, + LDNetTraceRouteListener, LDNetSocketListener { + private static final int CORE_POOL_SIZE = 1;// 4 + private static final int MAXIMUM_POOL_SIZE = 1;// 10 + private static final int KEEP_ALIVE = 10;// 10 + private static final BlockingQueue sWorkQueue = new LinkedBlockingQueue( + 2);// 2 + private static final ThreadFactory sThreadFactory = new ThreadFactory() { + private final AtomicInteger mCount = new AtomicInteger(1); + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "Trace #" + mCount.getAndIncrement()); + t.setPriority(Thread.MIN_PRIORITY); + return t; + } + }; + private static ThreadPoolExecutor sExecutor = null; + private final StringBuilder logInfo = new StringBuilder(256); + private String appCode; // 客户端标记 + private String appName; + private String appVersion; + private String UID; // 用户ID + private String deviceID; // 客户端机器ID,如果不传入会默认取API提供的机器ID + private String dormain; // 接口域名 + private String carrierName; + private String ISOCountryCode; + private String MobileCountryCode; + private String MobileNetCode; + private boolean isNetConnected;// 当前是否联网 + private boolean isDomainParseOk;// 域名解析是否成功 + private boolean isSocketConnected;// conected是否成功 + private Context context; + private String netType; + private String localIp; + private String gateWay; + private String dns1; + private String dns2; + private InetAddress[] remoteInet; + private List remoteIpList; + private LDNetSocket netSocker;// 监控socket的连接时间 + private LDNetPing netPinger; // 监控ping命令的执行时间 + private LDNetTraceRoute traceRouter; // 监控ping模拟traceroute的执行过程 + private boolean isRunning; + private LDNetDiagnoListener netDiagnolistener; // 将监控日志上报到前段页面 + private boolean isUseJNICConn = false; + private boolean isUseJNICTrace = true; + private TelephonyManager telManager = null; // 用于获取网络基本信息 + + public LDNetDiagnoService() { + super(); } - } - - @Override - protected void onProgressUpdate(String... values) { - if (this.isCancelled()) - return; - // TODO Auto-generated method stub - super.onProgressUpdate(values); - if (_netDiagnolistener != null) { - _netDiagnolistener.OnNetDiagnoUpdated(values[0]); + + /** + * 初始化网络诊断服务 + * + * @param theAppCode + * @param theDeviceID + * @param theUID + * @param theDormain + */ + public LDNetDiagnoService(Context context, String theAppCode, + String theAppName, String theAppVersion, String theUID, + String theDeviceID, String theDormain, String theCarrierName, + String theISOCountryCode, String theMobileCountryCode, + String theMobileNetCode, LDNetDiagnoListener theListener) { + super(); + this.context = context; + this.appCode = theAppCode; + this.appName = theAppName; + this.appVersion = theAppVersion; + this.UID = theUID; + this.deviceID = theDeviceID; + this.dormain = theDormain; + this.carrierName = theCarrierName; + this.ISOCountryCode = theISOCountryCode; + this.MobileCountryCode = theMobileCountryCode; + this.MobileNetCode = theMobileNetCode; + this.netDiagnolistener = theListener; + // + this.isRunning = false; + remoteIpList = new ArrayList(); + telManager = (TelephonyManager) context + .getSystemService(Context.TELEPHONY_SERVICE); + sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, + KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory); + } - } - - @Override - protected void onCancelled() { - this.stopNetDialogsis(); - } - - /** - * 开始诊断网络 - */ - public String startNetDiagnosis() { - if (TextUtils.isEmpty(this._dormain)) - return ""; - this._isRunning = true; - this._logInfo.setLength(0); - recordStepInfo("开始诊断...\n"); - recordCurrentAppVersion(); - recordLocalNetEnvironmentInfo(); - - if (_isNetConnected) { - // 获取运营商信息 - //recordStepInfo("\n开始获取运营商信息..."); - //String operatorInfo = requestOperatorInfo(); - //if (operatorInfo != null) { - //recordStepInfo(operatorInfo); - //} - - // TCP三次握手时间测试 - recordStepInfo("\n开始TCP连接测试..."); - _netSocker = LDNetSocket.getInstance(); - _netSocker._remoteInet = _remoteInet; - _netSocker._remoteIpList = _remoteIpList; - _netSocker.initListener(this); - _netSocker.isCConn = this._isUseJNICConn;// 设置是否启用C进行connected - _isSocketConnected = _netSocker.exec(_dormain); - - // 诊断ping信息, 同步过程 - - if (!(_isNetConnected && _isDomainParseOk && _isSocketConnected)) {// 联网&&DNS解析成功&&connect测试成功 - recordStepInfo("\n开始ping..."); - _netPinger = new LDNetPing(this, 4); - recordStepInfo("ping...127.0.0.1"); - _netPinger.exec("127.0.0.1", false); - recordStepInfo("ping本机IP..." + _localIp); - _netPinger.exec(_localIp, false); - if (LDNetUtil.NETWORKTYPE_WIFI.equals(_netType)) {// 在wifi下ping网关 - recordStepInfo("ping本地网关..." + _gateWay); - _netPinger.exec(_gateWay, false); + + @Override + protected String doInBackground(String... params) { + if (this.isCancelled()) { + return null; } - recordStepInfo("ping本地DNS1..." + _dns1); - _netPinger.exec(_dns1, false); - recordStepInfo("ping本地DNS2..." + _dns2); - _netPinger.exec(_dns2, false); - } - - if (_netPinger == null) { - _netPinger = new LDNetPing(this, 4); - } - if (_netPinger != null) { - //recordStepInfo("ping..." + LDNetUtil.OPEN_IP); - //_netPinger.exec(LDNetUtil.OPEN_IP, true); - } - - // 开始诊断traceRoute - recordStepInfo("\n开始traceroute..."); - _traceRouter = LDNetTraceRoute.getInstance(); - _traceRouter.initListenter(this); - _traceRouter.isCTrace = this._isUseJNICTrace; - _traceRouter.startTraceRoute(_dormain); - return _logInfo.toString(); - } else { - recordStepInfo("\n\n当前主机未联网,请检查网络!"); - return _logInfo.toString(); + // TODO Auto-generated method stub + return this.startNetDiagnosis(); } - } - - /** - * 停止诊断网络 - */ - public void stopNetDialogsis() { - if (_isRunning) { - if (_netSocker != null) { - _netSocker.resetInstance(); - _netSocker = null; - } - - if (_netPinger != null) { - _netPinger = null; - } - if (_traceRouter != null) { - _traceRouter.resetInstance(); - _traceRouter = null; - } - cancel(true);// 尝试去取消线程的执行 - if (sExecutor != null && !sExecutor.isShutdown()) { - sExecutor.shutdown(); - sExecutor = null; - } - - _isRunning = false; + + @Override + protected void onPostExecute(String result) { + if (this.isCancelled()) { + return; + } + super.onPostExecute(result); + // 线程执行结束 + recordStepInfo("\n网络诊断结束\n"); + this.stopNetDialogsis(); + if (netDiagnolistener != null) { + netDiagnolistener.OnNetDiagnoFinished(logInfo.toString()); + } } - } - - /** - * 设置是否需要JNICTraceRoute - * - * @param use - */ - public void setIfUseJNICConn(boolean use) { - this._isUseJNICConn = use; - } - - /** - * 设置是否需要JNICTraceRoute - * - * @param use - */ - public void setIfUseJNICTrace(boolean use) { - this._isUseJNICTrace = use; - } - - /** - * 打印整体loginInfo; - */ - public void printLogInfo() { - System.out.print(_logInfo); - } - - /** - * 如果调用者实现了stepInfo接口,输出信息 - * - * @param stepInfo - */ - private void recordStepInfo(String stepInfo) { - _logInfo.append(stepInfo + "\n"); - publishProgress(stepInfo + "\n"); - } - - /** - * traceroute 消息跟踪 - */ - @Override - public void OnNetTraceFinished() { - } - - @Override - public void OnNetTraceUpdated(String log) { - if (log == null) { - return; + + @Override + protected void onProgressUpdate(String... values) { + if (this.isCancelled()) { + return; + } + // TODO Auto-generated method stub + super.onProgressUpdate(values); + if (netDiagnolistener != null) { + netDiagnolistener.OnNetDiagnoUpdated(values[0]); + } + } + + @Override + protected void onCancelled() { + this.stopNetDialogsis(); + } + + /** + * 开始诊断网络 + */ + public String startNetDiagnosis() { + if (TextUtils.isEmpty(this.dormain)) { + return ""; + } + this.isRunning = true; + this.logInfo.setLength(0); + recordStepInfo("开始诊断...\n"); + recordCurrentAppVersion(); + recordLocalNetEnvironmentInfo(); + + if (isNetConnected) { + // 获取运营商信息 + //recordStepInfo("\n开始获取运营商信息..."); + //String operatorInfo = requestOperatorInfo(); + //if (operatorInfo != null) { + //recordStepInfo(operatorInfo); + //} + + // TCP三次握手时间测试 + recordStepInfo("\n开始TCP连接测试..."); + netSocker = LDNetSocket.getInstance(); + netSocker.remoteInet = remoteInet; + netSocker.remoteIpList = remoteIpList; + netSocker.initListener(this); + netSocker.isCConn = this.isUseJNICConn;// 设置是否启用C进行connected + isSocketConnected = netSocker.exec(dormain); + + // 诊断ping信息, 同步过程 + + if (!(isNetConnected && isDomainParseOk && isSocketConnected)) {// 联网&&DNS解析成功&&connect测试成功 + recordStepInfo("\n开始ping..."); + netPinger = new LDNetPing(this, 4); + recordStepInfo("ping...127.0.0.1"); + netPinger.exec("127.0.0.1", false); + recordStepInfo("ping本机IP..." + localIp); + netPinger.exec(localIp, false); + if (LDNetUtil.NETWORKTYPE_WIFI.equals(netType)) {// 在wifi下ping网关 + recordStepInfo("ping本地网关..." + gateWay); + netPinger.exec(gateWay, false); + } + recordStepInfo("ping本地DNS1..." + dns1); + netPinger.exec(dns1, false); + recordStepInfo("ping本地DNS2..." + dns2); + netPinger.exec(dns2, false); + } + + if (netPinger == null) { + netPinger = new LDNetPing(this, 4); + } + if (netPinger != null) { + //recordStepInfo("ping..." + LDNetUtil.OPEN_IP); + //netPinger.exec(LDNetUtil.OPEN_IP, true); + } + + // 开始诊断traceRoute + recordStepInfo("\n开始traceroute..."); + traceRouter = LDNetTraceRoute.getInstance(); + traceRouter.initListenter(this); + traceRouter.isCTrace = this.isUseJNICTrace; + traceRouter.startTraceRoute(dormain); + return logInfo.toString(); + } else { + recordStepInfo("\n\n当前主机未联网,请检查网络!"); + return logInfo.toString(); + } } - if (this._traceRouter != null && this._traceRouter.isCTrace) { - if (log.contains("ms") || log.contains("***")) { - log += "\n"; - } - _logInfo.append(log); - publishProgress(log); - } else { - this.recordStepInfo(log); + + /** + * 停止诊断网络 + */ + public void stopNetDialogsis() { + if (isRunning) { + if (netSocker != null) { + netSocker.resetInstance(); + netSocker = null; + } + + if (netPinger != null) { + netPinger = null; + } + if (traceRouter != null) { + traceRouter.resetInstance(); + traceRouter = null; + } + cancel(true);// 尝试去取消线程的执行 + if (sExecutor != null && !sExecutor.isShutdown()) { + sExecutor.shutdown(); + sExecutor = null; + } + + isRunning = false; + } } - } - - /** - * socket完成跟踪 - */ - @Override - public void OnNetSocketFinished(String log) { - _logInfo.append(log); - publishProgress(log); - } - - /** - * socket更新跟踪 - */ - @Override - public void OnNetSocketUpdated(String log) { - _logInfo.append(log); - publishProgress(log); - } - - /** - * 输出关于应用、机器、网络诊断的基本信息 - */ - private void recordCurrentAppVersion() { - // 输出应用版本信息和用户ID - recordStepInfo("应用code:\t" + _appCode); - recordStepInfo("应用名称:\t" + this._appName); - recordStepInfo("应用版本:\t" + this._appVersion); -// recordStepInfo("用户id:\t" + _UID); - - // 输出机器信息 - recordStepInfo("机器类型:\t" + android.os.Build.MANUFACTURER + ":" - + android.os.Build.BRAND + ":" + android.os.Build.MODEL); - recordStepInfo("系统版本:\t" + android.os.Build.VERSION.RELEASE); - if (_telManager != null && TextUtils.isEmpty(_deviceID)) { - _deviceID = _telManager.getDeviceId(); + + /** + * 设置是否需要JNICTraceRoute + * + * @param use + */ + public void setIfUseJNICConn(boolean use) { + this.isUseJNICConn = use; } - recordStepInfo("机器ID:\t" + _deviceID); - // 运营商信息 - if (TextUtils.isEmpty(_carrierName)) { - _carrierName = LDNetUtil.getMobileOperator(_context); + /** + * 设置是否需要JNICTraceRoute + * + * @param use + */ + public void setIfUseJNICTrace(boolean use) { + this.isUseJNICTrace = use; } - recordStepInfo("运营商:\t" + _carrierName); - if (_telManager != null && TextUtils.isEmpty(_ISOCountryCode)) { - _ISOCountryCode = _telManager.getNetworkCountryIso(); + /** + * 打印整体loginInfo; + */ + public void printLogInfo() { + System.out.print(logInfo); } - recordStepInfo("ISOCountryCode:\t" + _ISOCountryCode); - - if (_telManager != null && TextUtils.isEmpty(_MobileCountryCode)) { - String tmp = _telManager.getNetworkOperator(); - if (tmp.length() >= 3) { - _MobileCountryCode = tmp.substring(0, 3); - } - if (tmp.length() >= 5) { - _MobileNetCode = tmp.substring(3, 5); - } + + /** + * 如果调用者实现了stepInfo接口,输出信息 + * + * @param stepInfo + */ + private void recordStepInfo(String stepInfo) { + logInfo.append(stepInfo + "\n"); + publishProgress(stepInfo + "\n"); } - recordStepInfo("MobileCountryCode:\t" + _MobileCountryCode); - recordStepInfo("MobileNetworkCode:\t" + _MobileNetCode+"\n"); - } - - /** - * 输出本地网络环境信息 - */ - private void recordLocalNetEnvironmentInfo() { - recordStepInfo("诊断域名 " + _dormain + "..."); - - // 网络状态 - if (LDNetUtil.isNetworkConnected(_context)) { - _isNetConnected = true; - recordStepInfo("当前是否联网:\t" + "已联网"); - } else { - _isNetConnected = false; - recordStepInfo("当前是否联网:\t" + "未联网"); + + /** + * traceroute 消息跟踪 + */ + @Override + public void OnNetTraceFinished() { } - // 获取当前网络类型 - _netType = LDNetUtil.getNetWorkType(_context); - recordStepInfo("当前联网类型:\t" + _netType); - if (_isNetConnected) { - if (LDNetUtil.NETWORKTYPE_WIFI.equals(_netType)) { // wifi:获取本地ip和网关,其他类型:只获取ip - _localIp = LDNetUtil.getLocalIpByWifi(_context); - _gateWay = LDNetUtil.pingGateWayInWifi(_context); - } else { - _localIp = LDNetUtil.getLocalIpBy3G(); - } - recordStepInfo("本地IP:\t" + _localIp); - } else { - recordStepInfo("本地IP:\t" + "127.0.0.1"); + @Override + public void OnNetTraceUpdated(String log) { + if (log == null) { + return; + } + if (this.traceRouter != null && this.traceRouter.isCTrace) { + if (log.contains("ms") || log.contains("***")) { + log += "\n"; + } + logInfo.append(log); + publishProgress(log); + } else { + this.recordStepInfo(log); + } } - if (_gateWay != null) { - recordStepInfo("本地网关:\t" + this._gateWay); + + /** + * socket完成跟踪 + */ + @Override + public void OnNetSocketFinished(String log) { + logInfo.append(log); + publishProgress(log); } - // 获取本地DNS地址 - if (_isNetConnected) { - _dns1 = LDNetUtil.getLocalDns("dns1"); - _dns2 = LDNetUtil.getLocalDns("dns2"); - recordStepInfo("本地DNS:\t" + this._dns1 + "," + this._dns2); - } else { - recordStepInfo("本地DNS:\t" + "0.0.0.0" + "," + "0.0.0.0"); + /** + * socket更新跟踪 + */ + @Override + public void OnNetSocketUpdated(String log) { + logInfo.append(log); + publishProgress(log); } - // 获取远端域名的DNS解析地址 - if (_isNetConnected) { - recordStepInfo("远端域名:\t" + this._dormain); - _isDomainParseOk = parseDomain(this._dormain);// 域名解析 + /** + * 输出关于应用、机器、网络诊断的基本信息 + */ + private void recordCurrentAppVersion() { + // 输出应用版本信息和用户ID + recordStepInfo("应用code:\t" + appCode); + recordStepInfo("应用名称:\t" + this.appName); + recordStepInfo("应用版本:\t" + this.appVersion); +// recordStepInfo("用户id:\t" + UID); + + // 输出机器信息 + recordStepInfo("机器类型:\t" + android.os.Build.MANUFACTURER + ":" + + android.os.Build.BRAND + ":" + android.os.Build.MODEL); + recordStepInfo("系统版本:\t" + android.os.Build.VERSION.RELEASE); + if (telManager != null && TextUtils.isEmpty(deviceID)) { + deviceID = telManager.getDeviceId(); + } + recordStepInfo("机器ID:\t" + deviceID); + + // 运营商信息 + if (TextUtils.isEmpty(carrierName)) { + carrierName = LDNetUtil.getMobileOperator(context); + } + recordStepInfo("运营商:\t" + carrierName); + + if (telManager != null && TextUtils.isEmpty(ISOCountryCode)) { + ISOCountryCode = telManager.getNetworkCountryIso(); + } + recordStepInfo("ISOCountryCode:\t" + ISOCountryCode); + + if (telManager != null && TextUtils.isEmpty(MobileCountryCode)) { + String tmp = telManager.getNetworkOperator(); + if (tmp.length() >= 3) { + MobileCountryCode = tmp.substring(0, 3); + } + if (tmp.length() >= 5) { + MobileNetCode = tmp.substring(3, 5); + } + } + recordStepInfo("MobileCountryCode:\t" + MobileCountryCode); + recordStepInfo("MobileNetworkCode:\t" + MobileNetCode + "\n"); } - } - - /** - * 域名解析 - */ - private boolean parseDomain(String _dormain) { - boolean flag = false; - int len = 0; - String ipString = ""; - Map map = LDNetUtil.getDomainIp(_dormain); - String useTime = (String) map.get("useTime"); - _remoteInet = (InetAddress[]) map.get("remoteInet"); - String timeShow = null; - if (Integer.parseInt(useTime) > 5000) {// 如果大于1000ms,则换用s来显示 - timeShow = " (" + Integer.parseInt(useTime) / 1000 + "s)"; - } else { - timeShow = " (" + useTime + "ms)"; + + /** + * 输出本地网络环境信息 + */ + private void recordLocalNetEnvironmentInfo() { + recordStepInfo("诊断域名 " + dormain + "..."); + + // 网络状态 + if (LDNetUtil.isNetworkConnected(context)) { + isNetConnected = true; + recordStepInfo("当前是否联网:\t" + "已联网"); + } else { + isNetConnected = false; + recordStepInfo("当前是否联网:\t" + "未联网"); + } + + // 获取当前网络类型 + netType = LDNetUtil.getNetWorkType(context); + recordStepInfo("当前联网类型:\t" + netType); + if (isNetConnected) { + if (LDNetUtil.NETWORKTYPE_WIFI.equals(netType)) { // wifi:获取本地ip和网关,其他类型:只获取ip + localIp = LDNetUtil.getLocalIpByWifi(context); + gateWay = LDNetUtil.pingGateWayInWifi(context); + } else { + localIp = LDNetUtil.getLocalIpBy3G(); + } + recordStepInfo("本地IP:\t" + localIp); + } else { + recordStepInfo("本地IP:\t" + "127.0.0.1"); + } + if (gateWay != null) { + recordStepInfo("本地网关:\t" + this.gateWay); + } + + // 获取本地DNS地址 + if (isNetConnected) { + dns1 = LDNetUtil.getLocalDns("dns1"); + dns2 = LDNetUtil.getLocalDns("dns2"); + recordStepInfo("本地DNS:\t" + this.dns1 + "," + this.dns2); + } else { + recordStepInfo("本地DNS:\t" + "0.0.0.0" + "," + "0.0.0.0"); + } + + // 获取远端域名的DNS解析地址 + if (isNetConnected) { + recordStepInfo("远端域名:\t" + this.dormain); + isDomainParseOk = parseDomain(this.dormain);// 域名解析 + } } - if (_remoteInet != null) {// 解析正确 - len = _remoteInet.length; - for (int i = 0; i < len; i++) { - _remoteIpList.add(_remoteInet[i].getHostAddress()); - ipString += _remoteInet[i].getHostAddress() + ","; - } - ipString = ipString.substring(0, ipString.length() - 1); - recordStepInfo("DNS解析结果:\t" + ipString + timeShow); - flag = true; - } else {// 解析不到,判断第一次解析耗时,如果大于10s进行第二次解析 - if (Integer.parseInt(useTime) > 10000) { - map = LDNetUtil.getDomainIp(_dormain); - useTime = (String) map.get("useTime"); - _remoteInet = (InetAddress[]) map.get("remoteInet"); + + /** + * 域名解析 + */ + private boolean parseDomain(String _dormain) { + boolean flag = false; + int len = 0; + String ipString = ""; + Map map = LDNetUtil.getDomainIp(_dormain); + String useTime = (String) map.get("useTime"); + remoteInet = (InetAddress[]) map.get("remoteInet"); + String timeShow = null; if (Integer.parseInt(useTime) > 5000) {// 如果大于1000ms,则换用s来显示 - timeShow = " (" + Integer.parseInt(useTime) / 1000 + "s)"; + timeShow = " (" + Integer.parseInt(useTime) / 1000 + "s)"; } else { - timeShow = " (" + useTime + "ms)"; + timeShow = " (" + useTime + "ms)"; } - if (_remoteInet != null) { - len = _remoteInet.length; - for (int i = 0; i < len; i++) { - _remoteIpList.add(_remoteInet[i].getHostAddress()); - ipString += _remoteInet[i].getHostAddress() + ","; - } - ipString = ipString.substring(0, ipString.length() - 1); - recordStepInfo("DNS解析结果:\t" + ipString + timeShow); - flag = true; - } else { - recordStepInfo("DNS解析结果:\t" + "解析失败" + timeShow); + if (remoteInet != null) {// 解析正确 + len = remoteInet.length; + for (int i = 0; i < len; i++) { + remoteIpList.add(remoteInet[i].getHostAddress()); + ipString += remoteInet[i].getHostAddress() + ","; + } + ipString = ipString.substring(0, ipString.length() - 1); + recordStepInfo("DNS解析结果:\t" + ipString + timeShow); + flag = true; + } else {// 解析不到,判断第一次解析耗时,如果大于10s进行第二次解析 + if (Integer.parseInt(useTime) > 10000) { + map = LDNetUtil.getDomainIp(_dormain); + useTime = (String) map.get("useTime"); + remoteInet = (InetAddress[]) map.get("remoteInet"); + if (Integer.parseInt(useTime) > 5000) {// 如果大于1000ms,则换用s来显示 + timeShow = " (" + Integer.parseInt(useTime) / 1000 + "s)"; + } else { + timeShow = " (" + useTime + "ms)"; + } + if (remoteInet != null) { + len = remoteInet.length; + for (int i = 0; i < len; i++) { + remoteIpList.add(remoteInet[i].getHostAddress()); + ipString += remoteInet[i].getHostAddress() + ","; + } + ipString = ipString.substring(0, ipString.length() - 1); + recordStepInfo("DNS解析结果:\t" + ipString + timeShow); + flag = true; + } else { + recordStepInfo("DNS解析结果:\t" + "解析失败" + timeShow); + } + } else { + recordStepInfo("DNS解析结果:\t" + "解析失败" + timeShow); + } } - } else { - recordStepInfo("DNS解析结果:\t" + "解析失败" + timeShow); - } + return flag; } - return flag; - } - - /** - * 获取运营商信息 - */ - private String requestOperatorInfo() { - String res = null; - String url = LDNetUtil.OPERATOR_URL; - HttpURLConnection conn = null; - URL Operator_url; - try { - Operator_url = new URL(url); - conn = (HttpURLConnection) Operator_url.openConnection(); - conn.setRequestMethod("GET"); - conn.setConnectTimeout(1000 * 10); - conn.connect(); - int responseCode = conn.getResponseCode(); - if (responseCode == 200) { - res = LDNetUtil.getStringFromStream(conn.getInputStream()); - if (conn != null) { - conn.disconnect(); + + /** + * 获取运营商信息 + */ + private String requestOperatorInfo() { + String res = null; + String url = LDNetUtil.OPERATOR_URL; + HttpURLConnection conn = null; + URL Operator_url; + try { + Operator_url = new URL(url); + conn = (HttpURLConnection) Operator_url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(1000 * 10); + conn.connect(); + int responseCode = conn.getResponseCode(); + if (responseCode == 200) { + res = LDNetUtil.getStringFromStream(conn.getInputStream()); + if (conn != null) { + conn.disconnect(); + } + } + return res; + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + if (conn != null) { + conn.disconnect(); + } } - } - return res; - } catch (MalformedURLException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } finally { - if (conn != null) { - conn.disconnect(); - } + return res; } - return res; - } - - /** - * ping 消息跟踪 - */ - @Override - public void OnNetPingFinished(String log) { - this.recordStepInfo(log); - } - - private static final int CORE_POOL_SIZE = 1;// 4 - private static final int MAXIMUM_POOL_SIZE = 1;// 10 - private static final int KEEP_ALIVE = 10;// 10 - - private static final BlockingQueue sWorkQueue = new LinkedBlockingQueue( - 2);// 2 - private static final ThreadFactory sThreadFactory = new ThreadFactory() { - private final AtomicInteger mCount = new AtomicInteger(1); + /** + * ping 消息跟踪 + */ @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r, "Trace #" + mCount.getAndIncrement()); - t.setPriority(Thread.MIN_PRIORITY); - return t; + public void OnNetPingFinished(String log) { + this.recordStepInfo(log); } - }; - - private static ThreadPoolExecutor sExecutor = null; - @Override - protected ThreadPoolExecutor getThreadPoolExecutor() { - // TODO Auto-generated method stub - return sExecutor; - } + @Override + protected ThreadPoolExecutor getThreadPoolExecutor() { + // TODO Auto-generated method stub + return sExecutor; + } } diff --git a/app/src/main/java/com/netease/LDNetDiagnoService/LDNetPing.java b/app/src/main/java/com/netease/LDNetDiagnoService/LDNetPing.java index d2b1fc5..8494fdc 100644 --- a/app/src/main/java/com/netease/LDNetDiagnoService/LDNetPing.java +++ b/app/src/main/java/com/netease/LDNetDiagnoService/LDNetPing.java @@ -1,131 +1,128 @@ package com.netease.LDNetDiagnoService; +import android.util.Log; + +import com.netease.LDNetDiagnoUtils.LDPingParse; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.regex.Matcher; import java.util.regex.Pattern; -import android.util.Log; - -import com.netease.LDNetDiagnoUtils.LDPingParse; - /** * 直接通过ping命令监测网络 */ public class LDNetPing { - LDNetPingListener listener; // 回传ping的结果 - private final int _sendCount; // 每次ping发送数据包的个数 - - public LDNetPing(LDNetPingListener listener, int theSendCount) { - super(); - this.listener = listener; - this._sendCount = theSendCount; - } - - /** - * 监控NetPing的日志输出到Service - * - * @author panghui - * - */ - public interface LDNetPingListener { - public void OnNetPingFinished(String log); - } - - private static final String MATCH_PING_IP = "(?<=from ).*(?=: icmp_seq=1 ttl=)"; - - /** - * 执行ping命令,返回ping命令的全部控制台输出 - * - * @param ping - * @return - */ - private String execPing(PingTask ping, boolean isNeedL) { - String cmd = "ping -c "; - if (isNeedL) { - cmd = "ping -s 8185 -c "; + private static final String MATCH_PING_IP = "(?<=from ).*(?=: icmp_seq=1 ttl=)"; + private final int sendCount; // 每次ping发送数据包的个数 + LDNetPingListener listener; // 回传ping的结果 + + public LDNetPing(LDNetPingListener listener, int theSendCount) { + super(); + this.listener = listener; + this.sendCount = theSendCount; } - Process process = null; - String str = ""; - BufferedReader reader = null; - try { - process = Runtime.getRuntime().exec( - cmd + this._sendCount + " " + ping.getHost()); - reader = new BufferedReader(new InputStreamReader( - process.getInputStream())); - String line = null; - while ((line = reader.readLine()) != null) { - str += line; - } - reader.close(); - process.waitFor(); - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - try { - if (reader != null) { - reader.close(); + /** + * 执行ping命令,返回ping命令的全部控制台输出 + * + * @param ping + * @return + */ + private String execPing(PingTask ping, boolean isNeedL) { + String cmd = "ping -c "; + if (isNeedL) { + cmd = "ping -s 8185 -c "; + } + Process process = null; + String str = ""; + BufferedReader reader = null; + try { + process = Runtime.getRuntime().exec( + cmd + this.sendCount + " " + ping.getHost()); + reader = new BufferedReader(new InputStreamReader( + process.getInputStream())); + String line = null; + while ((line = reader.readLine()) != null) { + str += line; + } + reader.close(); + process.waitFor(); + + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + try { + if (reader != null) { + reader.close(); + } + process.destroy(); + } catch (Exception e) { + } } - process.destroy(); - } catch (Exception e) { - } + return str; } - return str; - } - /** - * 执行指定host的traceroute - * - * @param host - * @return - */ - public void exec(String host, boolean isNeedL) { - PingTask pingTask = new PingTask(host); - StringBuilder log = new StringBuilder(256); - String status = execPing(pingTask, isNeedL); - if (Pattern.compile(MATCH_PING_IP).matcher(status).find()) { - Log.i("LDNetPing", "status" + status); - log.append("\t" + status); - } else { - if (status.length() == 0) { - log.append("unknown host or network error"); - } else { - - log.append("timeout"); - } + /** + * 执行指定host的traceroute + * + * @param host + * @return + */ + public void exec(String host, boolean isNeedL) { + PingTask pingTask = new PingTask(host); + StringBuilder log = new StringBuilder(256); + String status = execPing(pingTask, isNeedL); + if (Pattern.compile(MATCH_PING_IP).matcher(status).find()) { + Log.i("LDNetPing", "status" + status); + log.append("\t" + status); + } else { + if (status.length() == 0) { + log.append("unknown host or network error"); + } else { + + log.append("timeout"); + } + } + String logStr = LDPingParse.getFormattingStr(host, log.toString()); + this.listener.OnNetPingFinished(logStr); } - String logStr = LDPingParse.getFormattingStr(host, log.toString()); - this.listener.OnNetPingFinished(logStr); - } - /** - * Ping任务 - * - * @author panghui - * - */ - private class PingTask { - - private String host; - private static final String MATCH_PING_HOST_IP = "(?<=\\().*?(?=\\))"; - - public String getHost() { - return host; + /** + * 监控NetPing的日志输出到Service + * + * @author panghui + */ + public interface LDNetPingListener { + public void OnNetPingFinished(String log); } - public PingTask(String host) { - super(); - this.host = host; - Pattern p = Pattern.compile(MATCH_PING_HOST_IP); - Matcher m = p.matcher(host); - if (m.find()) { - this.host = m.group(); - } + /** + * Ping任务 + * + * @author panghui + */ + private class PingTask { + + private static final String MATCH_PING_HOST_IP = "(?<=\\().*?(?=\\))"; + private String host; + + public PingTask(String host) { + super(); + this.host = host; + Pattern p = Pattern.compile(MATCH_PING_HOST_IP); + Matcher m = p.matcher(host); + if (m.find()) { + this.host = m.group(); + } + } + + public String getHost() { + return host; + } } - } } diff --git a/app/src/main/java/com/netease/LDNetDiagnoService/LDNetSocket.java b/app/src/main/java/com/netease/LDNetDiagnoService/LDNetSocket.java index 32f42c7..52f8d0c 100644 --- a/app/src/main/java/com/netease/LDNetDiagnoService/LDNetSocket.java +++ b/app/src/main/java/com/netease/LDNetDiagnoService/LDNetSocket.java @@ -1,5 +1,7 @@ package com.netease.LDNetDiagnoService; +import android.util.Log; + import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -7,205 +9,204 @@ import java.net.SocketTimeoutException; import java.util.List; -import android.util.Log; - public class LDNetSocket { - private static final int PORT = 80; - private static final int CONN_TIMES = 4; - private static final String TIMEOUT = "DNS解析正常,连接超时,TCP建立失败"; - private static final String IOERR = "DNS解析正常,IO异常,TCP建立失败"; - private static final String HOSTERR = "DNS解析失败,主机地址不可达"; - private static LDNetSocket instance=null; - private LDNetSocketListener listener; - private int timeOut = 6000;// 设置每次连接的timeout时间 - public InetAddress[] _remoteInet; - public List _remoteIpList; - private boolean[] isConnnected; - private final long[] RttTimes = new long[CONN_TIMES];// 用于存储三次测试中每次的RTT值 - - public boolean isCConn = true; - - private LDNetSocket() { - - } - public static LDNetSocket getInstance(){ - if(instance==null){ - instance=new LDNetSocket(); - } - return instance; - } - - public void initListener(LDNetSocketListener listener){ - this.listener=listener; - } - - /** - * 通过connect函数测试TCP的RTT时延 - */ - public boolean exec(String host) { - if (isCConn && loaded) { - try{ - startJNITelnet(host, "80"); //默认80端口 - return true; - }catch(UnsatisfiedLinkError e){ - e.printStackTrace(); - Log.i("LDNetSocket", "call jni failed, call execUseJava"); - return execUseJava(host); - } - } else { - return execUseJava(host); - } - } - - /** - * 使用java执行connected - */ - private boolean execUseJava(String host) { - if (_remoteInet != null && _remoteIpList != null) { - int len = _remoteInet.length; - isConnnected = new boolean[len]; - for (int i = 0; i < len; i++) { - if (i != 0) { - this.listener.OnNetSocketUpdated("\n"); + private static final int PORT = 80; + private static final int CONN_TIMES = 4; + private static final String TIMEOUT = "DNS解析正常,连接超时,TCP建立失败"; + private static final String IOERR = "DNS解析正常,IO异常,TCP建立失败"; + private static final String HOSTERR = "DNS解析失败,主机地址不可达"; + static boolean loaded; + private static LDNetSocket instance = null; + + static { + try { + System.loadLibrary("tracepath"); + loaded = true; + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); } - isConnnected[i] = execIP(_remoteInet[i], _remoteIpList.get(i)); - } - for (Boolean i : isConnnected) { - if (i == true) {// 一个连接成功即认为成功 - this.listener.OnNetSocketFinished("\n"); - return true; + } + + private final long[] RttTimes = new long[CONN_TIMES];// 用于存储三次测试中每次的RTT值 + public InetAddress[] remoteInet; + public List remoteIpList; + public boolean isCConn = true; + private LDNetSocketListener listener; + private int timeOut = 6000;// 设置每次连接的timeout时间 + private boolean[] isConnnected; + + private LDNetSocket() { + + } + + public static LDNetSocket getInstance() { + if (instance == null) { + instance = new LDNetSocket(); } - } + return instance; + } - } else { - this.listener.OnNetSocketFinished(HOSTERR); + public void initListener(LDNetSocketListener listener) { + this.listener = listener; } - this.listener.OnNetSocketFinished("\n"); - return false; - } - - /** - * 返回某个IP进行5次connect的最终结果 - */ - private boolean execIP(InetAddress inetAddress, String ip) { - boolean isConnected = true; - StringBuilder log = new StringBuilder(); - InetSocketAddress socketAddress = null; - if (inetAddress != null && ip != null) { - socketAddress = new InetSocketAddress(inetAddress, PORT); - int flag = 0; - this.listener.OnNetSocketUpdated("Connect to host: " + ip + "..." + "\n"); - for (int i = 0; i < CONN_TIMES; i++) { - execSocket(socketAddress, timeOut, i); - if (RttTimes[i] == -1) {// 一旦发生timeOut,则尝试加长连接时间 - this.listener.OnNetSocketUpdated((i + 1) + "'s time=" + "TimeOut" - + ", "); - timeOut += 4000; - if (i > 0 && RttTimes[i - 1] == -1) {// 连续两次连接超时,停止后续测试 - flag = -1; - break; - } - } else if (RttTimes[i] == -2) { - this.listener - .OnNetSocketUpdated((i + 1) + "'s time=" + "IOException"); - if (i > 0 && RttTimes[i - 1] == -2) {// 连续两次出现IO异常,停止后续测试 - flag = -2; - break; - } + + /** + * 通过connect函数测试TCP的RTT时延 + */ + public boolean exec(String host) { + if (isCConn && loaded) { + try { + startJNITelnet(host, "80"); //默认80端口 + return true; + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + Log.i("LDNetSocket", "call jni failed, call execUseJava"); + return execUseJava(host); + } } else { - this.listener.OnNetSocketUpdated((i + 1) + "'s time=" + RttTimes[i] - + "ms, "); + return execUseJava(host); } - } - long time = 0; - int count = 0; - if (flag == -1) { - // log.append(TIMEOUT); - isConnected = false; - } else if (flag == -2) { - // log.append(IOERR); - isConnected = false; - } else { - for (int i = 0; i < CONN_TIMES; i++) { - if (RttTimes[i] > 0) { - time += RttTimes[i]; - count++; - } + } + + /** + * 使用java执行connected + */ + private boolean execUseJava(String host) { + if (remoteInet != null && remoteIpList != null) { + int len = remoteInet.length; + isConnnected = new boolean[len]; + for (int i = 0; i < len; i++) { + if (i != 0) { + this.listener.OnNetSocketUpdated("\n"); + } + isConnnected[i] = execIP(remoteInet[i], remoteIpList.get(i)); + } + for (Boolean i : isConnnected) { + if (i == true) {// 一个连接成功即认为成功 + this.listener.OnNetSocketFinished("\n"); + return true; + } + } + + } else { + this.listener.OnNetSocketFinished(HOSTERR); } - if (count > 0) { - time = time / count; - log.append("average=" + time + "ms"); + this.listener.OnNetSocketFinished("\n"); + return false; + } + + /** + * 返回某个IP进行5次connect的最终结果 + */ + private boolean execIP(InetAddress inetAddress, String ip) { + boolean isConnected = true; + StringBuilder log = new StringBuilder(); + InetSocketAddress socketAddress = null; + if (inetAddress != null && ip != null) { + socketAddress = new InetSocketAddress(inetAddress, PORT); + int flag = 0; + this.listener.OnNetSocketUpdated("Connect to host: " + ip + "..." + "\n"); + for (int i = 0; i < CONN_TIMES; i++) { + execSocket(socketAddress, timeOut, i); + if (RttTimes[i] == -1) {// 一旦发生timeOut,则尝试加长连接时间 + this.listener.OnNetSocketUpdated((i + 1) + "'s time=" + "TimeOut" + + ", "); + timeOut += 4000; + if (i > 0 && RttTimes[i - 1] == -1) {// 连续两次连接超时,停止后续测试 + flag = -1; + break; + } + } else if (RttTimes[i] == -2) { + this.listener + .OnNetSocketUpdated((i + 1) + "'s time=" + "IOException"); + if (i > 0 && RttTimes[i - 1] == -2) {// 连续两次出现IO异常,停止后续测试 + flag = -2; + break; + } + } else { + this.listener.OnNetSocketUpdated((i + 1) + "'s time=" + RttTimes[i] + + "ms, "); + } + } + long time = 0; + int count = 0; + if (flag == -1) { + // log.append(TIMEOUT); + isConnected = false; + } else if (flag == -2) { + // log.append(IOERR); + isConnected = false; + } else { + for (int i = 0; i < CONN_TIMES; i++) { + if (RttTimes[i] > 0) { + time += RttTimes[i]; + count++; + } + } + if (count > 0) { + time = time / count; + log.append("average=" + time + "ms"); + } + } + } else { + isConnected = false; } - } - } else { - isConnected = false; + this.listener.OnNetSocketUpdated(log.toString()); + return isConnected; } - this.listener.OnNetSocketUpdated(log.toString()); - return isConnected; - } - - /** - * 针对某个IP第index次connect - */ - private void execSocket(InetSocketAddress socketAddress, int timeOut, - int index) { - Socket socket = null; - long start = 0; - long end = 0; - try { - socket = new Socket(); - start = System.currentTimeMillis(); - socket.connect(socketAddress, timeOut); - end = System.currentTimeMillis(); - RttTimes[index] = end - start; - } catch (SocketTimeoutException e) { - RttTimes[index] = -1;// 作为TIMEOUT标识 - e.printStackTrace(); - } catch (IOException e) { - RttTimes[index] = -2;// 作为IO异常标识 - e.printStackTrace(); - } finally { - if (socket != null) { + + /** + * 针对某个IP第index次connect + */ + private void execSocket(InetSocketAddress socketAddress, int timeOut, + int index) { + Socket socket = null; + long start = 0; + long end = 0; try { - socket.close(); + socket = new Socket(); + start = System.currentTimeMillis(); + socket.connect(socketAddress, timeOut); + end = System.currentTimeMillis(); + RttTimes[index] = end - start; + } catch (SocketTimeoutException e) { + RttTimes[index] = -1;// 作为TIMEOUT标识 + e.printStackTrace(); } catch (IOException e) { - e.printStackTrace(); + RttTimes[index] = -2;// 作为IO异常标识 + e.printStackTrace(); + } finally { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } - } } - } - public void resetInstance() { - if (instance != null) { - instance = null; + public void resetInstance() { + if (instance != null) { + instance = null; + } } - } - - /* - * 调用jni中native方法 - */ - public native void startJNITelnet(String host, String port); - - static boolean loaded; - static { - try { - System.loadLibrary("tracepath"); - loaded = true; - } catch (UnsatisfiedLinkError e) { - e.printStackTrace(); - } catch (Exception e) { - e.printStackTrace(); + + /* + * 调用jni中native方法 + */ + public native void startJNITelnet(String host, String port); + + public void printSocketInfo(String log) { + listener.OnNetSocketUpdated(log); + } + + public interface LDNetSocketListener { + public void OnNetSocketFinished(String log); + + public void OnNetSocketUpdated(String log); } - } - - public void printSocketInfo(String log){ - listener.OnNetSocketUpdated(log); - } - - public interface LDNetSocketListener { - public void OnNetSocketFinished(String log); - - public void OnNetSocketUpdated(String log); - } } diff --git a/app/src/main/java/com/netease/LDNetDiagnoService/LDNetTraceRoute.java b/app/src/main/java/com/netease/LDNetDiagnoService/LDNetTraceRoute.java index 9df2502..c425705 100644 --- a/app/src/main/java/com/netease/LDNetDiagnoService/LDNetTraceRoute.java +++ b/app/src/main/java/com/netease/LDNetDiagnoService/LDNetTraceRoute.java @@ -1,316 +1,311 @@ package com.netease.LDNetDiagnoService; +import android.util.Log; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.regex.Matcher; import java.util.regex.Pattern; -import android.util.Log; - /** * 通过ping模拟traceroute过程 - * + * * @author panghui - * */ public class LDNetTraceRoute { - private final String LOG_TAG = "LDNetTraceRoute"; - private static LDNetTraceRoute instance; + private static final String MATCH_TRACE_IP = "(?<=From )(?:[0-9]{1,3}\\.){3}[0-9]{1,3}"; + private static final String MATCH_PING_IP = "(?<=from ).*(?=: icmp_seq=1 ttl=)"; + private static final String MATCH_PING_TIME = "(?<=time=).*?ms"; + static boolean loaded; + private static LDNetTraceRoute instance; + + static { + try { + System.loadLibrary("tracepath"); + loaded = true; + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + } - private LDNetTraceRoute() { - } + private final String LOG_TAG = "LDNetTraceRoute"; + public boolean isCTrace = true; + LDNetTraceRouteListener listener; - public static LDNetTraceRoute getInstance() { - if (instance == null) { - instance = new LDNetTraceRoute(); - } - return instance; - } - - LDNetTraceRouteListener listener; - public boolean isCTrace = true; - - public void initListenter(LDNetTraceRouteListener listener) { - this.listener = listener; - } - - /** - * 监控NetPing的日志输出到Service - * - * @author panghui - * - */ - public interface LDNetTraceRouteListener { - public void OnNetTraceUpdated(String log); - - public void OnNetTraceFinished(); - } - - /** - * 执行指定host的traceroute - * - * @param host - * @return - */ - public void startTraceRoute(String host) { - if (isCTrace && loaded) { - try { - startJNICTraceRoute(host); - } catch (UnsatisfiedLinkError e) { - e.printStackTrace(); - // 如果c调用失败改调JAVA代码 - Log.i("LDNetTraceRoute", "调用java模拟traceRoute"); - TraceTask trace = new TraceTask(host, 1); - execTrace(trace); - } - } else { - TraceTask trace = new TraceTask(host, 1); - execTrace(trace); + private LDNetTraceRoute() { } - } - public void resetInstance() { - if (instance != null) { - instance = null; + public static LDNetTraceRoute getInstance() { + if (instance == null) { + instance = new LDNetTraceRoute(); + } + return instance; } - } - - /** - * 调用jni c函数执行traceroute过程 - */ - public native void startJNICTraceRoute(String traceCommand); - - static boolean loaded; - static { - try { - System.loadLibrary("tracepath"); - loaded = true; - } catch (UnsatisfiedLinkError e) { - e.printStackTrace(); - } catch (Exception e) { - e.printStackTrace(); + + public void initListenter(LDNetTraceRouteListener listener) { + this.listener = listener; } - } - - /** - * 供jni c函数回调 - * - * @param log - */ - public void printTraceInfo(String log) { - // Log.i(LOG_TAG, log); - listener.OnNetTraceUpdated(log); - } - - private static final String MATCH_TRACE_IP = "(?<=From )(?:[0-9]{1,3}\\.){3}[0-9]{1,3}"; - private static final String MATCH_PING_IP = "(?<=from ).*(?=: icmp_seq=1 ttl=)"; - private static final String MATCH_PING_TIME = "(?<=time=).*?ms"; - - /** - * 执行ping命令,返回ping命令的全部控制台输出 - * - * @param ping - * @return - */ - private String execPing(PingTask ping) { - Process process = null; - String str = ""; - BufferedReader reader = null; - try { - process = Runtime.getRuntime().exec("ping -c 1 " + ping.getHost()); - reader = new BufferedReader(new InputStreamReader( - process.getInputStream())); - String line = null; - while ((line = reader.readLine()) != null) { - str += line; - } - reader.close(); - process.waitFor(); - - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - try { - if (reader != null) { - reader.close(); + + /** + * 执行指定host的traceroute + * + * @param host + * @return + */ + public void startTraceRoute(String host) { + if (isCTrace && loaded) { + try { + startJNICTraceRoute(host); + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + // 如果c调用失败改调JAVA代码 + Log.i("LDNetTraceRoute", "调用java模拟traceRoute"); + TraceTask trace = new TraceTask(host, 1); + execTrace(trace); + } + } else { + TraceTask trace = new TraceTask(host, 1); + execTrace(trace); } - process.destroy(); - } catch (Exception e) { - } } - return str; - } - - /** - * 通过ping命令模拟执行traceroute的过程 - * - * @param trace - * @return - */ - private void execTrace(TraceTask trace) { - Pattern patternTrace = Pattern.compile(MATCH_TRACE_IP); - Pattern patternIp = Pattern.compile(MATCH_PING_IP); - Pattern patternTime = Pattern.compile(MATCH_PING_TIME); - - Process process = null; - BufferedReader reader = null; - boolean finish = false; - try { - // 通过ping的跳数控制,取得相应跳输的ip地址,然后再次执行ping命令读取时间 - while (!finish && trace.getHop() < 30) { - // 先发出ping命令获得某个跳数的ip地址 - String str = ""; - // -c 1 同时发送消息次数 -t是指跳数 - String command = "ping -c 1 -t " + trace.getHop() + " " - + trace.getHost(); - - process = Runtime.getRuntime().exec(command); - reader = new BufferedReader(new InputStreamReader( - process.getInputStream())); - String line = null; - while ((line = reader.readLine()) != null) { - str += line; - } - reader.close(); - process.waitFor(); - - Matcher m = patternTrace.matcher(str); - - // 如果成功获得trace:IP,则再次发送ping命令获取ping的时间 - StringBuilder log = new StringBuilder(256); - if (m.find()) { - String pingIp = m.group(); - PingTask pingTask = new PingTask(pingIp); - - String status = execPing(pingTask); - if (status.length() == 0) { - log.append("unknown host or network error\n"); - finish = true; - } else { - Matcher matcherTime = patternTime.matcher(status); - if (matcherTime.find()) { - String time = matcherTime.group(); - log.append(trace.getHop()); - log.append("\t\t"); - log.append(pingIp); - log.append("\t\t"); - log.append(time); - log.append("\t"); - } else { - log.append(trace.getHop()); - log.append("\t\t"); - log.append(pingIp); - log.append("\t\t timeout \t"); - } - listener.OnNetTraceUpdated(log.toString()); - trace.setHop(trace.getHop() + 1); - } + public void resetInstance() { + if (instance != null) { + instance = null; } + } + + /** + * 调用jni c函数执行traceroute过程 + */ + public native void startJNICTraceRoute(String traceCommand); + + /** + * 供jni c函数回调 + * + * @param log + */ + public void printTraceInfo(String log) { + // Log.i(LOG_TAG, log); + listener.OnNetTraceUpdated(log); + } - // 否则:what - else { - Matcher matchPingIp = patternIp.matcher(str); - if (matchPingIp.find()) { - String pingIp = matchPingIp.group(); - Matcher matcherTime = patternTime.matcher(str); - if (matcherTime.find()) { - String time = matcherTime.group(); - log.append(trace.getHop()); - log.append("\t\t"); - log.append(pingIp); - log.append("\t\t"); - log.append(time); - log.append("\t"); - listener.OnNetTraceUpdated(log.toString()); + /** + * 执行ping命令,返回ping命令的全部控制台输出 + * + * @param ping + * @return + */ + private String execPing(PingTask ping) { + Process process = null; + String str = ""; + BufferedReader reader = null; + try { + process = Runtime.getRuntime().exec("ping -c 1 " + ping.getHost()); + reader = new BufferedReader(new InputStreamReader( + process.getInputStream())); + String line = null; + while ((line = reader.readLine()) != null) { + str += line; } - finish = true; - } else { - if (str.length() == 0) { - log.append("unknown host or network error\t"); - finish = true; - } else { - log.append(trace.getHop()); - log.append("\t\t timeout \t"); - trace.setHop(trace.getHop() + 1); + reader.close(); + process.waitFor(); + + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + try { + if (reader != null) { + reader.close(); + } + process.destroy(); + } catch (Exception e) { } - listener.OnNetTraceUpdated(log.toString()); - } - }// else no match traceIPPattern - }// while - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - try { - if (reader != null) { - reader.close(); } - process.destroy(); - } catch (Exception e) { - } + return str; } - listener.OnNetTraceFinished(); - } + /** + * 通过ping命令模拟执行traceroute的过程 + * + * @param trace + * @return + */ + private void execTrace(TraceTask trace) { + Pattern patternTrace = Pattern.compile(MATCH_TRACE_IP); + Pattern patternIp = Pattern.compile(MATCH_PING_IP); + Pattern patternTime = Pattern.compile(MATCH_PING_TIME); + + Process process = null; + BufferedReader reader = null; + boolean finish = false; + try { + // 通过ping的跳数控制,取得相应跳输的ip地址,然后再次执行ping命令读取时间 + while (!finish && trace.getHop() < 30) { + // 先发出ping命令获得某个跳数的ip地址 + String str = ""; + // -c 1 同时发送消息次数 -t是指跳数 + String command = "ping -c 1 -t " + trace.getHop() + " " + + trace.getHost(); + + process = Runtime.getRuntime().exec(command); + reader = new BufferedReader(new InputStreamReader( + process.getInputStream())); + String line = null; + while ((line = reader.readLine()) != null) { + str += line; + } + reader.close(); + process.waitFor(); + + Matcher m = patternTrace.matcher(str); + + // 如果成功获得trace:IP,则再次发送ping命令获取ping的时间 + StringBuilder log = new StringBuilder(256); + if (m.find()) { + String pingIp = m.group(); + PingTask pingTask = new PingTask(pingIp); + + String status = execPing(pingTask); + if (status.length() == 0) { + log.append("unknown host or network error\n"); + finish = true; + } else { + Matcher matcherTime = patternTime.matcher(status); + if (matcherTime.find()) { + String time = matcherTime.group(); + log.append(trace.getHop()); + log.append("\t\t"); + log.append(pingIp); + log.append("\t\t"); + log.append(time); + log.append("\t"); + } else { + log.append(trace.getHop()); + log.append("\t\t"); + log.append(pingIp); + log.append("\t\t timeout \t"); + } + listener.OnNetTraceUpdated(log.toString()); + trace.setHop(trace.getHop() + 1); + } + + } + + // 否则:what + else { + Matcher matchPingIp = patternIp.matcher(str); + if (matchPingIp.find()) { + String pingIp = matchPingIp.group(); + Matcher matcherTime = patternTime.matcher(str); + if (matcherTime.find()) { + String time = matcherTime.group(); + log.append(trace.getHop()); + log.append("\t\t"); + log.append(pingIp); + log.append("\t\t"); + log.append(time); + log.append("\t"); + listener.OnNetTraceUpdated(log.toString()); + } + finish = true; + } else { + if (str.length() == 0) { + log.append("unknown host or network error\t"); + finish = true; + } else { + log.append(trace.getHop()); + log.append("\t\t timeout \t"); + trace.setHop(trace.getHop() + 1); + } + listener.OnNetTraceUpdated(log.toString()); + } + }// else no match traceIPPattern + }// while + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + try { + if (reader != null) { + reader.close(); + } + process.destroy(); + } catch (Exception e) { + } + } - /** - * Ping任务 - * - * @author panghui - * - */ - private class PingTask { + listener.OnNetTraceFinished(); + } - private String host; - private static final String MATCH_PING_HOST_IP = "(?<=\\().*?(?=\\))"; + /** + * 监控NetPing的日志输出到Service + * + * @author panghui + */ + public interface LDNetTraceRouteListener { + public void OnNetTraceUpdated(String log); - public String getHost() { - return host; + public void OnNetTraceFinished(); } - public PingTask(String host) { - super(); - this.host = host; - Pattern p = Pattern.compile(MATCH_PING_HOST_IP); - Matcher m = p.matcher(host); - if (m.find()) { - this.host = m.group(); - } + /** + * Ping任务 + * + * @author panghui + */ + private class PingTask { + + private static final String MATCH_PING_HOST_IP = "(?<=\\().*?(?=\\))"; + private String host; + + public PingTask(String host) { + super(); + this.host = host; + Pattern p = Pattern.compile(MATCH_PING_HOST_IP); + Matcher m = p.matcher(host); + if (m.find()) { + this.host = m.group(); + } - } - } - - /** - * 生成trace任务 - * - * @author panghui - * - */ - private class TraceTask { - private final String host; - private int hop; - - public TraceTask(String host, int hop) { - super(); - this.host = host; - this.hop = hop; - } + } - public String getHost() { - return host; + public String getHost() { + return host; + } } - public int getHop() { - return hop; - } + /** + * 生成trace任务 + * + * @author panghui + */ + private class TraceTask { + private final String host; + private int hop; + + public TraceTask(String host, int hop) { + super(); + this.host = host; + this.hop = hop; + } + + public String getHost() { + return host; + } + + public int getHop() { + return hop; + } - public void setHop(int hop) { - this.hop = hop; + public void setHop(int hop) { + this.hop = hop; + } } - } } diff --git a/app/src/main/java/com/netease/LDNetDiagnoUtils/LDNetUtil.java b/app/src/main/java/com/netease/LDNetDiagnoUtils/LDNetUtil.java index 5da1a79..48ccc17 100644 --- a/app/src/main/java/com/netease/LDNetDiagnoUtils/LDNetUtil.java +++ b/app/src/main/java/com/netease/LDNetDiagnoUtils/LDNetUtil.java @@ -1,5 +1,15 @@ package com.netease.LDNetDiagnoUtils; +import android.annotation.SuppressLint; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.DhcpInfo; +import android.net.NetworkInfo; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -12,278 +22,285 @@ import java.util.HashMap; import java.util.Map; -import android.annotation.SuppressLint; -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.DhcpInfo; -import android.net.NetworkInfo; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; -import android.telephony.TelephonyManager; -import android.text.TextUtils; - @SuppressLint("DefaultLocale") public class LDNetUtil { - public static final String OPEN_IP = "";// 可ping的IP地址 - public static final String OPERATOR_URL = ""; + public static final String OPEN_IP = "";// 可ping的IP地址 + public static final String OPERATOR_URL = ""; - public static final String NETWORKTYPE_INVALID = "UNKNOWN";// 没有网络 - public static final String NETWORKTYPE_WAP = "WAP"; // wap网络 - public static final String NETWORKTYPE_WIFI = "WIFI"; // wifi网络 + public static final String NETWORKTYPE_INVALID = "UNKNOWN";// 没有网络 + public static final String NETWORKTYPE_WAP = "WAP"; // wap网络 + public static final String NETWORKTYPE_WIFI = "WIFI"; // wifi网络 - @SuppressWarnings({ "deprecation" }) - public static String getNetWorkType(Context context) { - String mNetWorkType = null; - ConnectivityManager manager = (ConnectivityManager) context - .getSystemService(Context.CONNECTIVITY_SERVICE); - if (manager == null) { - return "ConnectivityManager not found"; - } - NetworkInfo networkInfo = manager.getActiveNetworkInfo(); - if (networkInfo != null && networkInfo.isConnected()) { - String type = networkInfo.getTypeName(); - if (type.equalsIgnoreCase("WIFI")) { - mNetWorkType = NETWORKTYPE_WIFI; - } else if (type.equalsIgnoreCase("MOBILE")) { - String proxyHost = android.net.Proxy.getDefaultHost(); - if (TextUtils.isEmpty(proxyHost)) { - mNetWorkType = mobileNetworkType(context); + @SuppressWarnings({"deprecation"}) + public static String getNetWorkType(Context context) { + String mNetWorkType = null; + ConnectivityManager manager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + if (manager == null) { + return "ConnectivityManager not found"; + } + NetworkInfo networkInfo = manager.getActiveNetworkInfo(); + if (networkInfo != null && networkInfo.isConnected()) { + String type = networkInfo.getTypeName(); + if ("WIFI".equalsIgnoreCase(type)) { + mNetWorkType = NETWORKTYPE_WIFI; + } else if ("MOBILE".equalsIgnoreCase(type)) { + String proxyHost = android.net.Proxy.getDefaultHost(); + if (TextUtils.isEmpty(proxyHost)) { + mNetWorkType = mobileNetworkType(context); + } else { + mNetWorkType = NETWORKTYPE_WAP; + } + } } else { - mNetWorkType = NETWORKTYPE_WAP; + mNetWorkType = NETWORKTYPE_INVALID; } - } - } else { - mNetWorkType = NETWORKTYPE_INVALID; + return mNetWorkType; } - return mNetWorkType; - } - /** - * 判断网络是否连接 - */ - public static Boolean isNetworkConnected(Context context) { - ConnectivityManager manager = (ConnectivityManager) context - .getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); - if (manager == null) { - return false; - } - NetworkInfo networkinfo = manager.getActiveNetworkInfo(); - if (networkinfo == null || !networkinfo.isAvailable()) { - return false; + /** + * 判断网络是否连接 + */ + public static Boolean isNetworkConnected(Context context) { + ConnectivityManager manager = (ConnectivityManager) context + .getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); + if (manager == null) { + return false; + } + NetworkInfo networkinfo = manager.getActiveNetworkInfo(); + if (networkinfo == null || !networkinfo.isAvailable()) { + return false; + } + return true; } - return true; - } - public static String getMobileOperator(Context context) { - TelephonyManager telManager = (TelephonyManager) context - .getSystemService(Context.TELEPHONY_SERVICE); - if(telManager==null) - return "未知运营商"; - String operator = telManager.getSimOperator(); - if (operator != null) { - if (operator.equals("46000") || operator.equals("46002") - || operator.equals("46007")) { - return "中国移动"; - } else if (operator.equals("46001")) { - return "中国联通"; - } else if (operator.equals("46003")) { - return "中国电信"; - } + /** + * 获取运营商 + * + * @param context + * @return + */ + public static String getMobileOperator(Context context) { + TelephonyManager telManager = (TelephonyManager) context.getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE); + if (telManager == null) { + return "未知运营商"; + } + // // 这个是以460开头 + // String imsi = telManager.getSubscriberId(); + // 这个是460xx + String operator = telManager.getSimOperator(); + // 中国移动46000、46002、46007 中国联通46001、46006 中国电信46003、46005 + if (operator != null) { + // 46000 GSM 46002 TD-S 46007 TD-S + if ("46000".equals(operator) || "46002".equals(operator) || "46007".equals(operator)) { + return "中国移动"; + //46001 GSM 46006 WCDMA + } else if ("46001".equals(operator) || "46006".equals(operator)) { + return "中国联通"; + //46011 FDD-LTE 46003 CDMA 46003 CDMA + } else if ("46003".equals(operator) || "46005".equals(operator) || "46011".equals(operator)) { + return "中国电信"; +// } else if ("46004".equals(operator)) { +// return "空运营商-测试"; + //中国动车和高铁的调度网,是移动的GSM-R铁路专网,是网络管理器专门为宽带无线接入的网络。 + } else if ("46020".equals(operator)) { + return "中国铁通"; + } + } + return "未知运营商"; } - return "未知运营商"; - } - /** - * 获取本机IP(wifi) - */ - public static String getLocalIpByWifi(Context context) { - WifiManager wifiManager = (WifiManager) context - .getSystemService(Context.WIFI_SERVICE); - if (wifiManager == null) { - return "wifiManager not found"; - } - WifiInfo wifiInfo = wifiManager.getConnectionInfo(); - if (wifiInfo == null) { - return "wifiInfo not found"; + /** + * 获取本机IP(wifi) + */ + public static String getLocalIpByWifi(Context context) { + WifiManager wifiManager = (WifiManager) context + .getSystemService(Context.WIFI_SERVICE); + if (wifiManager == null) { + return "wifiManager not found"; + } + WifiInfo wifiInfo = wifiManager.getConnectionInfo(); + if (wifiInfo == null) { + return "wifiInfo not found"; + } + int ipAddress = wifiInfo.getIpAddress(); + return String.format("%d.%d.%d.%d", (ipAddress & 0xff), + (ipAddress >> 8 & 0xff), (ipAddress >> 16 & 0xff), + (ipAddress >> 24 & 0xff)); } - int ipAddress = wifiInfo.getIpAddress(); - return String.format("%d.%d.%d.%d", (ipAddress & 0xff), - (ipAddress >> 8 & 0xff), (ipAddress >> 16 & 0xff), - (ipAddress >> 24 & 0xff)); - } - /** - * 获取本机IP(2G/3G/4G) - */ - public static String getLocalIpBy3G() { - try { - for (Enumeration en = NetworkInterface - .getNetworkInterfaces(); en.hasMoreElements();) { - NetworkInterface intf = en.nextElement(); - for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr - .hasMoreElements();) { - InetAddress inetAddress = enumIpAddr.nextElement(); - if (!inetAddress.isLoopbackAddress() - && inetAddress instanceof Inet4Address) { - // if (!inetAddress.isLoopbackAddress() && inetAddress - // instanceof Inet6Address) { - return inetAddress.getHostAddress().toString(); - } + /** + * 获取本机IP(2G/3G/4G) + */ + public static String getLocalIpBy3G() { + try { + for (Enumeration en = NetworkInterface + .getNetworkInterfaces(); en.hasMoreElements(); ) { + NetworkInterface intf = en.nextElement(); + for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr + .hasMoreElements(); ) { + InetAddress inetAddress = enumIpAddr.nextElement(); + if (!inetAddress.isLoopbackAddress() + && inetAddress instanceof Inet4Address) { + // if (!inetAddress.isLoopbackAddress() && inetAddress + // instanceof Inet6Address) { + return inetAddress.getHostAddress().toString(); + } + } + } + } catch (Exception e) { + e.printStackTrace(); } - } - } catch (Exception e) { - e.printStackTrace(); + return null; } - return null; - } - /** - * wifi状态下获取网关 - */ - public static String pingGateWayInWifi(Context context) { - String gateWay = null; - WifiManager wifiManager = (WifiManager) context - .getSystemService(Context.WIFI_SERVICE); - if (wifiManager == null) { - return "wifiManager not found"; - } - DhcpInfo dhcpInfo = wifiManager.getDhcpInfo(); - if (dhcpInfo != null) { - int tmp = dhcpInfo.gateway; - gateWay = String.format("%d.%d.%d.%d", (tmp & 0xff), (tmp >> 8 & 0xff), - (tmp >> 16 & 0xff), (tmp >> 24 & 0xff)); + /** + * wifi状态下获取网关 + */ + public static String pingGateWayInWifi(Context context) { + String gateWay = null; + WifiManager wifiManager = (WifiManager) context + .getSystemService(Context.WIFI_SERVICE); + if (wifiManager == null) { + return "wifiManager not found"; + } + DhcpInfo dhcpInfo = wifiManager.getDhcpInfo(); + if (dhcpInfo != null) { + int tmp = dhcpInfo.gateway; + gateWay = String.format("%d.%d.%d.%d", (tmp & 0xff), (tmp >> 8 & 0xff), + (tmp >> 16 & 0xff), (tmp >> 24 & 0xff)); + } + return gateWay; } - return gateWay; - } - /** - * 获取本地DNS - */ - public static String getLocalDns(String dns) { - Process process = null; - String str = ""; - BufferedReader reader = null; - try { - process = Runtime.getRuntime().exec("getprop net." + dns); - reader = new BufferedReader(new InputStreamReader( - process.getInputStream())); - String line = null; - while ((line = reader.readLine()) != null) { - str += line; - } - reader.close(); - process.waitFor(); - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - try { - if (reader != null) { - reader.close(); + /** + * 获取本地DNS + */ + public static String getLocalDns(String dns) { + Process process = null; + String str = ""; + BufferedReader reader = null; + try { + process = Runtime.getRuntime().exec("getprop net." + dns); + reader = new BufferedReader(new InputStreamReader( + process.getInputStream())); + String line = null; + while ((line = reader.readLine()) != null) { + str += line; + } + reader.close(); + process.waitFor(); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + try { + if (reader != null) { + reader.close(); + } + process.destroy(); + } catch (Exception e) { + } } - process.destroy(); - } catch (Exception e) { - } + return str.trim(); } - return str.trim(); - } - /** - * 域名解析 - */ - public static Map getDomainIp(String _dormain) { - Map map = new HashMap(); - long start = 0; - long end = 0; - String time = null; - InetAddress[] remoteInet = null; - try { - start = System.currentTimeMillis(); - remoteInet = InetAddress.getAllByName(_dormain); - if (remoteInet != null) { - end = System.currentTimeMillis(); - time = (end - start) + ""; - } - } catch (UnknownHostException e) { - end = System.currentTimeMillis(); - time = (end - start) + ""; - remoteInet = null; - e.printStackTrace(); - } finally { - map.put("remoteInet", remoteInet); - map.put("useTime", time); + /** + * 域名解析 + */ + public static Map getDomainIp(String dormain) { + Map map = new HashMap(); + long start = 0; + long end = 0; + String time = null; + InetAddress[] remoteInet = null; + try { + start = System.currentTimeMillis(); + remoteInet = InetAddress.getAllByName(dormain); + if (remoteInet != null) { + end = System.currentTimeMillis(); + time = (end - start) + ""; + } + } catch (UnknownHostException e) { + end = System.currentTimeMillis(); + time = (end - start) + ""; + remoteInet = null; + e.printStackTrace(); + } finally { + map.put("remoteInet", remoteInet); + map.put("useTime", time); + } + return map; } - return map; - } - private static String mobileNetworkType(Context context) { - TelephonyManager telephonyManager = (TelephonyManager) context - .getSystemService(Context.TELEPHONY_SERVICE); - if (telephonyManager == null) { - return "TM==null"; - } - switch (telephonyManager.getNetworkType()) { - case TelephonyManager.NETWORK_TYPE_1xRTT:// ~ 50-100 kbps - return "2G"; - case TelephonyManager.NETWORK_TYPE_CDMA:// ~ 14-64 kbps - return "2G"; - case TelephonyManager.NETWORK_TYPE_EDGE:// ~ 50-100 kbps - return "2G"; - case TelephonyManager.NETWORK_TYPE_EVDO_0:// ~ 400-1000 kbps - return "3G"; - case TelephonyManager.NETWORK_TYPE_EVDO_A:// ~ 600-1400 kbps - return "3G"; - case TelephonyManager.NETWORK_TYPE_GPRS:// ~ 100 kbps - return "2G"; - case TelephonyManager.NETWORK_TYPE_HSDPA:// ~ 2-14 Mbps - return "3G"; - case TelephonyManager.NETWORK_TYPE_HSPA:// ~ 700-1700 kbps - return "3G"; - case TelephonyManager.NETWORK_TYPE_HSUPA: // ~ 1-23 Mbps - return "3G"; - case TelephonyManager.NETWORK_TYPE_UMTS:// ~ 400-7000 kbps - return "3G"; - case TelephonyManager.NETWORK_TYPE_EHRPD:// ~ 1-2 Mbps - return "3G"; - case TelephonyManager.NETWORK_TYPE_EVDO_B: // ~ 5 Mbps - return "3G"; - case TelephonyManager.NETWORK_TYPE_HSPAP:// ~ 10-20 Mbps - return "3G"; - case TelephonyManager.NETWORK_TYPE_IDEN:// ~25 kbps - return "2G"; - case TelephonyManager.NETWORK_TYPE_LTE:// ~ 10+ Mbps - return "4G"; - case TelephonyManager.NETWORK_TYPE_UNKNOWN: - return "UNKNOWN"; - default: - return "4G"; + private static String mobileNetworkType(Context context) { + TelephonyManager telephonyManager = (TelephonyManager) context + .getSystemService(Context.TELEPHONY_SERVICE); + if (telephonyManager == null) { + return "TM==null"; + } + switch (telephonyManager.getNetworkType()) { + case TelephonyManager.NETWORK_TYPE_1xRTT:// ~ 50-100 kbps + return "2G"; + case TelephonyManager.NETWORK_TYPE_CDMA:// ~ 14-64 kbps + return "2G"; + case TelephonyManager.NETWORK_TYPE_EDGE:// ~ 50-100 kbps + return "2G"; + case TelephonyManager.NETWORK_TYPE_EVDO_0:// ~ 400-1000 kbps + return "3G"; + case TelephonyManager.NETWORK_TYPE_EVDO_A:// ~ 600-1400 kbps + return "3G"; + case TelephonyManager.NETWORK_TYPE_GPRS:// ~ 100 kbps + return "2G"; + case TelephonyManager.NETWORK_TYPE_HSDPA:// ~ 2-14 Mbps + return "3G"; + case TelephonyManager.NETWORK_TYPE_HSPA:// ~ 700-1700 kbps + return "3G"; + case TelephonyManager.NETWORK_TYPE_HSUPA: // ~ 1-23 Mbps + return "3G"; + case TelephonyManager.NETWORK_TYPE_UMTS:// ~ 400-7000 kbps + return "3G"; + case TelephonyManager.NETWORK_TYPE_EHRPD:// ~ 1-2 Mbps + return "3G"; + case TelephonyManager.NETWORK_TYPE_EVDO_B: // ~ 5 Mbps + return "3G"; + case TelephonyManager.NETWORK_TYPE_HSPAP:// ~ 10-20 Mbps + return "3G"; + case TelephonyManager.NETWORK_TYPE_IDEN:// ~25 kbps + return "2G"; + case TelephonyManager.NETWORK_TYPE_LTE:// ~ 10+ Mbps + return "4G"; + case TelephonyManager.NETWORK_TYPE_UNKNOWN: + return "UNKNOWN"; + default: + return "4G"; + } } - } - /** - * 输入流转变成字符串 - */ - public static String getStringFromStream(InputStream is) { - byte[] bytes = new byte[1024]; - int len = 0; - String res = ""; - try { - while ((len = is.read(bytes)) != -1) { - res = res + new String(bytes, 0, len, "gbk"); - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (is != null) { + /** + * 输入流转变成字符串 + */ + public static String getStringFromStream(InputStream is) { + byte[] bytes = new byte[1024]; + int len = 0; + String res = ""; try { - is.close(); + while ((len = is.read(bytes)) != -1) { + res = res + new String(bytes, 0, len, "gbk"); + } } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } - } + return res; } - return res; - } } diff --git a/app/src/main/java/com/netease/LDNetDiagnoUtils/LDPingParse.java b/app/src/main/java/com/netease/LDNetDiagnoUtils/LDPingParse.java index f4de482..244f4a4 100644 --- a/app/src/main/java/com/netease/LDNetDiagnoUtils/LDPingParse.java +++ b/app/src/main/java/com/netease/LDNetDiagnoUtils/LDPingParse.java @@ -6,92 +6,92 @@ import java.util.regex.Pattern; public class LDPingParse { - public static String getFormattingStr(String host, String log) { - StringBuilder logRes = new StringBuilder(); - if (log.contains("timeout")) { - logRes.append("ping: cannot resolve " + host + ": Timeout"); - } else if (log.contains("unknown")) { - logRes.append("ping: cannot resolve " + host + ": Unknown host"); - } else { - makePingResponse(log, logRes); + public static String getFormattingStr(String host, String log) { + StringBuilder logRes = new StringBuilder(); + if (log.contains("timeout")) { + logRes.append("ping: cannot resolve " + host + ": Timeout"); + } else if (log.contains("unknown")) { + logRes.append("ping: cannot resolve " + host + ": Unknown host"); + } else { + makePingResponse(log, logRes); + } + return logRes.toString(); } - return logRes.toString(); - } - public static void makePingResponse(String log, StringBuilder logRes) { - String hostIp = getIP(log); - List bytesList = getSumBytes(log); - List ttlList = getTTL(log); - List timeList = getTime(log); - List icmpList = getIcmp_seq(log); - int len = timeList.size(); - for (int i = 0; i < len - 1; i++) { - logRes.append(bytesList.get(i) + "bytes from " + hostIp + ": icmp_seq=#" - + icmpList.get(i) + " ttl=" + ttlList.get(i) + " time=" - + timeList.get(i) + "ms" + "\n"); + public static void makePingResponse(String log, StringBuilder logRes) { + String hostIp = getIP(log); + List bytesList = getSumBytes(log); + List ttlList = getTTL(log); + List timeList = getTime(log); + List icmpList = getIcmp_seq(log); + int len = timeList.size(); + for (int i = 0; i < len - 1; i++) { + logRes.append(bytesList.get(i) + "bytes from " + hostIp + ": icmp_seq=#" + + icmpList.get(i) + " ttl=" + ttlList.get(i) + " time=" + + timeList.get(i) + "ms" + "\n"); + } + logRes.append(bytesList.get(len - 1) + "bytes from " + hostIp + + ": icmp_seq=#" + icmpList.get(len - 1) + " ttl=" + + ttlList.get(len - 1) + " time=" + timeList.get(len - 1) + "ms"); } - logRes.append(bytesList.get(len - 1) + "bytes from " + hostIp - + ": icmp_seq=#" + icmpList.get(len - 1) + " ttl=" - + ttlList.get(len - 1) + " time=" + timeList.get(len - 1) + "ms"); - } - private static List getTime(String log) { - List timeList = new ArrayList(); - String regex = "(?<==)([\\.0-9\\s]+)(?=ms)"; - Pattern p = Pattern.compile(regex); - Matcher m = p.matcher(log); - while (m.find()) { - timeList.add(m.group().toString().trim()); + private static List getTime(String log) { + List timeList = new ArrayList(); + String regex = "(?<==)([\\.0-9\\s]+)(?=ms)"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(log); + while (m.find()) { + timeList.add(m.group().toString().trim()); + } + return timeList; } - return timeList; - } - private static List getSumBytes(String log) { - List bytesList = new ArrayList(); - String regex = "(?<=\\D)([\\s0-9]+)(?=bytes)"; - Pattern p = Pattern.compile(regex); - Matcher m = p.matcher(log); - while (m.find()) { - String string = m.group().toString().trim(); - if (m.group().toString().trim().matches("\\d+")) { - bytesList.add(string); - } + private static List getSumBytes(String log) { + List bytesList = new ArrayList(); + String regex = "(?<=\\D)([\\s0-9]+)(?=bytes)"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(log); + while (m.find()) { + String string = m.group().toString().trim(); + if (m.group().toString().trim().matches("\\d+")) { + bytesList.add(string); + } + } + return bytesList; } - return bytesList; - } - private static List getTTL(String log) { - List ttlList = new ArrayList(); - String regex = "(?<=ttl=)([0-9]+)(?=\\s)"; - Pattern p = Pattern.compile(regex); - Matcher m = p.matcher(log); - while (m.find()) { - String tmp = m.group().toString().trim(); - ttlList.add(tmp); + private static List getTTL(String log) { + List ttlList = new ArrayList(); + String regex = "(?<=ttl=)([0-9]+)(?=\\s)"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(log); + while (m.find()) { + String tmp = m.group().toString().trim(); + ttlList.add(tmp); + } + return ttlList; } - return ttlList; - } - private static String getIP(String log) { - String hostIp = null; - String regex = "(?<=\\()([\\d]+\\.)+[\\d]+(?=\\))"; - Pattern p = Pattern.compile(regex); - Matcher m = p.matcher(log); - while (m.find()) { - hostIp = m.group().toString().trim(); + private static String getIP(String log) { + String hostIp = null; + String regex = "(?<=\\()([\\d]+\\.)+[\\d]+(?=\\))"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(log); + while (m.find()) { + hostIp = m.group().toString().trim(); + } + return hostIp; } - return hostIp; - } - private static List getIcmp_seq(String log) { - List icmpList = new ArrayList(); - String regex = "(?<=icmp_seq=)([0-9]+)(?=\\s)"; - Pattern p = Pattern.compile(regex); - Matcher m = p.matcher(log); - while (m.find()) { - String tmp = m.group().toString().trim(); - icmpList.add(tmp); + private static List getIcmp_seq(String log) { + List icmpList = new ArrayList(); + String regex = "(?<=icmp_seq=)([0-9]+)(?=\\s)"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(log); + while (m.find()) { + String tmp = m.group().toString().trim(); + icmpList.add(tmp); + } + return icmpList; } - return icmpList; - } } diff --git a/app/src/main/java/net/lightbody/bmp/BrowserMobProxy.java b/app/src/main/java/net/lightbody/bmp/BrowserMobProxy.java index 28a9d80..3286b4e 100644 --- a/app/src/main/java/net/lightbody/bmp/BrowserMobProxy.java +++ b/app/src/main/java/net/lightbody/bmp/BrowserMobProxy.java @@ -8,6 +8,7 @@ import net.lightbody.bmp.proxy.CaptureType; import net.lightbody.bmp.proxy.auth.AuthType; import net.lightbody.bmp.proxy.dns.AdvancedHostResolver; + import org.littleshoot.proxy.HttpFiltersSource; import org.littleshoot.proxy.MitmManager; @@ -39,7 +40,7 @@ public interface BrowserMobProxy { * Starts the proxy on the specified port. The proxy will listen for connections on the network interface specified by the bindAddress, and will * also initiate connections to upstream servers on the same network interface. * - * @param port port to listen on + * @param port port to listen on * @param bindAddress address of the network interface on which the proxy will listen for connections and also attempt to connect to upstream servers. * @throws java.lang.IllegalStateException if the proxy has already been started */ @@ -49,7 +50,7 @@ public interface BrowserMobProxy { * Starts the proxy on the specified port. The proxy will listen for connections on the network interface specified by the clientBindAddress, and will * initiate connections to upstream servers from the network interface specified by the serverBindAddress. * - * @param port port to listen on + * @param port port to listen on * @param clientBindAddress address of the network interface on which the proxy will listen for connections * @param serverBindAddress address of the network interface on which the proxy will connect to upstream servers * @throws java.lang.IllegalStateException if the proxy has already been started @@ -68,7 +69,7 @@ public interface BrowserMobProxy { * @throws java.lang.IllegalStateException if the proxy has not been started. */ void stop(); - + /** * Like {@link #stop()}, shuts down the proxy server and no longer accepts incoming connections, but does not wait for any existing * network traffic to cease. Any existing connections to clients or to servers may be force-killed immediately. @@ -129,12 +130,17 @@ public interface BrowserMobProxy { /** * Starts a new HAR file with the specified page name and page title. Enables HAR capture if it was not previously enabled. * - * @param initialPageRef initial page name of the new HAR file + * @param initialPageRef initial page name of the new HAR file * @param initialPageTitle initial page title of the new HAR file * @return existing HAR file, or null if none exists or HAR capture was disabled */ Har newHar(String initialPageRef, String initialPageTitle); + /** + * @return A copy of HAR capture types currently in effect. The EnumSet cannot be used to modify the HAR capture types currently in effect. + */ + EnumSet getHarCaptureTypes(); + /** * Sets the data types that will be captured in the HAR file for future requests. Replaces any existing capture types with the specified * capture types. A null or empty set will not disable HAR capture, but will disable collection of @@ -161,11 +167,6 @@ public interface BrowserMobProxy { */ void setHarCaptureTypes(CaptureType... captureTypes); - /** - * @return A copy of HAR capture types currently in effect. The EnumSet cannot be used to modify the HAR capture types currently in effect. - */ - EnumSet getHarCaptureTypes(); - /** * Enables the specified HAR capture types. Does not replace or disable any other capture types that may already be enabled. * @@ -219,7 +220,7 @@ public interface BrowserMobProxy { * Starts a new HAR page using the specified pageRef as the page name and the pageTitle as the page title. Populates the * {@link net.lightbody.bmp.core.har.HarPageTimings#onLoad} value based on the amount of time the current page has been captured. * - * @param pageRef name of the new page + * @param pageRef name of the new page * @param pageTitle title of the new page * @return the HAR as it existed immediately after ending the current page * @throws java.lang.IllegalStateException if HAR capture has not been enabled via {@link #newHar()} or {@link #newHar(String)} @@ -234,6 +235,11 @@ public interface BrowserMobProxy { */ Har endHar(); + /** + * Returns the current bandwidth limit for reading, in bytes per second. + */ + long getReadBandwidthLimit(); + /** * Sets the maximum bandwidth to consume when reading server responses. * @@ -242,9 +248,9 @@ public interface BrowserMobProxy { void setReadBandwidthLimit(long bytesPerSecond); /** - * Returns the current bandwidth limit for reading, in bytes per second. + * Returns the current bandwidth limit for writing, in bytes per second. */ - long getReadBandwidthLimit(); + long getWriteBandwidthLimit(); /** * Sets the maximum bandwidth to consume when sending requests to servers. @@ -253,16 +259,11 @@ public interface BrowserMobProxy { */ void setWriteBandwidthLimit(long bytesPerSecond); - /** - * Returns the current bandwidth limit for writing, in bytes per second. - */ - long getWriteBandwidthLimit(); - /** * The minimum amount of time that will elapse between the time the proxy begins receiving a response from the server and the time the * proxy begins sending the response to the client. * - * @param latency minimum latency, or 0 for no minimum + * @param latency minimum latency, or 0 for no minimum * @param timeUnit TimeUnit for the latency */ void setLatency(long latency, TimeUnit timeUnit); @@ -272,7 +273,7 @@ public interface BrowserMobProxy { * specified time, the proxy will respond with an HTTP 502 Bad Gateway. The default value is 60 seconds. * * @param connectionTimeout maximum time to wait to establish a connection to a server, or 0 to wait indefinitely - * @param timeUnit TimeUnit for the connectionTimeout + * @param timeUnit TimeUnit for the connectionTimeout */ void setConnectTimeout(int connectionTimeout, TimeUnit timeUnit); @@ -283,7 +284,7 @@ public interface BrowserMobProxy { * connection to the client may be closed abruptly. The default value is 60 seconds. * * @param idleConnectionTimeout maximum time to allow a connection to remain idle, or 0 to wait indefinitely. - * @param timeUnit TimeUnit for the idleConnectionTimeout + * @param timeUnit TimeUnit for the idleConnectionTimeout */ void setIdleConnectionTimeout(int idleConnectionTimeout, TimeUnit timeUnit); @@ -294,7 +295,7 @@ public interface BrowserMobProxy { * connection to the client may be closed abruptly. The default value is 0 (wait indefinitely). * * @param requestTimeout maximum time to wait for an HTTP response, or 0 to wait indefinitely - * @param timeUnit TimeUnit for the requestTimeout + * @param timeUnit TimeUnit for the requestTimeout */ void setRequestTimeout(int requestTimeout, TimeUnit timeUnit); @@ -302,7 +303,7 @@ public interface BrowserMobProxy { * Enables automatic authorization for the specified domain and auth type. Every request sent to the specified domain will contain the * specified authorization information. * - * @param domain domain automatically send authorization information to + * @param domain domain automatically send authorization information to * @param username authorization username * @param password authorization password * @param authType authorization type @@ -344,7 +345,7 @@ public interface BrowserMobProxy { * For example, the following rewrite rule: * *

   {@code proxy.rewriteUrl("http://www\\.(yahoo|bing)\\.com/\\?(\\w+)=(\\w+)", "http://www.google.com/?originalDomain=$1&$2=$3");}
- * + *

* will match an HTTP request (but not HTTPS!) to www.yahoo.com or www.bing.com with exactly 1 query parameter, * and replace it with a call to www.google.com with an 'originalDomain' query parameter, as well as the original query parameter. *

@@ -357,7 +358,7 @@ public interface BrowserMobProxy { * will result in the proxy making a request to: *

   {@code http://www.google.com?originalDomain=bing&anotherParam=anotherValue}
* - * @param urlPattern URL-matching regular expression + * @param urlPattern URL-matching regular expression * @param replacementExpression an expression, which may optionally contain capture groups, which will replace any URL which matches urlPattern */ void rewriteUrl(String urlPattern, String replacementExpression); @@ -414,20 +415,12 @@ public interface BrowserMobProxy { *

* See {@link #blacklistRequests(String, int)} for details on the URL the urlPattern will match. * - * @param urlPattern URL-matching regular expression to blacklist - * @param statusCode HTTP status code to return + * @param urlPattern URL-matching regular expression to blacklist + * @param statusCode HTTP status code to return * @param httpMethodPattern regular expression matching a request's HTTP method */ void blacklistRequests(String urlPattern, int statusCode, String httpMethodPattern); - /** - * Replaces any existing blacklist with the specified blacklist. URLs will be evaluated against the blacklist in the order - * specified by the Collection's iterator. - * - * @param blacklist new blacklist entries - */ - void setBlacklist(Collection blacklist); - /** * Returns all blacklist entries currently in effect. Iterating over the returned Collection is guaranteed to return * blacklist entries in the order in which URLs are actually evaluated against the blacklist. @@ -436,6 +429,14 @@ public interface BrowserMobProxy { */ Collection getBlacklist(); + /** + * Replaces any existing blacklist with the specified blacklist. URLs will be evaluated against the blacklist in the order + * specified by the Collection's iterator. + * + * @param blacklist new blacklist entries + */ + void setBlacklist(Collection blacklist); + /** * Clears any existing blacklist. */ @@ -451,7 +452,7 @@ public interface BrowserMobProxy { * whitelist response code. * * @param urlPatterns URL-matching regular expressions to whitelist; null or an empty collection will enable an empty whitelist - * @param statusCode HTTP status code to return to clients when a URL matches a pattern + * @param statusCode HTTP status code to return to clients when a URL matches a pattern */ void whitelistRequests(Collection urlPatterns, int statusCode); @@ -505,7 +506,7 @@ public interface BrowserMobProxy { /** * Adds a new HTTP header to every request. If the header already exists on the request, it will be replaced with the specified header. * - * @param name name of the header to add + * @param name name of the header to add * @param value new header's value */ void addHeader(String name, String value); @@ -530,31 +531,38 @@ public interface BrowserMobProxy { Map getAllHeaders(); /** - * Sets the resolver that will be used to look up host names. To chain multiple resolvers, wrap a list - * of resolvers in a {@link net.lightbody.bmp.proxy.dns.ChainedHostResolver}. + * Returns the current host name resolver. * - * @param resolver host name resolver + * @return current host name resolver */ - void setHostNameResolver(AdvancedHostResolver resolver); + AdvancedHostResolver getHostNameResolver(); /** - * Returns the current host name resolver. + * Sets the resolver that will be used to look up host names. To chain multiple resolvers, wrap a list + * of resolvers in a {@link net.lightbody.bmp.proxy.dns.ChainedHostResolver}. * - * @return current host name resolver + * @param resolver host name resolver */ - AdvancedHostResolver getHostNameResolver(); + void setHostNameResolver(AdvancedHostResolver resolver); /** * Waits for existing network traffic to stop, and for the specified quietPeriod to elapse. Returns true if there is no network traffic * for the quiet period within the specified timeout, otherwise returns false. * * @param quietPeriod amount of time after which network traffic will be considered "stopped" - * @param timeout maximum amount of time to wait for network traffic to stop - * @param timeUnit TimeUnit for the quietPeriod and timeout + * @param timeout maximum amount of time to wait for network traffic to stop + * @param timeUnit TimeUnit for the quietPeriod and timeout * @return true if network traffic is stopped, otherwise false */ boolean waitForQuiescence(long quietPeriod, long timeout, TimeUnit timeUnit); + /** + * Returns the address and port of the upstream proxy. + * + * @return address and port of the upstream proxy, or null of there is none. + */ + InetSocketAddress getChainedProxy(); + /** * Instructs this proxy to route traffic through an upstream proxy. * @@ -564,13 +572,6 @@ public interface BrowserMobProxy { */ void setChainedProxy(InetSocketAddress chainedProxyAddress); - /** - * Returns the address and port of the upstream proxy. - * - * @return address and port of the upstream proxy, or null of there is none. - */ - InetSocketAddress getChainedProxy(); - /** * Adds a new filter factory (request/response interceptor) to the beginning of the HttpFilters chain. *

@@ -591,7 +592,7 @@ public interface BrowserMobProxy { * {@link org.littleshoot.proxy.HttpFilters} instance (typically, a subclass of {@link org.littleshoot.proxy.HttpFiltersAdapter}). * To disable or bypass a filter on a per-request basis, the filterRequest() method may return null. * - * @param filterFactory factory to generate HttpFilters + * @param filterFactory factory to generate HttpFilters */ void addLastHttpFilterFactory(HttpFiltersSource filterFactory); diff --git a/app/src/main/java/net/lightbody/bmp/BrowserMobProxyServer.java b/app/src/main/java/net/lightbody/bmp/BrowserMobProxyServer.java index f539a17..6759d1e 100644 --- a/app/src/main/java/net/lightbody/bmp/BrowserMobProxyServer.java +++ b/app/src/main/java/net/lightbody/bmp/BrowserMobProxyServer.java @@ -3,10 +3,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.MapMaker; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.HttpRequest; + import net.lightbody.bmp.client.ClientUtil; import net.lightbody.bmp.core.har.Har; import net.lightbody.bmp.core.har.HarLog; @@ -31,11 +28,7 @@ import net.lightbody.bmp.filters.RewriteUrlFilter; import net.lightbody.bmp.filters.UnregisterRequestFilter; import net.lightbody.bmp.filters.WhitelistFilter; -import net.lightbody.bmp.mitm.KeyStoreFileCertificateSource; import net.lightbody.bmp.mitm.TrustSource; -import net.lightbody.bmp.mitm.keys.ECKeyGenerator; -import net.lightbody.bmp.mitm.keys.RSAKeyGenerator; -import net.lightbody.bmp.mitm.manager.ImpersonatingMitmManager; import net.lightbody.bmp.proxy.ActivityMonitor; import net.lightbody.bmp.proxy.BlacklistEntry; import net.lightbody.bmp.proxy.CaptureType; @@ -46,6 +39,7 @@ import net.lightbody.bmp.proxy.dns.DelegatingHostResolver; import net.lightbody.bmp.util.BrowserMobHttpUtil; import net.lightbody.bmp.util.BrowserMobProxyUtil; + import org.littleshoot.proxy.ChainedProxy; import org.littleshoot.proxy.ChainedProxyAdapter; import org.littleshoot.proxy.ChainedProxyManager; @@ -69,7 +63,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Date; import java.util.EnumSet; import java.util.List; import java.util.Map; @@ -83,26 +76,27 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpObject; +import io.netty.handler.codec.http.HttpRequest; + /** * A LittleProxy-based implementation of {@link net.lightbody.bmp.BrowserMobProxy}. */ public class BrowserMobProxyServer implements BrowserMobProxy { + /** + * The default pseudonym to use when adding the Via header to proxied requests. + */ + public static final String VIA_HEADER_ALIAS = "browsermobproxy"; private static final Logger log = LoggerFactory.getLogger(BrowserMobProxyServer.class); - private static final HarNameVersion HAR_CREATOR_VERSION = new HarNameVersion("BrowserMob Proxy", BrowserMobProxyUtil.getVersionString()); - /* Default MITM resources */ private static final String RSA_KEYSTORE_RESOURCE = "/sslSupport/ca-keystore-rsa.p12"; private static final String EC_KEYSTORE_RESOURCE = "/sslSupport/ca-keystore-ec.p12"; private static final String KEYSTORE_TYPE = "PKCS12"; private static final String KEYSTORE_PRIVATE_KEY_ALIAS = "key"; private static final String KEYSTORE_PASSWORD = "password"; - - /** - * The default pseudonym to use when adding the Via header to proxied requests. - */ - public static final String VIA_HEADER_ALIAS = "browsermobproxy"; - /** * True only after the proxy has been successfully started. */ @@ -117,42 +111,64 @@ public class BrowserMobProxyServer implements BrowserMobProxy { * Tracks the current page count, for use when auto-generating HAR page names. */ private final AtomicInteger harPageCount = new AtomicInteger(0); - + /** + * The list of filterFactories that will generate the filters that implement browsermob-proxy behavior. + */ + private final List filterFactories = new CopyOnWriteArrayList<>(); + /** + * List of accepted URL patterns. Unlisted URL patterns will be rejected with the response code contained in the Whitelist. + */ + private final AtomicReference whitelist = new AtomicReference<>(Whitelist.WHITELIST_DISABLED); + /** + * Set to true once the HAR capture filter has been added to the filter chain. + */ + private final AtomicBoolean harCaptureFilterEnabled = new AtomicBoolean(false); + /** + * Set to true when LittleProxy has been bootstrapped with the default chained proxy. This allows modifying the chained proxy + * after the proxy has been started. + */ + private final AtomicBoolean bootstrappedWithDefaultChainedProxy = new AtomicBoolean(false); + /** + * Resolver to use when resolving hostnames to IP addresses. This is a bridge between {@link org.littleshoot.proxy.HostResolver} and + * {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver}. It allows the resolvers to be changed on-the-fly without re-bootstrapping the + * littleproxy server. The default resolver (native JDK resolver) can be changed using {@link #setHostNameResolver(net.lightbody.bmp.proxy.dns.AdvancedHostResolver)} and + * supplying one of the pre-defined resolvers in {@link ClientUtil}, such as {@link ClientUtil#createDnsJavaWithNativeFallbackResolver()} + * or {@link ClientUtil#createDnsJavaResolver()}. You can also build your own resolver, or use {@link net.lightbody.bmp.proxy.dns.ChainedHostResolver} + * to chain together multiple DNS resolvers. + */ + private final DelegatingHostResolver delegatingResolver = new DelegatingHostResolver(ClientUtil.createNativeCacheManipulatingResolver()); + private final ActivityMonitor activityMonitor = new ActivityMonitor(); + /** + * A mapping of hostnames to base64-encoded Basic auth credentials that will be added to the Authorization header for + * matching requests. + */ + private final ConcurrentMap basicAuthCredentials = new MapMaker() + .concurrencyLevel(1) + .makeMap(); /** * When true, MITM will be disabled. The proxy will no longer intercept HTTPS requests, but they will still be proxied. */ private volatile boolean mitmDisabled = false; - /** * The MITM manager that will be used for HTTPS requests. */ private volatile MitmManager mitmManager; - - /** - * The list of filterFactories that will generate the filters that implement browsermob-proxy behavior. - */ - private final List filterFactories = new CopyOnWriteArrayList<>(); - /** * List of rejected URL patterns */ private volatile Collection blacklistEntries = new CopyOnWriteArrayList<>(); - /** * List of URLs to rewrite */ private volatile CopyOnWriteArrayList rewriteRules = new CopyOnWriteArrayList<>(); - /** * The LittleProxy instance that performs all proxy operations. */ private volatile HttpProxyServer proxyServer; - /** * No capture types are enabled by default. */ private volatile EnumSet harCaptureTypes = EnumSet.noneOf(CaptureType.class); - /** * The current HAR being captured. */ @@ -169,94 +185,48 @@ public class BrowserMobProxyServer implements BrowserMobProxy { * Maximum bandwidth to consume when writing requests to servers. */ private volatile long writeBandwidthLimitBps; - /** - * List of accepted URL patterns. Unlisted URL patterns will be rejected with the response code contained in the Whitelist. - */ - private final AtomicReference whitelist = new AtomicReference<>(Whitelist.WHITELIST_DISABLED); - /** * Additional headers that will be sent with every request. The map is declared as a ConcurrentMap to indicate that writes may be performed * by other threads concurrently (e.g. due to an incoming REST call), but the concurrencyLevel is set to 1 because modifications to the * additionalHeaders are rare, and in most cases happen only once, at start-up. */ private volatile ConcurrentMap additionalHeaders = new MapMaker().concurrencyLevel(1).makeMap(); - /** * The amount of time to wait while connecting to a server. */ private volatile int connectTimeoutMs; - /** * The amount of time a connection to a server can remain idle while receiving data from the server. */ private volatile int idleConnectionTimeoutSec; - /** * The amount of time to wait before forwarding the response to the client. */ private volatile int latencyMs; - - /** - * Set to true once the HAR capture filter has been added to the filter chain. - */ - private final AtomicBoolean harCaptureFilterEnabled = new AtomicBoolean(false); - - /** - * Set to true when LittleProxy has been bootstrapped with the default chained proxy. This allows modifying the chained proxy - * after the proxy has been started. - */ - private final AtomicBoolean bootstrappedWithDefaultChainedProxy = new AtomicBoolean(false); - /** * The address of an upstream chained proxy to route traffic through. */ private volatile InetSocketAddress upstreamProxyAddress; - /** * The chained proxy manager that manages upstream proxies. */ private volatile ChainedProxyManager chainedProxyManager; - /** * The address of the network interface from which the proxy will initiate connections. */ private volatile InetAddress serverBindAddress; - /** * The TrustSource that will be used to validate servers' certificates. If null, will not validate server certificates. */ private volatile TrustSource trustSource = TrustSource.defaultTrustSource(); - /** * When true, use Elliptic Curve keys and certificates when impersonating upstream servers. */ private volatile boolean useEcc = false; - - /** - * Resolver to use when resolving hostnames to IP addresses. This is a bridge between {@link org.littleshoot.proxy.HostResolver} and - * {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver}. It allows the resolvers to be changed on-the-fly without re-bootstrapping the - * littleproxy server. The default resolver (native JDK resolver) can be changed using {@link #setHostNameResolver(net.lightbody.bmp.proxy.dns.AdvancedHostResolver)} and - * supplying one of the pre-defined resolvers in {@link ClientUtil}, such as {@link ClientUtil#createDnsJavaWithNativeFallbackResolver()} - * or {@link ClientUtil#createDnsJavaResolver()}. You can also build your own resolver, or use {@link net.lightbody.bmp.proxy.dns.ChainedHostResolver} - * to chain together multiple DNS resolvers. - */ - private final DelegatingHostResolver delegatingResolver = new DelegatingHostResolver(ClientUtil.createNativeCacheManipulatingResolver()); - - private final ActivityMonitor activityMonitor = new ActivityMonitor(); - /** * The acceptor and worker thread configuration for the Netty thread pools. */ private volatile ThreadPoolConfiguration threadPoolConfiguration; - - /** - * A mapping of hostnames to base64-encoded Basic auth credentials that will be added to the Authorization header for - * matching requests. - */ - private final ConcurrentMap basicAuthCredentials = new MapMaker() - .concurrencyLevel(1) - .makeMap(); - /** * Base64-encoded credentials to use to authenticate with the upstream proxy. */ @@ -331,7 +301,7 @@ public int getMaximumResponseBufferSizeInBytes() { try { bootstrap.withManInTheMiddle(new CertificateSniffingMitmManager( new Authority())); - }catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); } } @@ -363,7 +333,7 @@ public void filterRequest(HttpObject httpObject) { String chainedProxyAuth = chainedProxyCredentials; if (chainedProxyAuth != null) { if (httpObject instanceof HttpRequest) { - HttpHeaders.addHeader((HttpRequest)httpObject, HttpHeaders.Names.PROXY_AUTHORIZATION, "Basic " + chainedProxyAuth); + HttpHeaders.addHeader((HttpRequest) httpObject, HttpHeaders.Names.PROXY_AUTHORIZATION, "Basic " + chainedProxyAuth); } } } @@ -487,13 +457,18 @@ public Har newHar(String initialPageRef, String initialPageTitle) { harPageCount.set(0); - this.har = new Har(new HarLog(HAR_CREATOR_VERSION,this)); + this.har = new Har(new HarLog(HAR_CREATOR_VERSION, this)); newPage(initialPageRef, initialPageTitle); return oldHar; } + @Override + public EnumSet getHarCaptureTypes() { + return EnumSet.copyOf(harCaptureTypes); + } + @Override public void setHarCaptureTypes(Set harCaptureSettings) { if (harCaptureSettings == null || harCaptureSettings.isEmpty()) { @@ -512,11 +487,6 @@ public void setHarCaptureTypes(CaptureType... captureTypes) { } } - @Override - public EnumSet getHarCaptureTypes() { - return EnumSet.copyOf(harCaptureTypes); - } - @Override public void enableHarCaptureTypes(Set captureTypes) { harCaptureTypes.addAll(captureTypes); @@ -602,6 +572,11 @@ public Har endHar() { return oldHar; } + @Override + public long getReadBandwidthLimit() { + return readBandwidthLimitBps; + } + @Override public void setReadBandwidthLimit(long bytesPerSecond) { this.readBandwidthLimitBps = bytesPerSecond; @@ -612,8 +587,8 @@ public void setReadBandwidthLimit(long bytesPerSecond) { } @Override - public long getReadBandwidthLimit() { - return readBandwidthLimitBps; + public long getWriteBandwidthLimit() { + return writeBandwidthLimitBps; } @Override @@ -625,11 +600,6 @@ public void setWriteBandwidthLimit(long bytesPerSecond) { } } - @Override - public long getWriteBandwidthLimit() { - return writeBandwidthLimitBps; - } - public void endPage() { if (har == null) { throw new IllegalStateException("No HAR exists for this proxy. Use newHar() to create a new HAR."); @@ -642,7 +612,7 @@ public void endPage() { return; } - previousPage.getPageTimings().setOnLoad(new Date().getTime() - previousPage.getStartedDateTime().getTime()); + previousPage.getPageTimings().setOnLoad(System.currentTimeMillis() - previousPage.getStartedDateTime().getTime()); } @Override @@ -758,13 +728,13 @@ public void blacklistRequests(String pattern, int responseCode, String method) { } @Override - public void setBlacklist(Collection blacklist) { - this.blacklistEntries = new CopyOnWriteArrayList<>(blacklist); + public Collection getBlacklist() { + return Collections.unmodifiableCollection(blacklistEntries); } @Override - public Collection getBlacklist() { - return Collections.unmodifiableCollection(blacklistEntries); + public void setBlacklist(Collection blacklist) { + this.blacklistEntries = new CopyOnWriteArrayList<>(blacklist); } @Override @@ -872,13 +842,13 @@ public Map getAllHeaders() { } @Override - public void setHostNameResolver(AdvancedHostResolver resolver) { - delegatingResolver.setResolver(resolver); + public AdvancedHostResolver getHostNameResolver() { + return delegatingResolver.getResolver(); } @Override - public AdvancedHostResolver getHostNameResolver() { - return delegatingResolver.getResolver(); + public void setHostNameResolver(AdvancedHostResolver resolver) { + delegatingResolver.setResolver(resolver); } @Override @@ -886,6 +856,11 @@ public boolean waitForQuiescence(long quietPeriod, long timeout, TimeUnit timeUn return activityMonitor.waitForQuiescence(quietPeriod, timeout, timeUnit); } + @Override + public InetSocketAddress getChainedProxy() { + return upstreamProxyAddress; + } + /** * Instructs this proxy to route traffic through an upstream proxy. * @@ -903,11 +878,6 @@ public void setChainedProxy(InetSocketAddress chainedProxyAddress) { upstreamProxyAddress = chainedProxyAddress; } - @Override - public InetSocketAddress getChainedProxy() { - return upstreamProxyAddress; - } - /** * Allows access to the LittleProxy {@link ChainedProxyManager} for fine-grained control of the chained proxies. To enable a single * chained proxy, {@link BrowserMobProxy#setChainedProxy(InetSocketAddress)} is generally more convenient. @@ -1004,15 +974,6 @@ public List getFilterFactories() { return filterFactories; } - @Override - public void setMitmDisabled(boolean mitmDisabled) throws IllegalStateException { - if (isStarted()) { - throw new IllegalStateException("Cannot disable MITM after the proxy has been started"); - } - - this.mitmDisabled = mitmDisabled; - } - @Override public void setMitmManager(MitmManager mitmManager) { this.mitmManager = mitmManager; @@ -1046,6 +1007,15 @@ public boolean isMitmDisabled() { return this.mitmDisabled; } + @Override + public void setMitmDisabled(boolean mitmDisabled) throws IllegalStateException { + if (isStarted()) { + throw new IllegalStateException("Cannot disable MITM after the proxy has been started"); + } + + this.mitmDisabled = mitmDisabled; + } + public void setUseEcc(boolean useEcc) { this.useEcc = useEcc; } diff --git a/app/src/main/java/net/lightbody/bmp/client/ClientUtil.java b/app/src/main/java/net/lightbody/bmp/client/ClientUtil.java index 0937d48..f62f79f 100644 --- a/app/src/main/java/net/lightbody/bmp/client/ClientUtil.java +++ b/app/src/main/java/net/lightbody/bmp/client/ClientUtil.java @@ -1,7 +1,7 @@ package net.lightbody.bmp.client; import com.google.common.collect.ImmutableList; -import net.lightbody.bmp.BrowserMobProxy; + import net.lightbody.bmp.proxy.dns.AdvancedHostResolver; import net.lightbody.bmp.proxy.dns.ChainedHostResolver; import net.lightbody.bmp.proxy.dns.DnsJavaResolver; @@ -9,7 +9,6 @@ import net.lightbody.bmp.proxy.dns.NativeResolver; import java.net.InetAddress; -import java.net.InetSocketAddress; import java.net.UnknownHostException; /** diff --git a/app/src/main/java/net/lightbody/bmp/core/har/HarCookie.java b/app/src/main/java/net/lightbody/bmp/core/har/HarCookie.java index 2057823..edaa04f 100644 --- a/app/src/main/java/net/lightbody/bmp/core/har/HarCookie.java +++ b/app/src/main/java/net/lightbody/bmp/core/har/HarCookie.java @@ -29,18 +29,18 @@ public String getValue() { return value; } - public String getDecodeValue(){ + public void setValue(String value) { + this.value = value; + } + + public String getDecodeValue() { try { return URLDecoder.decode(value); - }catch (Exception e){ + } catch (Exception e) { return value; } } - public void setValue(String value) { - this.value = value; - } - public String getPath() { return path; } diff --git a/app/src/main/java/net/lightbody/bmp/core/har/HarEntry.java b/app/src/main/java/net/lightbody/bmp/core/har/HarEntry.java index 1c3f474..2e7e40e 100644 --- a/app/src/main/java/net/lightbody/bmp/core/har/HarEntry.java +++ b/app/src/main/java/net/lightbody/bmp/core/har/HarEntry.java @@ -49,13 +49,14 @@ public void setStartedDateTime(Date startedDateTime) { * Rather than storing the time directly, calculate the time from the HarTimings as required in the HAR spec. * From https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/HAR/Overview.html, * section 4.2.16 timings: -

-     Following must be true in case there are no -1 values (entry is an object in log.entries) :
-
-     entry.time == entry.timings.blocked + entry.timings.dns +
-     entry.timings.connect + entry.timings.send + entry.timings.wait +
-     entry.timings.receive;
-     
+ *
+     * Following must be true in case there are no -1 values (entry is an object in log.entries) :
+     *
+     * entry.time == entry.timings.blocked + entry.timings.dns +
+     * entry.timings.connect + entry.timings.send + entry.timings.wait +
+     * entry.timings.receive;
+     * 
+ * * @return time for this HAR entry, in milliseconds */ public long getTime() { diff --git a/app/src/main/java/net/lightbody/bmp/core/har/HarLog.java b/app/src/main/java/net/lightbody/bmp/core/har/HarLog.java index dc765ca..e739537 100644 --- a/app/src/main/java/net/lightbody/bmp/core/har/HarLog.java +++ b/app/src/main/java/net/lightbody/bmp/core/har/HarLog.java @@ -14,7 +14,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class HarLog { private final String version = "1.2"; - private volatile HarNameVersion creator = new HarNameVersion("BrowserMob Proxy", BrowserMobProxyUtil.getVersionString()); + private volatile HarNameVersion creator = new HarNameVersion("BrowserMob Proxy", BrowserMobProxyUtil.getVersionString()); private volatile HarNameVersion browser; private List pages = new CopyOnWriteArrayList(); private List entries = new CopyOnWriteArrayList(); @@ -24,7 +24,7 @@ public class HarLog { public HarLog() { } - public HarLog(HarNameVersion creator,BrowserMobProxyServer server) { + public HarLog(HarNameVersion creator, BrowserMobProxyServer server) { this.creator = creator; this.server = server; } @@ -33,30 +33,30 @@ public void addPage(HarPage page) { pages.add(page); } - public Boolean deletePage(HarPage page){ + public Boolean deletePage(HarPage page) { return pages.remove(page); } public synchronized void addEntry(HarEntry entry) { int count = 0; - for (HarEntry har:entries) { + for (HarEntry har : entries) { if (entry.getPageref().equals(har.getPageref())) { count++; } } - if(count >= 999) { - if(server!=null){ + if (count >= 999) { + if (server != null) { String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA) .format(new Date(System.currentTimeMillis())); // 检查是否存在重复添加 Boolean repeatAdd = false; - for (HarPage page:pages) { - if(page.getId().equals(time)){ + for (HarPage page : pages) { + if (page.getId().equals(time)) { repeatAdd = true; } } - if(!repeatAdd) { + if (!repeatAdd) { server.newPage(time); } } @@ -64,7 +64,7 @@ public synchronized void addEntry(HarEntry entry) { entries.add(entry); } - public void clearAllEntries(){ + public void clearAllEntries() { entries.clear(); } @@ -92,14 +92,14 @@ public List getPages() { return pages; } - public List getEntries() { - return entries; - } - public void setPages(List pages) { this.pages = pages; } + public List getEntries() { + return entries; + } + public void setEntries(List entries) { this.entries = entries; } diff --git a/app/src/main/java/net/lightbody/bmp/core/har/HarNameValuePair.java b/app/src/main/java/net/lightbody/bmp/core/har/HarNameValuePair.java index 67dcd47..3fb6503 100644 --- a/app/src/main/java/net/lightbody/bmp/core/har/HarNameValuePair.java +++ b/app/src/main/java/net/lightbody/bmp/core/har/HarNameValuePair.java @@ -19,27 +19,36 @@ public String getValue() { return value; } - public String getDecodeValue(){ + public String getDecodeValue() { try { return URLDecoder.decode(value); - }catch (Exception e){ + } catch (Exception e) { return value; } } + @Override public String toString() { return name + "=" + value; } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } HarNameValuePair that = (HarNameValuePair) o; - if (name != null ? !name.equals(that.name) : that.name != null) return false; - if (value != null ? !value.equals(that.value) : that.value != null) return false; + if (name != null ? !name.equals(that.name) : that.name != null) { + return false; + } + if (value != null ? !value.equals(that.value) : that.value != null) { + return false; + } return true; } diff --git a/app/src/main/java/net/lightbody/bmp/core/har/HarPage.java b/app/src/main/java/net/lightbody/bmp/core/har/HarPage.java index 61d3ca5..09f101d 100644 --- a/app/src/main/java/net/lightbody/bmp/core/har/HarPage.java +++ b/app/src/main/java/net/lightbody/bmp/core/har/HarPage.java @@ -7,10 +7,10 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class HarPage { + private final HarPageTimings pageTimings = new HarPageTimings(); private volatile String id; private volatile Date startedDateTime; private volatile String title = ""; - private final HarPageTimings pageTimings = new HarPageTimings(); private volatile String comment = ""; public HarPage() { diff --git a/app/src/main/java/net/lightbody/bmp/core/har/HarRequest.java b/app/src/main/java/net/lightbody/bmp/core/har/HarRequest.java index 2cfe689..51f9f62 100644 --- a/app/src/main/java/net/lightbody/bmp/core/har/HarRequest.java +++ b/app/src/main/java/net/lightbody/bmp/core/har/HarRequest.java @@ -7,12 +7,12 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class HarRequest { - private volatile String method; - private volatile String url; - private volatile String httpVersion; private final List cookies = new CopyOnWriteArrayList(); private final List headers = new CopyOnWriteArrayList(); private final List queryString = new CopyOnWriteArrayList(); + private volatile String method; + private volatile String url; + private volatile String httpVersion; private volatile HarPostData postData; private volatile long headersSize; // Odd grammar in spec private volatile long bodySize; diff --git a/app/src/main/java/net/lightbody/bmp/core/har/HarResponse.java b/app/src/main/java/net/lightbody/bmp/core/har/HarResponse.java index f82f248..0e47a00 100644 --- a/app/src/main/java/net/lightbody/bmp/core/har/HarResponse.java +++ b/app/src/main/java/net/lightbody/bmp/core/har/HarResponse.java @@ -8,12 +8,12 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class HarResponse { - private volatile int status; - private volatile String statusText; - private volatile String httpVersion; private final List cookies = new CopyOnWriteArrayList(); private final List headers = new CopyOnWriteArrayList(); private final HarContent content = new HarContent(); + private volatile int status; + private volatile String statusText; + private volatile String httpVersion; private volatile String redirectURL = ""; /* the values of headersSize and bodySize are set to -1 by default, in accordance with the HAR spec: diff --git a/app/src/main/java/net/lightbody/bmp/core/har/HarTimings.java b/app/src/main/java/net/lightbody/bmp/core/har/HarTimings.java index efb8d86..bdb05d9 100644 --- a/app/src/main/java/net/lightbody/bmp/core/har/HarTimings.java +++ b/app/src/main/java/net/lightbody/bmp/core/har/HarTimings.java @@ -49,7 +49,7 @@ public long getDns(TimeUnit timeUnit) { public void setDns(long dns, TimeUnit timeUnit) { if (dns == -1) { this.dnsNanos = -1; - } else{ + } else { this.dnsNanos = TimeUnit.NANOSECONDS.convert(dns, timeUnit); } } diff --git a/app/src/main/java/net/lightbody/bmp/filters/AddHeadersFilter.java b/app/src/main/java/net/lightbody/bmp/filters/AddHeadersFilter.java index 5b584ec..312dc5b 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/AddHeadersFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/AddHeadersFilter.java @@ -1,13 +1,14 @@ package net.lightbody.bmp.filters; -import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; import org.littleshoot.proxy.HttpFiltersAdapter; import java.util.Collections; import java.util.Map; +import io.netty.handler.codec.http.HttpObject; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; + /** * Adds the headers specified in the constructor to this request. The filter does not make a defensive copy of the map, so there is no guarantee * that the map at the time of construction will contain the same values when the filter is actually invoked, if the map is modified concurrently. diff --git a/app/src/main/java/net/lightbody/bmp/filters/AutoBasicAuthFilter.java b/app/src/main/java/net/lightbody/bmp/filters/AutoBasicAuthFilter.java index 758f9bf..0ffa804 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/AutoBasicAuthFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/AutoBasicAuthFilter.java @@ -1,13 +1,14 @@ package net.lightbody.bmp.filters; +import org.littleshoot.proxy.impl.ProxyUtils; + +import java.util.Map; + import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; -import org.littleshoot.proxy.impl.ProxyUtils; - -import java.util.Map; /** * A filter that adds Basic authentication information to non-CONNECT requests. Takes a map of domain names to base64-encoded diff --git a/app/src/main/java/net/lightbody/bmp/filters/BlacklistFilter.java b/app/src/main/java/net/lightbody/bmp/filters/BlacklistFilter.java index b267673..14d2bf1 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/BlacklistFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/BlacklistFilter.java @@ -1,5 +1,10 @@ package net.lightbody.bmp.filters; +import net.lightbody.bmp.proxy.BlacklistEntry; + +import java.util.Collection; +import java.util.Collections; + import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.HttpHeaders; @@ -8,10 +13,6 @@ import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; -import net.lightbody.bmp.proxy.BlacklistEntry; - -import java.util.Collection; -import java.util.Collections; /** * Applies blacklist entries to this request. The filter does not make a defensive copy of the blacklist entries, so there is no guarantee diff --git a/app/src/main/java/net/lightbody/bmp/filters/BrowserMobHttpFilterChain.java b/app/src/main/java/net/lightbody/bmp/filters/BrowserMobHttpFilterChain.java index 6ddf133..26b7013 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/BrowserMobHttpFilterChain.java +++ b/app/src/main/java/net/lightbody/bmp/filters/BrowserMobHttpFilterChain.java @@ -1,13 +1,7 @@ package net.lightbody.bmp.filters; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.DefaultFullHttpResponse; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.handler.codec.http.HttpResponseStatus; import net.lightbody.bmp.BrowserMobProxyServer; + import org.littleshoot.proxy.HttpFilters; import org.littleshoot.proxy.HttpFiltersAdapter; import org.littleshoot.proxy.HttpFiltersSource; @@ -19,6 +13,14 @@ import java.util.Collections; import java.util.List; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpObject; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; + /** * The filter "driver" that delegates to all chained filters specified by the proxy server. */ diff --git a/app/src/main/java/net/lightbody/bmp/filters/ClientRequestCaptureFilter.java b/app/src/main/java/net/lightbody/bmp/filters/ClientRequestCaptureFilter.java index d6bd1b5..c25bf96 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/ClientRequestCaptureFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/ClientRequestCaptureFilter.java @@ -1,5 +1,12 @@ package net.lightbody.bmp.filters; +import net.lightbody.bmp.util.BrowserMobHttpUtil; + +import org.littleshoot.proxy.HttpFiltersAdapter; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpContent; @@ -8,11 +15,6 @@ import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.LastHttpContent; -import net.lightbody.bmp.util.BrowserMobHttpUtil; -import org.littleshoot.proxy.HttpFiltersAdapter; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; /** * This filter captures requests from the client (headers and content). @@ -22,18 +24,16 @@ * {@link net.lightbody.bmp.filters.HarCaptureFilter} for an example of the latter). */ public class ClientRequestCaptureFilter extends HttpFiltersAdapter { - /** - * Populated by clientToProxyRequest() when processing the HttpRequest object. Unlike originalRequest, - * this represents the "real" request that is being sent to the server, including headers. - */ - private volatile HttpRequest httpRequest; - /** * Populated by clientToProxyRequest() when processing the HttpContent objects. If the request is chunked, * it will be populated across multiple calls to clientToProxyRequest(). */ private final ByteArrayOutputStream requestContents = new ByteArrayOutputStream(); - + /** + * Populated by clientToProxyRequest() when processing the HttpRequest object. Unlike originalRequest, + * this represents the "real" request that is being sent to the server, including headers. + */ + private volatile HttpRequest httpRequest; /** * Populated by clientToProxyRequest() when processing the LastHttpContent. */ @@ -60,7 +60,7 @@ public HttpResponse clientToProxyRequest(HttpObject httpObject) { if (httpContent instanceof LastHttpContent) { LastHttpContent lastHttpContent = (LastHttpContent) httpContent; - trailingHeaders = lastHttpContent .trailingHeaders(); + trailingHeaders = lastHttpContent.trailingHeaders(); } } diff --git a/app/src/main/java/net/lightbody/bmp/filters/HarCaptureFilter.java b/app/src/main/java/net/lightbody/bmp/filters/HarCaptureFilter.java index ca8c044..7bdc36c 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/HarCaptureFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/HarCaptureFilter.java @@ -2,18 +2,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.io.BaseEncoding; -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.HttpContent; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.handler.codec.http.LastHttpContent; -import io.netty.handler.codec.http.QueryStringDecoder; -import io.netty.handler.codec.http.cookie.ClientCookieDecoder; -import io.netty.handler.codec.http.cookie.Cookie; -import io.netty.handler.codec.http.cookie.ServerCookieDecoder; + import net.lightbody.bmp.core.har.Har; import net.lightbody.bmp.core.har.HarCookie; import net.lightbody.bmp.core.har.HarEntry; @@ -27,6 +16,7 @@ import net.lightbody.bmp.filters.util.HarCaptureUtil; import net.lightbody.bmp.proxy.CaptureType; import net.lightbody.bmp.util.BrowserMobHttpUtil; + import org.littleshoot.proxy.impl.ProxyUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +34,19 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpObject; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.LastHttpContent; +import io.netty.handler.codec.http.QueryStringDecoder; +import io.netty.handler.codec.http.cookie.ClientCookieDecoder; +import io.netty.handler.codec.http.cookie.Cookie; +import io.netty.handler.codec.http.cookie.ServerCookieDecoder; + public class HarCaptureFilter extends HttpsAwareFiltersAdapter { private static final Logger log = LoggerFactory.getLogger(HarCaptureFilter.class); @@ -75,37 +78,29 @@ public class HarCaptureFilter extends HttpsAwareFiltersAdapter { * The CaptureType data types to capture in this request. */ private final EnumSet dataToCapture; - - /** - * Populated by proxyToServerResolutionStarted when DNS resolution starts. If any previous filters already resolved the address, their resolution time - * will not be included in this time. - */ - private volatile long dnsResolutionStartedNanos; - - private volatile long connectionQueuedNanos; - private volatile long connectionStartedNanos; - - private volatile long sendStartedNanos; - private volatile long sendFinishedNanos; - - private volatile long responseReceiveStartedNanos; - /** * The address of the client making the request. Captured in the constructor and used when calculating and capturing ssl handshake and connect * timing information for SSL connections. */ private final InetSocketAddress clientAddress; - /** * Request body size is determined by the actual size of the data the client sends. The filter does not use the Content-Length header to determine request size. */ private final AtomicInteger requestBodySize = new AtomicInteger(0); - /** * Response body size is determined by the actual size of the data the server sends. */ private final AtomicInteger responseBodySize = new AtomicInteger(0); - + /** + * Populated by proxyToServerResolutionStarted when DNS resolution starts. If any previous filters already resolved the address, their resolution time + * will not be included in this time. + */ + private volatile long dnsResolutionStartedNanos; + private volatile long connectionQueuedNanos; + private volatile long connectionStartedNanos; + private volatile long sendStartedNanos; + private volatile long sendFinishedNanos; + private volatile long responseReceiveStartedNanos; /** * The "real" original request, as captured by the {@link #clientToProxyRequest(io.netty.handler.codec.http.HttpObject)} method. */ @@ -123,16 +118,16 @@ public class HarCaptureFilter extends HttpsAwareFiltersAdapter { *

* Regardless of the CaptureTypes specified in dataToCapture, the HarCaptureFilter will always capture: *

    - *
  • Request and response sizes
  • - *
  • HTTP request and status lines
  • - *
  • Page timing information
  • + *
  • Request and response sizes
  • + *
  • HTTP request and status lines
  • + *
  • Page timing information
  • *
* * @param originalRequest the original HttpRequest from the HttpFiltersSource factory - * @param har a reference to the ProxyServer's current HAR file at the time this request is received (can be null if HAR capture is not required) - * @param currentPageRef the ProxyServer's currentPageRef at the time this request is received from the client - * @param dataToCapture the data types to capture for this request. null or empty set indicates only basic information will be - * captured (see {@link net.lightbody.bmp.proxy.CaptureType} for information on data collected for each CaptureType) + * @param har a reference to the ProxyServer's current HAR file at the time this request is received (can be null if HAR capture is not required) + * @param currentPageRef the ProxyServer's currentPageRef at the time this request is received from the client + * @param dataToCapture the data types to capture for this request. null or empty set indicates only basic information will be + * captured (see {@link net.lightbody.bmp.proxy.CaptureType} for information on data collected for each CaptureType) */ public HarCaptureFilter(HttpRequest originalRequest, ChannelHandlerContext ctx, Har har, String currentPageRef, Set dataToCapture) { super(originalRequest, ctx); @@ -408,7 +403,7 @@ protected void captureRequestContent(HttpRequest httpRequest, byte[] fullMessage Charset charset; try { - charset = BrowserMobHttpUtil.readCharsetInContentTypeHeader(contentType); + charset = BrowserMobHttpUtil.readCharsetInContentTypeHeader(contentType); } catch (UnsupportedCharsetException e) { log.warn("Found unsupported character set in Content-Type header '{}' in HTTP request to {}. Content will not be captured in HAR.", contentType, httpRequest.getUri(), e); return; diff --git a/app/src/main/java/net/lightbody/bmp/filters/HttpConnectHarCaptureFilter.java b/app/src/main/java/net/lightbody/bmp/filters/HttpConnectHarCaptureFilter.java index 1717370..3716e00 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/HttpConnectHarCaptureFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/HttpConnectHarCaptureFilter.java @@ -1,10 +1,7 @@ package net.lightbody.bmp.filters; import com.google.common.cache.CacheBuilder; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; + import net.lightbody.bmp.core.har.Har; import net.lightbody.bmp.core.har.HarEntry; import net.lightbody.bmp.core.har.HarRequest; @@ -13,6 +10,7 @@ import net.lightbody.bmp.filters.support.HttpConnectTiming; import net.lightbody.bmp.filters.util.HarCaptureUtil; import net.lightbody.bmp.util.HttpUtil; + import org.littleshoot.proxy.impl.ProxyUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,6 +21,11 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpObject; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; + /** * This filter captures HAR data for HTTP CONNECT requests. CONNECTs are "meta" requests that must be made before HTTPS * requests, but are not populated as separate requests in the HAR. Most information from HTTP CONNECTs (such as SSL @@ -31,89 +34,74 @@ * static methods. This filter also handles HTTP CONNECT errors and creates HAR entries for those errors, since there * would otherwise not be any record in the HAR of the error (if the CONNECT fails, there will be no subsequent "real" * request in which to record the error). - * */ public class HttpConnectHarCaptureFilter extends HttpsAwareFiltersAdapter implements ModifiedRequestAwareFilter { private static final Logger log = LoggerFactory.getLogger(HttpConnectHarCaptureFilter.class); - + /** + * The maximum amount of time to save timing information between an HTTP CONNECT and the subsequent HTTP request. Typically this is done + * immediately, but if for some reason it is not (e.g. due to a client crash or dropped connection), the timing information will be + * kept for this long before being evicted to prevent a memory leak. If a subsequent request does come through after eviction, it will still + * be recorded, but the timing information will not be populated in the HAR. + */ + private static final int HTTP_CONNECT_TIMING_EVICTION_SECONDS = 60; + /** + * Concurrency of the httpConnectTiming map. Should be approximately equal to the maximum number of simultaneous connection + * attempts (but not necessarily simultaneous connections). A lower value will inhibit performance. + * TODO: tune this value for a large number of concurrent requests. develop a non-cache-based mechanism of passing ssl timings to subsequent requests. + */ + private static final int HTTP_CONNECT_TIMING_CONCURRENCY_LEVEL = 50; + /** + * Stores SSL connection timing information from HTTP CONNNECT requests. This timing information is stored in the first HTTP request + * after the CONNECT, not in the CONNECT itself, so it needs to be stored across requests. + *

+ * This is the only state stored across multiple requests. + */ + private static final ConcurrentMap httpConnectTimes = + CacheBuilder.newBuilder() + .expireAfterWrite(HTTP_CONNECT_TIMING_EVICTION_SECONDS, TimeUnit.SECONDS) + .concurrencyLevel(HTTP_CONNECT_TIMING_CONCURRENCY_LEVEL) + .build() + .asMap(); /** * The currently active HAR at the time the current request is received. */ private final Har har; - /** * The currently active page ref at the time the current request is received. */ private final String currentPageRef; - + /** + * The address of the client making the request. Captured in the constructor and used when calculating and capturing ssl handshake and connect + * timing information for SSL connections. + */ + private final InetSocketAddress clientAddress; + /** + * Stores HTTP CONNECT timing information for this request, if it is an HTTP CONNECT. + */ + private final HttpConnectTiming httpConnectTiming; /** * The time this CONNECT began. Used to populate the HAR entry in case of failure. */ private volatile Date requestStartTime; - /** * True if this filter instance processed a {@link #proxyToServerResolutionSucceeded(String, java.net.InetSocketAddress)} call, indicating * that the hostname was resolved and populated in the HAR (if this is not a CONNECT). */ // private volatile boolean addressResolved = false; private volatile InetAddress resolvedAddress; - /** * Populated by proxyToServerResolutionStarted when DNS resolution starts. If any previous filters already resolved the address, their resolution time * will not be included in this time. See {@link HarCaptureFilter#dnsResolutionStartedNanos}. */ private volatile long dnsResolutionStartedNanos; - private volatile long dnsResolutionFinishedNanos; - private volatile long connectionQueuedNanos; private volatile long connectionStartedNanos; private volatile long connectionSucceededTimeNanos; private volatile long sendStartedNanos; private volatile long sendFinishedNanos; - private volatile long responseReceiveStartedNanos; private volatile long sslHandshakeStartedNanos; - - /** - * The address of the client making the request. Captured in the constructor and used when calculating and capturing ssl handshake and connect - * timing information for SSL connections. - */ - private final InetSocketAddress clientAddress; - - /** - * Stores HTTP CONNECT timing information for this request, if it is an HTTP CONNECT. - */ - private final HttpConnectTiming httpConnectTiming; - - /** - * The maximum amount of time to save timing information between an HTTP CONNECT and the subsequent HTTP request. Typically this is done - * immediately, but if for some reason it is not (e.g. due to a client crash or dropped connection), the timing information will be - * kept for this long before being evicted to prevent a memory leak. If a subsequent request does come through after eviction, it will still - * be recorded, but the timing information will not be populated in the HAR. - */ - private static final int HTTP_CONNECT_TIMING_EVICTION_SECONDS = 60; - - /** - * Concurrency of the httpConnectTiming map. Should be approximately equal to the maximum number of simultaneous connection - * attempts (but not necessarily simultaneous connections). A lower value will inhibit performance. - * TODO: tune this value for a large number of concurrent requests. develop a non-cache-based mechanism of passing ssl timings to subsequent requests. - */ - private static final int HTTP_CONNECT_TIMING_CONCURRENCY_LEVEL = 50; - - /** - * Stores SSL connection timing information from HTTP CONNNECT requests. This timing information is stored in the first HTTP request - * after the CONNECT, not in the CONNECT itself, so it needs to be stored across requests. - * - * This is the only state stored across multiple requests. - */ - private static final ConcurrentMap httpConnectTimes = - CacheBuilder.newBuilder() - .expireAfterWrite(HTTP_CONNECT_TIMING_EVICTION_SECONDS, TimeUnit.SECONDS) - .concurrencyLevel(HTTP_CONNECT_TIMING_CONCURRENCY_LEVEL) - .build() - .asMap(); - private volatile HttpRequest modifiedHttpRequest; public HttpConnectHarCaptureFilter(HttpRequest originalRequest, ChannelHandlerContext ctx, Har har, String currentPageRef) { @@ -137,6 +125,16 @@ public HttpConnectHarCaptureFilter(HttpRequest originalRequest, ChannelHandlerCo httpConnectTimes.put(clientAddress, httpConnectTiming); } + /** + * Retrieves and removes (thus "consumes") the SSL timing information from the connection cache for the specified address. + * + * @param clientAddress the address of the client connection that established the HTTP tunnel + * @return the timing information for the tunnel previously established from the clientAddress + */ + public static HttpConnectTiming consumeConnectTimingForConnection(InetSocketAddress clientAddress) { + return httpConnectTimes.remove(clientAddress); + } + @Override public HttpResponse clientToProxyRequest(HttpObject httpObject) { if (httpObject instanceof HttpRequest) { @@ -226,7 +224,6 @@ public void proxyToServerConnectionQueued() { this.connectionQueuedNanos = System.nanoTime(); } - @Override public InetSocketAddress proxyToServerResolutionStarted(String resolvingServerHostAndPort) { dnsResolutionStartedNanos = System.nanoTime(); @@ -376,16 +373,6 @@ private HarRequest createRequestForFailedConnect(HttpRequest httpConnectRequest) return new HarRequest(httpConnectRequest.getMethod().toString(), url, httpConnectRequest.getProtocolVersion().text()); } - /** - * Retrieves and removes (thus "consumes") the SSL timing information from the connection cache for the specified address. - * - * @param clientAddress the address of the client connection that established the HTTP tunnel - * @return the timing information for the tunnel previously established from the clientAddress - */ - public static HttpConnectTiming consumeConnectTimingForConnection(InetSocketAddress clientAddress) { - return httpConnectTimes.remove(clientAddress); - } - @Override public void setModifiedHttpRequest(HttpRequest modifiedHttpRequest) { this.modifiedHttpRequest = modifiedHttpRequest; diff --git a/app/src/main/java/net/lightbody/bmp/filters/HttpsAwareFiltersAdapter.java b/app/src/main/java/net/lightbody/bmp/filters/HttpsAwareFiltersAdapter.java index e727f2b..16cfc06 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/HttpsAwareFiltersAdapter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/HttpsAwareFiltersAdapter.java @@ -1,14 +1,17 @@ package net.lightbody.bmp.filters; import com.google.common.net.HostAndPort; + +import net.lightbody.bmp.util.BrowserMobHttpUtil; +import net.lightbody.bmp.util.HttpUtil; + +import org.littleshoot.proxy.HttpFiltersAdapter; +import org.littleshoot.proxy.impl.ProxyUtils; + import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpRequest; import io.netty.util.Attribute; import io.netty.util.AttributeKey; -import net.lightbody.bmp.util.HttpUtil; -import net.lightbody.bmp.util.BrowserMobHttpUtil; -import org.littleshoot.proxy.HttpFiltersAdapter; -import org.littleshoot.proxy.impl.ProxyUtils; /** * The HttpsAwareFiltersAdapter exposes the original host and the "real" host (after filter modifications) to filters for HTTPS diff --git a/app/src/main/java/net/lightbody/bmp/filters/HttpsHostCaptureFilter.java b/app/src/main/java/net/lightbody/bmp/filters/HttpsHostCaptureFilter.java index f2a52a0..6486178 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/HttpsHostCaptureFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/HttpsHostCaptureFilter.java @@ -1,14 +1,16 @@ package net.lightbody.bmp.filters; +import net.lightbody.bmp.util.BrowserMobHttpUtil; + +import org.littleshoot.proxy.HttpFiltersAdapter; +import org.littleshoot.proxy.impl.ProxyUtils; + import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; import io.netty.util.Attribute; import io.netty.util.AttributeKey; -import net.lightbody.bmp.util.BrowserMobHttpUtil; -import org.littleshoot.proxy.HttpFiltersAdapter; -import org.littleshoot.proxy.impl.ProxyUtils; /** * Captures the host for HTTPS requests and stores the value in the ChannelHandlerContext for use by {@link HttpsAwareFiltersAdapter} diff --git a/app/src/main/java/net/lightbody/bmp/filters/HttpsOriginalHostCaptureFilter.java b/app/src/main/java/net/lightbody/bmp/filters/HttpsOriginalHostCaptureFilter.java index 4a6894c..029e665 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/HttpsOriginalHostCaptureFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/HttpsOriginalHostCaptureFilter.java @@ -1,11 +1,11 @@ package net.lightbody.bmp.filters; +import org.littleshoot.proxy.impl.ProxyUtils; + import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpRequest; import io.netty.util.Attribute; import io.netty.util.AttributeKey; -import org.littleshoot.proxy.HttpFiltersAdapter; -import org.littleshoot.proxy.impl.ProxyUtils; /** * Captures the original host for HTTPS requests and stores the value in the ChannelHandlerContext for use by {@link HttpsAwareFiltersAdapter} diff --git a/app/src/main/java/net/lightbody/bmp/filters/LatencyFilter.java b/app/src/main/java/net/lightbody/bmp/filters/LatencyFilter.java index 10f90df..841f6e0 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/LatencyFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/LatencyFilter.java @@ -1,14 +1,15 @@ package net.lightbody.bmp.filters; -import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; import org.littleshoot.proxy.HttpFiltersAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.TimeUnit; +import io.netty.handler.codec.http.HttpObject; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; + /** * Adds latency to a response before sending it to the client. This filter always adds the specified latency, even if the latency * between the proxy and the remote server already exceeds this value. diff --git a/app/src/main/java/net/lightbody/bmp/filters/RegisterRequestFilter.java b/app/src/main/java/net/lightbody/bmp/filters/RegisterRequestFilter.java index 32dc128..e63f957 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/RegisterRequestFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/RegisterRequestFilter.java @@ -1,11 +1,13 @@ package net.lightbody.bmp.filters; +import net.lightbody.bmp.proxy.ActivityMonitor; + +import org.littleshoot.proxy.HttpFiltersAdapter; + import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; -import net.lightbody.bmp.proxy.ActivityMonitor; -import org.littleshoot.proxy.HttpFiltersAdapter; /** * Registers this request with the {@link net.lightbody.bmp.proxy.ActivityMonitor} when the HttpRequest is received from the client. diff --git a/app/src/main/java/net/lightbody/bmp/filters/RequestFilter.java b/app/src/main/java/net/lightbody/bmp/filters/RequestFilter.java index 4718d2b..4f7ce73 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/RequestFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/RequestFilter.java @@ -1,10 +1,11 @@ package net.lightbody.bmp.filters; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; import net.lightbody.bmp.util.HttpMessageContents; import net.lightbody.bmp.util.HttpMessageInfo; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; + /** * A functional interface to simplify modification and manipulation of requests. */ @@ -15,8 +16,8 @@ public interface RequestFilter { * contents may be modified using the {@link HttpMessageContents#setTextContents(String)} or {@link HttpMessageContents#setBinaryContents(byte[])} * methods. The request can be "short-circuited" by returning a non-null value. * - * @param request The request object, including method, URI, headers, etc. Modifications to the request object will be reflected in the request sent to the server. - * @param contents The request contents. + * @param request The request object, including method, URI, headers, etc. Modifications to the request object will be reflected in the request sent to the server. + * @param contents The request contents. * @param messageInfo Additional information relating to the HTTP message. * @return if the return value is non-null, the proxy will suppress the request and send the specified response to the client immediately */ diff --git a/app/src/main/java/net/lightbody/bmp/filters/RequestFilterAdapter.java b/app/src/main/java/net/lightbody/bmp/filters/RequestFilterAdapter.java index 2139ceb..e682ed9 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/RequestFilterAdapter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/RequestFilterAdapter.java @@ -1,14 +1,16 @@ package net.lightbody.bmp.filters; +import net.lightbody.bmp.util.HttpMessageContents; +import net.lightbody.bmp.util.HttpMessageInfo; + +import org.littleshoot.proxy.HttpFilters; +import org.littleshoot.proxy.HttpFiltersSourceAdapter; + import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpMessage; import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; -import net.lightbody.bmp.util.HttpMessageContents; -import net.lightbody.bmp.util.HttpMessageInfo; -import org.littleshoot.proxy.HttpFilters; -import org.littleshoot.proxy.HttpFiltersSourceAdapter; /** * A filter adapter for {@link RequestFilter} implementations. Executes the filter when the {@link HttpFilters#clientToProxyRequest(HttpObject)} @@ -78,7 +80,7 @@ public FilterSource(RequestFilter filter) { * be enabled if any filter has a maximum request or response buffer size greater than 0. See * {@link org.littleshoot.proxy.HttpFiltersSource#getMaximumRequestBufferSizeInBytes()} for details.) * - * @param filter RequestFilter to invoke + * @param filter RequestFilter to invoke * @param maximumRequestBufferSizeInBytes maximum buffer size when aggregating Requests for filtering */ public FilterSource(RequestFilter filter, int maximumRequestBufferSizeInBytes) { diff --git a/app/src/main/java/net/lightbody/bmp/filters/ResolvedHostnameCacheFilter.java b/app/src/main/java/net/lightbody/bmp/filters/ResolvedHostnameCacheFilter.java index 0cc4dca..0c2cb83 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/ResolvedHostnameCacheFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/ResolvedHostnameCacheFilter.java @@ -3,14 +3,16 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.net.HostAndPort; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.HttpRequest; + import org.littleshoot.proxy.HttpFiltersAdapter; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.concurrent.TimeUnit; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpRequest; + /** * Caches hostname resolutions reported by the {@link org.littleshoot.proxy.HttpFilters#proxyToServerResolutionSucceeded(String, InetSocketAddress)} * filter method. Allows access to the resolved IP address on subsequent requests, when the address is not re-resolved because @@ -45,6 +47,16 @@ public ResolvedHostnameCacheFilter(HttpRequest originalRequest, ChannelHandlerCo super(originalRequest, ctx); } + /** + * Returns the (cached) address that was previously resolved for the specified host. + * + * @param host hostname that was previously resolved (without a port) + * @return the resolved IP address for the host, or null if the resolved address is not in the cache + */ + public static String getPreviouslyResolvedAddressForHost(String host) { + return resolvedAddresses.getIfPresent(host); + } + @Override public void proxyToServerResolutionSucceeded(String serverHostAndPort, InetSocketAddress resolvedRemoteAddress) { // the address *should* always be resolved at this point @@ -60,14 +72,4 @@ public void proxyToServerResolutionSucceeded(String serverHostAndPort, InetSocke } } } - - /** - * Returns the (cached) address that was previously resolved for the specified host. - * - * @param host hostname that was previously resolved (without a port) - * @return the resolved IP address for the host, or null if the resolved address is not in the cache - */ - public static String getPreviouslyResolvedAddressForHost(String host) { - return resolvedAddresses.getIfPresent(host); - } } diff --git a/app/src/main/java/net/lightbody/bmp/filters/ResponseFilter.java b/app/src/main/java/net/lightbody/bmp/filters/ResponseFilter.java index 14ed7d4..4d72a38 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/ResponseFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/ResponseFilter.java @@ -1,9 +1,10 @@ package net.lightbody.bmp.filters; -import io.netty.handler.codec.http.HttpResponse; import net.lightbody.bmp.util.HttpMessageContents; import net.lightbody.bmp.util.HttpMessageInfo; +import io.netty.handler.codec.http.HttpResponse; + /** * A functional interface to simplify modification and manipulation of responses. */ @@ -14,8 +15,8 @@ public interface ResponseFilter { * contents may be modified using the {@link HttpMessageContents#setTextContents(String)} or {@link HttpMessageContents#setBinaryContents(byte[])} * methods. * - * @param response The response object, including URI, headers, status line, etc. Modifications to the response object will be reflected in the client response. - * @param contents The response contents. + * @param response The response object, including URI, headers, status line, etc. Modifications to the response object will be reflected in the client response. + * @param contents The response contents. * @param messageInfo Additional information relating to the HTTP message. */ void filterResponse(HttpResponse response, HttpMessageContents contents, HttpMessageInfo messageInfo); diff --git a/app/src/main/java/net/lightbody/bmp/filters/ResponseFilterAdapter.java b/app/src/main/java/net/lightbody/bmp/filters/ResponseFilterAdapter.java index 7edc34d..1bf691d 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/ResponseFilterAdapter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/ResponseFilterAdapter.java @@ -1,14 +1,16 @@ package net.lightbody.bmp.filters; +import net.lightbody.bmp.util.HttpMessageContents; +import net.lightbody.bmp.util.HttpMessageInfo; + +import org.littleshoot.proxy.HttpFilters; +import org.littleshoot.proxy.HttpFiltersSourceAdapter; + import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpMessage; import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; -import net.lightbody.bmp.util.HttpMessageContents; -import net.lightbody.bmp.util.HttpMessageInfo; -import org.littleshoot.proxy.HttpFilters; -import org.littleshoot.proxy.HttpFiltersSourceAdapter; /** * A filter adapter for {@link ResponseFilter} implementations. Executes the filter when the {@link HttpFilters#serverToProxyResponse(HttpObject)} @@ -85,7 +87,7 @@ public FilterSource(ResponseFilter filter) { * be enabled if any filter has a maximum request or response buffer size greater than 0. See * {@link org.littleshoot.proxy.HttpFiltersSource#getMaximumResponseBufferSizeInBytes()} for details.) * - * @param filter ResponseFilter to invoke + * @param filter ResponseFilter to invoke * @param maximumResponseBufferSizeInBytes maximum buffer size when aggregating responses for filtering */ public FilterSource(ResponseFilter filter, int maximumResponseBufferSizeInBytes) { diff --git a/app/src/main/java/net/lightbody/bmp/filters/RewriteUrlFilter.java b/app/src/main/java/net/lightbody/bmp/filters/RewriteUrlFilter.java index ef0e223..1a69a6c 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/RewriteUrlFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/RewriteUrlFilter.java @@ -1,13 +1,9 @@ package net.lightbody.bmp.filters; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import net.lightbody.bmp.util.HttpUtil; import net.lightbody.bmp.proxy.RewriteRule; import net.lightbody.bmp.util.BrowserMobHttpUtil; +import net.lightbody.bmp.util.HttpUtil; + import org.littleshoot.proxy.impl.ProxyUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,6 +13,12 @@ import java.util.Collections; import java.util.regex.Matcher; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpObject; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; + /** * Applies rewrite rules to the specified request. If a rewrite rule matches, the request's URI will be overwritten with the rewritten URI. * The filter does not make a defensive copy of the rewrite rule collection, so there is no guarantee diff --git a/app/src/main/java/net/lightbody/bmp/filters/ServerResponseCaptureFilter.java b/app/src/main/java/net/lightbody/bmp/filters/ServerResponseCaptureFilter.java index e44cbde..b84ccdd 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/ServerResponseCaptureFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/ServerResponseCaptureFilter.java @@ -1,5 +1,14 @@ package net.lightbody.bmp.filters; +import net.lightbody.bmp.util.BrowserMobHttpUtil; + +import org.littleshoot.proxy.HttpFiltersAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpContent; @@ -8,13 +17,6 @@ import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.LastHttpContent; -import net.lightbody.bmp.util.BrowserMobHttpUtil; -import org.littleshoot.proxy.HttpFiltersAdapter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; /** * This filter captures responses from the server (headers and content). The filter can also decompress contents if desired. @@ -25,49 +27,41 @@ */ public class ServerResponseCaptureFilter extends HttpFiltersAdapter { private static final Logger log = LoggerFactory.getLogger(ServerResponseCaptureFilter.class); - - /** - * Populated by serverToProxyResponse() when processing the HttpResponse object - */ - private volatile HttpResponse httpResponse; - /** * Populated by serverToProxyResponse() as it receives HttpContent responses. If the response is chunked, it will * be populated across multiple calls to proxyToServerResponse(). */ private final ByteArrayOutputStream rawResponseContents = new ByteArrayOutputStream(); - + /** + * User option indicating compressed content should be uncompressed. + */ + private final boolean decompressEncodedContent; + /** + * Populated by serverToProxyResponse() when processing the HttpResponse object + */ + private volatile HttpResponse httpResponse; /** * Populated when processing the LastHttpContent. If the response is compressed and decompression is requested, * this contains the entire decompressed response. Otherwise it contains the raw response. */ private volatile byte[] fullResponseContents; - /** * Populated by serverToProxyResponse() when it processes the LastHttpContent object. */ private volatile HttpHeaders trailingHeaders; - /** * Set to true when processing the LastHttpContent if the server indicates there is a content encoding. */ private volatile boolean responseCompressed; - /** * Set to true when processing the LastHttpContent if decompression was requested and successful. */ private volatile boolean decompressionSuccessful; - /** * Populated when processing the LastHttpContent. */ private volatile String contentEncoding; - /** - * User option indicating compressed content should be uncompressed. - */ - private final boolean decompressEncodedContent; - public ServerResponseCaptureFilter(HttpRequest originalRequest, boolean decompressEncodedContent) { super(originalRequest); @@ -116,7 +110,7 @@ protected void captureFullResponseContents() { if (decompressEncodedContent) { decompressContents(); - } else { + } else { // will not decompress response } } else { @@ -128,12 +122,12 @@ protected void captureFullResponseContents() { protected void decompressContents() { if (contentEncoding.equalsIgnoreCase(HttpHeaders.Values.GZIP) || contentEncoding.equalsIgnoreCase(HttpHeaders.Values.DEFLATE)) { try { - fullResponseContents = BrowserMobHttpUtil.decompressContents(getRawResponseContents(),contentEncoding); + fullResponseContents = BrowserMobHttpUtil.decompressContents(getRawResponseContents(), contentEncoding); decompressionSuccessful = true; } catch (RuntimeException e) { log.warn("Failed to decompress response with encoding type " + contentEncoding + " when decoding request from " + originalRequest.getUri(), e); } - } else{ + } else { log.warn("Cannot decode unsupported content encoding type {}", contentEncoding); } } diff --git a/app/src/main/java/net/lightbody/bmp/filters/UnregisterRequestFilter.java b/app/src/main/java/net/lightbody/bmp/filters/UnregisterRequestFilter.java index 1ffff0f..94bc03e 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/UnregisterRequestFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/UnregisterRequestFilter.java @@ -1,11 +1,13 @@ package net.lightbody.bmp.filters; +import net.lightbody.bmp.proxy.ActivityMonitor; + +import org.littleshoot.proxy.HttpFiltersAdapter; + import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.LastHttpContent; -import net.lightbody.bmp.proxy.ActivityMonitor; -import org.littleshoot.proxy.HttpFiltersAdapter; /** * Unregisters this request with the {@link net.lightbody.bmp.proxy.ActivityMonitor} when the LastHttpContent is sent to the client. diff --git a/app/src/main/java/net/lightbody/bmp/filters/WhitelistFilter.java b/app/src/main/java/net/lightbody/bmp/filters/WhitelistFilter.java index ad8fbf6..5196716 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/WhitelistFilter.java +++ b/app/src/main/java/net/lightbody/bmp/filters/WhitelistFilter.java @@ -1,5 +1,11 @@ package net.lightbody.bmp.filters; +import org.littleshoot.proxy.impl.ProxyUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Pattern; + import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.HttpHeaders; @@ -7,11 +13,6 @@ import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; -import org.littleshoot.proxy.impl.ProxyUtils; - -import java.util.Collection; -import java.util.Collections; -import java.util.regex.Pattern; /** * Checks this request against the whitelist, and returns the modified response if the request is not in the whitelist. The filter does not @@ -23,7 +24,7 @@ public class WhitelistFilter extends HttpsAwareFiltersAdapter { private final int whitelistResponseCode; private final Collection whitelistUrls; - public WhitelistFilter(HttpRequest originalRequest, ChannelHandlerContext ctx, boolean whitelistEnabled,int whitelistResponseCode, + public WhitelistFilter(HttpRequest originalRequest, ChannelHandlerContext ctx, boolean whitelistEnabled, int whitelistResponseCode, Collection whitelistUrls) { super(originalRequest, ctx); diff --git a/app/src/main/java/net/lightbody/bmp/filters/support/HttpConnectTiming.java b/app/src/main/java/net/lightbody/bmp/filters/support/HttpConnectTiming.java index c18070f..408890f 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/support/HttpConnectTiming.java +++ b/app/src/main/java/net/lightbody/bmp/filters/support/HttpConnectTiming.java @@ -5,11 +5,11 @@ * "real" request to the same host. The HTTP CONNECT and the "real" HTTP requests are processed in different HarCaptureFilter instances. *

* Note: The connect time must include the ssl time. According to the HAR spec at https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/HAR/Overview.htm: -

- ssl [number, optional] (new in 1.2) - Time required for SSL/TLS negotiation. If this field is defined then the time is also
- included in the connect field (to ensure backward compatibility with HAR 1.1). Use -1 if the timing does not apply to the
- current request.
- 
+ *
+ * ssl [number, optional] (new in 1.2) - Time required for SSL/TLS negotiation. If this field is defined then the time is also
+ * included in the connect field (to ensure backward compatibility with HAR 1.1). Use -1 if the timing does not apply to the
+ * current request.
+ * 
*/ public class HttpConnectTiming { private volatile long blockedTimeNanos = -1; @@ -17,35 +17,35 @@ public class HttpConnectTiming { private volatile long connectTimeNanos = -1; private volatile long sslHandshakeTimeNanos = -1; - public void setConnectTimeNanos(long connectTimeNanos) { - this.connectTimeNanos = connectTimeNanos; - } - - public void setSslHandshakeTimeNanos(long sslHandshakeTimeNanos) { - this.sslHandshakeTimeNanos = sslHandshakeTimeNanos; - } - - public void setBlockedTimeNanos(long blockedTimeNanos) { - this.blockedTimeNanos = blockedTimeNanos; - } - - public void setDnsTimeNanos(long dnsTimeNanos) { - this.dnsTimeNanos = dnsTimeNanos; - } - public long getConnectTimeNanos() { return connectTimeNanos; } + public void setConnectTimeNanos(long connectTimeNanos) { + this.connectTimeNanos = connectTimeNanos; + } + public long getSslHandshakeTimeNanos() { return sslHandshakeTimeNanos; } + public void setSslHandshakeTimeNanos(long sslHandshakeTimeNanos) { + this.sslHandshakeTimeNanos = sslHandshakeTimeNanos; + } + public long getBlockedTimeNanos() { return blockedTimeNanos; } + public void setBlockedTimeNanos(long blockedTimeNanos) { + this.blockedTimeNanos = blockedTimeNanos; + } + public long getDnsTimeNanos() { return dnsTimeNanos; } + + public void setDnsTimeNanos(long dnsTimeNanos) { + this.dnsTimeNanos = dnsTimeNanos; + } } diff --git a/app/src/main/java/net/lightbody/bmp/filters/util/HarCaptureUtil.java b/app/src/main/java/net/lightbody/bmp/filters/util/HarCaptureUtil.java index 1ac47f9..92f8859 100644 --- a/app/src/main/java/net/lightbody/bmp/filters/util/HarCaptureUtil.java +++ b/app/src/main/java/net/lightbody/bmp/filters/util/HarCaptureUtil.java @@ -87,7 +87,7 @@ public static String getResponseTimedOutErrorMessage() { /** * Returns the error message for the HAR response when no response was received from the server (e.g. when the * browser is closed). - * + * * @return the no response received error message */ public static String getNoResponseReceivedErrorMessage() { diff --git a/app/src/main/java/net/lightbody/bmp/mitm/CertificateInfoGenerator.java b/app/src/main/java/net/lightbody/bmp/mitm/CertificateInfoGenerator.java index 21c064f..ac86b50 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/CertificateInfoGenerator.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/CertificateInfoGenerator.java @@ -11,7 +11,7 @@ public interface CertificateInfoGenerator { /** * Generate a certificate for the specified hostnames, optionally using parameters from the originalCertificate. * - * @param hostnames the hostnames to generate the certificate for, which may include wildcards + * @param hostnames the hostnames to generate the certificate for, which may include wildcards * @param originalCertificate original X.509 certificate sent by the upstream server, which may be null * @return CertificateInfo to be used to create an X509Certificate for the specified hostnames */ diff --git a/app/src/main/java/net/lightbody/bmp/mitm/KeyStoreFileCertificateSource.java b/app/src/main/java/net/lightbody/bmp/mitm/KeyStoreFileCertificateSource.java index 5771723..719f79c 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/KeyStoreFileCertificateSource.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/KeyStoreFileCertificateSource.java @@ -2,9 +2,11 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; + import net.lightbody.bmp.mitm.exception.CertificateSourceException; import net.lightbody.bmp.mitm.tools.DefaultSecurityProviderTool; import net.lightbody.bmp.mitm.tools.SecurityProviderTool; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +44,8 @@ public CertificateAndKey get() { /** * Creates a {@link CertificateAndKeySource} that loads an existing {@link KeyStore} from a classpath resource. - * @param keyStoreType the KeyStore type, such as PKCS12 or JKS + * + * @param keyStoreType the KeyStore type, such as PKCS12 or JKS * @param keyStoreClasspathResource classpath resource to load (for example, "/keystore.jks") * @param privateKeyAlias the alias of the private key in the KeyStore * @param keyStorePassword te KeyStore password @@ -70,7 +73,8 @@ public KeyStoreFileCertificateSource(String keyStoreType, String keyStoreClasspa /** * Creates a {@link CertificateAndKeySource} that loads an existing {@link KeyStore} from a classpath resource. - * @param keyStoreType the KeyStore type, such as PKCS12 or JKS + * + * @param keyStoreType the KeyStore type, such as PKCS12 or JKS * @param keyStoreFile KeyStore file to load * @param privateKeyAlias the alias of the private key in the KeyStore * @param keyStorePassword te KeyStore password diff --git a/app/src/main/java/net/lightbody/bmp/mitm/PemFileCertificateSource.java b/app/src/main/java/net/lightbody/bmp/mitm/PemFileCertificateSource.java index 6bf5253..73d44a8 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/PemFileCertificateSource.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/PemFileCertificateSource.java @@ -2,9 +2,11 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; + import net.lightbody.bmp.mitm.tools.DefaultSecurityProviderTool; import net.lightbody.bmp.mitm.tools.SecurityProviderTool; import net.lightbody.bmp.mitm.util.EncryptionUtil; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/net/lightbody/bmp/mitm/RootCertificateGenerator.java b/app/src/main/java/net/lightbody/bmp/mitm/RootCertificateGenerator.java index ac4c7a0..c7d1452 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/RootCertificateGenerator.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/RootCertificateGenerator.java @@ -2,12 +2,14 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; + import net.lightbody.bmp.mitm.keys.KeyGenerator; import net.lightbody.bmp.mitm.keys.RSAKeyGenerator; import net.lightbody.bmp.mitm.tools.DefaultSecurityProviderTool; import net.lightbody.bmp.mitm.tools.SecurityProviderTool; import net.lightbody.bmp.mitm.util.EncryptionUtil; import net.lightbody.bmp.mitm.util.MitmConstants; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,20 +36,14 @@ */ public class RootCertificateGenerator implements CertificateAndKeySource { private static final Logger log = LoggerFactory.getLogger(RootCertificateGenerator.class); - - private final CertificateInfo rootCertificateInfo; - - private final String messageDigest; - - private final KeyGenerator keyGenerator; - - private final SecurityProviderTool securityProviderTool; - /** * The default algorithm to use when encrypting objects in PEM files (such as private keys). */ private static final String DEFAULT_PEM_ENCRYPTION_ALGORITHM = "AES-128-CBC"; - + private final CertificateInfo rootCertificateInfo; + private final String messageDigest; + private final KeyGenerator keyGenerator; + private final SecurityProviderTool securityProviderTool; /** * The new root certificate and private key are generated only once, even across multiple calls to {@link #load()}}, * to allow users to save the new generated root certificate for use in browsers/other HTTP clients. @@ -85,6 +81,34 @@ public RootCertificateGenerator(CertificateInfo rootCertificateInfo, this.securityProviderTool = securityProviderTool; } + /** + * Convenience method to return a new {@link Builder} instance. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Creates a default CN field for a certificate, using the hostname of this machine and the current time. + */ + private static String getDefaultCommonName() { + String hostname; + try { + hostname = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + hostname = "localhost"; + } + + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz"); + + String currentDateTime = dateFormat.format(new Date()); + + String defaultCN = "Generated CA (" + hostname + ") " + currentDateTime; + + // CN fields can only be 64 characters + return defaultCN.length() <= 64 ? defaultCN : defaultCN.substring(0, 63); + } + @Override public CertificateAndKey load() { // only generate the materials once, so they can can be saved if desired @@ -175,13 +199,6 @@ public void saveRootCertificateAndKey(String keyStoreType, securityProviderTool.saveKeyStore(file, keyStore, password); } - /** - * Convenience method to return a new {@link Builder} instance. - */ - public static Builder builder() { - return new Builder(); - } - /** * A Builder for {@link RootCertificateGenerator}s. Initialized with suitable default values suitable for most purposes. */ @@ -235,25 +252,4 @@ public RootCertificateGenerator build() { return new RootCertificateGenerator(certificateInfo, messageDigest, keyGenerator, securityProviderTool); } } - - /** - * Creates a default CN field for a certificate, using the hostname of this machine and the current time. - */ - private static String getDefaultCommonName() { - String hostname; - try { - hostname = InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - hostname = "localhost"; - } - - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz"); - - String currentDateTime = dateFormat.format(new Date()); - - String defaultCN = "Generated CA (" + hostname + ") " + currentDateTime; - - // CN fields can only be 64 characters - return defaultCN.length() <= 64 ? defaultCN : defaultCN.substring(0, 63); - } } diff --git a/app/src/main/java/net/lightbody/bmp/mitm/TrustSource.java b/app/src/main/java/net/lightbody/bmp/mitm/TrustSource.java index 0aa3ebf..6d91161 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/TrustSource.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/TrustSource.java @@ -2,6 +2,7 @@ import com.google.common.collect.ObjectArrays; import com.google.common.io.Files; + import net.lightbody.bmp.mitm.exception.UncheckedIOException; import net.lightbody.bmp.mitm.util.TrustUtil; @@ -15,19 +16,19 @@ /** * A source of trusted root certificate authorities. Provides static methods to obtain default trust sources: *
    - *
  • {@link #defaultTrustSource()}- both the built-in and JVM-trusted CAs
  • - *
  • {@link #javaTrustSource()} - only default CAs trusted by the JVM
  • - *
  • {@link #builtinTrustSource()} - only built-in trusted CAs (ultimately derived from Firefox's trust list)
  • + *
  • {@link #defaultTrustSource()}- both the built-in and JVM-trusted CAs
  • + *
  • {@link #javaTrustSource()} - only default CAs trusted by the JVM
  • + *
  • {@link #builtinTrustSource()} - only built-in trusted CAs (ultimately derived from Firefox's trust list)
  • *
- * + *

* Custom TrustSources can be built by starting with {@link #empty()}, then calling the various add() methods to add * PEM-encoded files and Strings, KeyStores, and X509Certificates to the TrustSource. For example: *

* - * TrustSource customTrustSource = TrustSource.empty() - * .add(myX509Certificate) - * .add(pemFileContainingMyCA) - * .add(javaKeyStore); + * TrustSource customTrustSource = TrustSource.empty() + * .add(myX509Certificate) + * .add(pemFileContainingMyCA) + * .add(javaKeyStore); * *

* Note: This class is immutable, so calls to add() will return a new instance, rather than modifying the existing instance. @@ -64,14 +65,6 @@ protected TrustSource(X509Certificate... trustedCAs) { } } - /** - * Returns the X509 certificates considered "trusted" by this TrustSource. This method will not return null, but - * may return an empty array. - */ - public X509Certificate[] getTrustedCAs() { - return trustedCAs; - } - /** * Returns a TrustSource that contains no trusted CAs. Can be used in conjunction with the add() methods to build * a TrustSource containing custom CAs from a variety of sources (PEM files, KeyStores, etc.). @@ -103,6 +96,14 @@ public static TrustSource javaTrustSource() { return new TrustSource(TrustUtil.getJavaTrustedCAs()); } + /** + * Returns the X509 certificates considered "trusted" by this TrustSource. This method will not return null, but + * may return an empty array. + */ + public X509Certificate[] getTrustedCAs() { + return trustedCAs; + } + /** * Returns a new TrustSource containing the same trusted CAs as this TrustSource, plus zero or more CAs contained in * the PEM-encoded String. The String may contain multiple certificates and may contain comments or other non-PEM-encoded diff --git a/app/src/main/java/net/lightbody/bmp/mitm/manager/ImpersonatingMitmManager.java b/app/src/main/java/net/lightbody/bmp/mitm/manager/ImpersonatingMitmManager.java index 6e14426..fcc5de7 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/manager/ImpersonatingMitmManager.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/manager/ImpersonatingMitmManager.java @@ -7,11 +7,7 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.ImmutableList; -import io.netty.buffer.ByteBufAllocator; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.ssl.SupportedCipherSuiteFilter; + import net.lightbody.bmp.mitm.CertificateAndKey; import net.lightbody.bmp.mitm.CertificateAndKeySource; import net.lightbody.bmp.mitm.CertificateInfo; @@ -31,14 +27,11 @@ import net.lightbody.bmp.mitm.util.MitmConstants; import net.lightbody.bmp.mitm.util.SslUtil; import net.lightbody.bmp.util.HttpUtil; + import org.littleshoot.proxy.MitmManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLParameters; -import javax.net.ssl.SSLSession; import java.security.KeyPair; import java.security.PrivateKey; import java.security.cert.X509Certificate; @@ -49,6 +42,17 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SupportedCipherSuiteFilter; + /** * An {@link MitmManager} that will create SSLEngines for clients that present impersonated certificates for upstream servers. The impersonated * certificates will be signed using the certificate and private key specified in an {@link #rootCertificateSource}. The impersonated server @@ -66,46 +70,39 @@ public class ImpersonatingMitmManager implements MitmManager { * Cipher suites allowed on client connections to the proxy. */ private final List clientCipherSuites; - - /** - * The SSLContext that will be used for communications with all upstream servers. This can be reused, so store it as a lazily-loaded singleton. - */ - private final Supplier upstreamServerSslContext = Suppliers.memoize(new Supplier() { - @Override - public SslContext get() { - return SslUtil.getUpstreamServerSslContext(serverCipherSuites, trustSource); - } - }); - /** * Cache for impersonating netty SslContexts. SslContexts can be safely reused, so caching the impersonating contexts avoids * repeatedly re-impersonating upstream servers. */ private final Cache sslContextCache; - /** * Generator used to create public and private keys for the server certificates. */ private final KeyGenerator serverKeyGenerator; - /** * The source of the CA's {@link CertificateAndKey} that will be used to sign generated server certificates. */ private final CertificateAndKeySource rootCertificateSource; - /** * The message digest used to sign the server certificate, such as SHA512. * See https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#MessageDigest for information * on supported message digests. */ private final String serverCertificateMessageDigest; - /** * The source of trusted root CAs. May be null, which disables all upstream certificate validation. Disabling upstream * certificate validation allows attackers to intercept communciations and should only be used during testing. */ private final TrustSource trustSource; - + /** + * The SSLContext that will be used for communications with all upstream servers. This can be reused, so store it as a lazily-loaded singleton. + */ + private final Supplier upstreamServerSslContext = Suppliers.memoize(new Supplier() { + @Override + public SslContext get() { + return SslUtil.getUpstreamServerSslContext(serverCipherSuites, trustSource); + } + }); /** * Utility used to generate {@link CertificateInfo} objects when impersonating an upstream server. */ @@ -115,7 +112,10 @@ public SslContext get() { * Tool implementation that is used to generate, sign, and otherwise manipulate server certificates. */ private final SecurityProviderTool securityProviderTool; - + /** + * Simple server certificate generation statistics. + */ + private final CertificateGenerationStatistics statistics = new CertificateGenerationStatistics(); /** * The CA root root certificate used to sign generated server certificates. {@link CertificateAndKeySource#load()} * is only called once to retrieve the CA root certificate, which will be used to impersonate all server certificates. @@ -127,11 +127,6 @@ public CertificateAndKey get() { } }); - /** - * Simple server certificate generation statistics. - */ - private final CertificateGenerationStatistics statistics = new CertificateGenerationStatistics(); - /** * Creates a new ImpersonatingMitmManager. In general, use {@link ImpersonatingMitmManager.Builder} * to construct new instances. @@ -190,6 +185,26 @@ public ImpersonatingMitmManager(CertificateAndKeySource rootCertificateSource, log.debug("Allowed ciphers for client connections to proxy (some ciphers may not be available): {}", clientCipherSuites); } + /** + * Convenience method to return a new {@link Builder} instance default default values: a {@link RootCertificateGenerator} + * that dynamically generates an RSA root certificate and RSA server certificates. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Convenience method to return a new {@link Builder} instance that will dynamically create EC root certificates and + * EC server certificates, but otherwise uses default values. + */ + public static Builder builderWithECC() { + return new Builder() + .serverKeyGenerator(new ECKeyGenerator()) + .rootCertificateSource(RootCertificateGenerator.builder() + .keyGenerator(new ECKeyGenerator()) + .build()); + } + @Override public SSLEngine serverSslEngine() { try { @@ -238,7 +253,7 @@ public SSLEngine clientSslEngineFor(HttpRequest httpRequest, SSLSession sslSessi * which impersonates the specified hostname. * * @param hostnameToImpersonate the hostname for which the impersonated SSLContext is being requested - * @param sslSession the upstream server SSLSession + * @param sslSession the upstream server SSLSession * @return SSLContext which will present an impersonated certificate */ private SslContext getHostnameImpersonatingSslContext(final String hostnameToImpersonate, final SSLSession sslSession) { @@ -261,7 +276,7 @@ public SslContext call() throws Exception { * This is a convenience method for {@link #createImpersonatingSslContext(CertificateInfo)} that generates the * {@link CertificateInfo} from the specified hostname using the {@link #certificateInfoGenerator}. * - * @param sslSession sslSession between the proxy and the upstream server + * @param sslSession sslSession between the proxy and the upstream server * @param hostnameToImpersonate hostname (supplied by the client's HTTP CONNECT) that will be impersonated * @return an SSLContext presenting a certificate matching the hostnameToImpersonate */ @@ -343,26 +358,6 @@ public CertificateGenerationStatistics getStatistics() { return this.statistics; } - /** - * Convenience method to return a new {@link Builder} instance default default values: a {@link RootCertificateGenerator} - * that dynamically generates an RSA root certificate and RSA server certificates. - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Convenience method to return a new {@link Builder} instance that will dynamically create EC root certificates and - * EC server certificates, but otherwise uses default values. - */ - public static Builder builderWithECC() { - return new Builder() - .serverKeyGenerator(new ECKeyGenerator()) - .rootCertificateSource(RootCertificateGenerator.builder() - .keyGenerator(new ECKeyGenerator()) - .build()); - } - /** * A Builder for {@link ImpersonatingMitmManager}s. Initialized with suitable default values suitable for most purposes. */ diff --git a/app/src/main/java/net/lightbody/bmp/mitm/tools/BouncyCastleSecurityProviderTool.java b/app/src/main/java/net/lightbody/bmp/mitm/tools/BouncyCastleSecurityProviderTool.java index ae27cf5..005c76c 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/tools/BouncyCastleSecurityProviderTool.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/tools/BouncyCastleSecurityProviderTool.java @@ -1,12 +1,14 @@ package net.lightbody.bmp.mitm.tools; import com.google.common.net.InetAddresses; + import net.lightbody.bmp.mitm.CertificateAndKey; import net.lightbody.bmp.mitm.CertificateInfo; import net.lightbody.bmp.mitm.exception.CertificateCreationException; import net.lightbody.bmp.mitm.exception.ExportException; import net.lightbody.bmp.mitm.exception.ImportException; import net.lightbody.bmp.mitm.util.EncryptionUtil; + import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; @@ -40,7 +42,6 @@ import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import javax.net.ssl.KeyManager; import java.io.File; import java.io.IOException; import java.io.Reader; @@ -58,15 +59,142 @@ import java.util.ArrayList; import java.util.List; +import javax.net.ssl.KeyManager; + public class BouncyCastleSecurityProviderTool implements SecurityProviderTool { + /** + * The size of certificate serial numbers, in bits. + */ + private static final int CERTIFICATE_SERIAL_NUMBER_SIZE = 160; + static { Security.addProvider(new BouncyCastleProvider()); } /** - * The size of certificate serial numbers, in bits. + * Creates an X500Name based on the specified certificateInfo. + * + * @param certificateInfo information to populate the X500Name with + * @return a new X500Name object for use as a subject or issuer */ - private static final int CERTIFICATE_SERIAL_NUMBER_SIZE = 160; + private static X500Name createX500NameForCertificate(CertificateInfo certificateInfo) { + X500NameBuilder x500NameBuilder = new X500NameBuilder(BCStyle.INSTANCE); + + if (certificateInfo.getCommonName() != null) { + x500NameBuilder.addRDN(BCStyle.CN, certificateInfo.getCommonName()); + } + + if (certificateInfo.getOrganization() != null) { + x500NameBuilder.addRDN(BCStyle.O, certificateInfo.getOrganization()); + } + + if (certificateInfo.getOrganizationalUnit() != null) { + x500NameBuilder.addRDN(BCStyle.OU, certificateInfo.getOrganizationalUnit()); + } + + if (certificateInfo.getEmail() != null) { + x500NameBuilder.addRDN(BCStyle.E, certificateInfo.getEmail()); + } + + if (certificateInfo.getLocality() != null) { + x500NameBuilder.addRDN(BCStyle.L, certificateInfo.getLocality()); + } + + if (certificateInfo.getState() != null) { + x500NameBuilder.addRDN(BCStyle.ST, certificateInfo.getState()); + } + + if (certificateInfo.getCountryCode() != null) { + x500NameBuilder.addRDN(BCStyle.C, certificateInfo.getCountryCode()); + } + + // TODO: Add more X.509 certificate fields as needed + + return x500NameBuilder.build(); + } + + /** + * Converts a list of domain name Subject Alternative Names into ASN1Encodable GeneralNames objects, for use with + * the Bouncy Castle certificate builder. + * + * @param subjectAlternativeNames domain name SANs to convert + * @return a GeneralNames instance that includes the specifie dsubjectAlternativeNames as DNS name fields + */ + private static GeneralNames getDomainNameSANsAsASN1Encodable(List subjectAlternativeNames) { + List encodedSANs = new ArrayList<>(subjectAlternativeNames.size()); + for (String subjectAlternativeName : subjectAlternativeNames) { + // IP addresses use the IP Address tag instead of the DNS Name tag in the SAN list + boolean isIpAddress = InetAddresses.isInetAddress(subjectAlternativeName); + GeneralName generalName = new GeneralName(isIpAddress ? GeneralName.iPAddress : GeneralName.dNSName, subjectAlternativeName); + encodedSANs.add(generalName); + } + + return new GeneralNames(encodedSANs.toArray(new GeneralName[encodedSANs.size()])); + } + + /** + * Creates a ContentSigner that can be used to sign certificates with the given private key and signature algorithm. + * + * @param certAuthorityPrivateKey the private key to use to sign certificates + * @param signatureAlgorithm the algorithm to use to sign certificates + * @return a ContentSigner + */ + private static ContentSigner getCertificateSigner(PrivateKey certAuthorityPrivateKey, String signatureAlgorithm) { + try { + return new JcaContentSignerBuilder(signatureAlgorithm) + .build(certAuthorityPrivateKey); + } catch (OperatorCreationException e) { + throw new CertificateCreationException("Unable to create ContentSigner using signature algorithm: " + signatureAlgorithm, e); + } + } + + /** + * Converts a Bouncy Castle X509CertificateHolder into a JCA X590Certificate. + * + * @param bouncyCastleCertificate BC X509CertificateHolder + * @return JCA X509Certificate + */ + private static X509Certificate convertToJcaCertificate(X509CertificateHolder bouncyCastleCertificate) { + try { + return new JcaX509CertificateConverter() + .getCertificate(bouncyCastleCertificate); + } catch (CertificateException e) { + throw new CertificateCreationException("Unable to convert X590CertificateHolder to JCA X590Certificate", e); + } + } + + /** + * Creates the SubjectKeyIdentifier for a Bouncy Castle X590CertificateHolder. + * + * @param key public key to identify + * @return SubjectKeyIdentifier for the specified key + */ + private static SubjectKeyIdentifier createSubjectKeyIdentifier(Key key) { + SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(key.getEncoded()); + + return new BcX509ExtensionUtils().createSubjectKeyIdentifier(publicKeyInfo); + } + + /** + * Encodes the specified security object in PEM format, using the specified encryptor. If the encryptor is null, + * the object will not be encrypted in the generated String. + * + * @param object object to encrypt (certificate, private key, etc.) + * @param encryptor engine to encrypt the resulting PEM String, or null if no encryption should be used + * @return a PEM-encoded String + */ + private static String encodeObjectAsPemString(Object object, PEMEncryptor encryptor) { + StringWriter stringWriter = new StringWriter(); + + try (JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { + pemWriter.writeObject(object, encryptor); + pemWriter.flush(); + } catch (IOException e) { + throw new ExportException("Unable to generate PEM string representing object", e); + } + + return stringWriter.toString(); + } @Override public CertificateAndKey createServerCertificate(CertificateInfo certificateInfo, @@ -258,130 +386,4 @@ public void saveKeyStore(File file, KeyStore keyStore, String keystorePassword) public KeyManager[] getKeyManagers(KeyStore keyStore, String keyStorePassword) { return new KeyManager[0]; } - - - /** - * Creates an X500Name based on the specified certificateInfo. - * - * @param certificateInfo information to populate the X500Name with - * @return a new X500Name object for use as a subject or issuer - */ - private static X500Name createX500NameForCertificate(CertificateInfo certificateInfo) { - X500NameBuilder x500NameBuilder = new X500NameBuilder(BCStyle.INSTANCE); - - if (certificateInfo.getCommonName() != null) { - x500NameBuilder.addRDN(BCStyle.CN, certificateInfo.getCommonName()); - } - - if (certificateInfo.getOrganization() != null) { - x500NameBuilder.addRDN(BCStyle.O, certificateInfo.getOrganization()); - } - - if (certificateInfo.getOrganizationalUnit() != null) { - x500NameBuilder.addRDN(BCStyle.OU, certificateInfo.getOrganizationalUnit()); - } - - if (certificateInfo.getEmail() != null) { - x500NameBuilder.addRDN(BCStyle.E, certificateInfo.getEmail()); - } - - if (certificateInfo.getLocality() != null) { - x500NameBuilder.addRDN(BCStyle.L, certificateInfo.getLocality()); - } - - if (certificateInfo.getState() != null) { - x500NameBuilder.addRDN(BCStyle.ST, certificateInfo.getState()); - } - - if (certificateInfo.getCountryCode() != null) { - x500NameBuilder.addRDN(BCStyle.C, certificateInfo.getCountryCode()); - } - - // TODO: Add more X.509 certificate fields as needed - - return x500NameBuilder.build(); - } - - /** - * Converts a list of domain name Subject Alternative Names into ASN1Encodable GeneralNames objects, for use with - * the Bouncy Castle certificate builder. - * - * @param subjectAlternativeNames domain name SANs to convert - * @return a GeneralNames instance that includes the specifie dsubjectAlternativeNames as DNS name fields - */ - private static GeneralNames getDomainNameSANsAsASN1Encodable(List subjectAlternativeNames) { - List encodedSANs = new ArrayList<>(subjectAlternativeNames.size()); - for (String subjectAlternativeName : subjectAlternativeNames) { - // IP addresses use the IP Address tag instead of the DNS Name tag in the SAN list - boolean isIpAddress = InetAddresses.isInetAddress(subjectAlternativeName); - GeneralName generalName = new GeneralName(isIpAddress ? GeneralName.iPAddress : GeneralName.dNSName, subjectAlternativeName); - encodedSANs.add(generalName); - } - - return new GeneralNames(encodedSANs.toArray(new GeneralName[encodedSANs.size()])); - } - - /** - * Creates a ContentSigner that can be used to sign certificates with the given private key and signature algorithm. - * - * @param certAuthorityPrivateKey the private key to use to sign certificates - * @param signatureAlgorithm the algorithm to use to sign certificates - * @return a ContentSigner - */ - private static ContentSigner getCertificateSigner(PrivateKey certAuthorityPrivateKey, String signatureAlgorithm) { - try { - return new JcaContentSignerBuilder(signatureAlgorithm) - .build(certAuthorityPrivateKey); - } catch (OperatorCreationException e) { - throw new CertificateCreationException("Unable to create ContentSigner using signature algorithm: " + signatureAlgorithm, e); - } - } - - /** - * Converts a Bouncy Castle X509CertificateHolder into a JCA X590Certificate. - * - * @param bouncyCastleCertificate BC X509CertificateHolder - * @return JCA X509Certificate - */ - private static X509Certificate convertToJcaCertificate(X509CertificateHolder bouncyCastleCertificate) { - try { - return new JcaX509CertificateConverter() - .getCertificate(bouncyCastleCertificate); - } catch (CertificateException e) { - throw new CertificateCreationException("Unable to convert X590CertificateHolder to JCA X590Certificate", e); - } - } - - /** - * Creates the SubjectKeyIdentifier for a Bouncy Castle X590CertificateHolder. - * - * @param key public key to identify - * @return SubjectKeyIdentifier for the specified key - */ - private static SubjectKeyIdentifier createSubjectKeyIdentifier(Key key) { - SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(key.getEncoded()); - - return new BcX509ExtensionUtils().createSubjectKeyIdentifier(publicKeyInfo); - } - - /** - * Encodes the specified security object in PEM format, using the specified encryptor. If the encryptor is null, - * the object will not be encrypted in the generated String. - * - * @param object object to encrypt (certificate, private key, etc.) - * @param encryptor engine to encrypt the resulting PEM String, or null if no encryption should be used - * @return a PEM-encoded String - */ - private static String encodeObjectAsPemString(Object object, PEMEncryptor encryptor) { - StringWriter stringWriter = new StringWriter(); - - try (JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { - pemWriter.writeObject(object, encryptor); - pemWriter.flush(); - } catch (IOException e) { - throw new ExportException("Unable to generate PEM string representing object", e); - } - - return stringWriter.toString(); - } } diff --git a/app/src/main/java/net/lightbody/bmp/mitm/tools/DefaultSecurityProviderTool.java b/app/src/main/java/net/lightbody/bmp/mitm/tools/DefaultSecurityProviderTool.java index 501f5a6..a62ce4f 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/tools/DefaultSecurityProviderTool.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/tools/DefaultSecurityProviderTool.java @@ -1,13 +1,13 @@ package net.lightbody.bmp.mitm.tools; import com.google.common.io.CharStreams; + import net.lightbody.bmp.mitm.CertificateAndKey; import net.lightbody.bmp.mitm.CertificateInfo; import net.lightbody.bmp.mitm.exception.ImportException; import net.lightbody.bmp.mitm.exception.KeyStoreAccessException; import net.lightbody.bmp.mitm.util.KeyStoreUtil; -import javax.net.ssl.KeyManager; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; @@ -26,6 +26,8 @@ import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import javax.net.ssl.KeyManager; + /** * A {@link SecurityProviderTool} implementation that uses the default system Security provider where possible, but uses the * Bouncy Castle provider for operations that the JCA does not provide or implement (e.g. certificate generation and signing). diff --git a/app/src/main/java/net/lightbody/bmp/mitm/tools/SecurityProviderTool.java b/app/src/main/java/net/lightbody/bmp/mitm/tools/SecurityProviderTool.java index 8b6df2a..55c3247 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/tools/SecurityProviderTool.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/tools/SecurityProviderTool.java @@ -3,7 +3,6 @@ import net.lightbody.bmp.mitm.CertificateAndKey; import net.lightbody.bmp.mitm.CertificateInfo; -import javax.net.ssl.KeyManager; import java.io.File; import java.io.Reader; import java.security.KeyPair; @@ -12,6 +11,8 @@ import java.security.cert.Certificate; import java.security.cert.X509Certificate; +import javax.net.ssl.KeyManager; + /** * Generic interface for functionality provided by a Security Provider. */ diff --git a/app/src/main/java/net/lightbody/bmp/mitm/trustmanager/InsecureExtendedTrustManager.java b/app/src/main/java/net/lightbody/bmp/mitm/trustmanager/InsecureExtendedTrustManager.java index 14d23f8..465efdb 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/trustmanager/InsecureExtendedTrustManager.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/trustmanager/InsecureExtendedTrustManager.java @@ -1,12 +1,8 @@ package net.lightbody.bmp.mitm.trustmanager; -import io.netty.util.internal.EmptyArrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; import java.net.Socket; import java.security.KeyStore; import java.security.KeyStoreException; @@ -14,20 +10,25 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import cn.darkal.networkdiagnosis.Utils.X509ExtendedTrustManager; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +import cn.darkal.networkdiagnosis.Utils.AbstractX509ExtendedTrustManager; +import io.netty.util.internal.EmptyArrays; /** - * An {@link X509ExtendedTrustManager} and {@link javax.net.ssl.X509TrustManager} that will accept all server and client - * certificates. Before accepting a certificate, the InsecureExtendedTrustManager uses the default X509ExtendedTrustManager + * An {@link AbstractX509ExtendedTrustManager} and {@link javax.net.ssl.X509TrustManager} that will accept all server and client + * certificates. Before accepting a certificate, the InsecureExtendedTrustManager uses the default AbstractX509ExtendedTrustManager * to determine if the certificate would otherwise be trusted, and logs a debug-level message if it is not trusted. */ -public class InsecureExtendedTrustManager extends X509ExtendedTrustManager { +public class InsecureExtendedTrustManager extends AbstractX509ExtendedTrustManager { private static final Logger log = LoggerFactory.getLogger(InsecureExtendedTrustManager.class); /** - * An {@link X509ExtendedTrustManager} that does no certificate validation whatsoever. + * An {@link AbstractX509ExtendedTrustManager} that does no certificate validation whatsoever. */ - private static final X509ExtendedTrustManager NOOP_EXTENDED_TRUST_MANAGER = new X509ExtendedTrustManager() { + private static final AbstractX509ExtendedTrustManager NOOP_EXTENDED_TRUST_MANAGER = new AbstractX509ExtendedTrustManager() { @Override public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException { } @@ -61,7 +62,33 @@ public X509Certificate[] getAcceptedIssuers() { /** * The default extended trust manager, which will be used to determine if certificates would otherwise be trusted. */ - protected static final X509ExtendedTrustManager DEFAULT_EXTENDED_TRUST_MANAGER = getDefaultExtendedTrustManager(); + protected static final AbstractX509ExtendedTrustManager DEFAULT_EXTENDED_TRUST_MANAGER = getDefaultExtendedTrustManager(); + + /** + * Returns the JDK's default AbstractX509ExtendedTrustManager, or a no-op trust manager if the default cannot be found. + */ + private static AbstractX509ExtendedTrustManager getDefaultExtendedTrustManager() { + TrustManagerFactory trustManagerFactory; + try { + trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + // initialize the TrustManagerFactory with the default KeyStore + trustManagerFactory.init((KeyStore) null); + } catch (NoSuchAlgorithmException | KeyStoreException e) { + log.debug("Unable to initialize default TrustManagerFactory. Using no-op AbstractX509ExtendedTrustManager.", e); + return NOOP_EXTENDED_TRUST_MANAGER; + } + + // find the AbstractX509ExtendedTrustManager in the list of registered trust managers + for (TrustManager tm : trustManagerFactory.getTrustManagers()) { + if (tm instanceof AbstractX509ExtendedTrustManager) { + return (AbstractX509ExtendedTrustManager) tm; + } + } + + // no default AbstractX509ExtendedTrustManager found, so return a no-op + log.debug("No default AbstractX509ExtendedTrustManager found. Using no-op."); + return NOOP_EXTENDED_TRUST_MANAGER; + } @Override public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException { @@ -121,30 +148,4 @@ public void checkServerTrusted(X509Certificate[] x509Certificates, String s) thr public X509Certificate[] getAcceptedIssuers() { return EmptyArrays.EMPTY_X509_CERTIFICATES; } - - /** - * Returns the JDK's default X509ExtendedTrustManager, or a no-op trust manager if the default cannot be found. - */ - private static X509ExtendedTrustManager getDefaultExtendedTrustManager() { - TrustManagerFactory trustManagerFactory; - try { - trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - // initialize the TrustManagerFactory with the default KeyStore - trustManagerFactory.init((KeyStore) null); - } catch (NoSuchAlgorithmException | KeyStoreException e) { - log.debug("Unable to initialize default TrustManagerFactory. Using no-op X509ExtendedTrustManager.", e); - return NOOP_EXTENDED_TRUST_MANAGER; - } - - // find the X509ExtendedTrustManager in the list of registered trust managers - for (TrustManager tm : trustManagerFactory.getTrustManagers()) { - if (tm instanceof X509ExtendedTrustManager) { - return (X509ExtendedTrustManager) tm; - } - } - - // no default X509ExtendedTrustManager found, so return a no-op - log.debug("No default X509ExtendedTrustManager found. Using no-op."); - return NOOP_EXTENDED_TRUST_MANAGER; - } } diff --git a/app/src/main/java/net/lightbody/bmp/mitm/trustmanager/InsecureTrustManagerFactory.java b/app/src/main/java/net/lightbody/bmp/mitm/trustmanager/InsecureTrustManagerFactory.java index 176e02b..544d809 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/trustmanager/InsecureTrustManagerFactory.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/trustmanager/InsecureTrustManagerFactory.java @@ -16,17 +16,18 @@ package net.lightbody.bmp.mitm.trustmanager; -import cn.darkal.networkdiagnosis.Utils.X509ExtendedTrustManager; -import io.netty.handler.ssl.util.SimpleTrustManagerFactory; +import java.security.KeyStore; import javax.net.ssl.ManagerFactoryParameters; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; -import java.security.KeyStore; + +import cn.darkal.networkdiagnosis.Utils.AbstractX509ExtendedTrustManager; +import io.netty.handler.ssl.util.SimpleTrustManagerFactory; /** * Note: This is a modified version of {@link io.netty.handler.ssl.util.InsecureTrustManagerFactory} from Netty - * 4.0.36. Unlike the netty version, this class returns an {@link X509ExtendedTrustManager} instead of an + * 4.0.36. Unlike the netty version, this class returns an {@link AbstractX509ExtendedTrustManager} instead of an * {@link javax.net.ssl.X509TrustManager} instance, which allows us to bypass additional certificate validations. *

* An insecure {@link TrustManagerFactory} that trusts all X.509 certificates without any verification. @@ -40,7 +41,7 @@ public class InsecureTrustManagerFactory extends SimpleTrustManagerFactory { public static final TrustManagerFactory INSTANCE = new InsecureTrustManagerFactory(); - public static final X509ExtendedTrustManager tm = new InsecureExtendedTrustManager(); + public static final AbstractX509ExtendedTrustManager tm = new InsecureExtendedTrustManager(); @Override protected void engineInit(KeyStore keyStore) throws Exception { diff --git a/app/src/main/java/net/lightbody/bmp/mitm/util/EncryptionUtil.java b/app/src/main/java/net/lightbody/bmp/mitm/util/EncryptionUtil.java index 037f37c..687776f 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/util/EncryptionUtil.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/util/EncryptionUtil.java @@ -3,7 +3,6 @@ import net.lightbody.bmp.mitm.exception.ExportException; import net.lightbody.bmp.mitm.exception.ImportException; -import javax.crypto.Cipher; import java.io.File; import java.io.IOException; import java.math.BigInteger; @@ -16,6 +15,8 @@ import java.security.interfaces.RSAKey; import java.util.Random; +import javax.crypto.Cipher; + /** * A collection of simple JCA-related utilities. */ @@ -84,7 +85,7 @@ public static boolean isEcKey(Key key) { /** * Convenience method to write PEM data to a file. The file will be encoded in the US_ASCII character set. * - * @param file file to write to + * @param file file to write to * @param pemDataToWrite PEM data to write to the file */ public static void writePemStringToFile(File file, String pemDataToWrite) { diff --git a/app/src/main/java/net/lightbody/bmp/mitm/util/KeyStoreUtil.java b/app/src/main/java/net/lightbody/bmp/mitm/util/KeyStoreUtil.java index 7edfc47..d8a7043 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/util/KeyStoreUtil.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/util/KeyStoreUtil.java @@ -2,8 +2,6 @@ import net.lightbody.bmp.mitm.exception.KeyStoreAccessException; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; import java.io.IOException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -15,6 +13,9 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; + /** * Utility for loading, saving, and manipulating {@link KeyStore}s. */ diff --git a/app/src/main/java/net/lightbody/bmp/mitm/util/MitmConstants.java b/app/src/main/java/net/lightbody/bmp/mitm/util/MitmConstants.java index bb24ad2..351417a 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/util/MitmConstants.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/util/MitmConstants.java @@ -10,7 +10,7 @@ public class MitmConstants { * this question for details: http://crypto.stackexchange.com/questions/26336/sha512-faster-than-sha256. SHA384 is * SHA512 with a smaller output size. */ - public static final String DEFAULT_MESSAGE_DIGEST = is32BitJvm() ? "SHA256": "SHA384"; + public static final String DEFAULT_MESSAGE_DIGEST = is32BitJvm() ? "SHA256" : "SHA384"; /** * The default {@link java.security.KeyStore} type to use when creating KeyStores (e.g. for impersonated server diff --git a/app/src/main/java/net/lightbody/bmp/mitm/util/SslUtil.java b/app/src/main/java/net/lightbody/bmp/mitm/util/SslUtil.java index 198f194..ddd8a91 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/util/SslUtil.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/util/SslUtil.java @@ -3,20 +3,14 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.io.CharStreams; -import io.netty.handler.ssl.OpenSsl; -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.ssl.SupportedCipherSuiteFilter; -import net.lightbody.bmp.mitm.trustmanager.InsecureTrustManagerFactory; + import net.lightbody.bmp.mitm.TrustSource; import net.lightbody.bmp.mitm.exception.SslContextInitializationException; +import net.lightbody.bmp.mitm.trustmanager.InsecureTrustManagerFactory; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -29,6 +23,16 @@ import java.util.Collections; import java.util.List; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; + +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SupportedCipherSuiteFilter; + /** * Utility for creating SSLContexts. */ @@ -74,8 +78,8 @@ public List get() { * supply an appropriate trustSource except in extraordinary circumstances (e.g. testing with dynamically-generated * certificates). * - * @param cipherSuites cipher suites to allow when connecting to the upstream server - * @param trustSource the trust store that will be used to validate upstream servers' certificates, or null to accept all upstream server certificates + * @param cipherSuites cipher suites to allow when connecting to the upstream server + * @param trustSource the trust store that will be used to validate upstream servers' certificates, or null to accept all upstream server certificates * @return an SSLContext to connect to upstream servers with */ public static SslContext getUpstreamServerSslContext(Collection cipherSuites, TrustSource trustSource) { diff --git a/app/src/main/java/net/lightbody/bmp/mitm/util/TrustUtil.java b/app/src/main/java/net/lightbody/bmp/mitm/util/TrustUtil.java index 156f910..536e7d5 100644 --- a/app/src/main/java/net/lightbody/bmp/mitm/util/TrustUtil.java +++ b/app/src/main/java/net/lightbody/bmp/mitm/util/TrustUtil.java @@ -2,18 +2,17 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; + import net.lightbody.bmp.mitm.exception.KeyStoreAccessException; import net.lightbody.bmp.mitm.exception.TrustSourceException; import net.lightbody.bmp.mitm.exception.UncheckedIOException; import net.lightbody.bmp.mitm.tools.DefaultSecurityProviderTool; import net.lightbody.bmp.mitm.tools.SecurityProviderTool; import net.lightbody.bmp.util.ClasspathResourceUtil; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; import java.io.StringReader; import java.nio.charset.StandardCharsets; import java.security.KeyStore; @@ -28,27 +27,27 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + /** * Utility class for interacting with the default trust stores on this JVM. */ public class TrustUtil { + /** + * Empty X509 certificate array, useful for indicating an empty root CA trust store. + */ + public static final X509Certificate[] EMPTY_CERTIFICATE_ARRAY = new X509Certificate[0]; private static final Logger log = LoggerFactory.getLogger(TrustUtil.class); - /** * Regex that matches a single certificate within a PEM file containing (potentially multiple) certificates. */ private static final Pattern CA_PEM_PATTERN = Pattern.compile("-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----", Pattern.DOTALL); - /** * The file containing the built-in list of trusted CAs. */ private static final String DEFAULT_TRUSTED_CA_RESOURCE = "/cacerts.pem"; - - /** - * Empty X509 certificate array, useful for indicating an empty root CA trust store. - */ - public static final X509Certificate[] EMPTY_CERTIFICATE_ARRAY = new X509Certificate[0]; - /** * Security provider used to transform PEM files into Certificates. * TODO: Modify the architecture of TrustUtil and TrustSource so that they do not need a hard-coded SecurityProviderTool. @@ -103,7 +102,7 @@ public static X509Certificate[] getBuiltinTrustedCAs() { * Returns the list of root CAs trusted by default in this JVM, according to the TrustManager returned by * {@link #getDefaultJavaTrustManager()}. */ - public static X509Certificate[] getJavaTrustedCAs() { + public static X509Certificate[] getJavaTrustedCAs() { return javaTrustedCAs.get(); } diff --git a/app/src/main/java/net/lightbody/bmp/proxy/BlacklistEntry.java b/app/src/main/java/net/lightbody/bmp/proxy/BlacklistEntry.java index 671fff9..71d078d 100644 --- a/app/src/main/java/net/lightbody/bmp/proxy/BlacklistEntry.java +++ b/app/src/main/java/net/lightbody/bmp/proxy/BlacklistEntry.java @@ -24,8 +24,8 @@ public BlacklistEntry(String urlPattern, int statusCode) { /** * Creates a new BlacklistEntry which will match both a URL and an HTTP method * - * @param urlPattern URL pattern to blacklist - * @param statusCode status code to return for blacklisted URL + * @param urlPattern URL pattern to blacklist + * @param statusCode status code to return for blacklisted URL * @param httpMethodPattern HTTP method to match (e.g. GET, PUT, PATCH, etc.) */ public BlacklistEntry(String urlPattern, int statusCode, String httpMethodPattern) { @@ -42,7 +42,7 @@ public BlacklistEntry(String urlPattern, int statusCode, String httpMethodPatter * Determines if this BlacklistEntry matches the given URL. Attempts to match both the URL and the * HTTP method. * - * @param url possibly-blacklisted URL + * @param url possibly-blacklisted URL * @param httpMethod HTTP method this URL is being accessed with * @return true if the URL matches this BlacklistEntry */ diff --git a/app/src/main/java/net/lightbody/bmp/proxy/CaptureType.java b/app/src/main/java/net/lightbody/bmp/proxy/CaptureType.java index cf3b98c..9a6e536 100644 --- a/app/src/main/java/net/lightbody/bmp/proxy/CaptureType.java +++ b/app/src/main/java/net/lightbody/bmp/proxy/CaptureType.java @@ -21,7 +21,6 @@ public enum CaptureType { * Non-binary HTTP request content, such as post data or other text-based request payload. * See {@link net.lightbody.bmp.util.BrowserMobHttpUtil#hasTextualContent(String)} for a list of Content-Types that * are considered non-binary. - * */ REQUEST_CONTENT, diff --git a/app/src/main/java/net/lightbody/bmp/proxy/RewriteRule.java b/app/src/main/java/net/lightbody/bmp/proxy/RewriteRule.java index c5ec97e..5faf166 100644 --- a/app/src/main/java/net/lightbody/bmp/proxy/RewriteRule.java +++ b/app/src/main/java/net/lightbody/bmp/proxy/RewriteRule.java @@ -24,13 +24,21 @@ public String getReplace() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } RewriteRule that = (RewriteRule) o; - if (!pattern.equals(that.pattern)) return false; - if (!replace.equals(that.replace)) return false; + if (!pattern.equals(that.pattern)) { + return false; + } + if (!replace.equals(that.replace)) { + return false; + } return true; } diff --git a/app/src/main/java/net/lightbody/bmp/proxy/Whitelist.java b/app/src/main/java/net/lightbody/bmp/proxy/Whitelist.java index ea015ee..868e46a 100644 --- a/app/src/main/java/net/lightbody/bmp/proxy/Whitelist.java +++ b/app/src/main/java/net/lightbody/bmp/proxy/Whitelist.java @@ -15,14 +15,13 @@ * whitelist reference to a new whitelist. */ public class Whitelist { - private final List patterns; - private final int statusCode; - private final boolean enabled; - /** * A disabled Whitelist. */ public static final Whitelist WHITELIST_DISABLED = new Whitelist(); + private final List patterns; + private final int statusCode; + private final boolean enabled; /** * Creates an empty, disabled Whitelist. @@ -56,7 +55,7 @@ public Whitelist(String[] patterns, int statusCode) { * Creates a whitelist for the specified patterns, returning the given statusCode when a URL does not match one of the patterns. * A null or empty collection will result in an empty whitelist. * - * @param patterns URL-matching regular expression patterns to whitelist + * @param patterns URL-matching regular expression patterns to whitelist * @param statusCode the HTTP status code to return when a request URL matches a whitelist pattern */ public Whitelist(Collection patterns, int statusCode) { diff --git a/app/src/main/java/net/lightbody/bmp/proxy/dns/BasicHostResolver.java b/app/src/main/java/net/lightbody/bmp/proxy/dns/AbstractBasicHostResolver.java similarity index 97% rename from app/src/main/java/net/lightbody/bmp/proxy/dns/BasicHostResolver.java rename to app/src/main/java/net/lightbody/bmp/proxy/dns/AbstractBasicHostResolver.java index b07bd6d..8378e93 100644 --- a/app/src/main/java/net/lightbody/bmp/proxy/dns/BasicHostResolver.java +++ b/app/src/main/java/net/lightbody/bmp/proxy/dns/AbstractBasicHostResolver.java @@ -9,7 +9,7 @@ * Use this class to supply a {@link HostResolver} to {@link net.lightbody.bmp.BrowserMobProxy#setHostNameResolver(AdvancedHostResolver)} * if you do not need {@link AdvancedHostResolver} functionality. */ -public abstract class BasicHostResolver implements AdvancedHostResolver { +public abstract class AbstractBasicHostResolver implements AdvancedHostResolver { @Override public void remapHosts(Map hostRemappings) { throw new UnsupportedOperationException(new Throwable().getStackTrace()[0].getMethodName() + " is not supported by this host resolver (" + this.getClass().getName() + ")"); diff --git a/app/src/main/java/net/lightbody/bmp/proxy/dns/AbstractHostNameRemapper.java b/app/src/main/java/net/lightbody/bmp/proxy/dns/AbstractHostNameRemapper.java index 253b5f4..20040e3 100644 --- a/app/src/main/java/net/lightbody/bmp/proxy/dns/AbstractHostNameRemapper.java +++ b/app/src/main/java/net/lightbody/bmp/proxy/dns/AbstractHostNameRemapper.java @@ -19,7 +19,7 @@ public abstract class AbstractHostNameRemapper implements AdvancedHostResolver { * Host name remappings, maintained as a reference to an ImmutableMap. The ImmutableMap type is specified explicitly because ImmutableMap * guarantees the iteration order of the map's entries. Specifying ImmutableMap also makes clear that the underlying map will never change, * and that any modifications to the host name remappings will result in an entirely new map. - * + *

* The current implementation does not actually use any of the special features of AtomicReference, but it does rely on synchronizing on * the AtomicReference when performing write operations. It could be replaced by a volatile reference to a Map and separate lock object. */ diff --git a/app/src/main/java/net/lightbody/bmp/proxy/dns/AdvancedHostResolver.java b/app/src/main/java/net/lightbody/bmp/proxy/dns/AdvancedHostResolver.java index 5773e36..78b4f6a 100644 --- a/app/src/main/java/net/lightbody/bmp/proxy/dns/AdvancedHostResolver.java +++ b/app/src/main/java/net/lightbody/bmp/proxy/dns/AdvancedHostResolver.java @@ -71,7 +71,7 @@ public interface AdvancedHostResolver extends HostResolver { * DNS implementation. For example, the Oracle JVM's DNS cache only supports timeouts in whole seconds, so specifying a timeout of 1200ms will result * in a timeout of 1 second. * - * @param timeout maximum lookup time + * @param timeout maximum lookup time * @param timeUnit units of the timeout value */ void setPositiveDNSCacheTimeout(int timeout, TimeUnit timeUnit); @@ -83,7 +83,7 @@ public interface AdvancedHostResolver extends HostResolver { * DNS implementation. For example, the Oracle JVM's DNS cache only supports timeouts in whole seconds, so specifying a timeout of 1200ms will result * in a timeout of 1 second. * - * @param timeout maximum lookup time + * @param timeout maximum lookup time * @param timeUnit units of the timeout value */ void setNegativeDNSCacheTimeout(int timeout, TimeUnit timeUnit); diff --git a/app/src/main/java/net/lightbody/bmp/proxy/dns/ChainedHostResolver.java b/app/src/main/java/net/lightbody/bmp/proxy/dns/ChainedHostResolver.java index dd5ae00..a88fe1c 100644 --- a/app/src/main/java/net/lightbody/bmp/proxy/dns/ChainedHostResolver.java +++ b/app/src/main/java/net/lightbody/bmp/proxy/dns/ChainedHostResolver.java @@ -25,13 +25,13 @@ *

* The atomic write methods specified by AdvancedHostResolver are: *

    - *
  • {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#remapHost(String, String)}
  • - *
  • {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#remapHosts(java.util.Map)}
  • - *
  • {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#removeHostRemapping(String)}
  • - *
  • {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#clearHostRemappings()}
  • - *
  • {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#setNegativeDNSCacheTimeout(int, java.util.concurrent.TimeUnit)}
  • - *
  • {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#setPositiveDNSCacheTimeout(int, java.util.concurrent.TimeUnit)}
  • - *
  • {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#clearDNSCache()}
  • + *
  • {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#remapHost(String, String)}
  • + *
  • {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#remapHosts(java.util.Map)}
  • + *
  • {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#removeHostRemapping(String)}
  • + *
  • {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#clearHostRemappings()}
  • + *
  • {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#setNegativeDNSCacheTimeout(int, java.util.concurrent.TimeUnit)}
  • + *
  • {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#setPositiveDNSCacheTimeout(int, java.util.concurrent.TimeUnit)}
  • + *
  • {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#clearDNSCache()}
  • *
*/ public class ChainedHostResolver implements AdvancedHostResolver { diff --git a/app/src/main/java/net/lightbody/bmp/proxy/dns/DelegatingHostResolver.java b/app/src/main/java/net/lightbody/bmp/proxy/dns/DelegatingHostResolver.java index 7f6bdf2..7074b02 100644 --- a/app/src/main/java/net/lightbody/bmp/proxy/dns/DelegatingHostResolver.java +++ b/app/src/main/java/net/lightbody/bmp/proxy/dns/DelegatingHostResolver.java @@ -10,7 +10,7 @@ /** * A LittleProxy HostResolver that delegates to the specified {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver} instance. This class * serves as a bridge between {@link AdvancedHostResolver} and {@link org.littleshoot.proxy.HostResolver}. -*/ + */ public class DelegatingHostResolver implements org.littleshoot.proxy.HostResolver { private volatile AdvancedHostResolver resolver; diff --git a/app/src/main/java/net/lightbody/bmp/proxy/dns/DnsJavaResolver.java b/app/src/main/java/net/lightbody/bmp/proxy/dns/DnsJavaResolver.java index 432032e..d123adf 100644 --- a/app/src/main/java/net/lightbody/bmp/proxy/dns/DnsJavaResolver.java +++ b/app/src/main/java/net/lightbody/bmp/proxy/dns/DnsJavaResolver.java @@ -1,6 +1,7 @@ package net.lightbody.bmp.proxy.dns; import com.google.common.net.InetAddresses; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xbill.DNS.AAAARecord; @@ -28,16 +29,14 @@ */ public class DnsJavaResolver extends AbstractHostNameRemapper implements AdvancedHostResolver { private static final Logger log = LoggerFactory.getLogger(DnsJavaResolver.class); - - /** - * DNS cache used for dnsjava lookups. - */ - private final Cache cache = new Cache(); - /** * Maximum number of times to retry a DNS lookup due to a failure to connect to the DNS server. */ private static final int DNS_NETWORK_FAILURE_RETRY_COUNT = 5; + /** + * DNS cache used for dnsjava lookups. + */ + private final Cache cache = new Cache(); @Override public void clearDNSCache() { diff --git a/app/src/main/java/net/lightbody/bmp/util/BrowserMobHttpUtil.java b/app/src/main/java/net/lightbody/bmp/util/BrowserMobHttpUtil.java index c260ca7..9947fa3 100644 --- a/app/src/main/java/net/lightbody/bmp/util/BrowserMobHttpUtil.java +++ b/app/src/main/java/net/lightbody/bmp/util/BrowserMobHttpUtil.java @@ -3,12 +3,10 @@ import com.google.common.io.BaseEncoding; import com.google.common.net.HostAndPort; import com.google.common.net.MediaType; -import io.netty.buffer.ByteBuf; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; + import net.lightbody.bmp.exception.DecompressionException; import net.lightbody.bmp.exception.UnsupportedCharsetException; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,12 +22,15 @@ import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; + /** * Utility class with static methods for processing HTTP requests and responses. */ public class BrowserMobHttpUtil { - private static final Logger log = LoggerFactory.getLogger(BrowserMobHttpUtil.class); - /** * Default MIME content type if no Content-Type header is present. According to the HTTP 1.1 spec, section 7.2.1: *
@@ -41,7 +42,6 @@ public class BrowserMobHttpUtil {
      * 
*/ public static final String UNKNOWN_CONTENT_TYPE = "application/octet-stream"; - /** * The default charset when the Content-Type header does not specify a charset. According to RFC 7231 Appendix B: *
@@ -50,16 +50,16 @@ public class BrowserMobHttpUtil {
      *     Likewise, special treatment of ISO-8859-1 has been removed from the
      *     Accept-Charset header field.
      * 
- * + *

* Technically, we would have to determine the charset on a per-content-type basis, but generally speaking, UTF-8 is a * pretty safe default. (NOTE: In the previous HTTP/1.1 spec, section 3.7.1, the default charset was defined as ISO-8859-1.) */ public static final Charset DEFAULT_HTTP_CHARSET = StandardCharsets.UTF_8; - /** * Buffer size when decompressing content. */ public static final int DECOMPRESS_BUFFER_SIZE = 16192; + private static final Logger log = LoggerFactory.getLogger(BrowserMobHttpUtil.class); /** * Returns the size of the headers, including the 2 CRLFs at the end of the header block. @@ -83,13 +83,13 @@ public static long getHeaderSize(HttpHeaders headers) { * @return decompressed bytes * @throws DecompressionException thrown if the fullMessage cannot be read or decompressed for any reason */ - public static byte[] decompressContents(byte[] fullMessage,String type) throws DecompressionException { + public static byte[] decompressContents(byte[] fullMessage, String type) throws DecompressionException { InflaterInputStream reader = null; ByteArrayOutputStream uncompressed; try { - if(type.equalsIgnoreCase(HttpHeaders.Values.GZIP)) { + if (type.equalsIgnoreCase(HttpHeaders.Values.GZIP)) { reader = new GZIPInputStream(new ByteArrayInputStream(fullMessage)); - }else if(type.equalsIgnoreCase(HttpHeaders.Values.DEFLATE)) { + } else if (type.equalsIgnoreCase(HttpHeaders.Values.DEFLATE)) { reader = new InflaterInputStream(new ByteArrayInputStream(fullMessage), new Inflater(true)); } @@ -135,11 +135,11 @@ public static byte[] decompressContents(byte[] fullMessage,String type) throws D public static boolean hasTextualContent(String contentType) { return contentType != null && (contentType.startsWith("text/") || - contentType.startsWith("application/x-javascript") || - contentType.startsWith("application/javascript") || - contentType.startsWith("application/json") || - contentType.startsWith("application/xml") || - contentType.startsWith("application/xhtml+xml") + contentType.startsWith("application/x-javascript") || + contentType.startsWith("application/javascript") || + contentType.startsWith("application/json") || + contentType.startsWith("application/xml") || + contentType.startsWith("application/xhtml+xml") ); } @@ -190,7 +190,7 @@ public static Charset readCharsetInContentTypeHeader(String contentTypeHeader) t MediaType mediaType; try { - mediaType = MediaType.parse(contentTypeHeader); + mediaType = MediaType.parse(contentTypeHeader); } catch (IllegalArgumentException e) { log.info("Unable to parse Content-Type header: {}. Content-Type header will be ignored.", contentTypeHeader, e); return null; @@ -275,7 +275,7 @@ public static boolean isRedirect(HttpResponse httpResponse) { * parsing the hostname, but makes no guarantees. In general, it should be validated externally, if necessary. * * @param hostWithPort string containing a hostname and optional port - * @param portNumber port to remove from the string + * @param portNumber port to remove from the string * @return string with the specified port removed, or the original string if it did not contain the portNumber */ public static String removeMatchingPort(String hostWithPort, int portNumber) { diff --git a/app/src/main/java/net/lightbody/bmp/util/BrowserMobProxyUtil.java b/app/src/main/java/net/lightbody/bmp/util/BrowserMobProxyUtil.java index 62f33cf..f11a71c 100644 --- a/app/src/main/java/net/lightbody/bmp/util/BrowserMobProxyUtil.java +++ b/app/src/main/java/net/lightbody/bmp/util/BrowserMobProxyUtil.java @@ -2,11 +2,13 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; + import net.lightbody.bmp.core.har.Har; import net.lightbody.bmp.core.har.HarEntry; import net.lightbody.bmp.core.har.HarLog; import net.lightbody.bmp.core.har.HarPage; import net.lightbody.bmp.mitm.exception.UncheckedIOException; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,7 +47,7 @@ public String get() { * the specified pageRef. Does not perform a "deep copy", so any subsequent modification to the entries or pages will * be reflected in the copied har. * - * @param har existing har to copy + * @param har existing har to copy * @param pageRef last page ID to copy * @return copy of a {@link Har} with entries and pages from the original har, or null if the input har is null */ diff --git a/app/src/main/java/net/lightbody/bmp/util/ClasspathResourceUtil.java b/app/src/main/java/net/lightbody/bmp/util/ClasspathResourceUtil.java index 66bd3b1..2cb339c 100644 --- a/app/src/main/java/net/lightbody/bmp/util/ClasspathResourceUtil.java +++ b/app/src/main/java/net/lightbody/bmp/util/ClasspathResourceUtil.java @@ -1,6 +1,7 @@ package net.lightbody.bmp.util; import com.google.common.io.CharStreams; + import net.lightbody.bmp.mitm.exception.UncheckedIOException; import java.io.FileNotFoundException; @@ -21,7 +22,7 @@ public class ClasspathResourceUtil { * method throws a FileNotFoundException wrapped in an UncheckedIOException. * * @param resource classpath resource to load - * @param charset charset to use to decode the classpath resource + * @param charset charset to use to decode the classpath resource * @return a String * @throws UncheckedIOException if the classpath resource cannot be found or cannot be read for any reason */ diff --git a/app/src/main/java/net/lightbody/bmp/util/HttpMessageContents.java b/app/src/main/java/net/lightbody/bmp/util/HttpMessageContents.java index 969a4d8..d904f1e 100644 --- a/app/src/main/java/net/lightbody/bmp/util/HttpMessageContents.java +++ b/app/src/main/java/net/lightbody/bmp/util/HttpMessageContents.java @@ -1,17 +1,19 @@ package net.lightbody.bmp.util; -import io.netty.handler.codec.http.FullHttpMessage; -import io.netty.handler.codec.http.HttpHeaders; import net.lightbody.bmp.exception.UnsupportedCharsetException; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.charset.Charset; +import io.netty.handler.codec.http.FullHttpMessage; +import io.netty.handler.codec.http.HttpHeaders; + /** * Helper class to wrap the contents of an {@link io.netty.handler.codec.http.HttpMessage}. Contains convenience methods to extract and * manipulate the contents of the wrapped {@link io.netty.handler.codec.http.HttpMessage}. - * + *

* TODO: Currently this class only wraps FullHttpMessages, since it must modify the Content-Length header; determine if this may be applied to chunked messages as well */ public class HttpMessageContents { @@ -27,36 +29,6 @@ public HttpMessageContents(FullHttpMessage httpMessage) { this.httpMessage = httpMessage; } - /** - * Replaces the contents of the wrapped HttpMessage with the specified text contents, encoding them in the character set specified by the - * message's Content-Type header. Note that this method does not update the Content-Type header, so if the content type will change as a - * result of this call, the Content-Type header should be updated before calling this method. - * - * @param newContents new message contents - */ - public void setTextContents(String newContents) { - HttpObjectUtil.replaceTextHttpEntityBody(httpMessage, newContents); - - // replaced the contents, so clear the local cache - textContents = null; - binaryContents = null; - } - - /** - * Replaces the contents of the wrapped HttpMessage with the specified binary contents. Note that this method does not update the - * Content-Type header, so if the content type will change as a result of this call, the Content-Type header should be updated before - * calling this method. - * - * @param newBinaryContents new message contents - */ - public void setBinaryContents(byte[] newBinaryContents) { - HttpObjectUtil.replaceBinaryHttpEntityBody(httpMessage, newBinaryContents); - - // replaced the contents, so clear the local cache - binaryContents = null; - textContents = null; - } - /** * Retrieves the contents of this message as a String, decoded according to the message's Content-Type header. This method caches * the contents, so repeated calls to this method should not incur a penalty; however, modifications to the message contents @@ -74,6 +46,21 @@ public String getTextContents() throws java.nio.charset.UnsupportedCharsetExcept return textContents; } + /** + * Replaces the contents of the wrapped HttpMessage with the specified text contents, encoding them in the character set specified by the + * message's Content-Type header. Note that this method does not update the Content-Type header, so if the content type will change as a + * result of this call, the Content-Type header should be updated before calling this method. + * + * @param newContents new message contents + */ + public void setTextContents(String newContents) { + HttpObjectUtil.replaceTextHttpEntityBody(httpMessage, newContents); + + // replaced the contents, so clear the local cache + textContents = null; + binaryContents = null; + } + /** * Retrieves the binary contents of this message. This method caches the contents, so repeated calls to this method should not incur a * penalty; however, modifications to the message contents outside of this class will result in stale data returned from this method. @@ -89,6 +76,21 @@ public byte[] getBinaryContents() { return binaryContents; } + /** + * Replaces the contents of the wrapped HttpMessage with the specified binary contents. Note that this method does not update the + * Content-Type header, so if the content type will change as a result of this call, the Content-Type header should be updated before + * calling this method. + * + * @param newBinaryContents new message contents + */ + public void setBinaryContents(byte[] newBinaryContents) { + HttpObjectUtil.replaceBinaryHttpEntityBody(httpMessage, newBinaryContents); + + // replaced the contents, so clear the local cache + binaryContents = null; + textContents = null; + } + /** * Retrieves the Content-Type header of this message. If no Content-Type is present, returns the assumed default Content-Type (see * {@link BrowserMobHttpUtil#UNKNOWN_CONTENT_TYPE}). diff --git a/app/src/main/java/net/lightbody/bmp/util/HttpObjectUtil.java b/app/src/main/java/net/lightbody/bmp/util/HttpObjectUtil.java index 169a878..e93eccd 100644 --- a/app/src/main/java/net/lightbody/bmp/util/HttpObjectUtil.java +++ b/app/src/main/java/net/lightbody/bmp/util/HttpObjectUtil.java @@ -1,15 +1,17 @@ package net.lightbody.bmp.util; -import io.netty.handler.codec.http.FullHttpMessage; -import io.netty.handler.codec.http.HttpContent; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpMessage; import net.lightbody.bmp.exception.UnsupportedCharsetException; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.charset.Charset; +import io.netty.handler.codec.http.FullHttpMessage; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMessage; + /** * Utility class to assist with manipulation of {@link io.netty.handler.codec.http.HttpObject} instances, including * {@link io.netty.handler.codec.http.HttpMessage} and {@link io.netty.handler.codec.http.HttpContent}. @@ -21,10 +23,10 @@ public class HttpObjectUtil { * Replaces the entity body of the message with the specified contents. Encodes the message contents according to charset in the message's * Content-Type header, or uses {@link BrowserMobHttpUtil#DEFAULT_HTTP_CHARSET} if none is specified. * Note: If the charset of the message is not supported on this platform, this will throw an {@link java.nio.charset.UnsupportedCharsetException}. - * + *

* TODO: Currently this method only works for FullHttpMessages, since it must modify the Content-Length header; determine if this may be applied to chunked messages as well * - * @param message the HTTP message to manipulate + * @param message the HTTP message to manipulate * @param newContents the new entity body contents * @throws java.nio.charset.UnsupportedCharsetException if the charset in the message is not supported on this platform */ @@ -36,7 +38,7 @@ public static void replaceTextHttpEntityBody(FullHttpMessage message, String new try { messageCharset = BrowserMobHttpUtil.readCharsetInContentTypeHeader(contentTypeHeader); } catch (UnsupportedCharsetException e) { - java.nio.charset.UnsupportedCharsetException cause = e.getUnsupportedCharsetExceptionCause() ; + java.nio.charset.UnsupportedCharsetException cause = e.getUnsupportedCharsetExceptionCause(); log.error("Found unsupported character set in Content-Type header '{}' while attempting to replace contents of HTTP message.", contentTypeHeader, cause); throw cause; @@ -56,7 +58,7 @@ public static void replaceTextHttpEntityBody(FullHttpMessage message, String new * Replaces an HTTP entity body with the specified binary contents. * TODO: Currently this method only works for FullHttpMessages, since it must modify the Content-Length header; determine if this may be applied to chunked messages as well * - * @param message the HTTP message to manipulate + * @param message the HTTP message to manipulate * @param newBinaryContents the new entity body contents */ public static void replaceBinaryHttpEntityBody(FullHttpMessage message, byte[] newBinaryContents) { @@ -74,7 +76,7 @@ public static void replaceBinaryHttpEntityBody(FullHttpMessage message, byte[] n * the character set is not specified or is unknown, you still must specify a suitable default charset (see {@link BrowserMobHttpUtil#DEFAULT_HTTP_CHARSET}). * * @param httpContent HTTP content object to extract the entity body from - * @param charset character set of the entity body + * @param charset character set of the entity body * @return String representation of the entity body * @throws IllegalArgumentException if the charset is null */ diff --git a/app/src/main/java/net/lightbody/bmp/util/HttpUtil.java b/app/src/main/java/net/lightbody/bmp/util/HttpUtil.java index 2cc2a21..360db12 100644 --- a/app/src/main/java/net/lightbody/bmp/util/HttpUtil.java +++ b/app/src/main/java/net/lightbody/bmp/util/HttpUtil.java @@ -1,14 +1,15 @@ package net.lightbody.bmp.util; import com.google.common.net.HostAndPort; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpRequest; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import java.util.Locale; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpRequest; + /** * Contains utility methods for netty {@link HttpRequest} and related objects. */ diff --git a/app/src/main/java/org/littleshoot/proxy/mitm/Authority.java b/app/src/main/java/org/littleshoot/proxy/mitm/Authority.java index a7451f5..95a616f 100755 --- a/app/src/main/java/org/littleshoot/proxy/mitm/Authority.java +++ b/app/src/main/java/org/littleshoot/proxy/mitm/Authority.java @@ -6,7 +6,7 @@ /** * Parameter object holding personal informations given to a SSLEngineSource. - * + *

* XXX consider to inline within the interface SslEngineSource, if MITM is core */ public class Authority { @@ -37,7 +37,7 @@ public Authority() { password = "Be Your Own Lantern".toCharArray(); organization = "LittleProxy-mitm"; // proxy name commonName = organization + ", describe proxy here"; // MITM is bad - // normally + // normally organizationalUnitName = "Certificate Authority"; certOrganization = organization; // proxy name certOrganizationalUnitName = organization @@ -49,9 +49,9 @@ public Authority() { * authority informations */ public Authority(File keyStoreDir, String alias, char[] password, - String commonName, String organization, - String organizationalUnitName, String certOrganization, - String certOrganizationalUnitName) { + String commonName, String organization, + String organizationalUnitName, String certOrganization, + String certOrganizationalUnitName) { super(); this.keyStoreDir = keyStoreDir; this.alias = alias; diff --git a/app/src/main/java/org/littleshoot/proxy/mitm/BouncyCastleSslEngineSource.java b/app/src/main/java/org/littleshoot/proxy/mitm/BouncyCastleSslEngineSource.java index 24973dc..c91044c 100755 --- a/app/src/main/java/org/littleshoot/proxy/mitm/BouncyCastleSslEngineSource.java +++ b/app/src/main/java/org/littleshoot/proxy/mitm/BouncyCastleSslEngineSource.java @@ -1,6 +1,14 @@ package org.littleshoot.proxy.mitm; -import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import org.apache.commons.io.IOUtils; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.operator.OperatorCreationException; +import org.littleshoot.proxy.SslEngineSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileInputStream; @@ -28,28 +36,20 @@ import javax.net.ssl.SSLParameters; import javax.net.ssl.TrustManager; -import org.apache.commons.io.IOUtils; -import org.bouncycastle.openssl.jcajce.JcaPEMWriter; -import org.bouncycastle.operator.OperatorCreationException; -import org.littleshoot.proxy.SslEngineSource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; /** * A {@link SslEngineSource} which creates a key store with a Root Certificate * Authority. The certificates are generated lazily if the given key store file * doesn't yet exist. - * + *

* The root certificate is exported in PEM format to be used in a browser. The * proxy application presents for every host a dynamically created certificate * to the browser, signed by this certificate authority. - * + *

* This facilitates the proxy to handle as a "Man In The Middle" to filter the * decrypted content in clear text. - * + *

* The hard part was done by mawoki. It's derived from Zed Attack Proxy (ZAP). * ZAP is an HTTP/HTTPS proxy for assessing web application security. Copyright * 2011 mawoki@ymail.com Licensed under the Apache License, Version 2.0 @@ -84,24 +84,19 @@ public class BouncyCastleSslEngineSource implements SslEngineSource { * initializes a SSL context. Exceptions will be thrown to let the manager * decide how to react. Don't install a MITM manager in the proxy in case of * a failure. - * - * @param authority - * a parameter object to provide personal informations of the - * Certificate Authority and the dynamic certificates. - * + * + * @param authority a parameter object to provide personal informations of the + * Certificate Authority and the dynamic certificates. * @param trustAllServers - * * @param sendCerts - * - * @param sslContexts - * a cache to store dynamically created server certificates. - * Generation takes between 50 to 500ms, but only once per - * thread, since there is a connection cache too. It's save to - * give a null cache to prevent memory or locking issues. + * @param sslContexts a cache to store dynamically created server certificates. + * Generation takes between 50 to 500ms, but only once per + * thread, since there is a connection cache too. It's save to + * give a null cache to prevent memory or locking issues. */ public BouncyCastleSslEngineSource(Authority authority, - boolean trustAllServers, boolean sendCerts, - Cache sslContexts) + boolean trustAllServers, boolean sendCerts, + Cache sslContexts) throws GeneralSecurityException, OperatorCreationException, RootCertificateException, IOException { this.authority = authority; @@ -118,17 +113,14 @@ public BouncyCastleSslEngineSource(Authority authority, * dynamically created server certificates. Exceptions will be thrown to let * the manager decide how to react. Don't install a MITM manager in the * proxy in case of a failure. - * - * @param authority - * a parameter object to provide personal informations of the - * Certificate Authority and the dynamic certificates. - * + * + * @param authority a parameter object to provide personal informations of the + * Certificate Authority and the dynamic certificates. * @param trustAllServers - * * @param sendCerts */ public BouncyCastleSslEngineSource(Authority authority, - boolean trustAllServers, boolean sendCerts) + boolean trustAllServers, boolean sendCerts) throws RootCertificateException, GeneralSecurityException, IOException, OperatorCreationException { this(authority, trustAllServers, sendCerts, @@ -145,7 +137,9 @@ private static Cache initDefaultCertificateCache() { private void filterWeakCipherSuites(SSLEngine sslEngine) { List ciphers = new LinkedList(); for (String each : sslEngine.getEnabledCipherSuites()) { - if (each.equals("TLS_DHE_RSA_WITH_AES_128_CBC_SHA") || each.equals("TLS_DHE_RSA_WITH_AES_256_CBC_SHA")) { + if ("TLS_DHE_RSA_WITH_AES_128_CBC_SHA".equals(each) + || "TLS_DHE_RSA_WITH_AES_256_CBC_SHA".equals(each) + ) { LOG.debug("Removed cipher {}", each); } else { ciphers.add(each); @@ -166,6 +160,7 @@ private void filterWeakCipherSuites(SSLEngine sslEngine) { } } + @Override public SSLEngine newSslEngine() { SSLEngine sslEngine = sslContext.createSSLEngine(); filterWeakCipherSuites(sslEngine); @@ -246,7 +241,7 @@ private void initializeSSLContext() throws GeneralSecurityException, trustManagers = InsecureTrustManagerFactory.INSTANCE .getTrustManagers(); } else { - trustManagers = new TrustManager[] { new MergeTrustManager(ks) }; + trustManagers = new TrustManager[]{new MergeTrustManager(ks)}; } KeyManager[] keyManagers; @@ -282,25 +277,17 @@ private KeyStore loadKeyStore() throws GeneralSecurityException, * Generates an 1024 bit RSA key pair using SHA1PRNG. Thoughts: 2048 takes * much longer time on older CPUs. And for almost every client, 1024 is * sufficient. - * + *

* Derived from Zed Attack Proxy (ZAP). ZAP is an HTTP/HTTPS proxy for * assessing web application security. Copyright 2011 mawoki@ymail.com * Licensed under the Apache License, Version 2.0 - * - * @param commonName - * the common name to use in the server certificate - * - * @param subjectAlternativeNames - * a List of the subject alternative names to use in the server - * certificate, could be empty, but must not be null - * - * @see org.parosproxy.paros.security.SslCertificateServiceImpl. - * createCertForHost(String) - * @see org.parosproxy.paros.network.SSLConnector.getTunnelSSLSocketFactory( - * String) + * + * @param commonName the common name to use in the server certificate + * @param subjectAlternativeNames a List of the subject alternative names to use in the server + * certificate, could be empty, but must not be null */ public SSLEngine createCertForHost(final String commonName, - final SubjectAlternativeNameHolder subjectAlternativeNames) + final SubjectAlternativeNameHolder subjectAlternativeNames) throws GeneralSecurityException, OperatorCreationException, IOException, ExecutionException { if (commonName == null) { @@ -328,7 +315,7 @@ public SSLContext call() throws Exception { } private SSLContext createServerContext(String commonName, - SubjectAlternativeNameHolder subjectAlternativeNames) + SubjectAlternativeNameHolder subjectAlternativeNames) throws GeneralSecurityException, IOException, OperatorCreationException { @@ -346,7 +333,7 @@ private SSLContext createServerContext(String commonName, } public void initializeServerCertificates(String commonName, - SubjectAlternativeNameHolder subjectAlternativeNames) + SubjectAlternativeNameHolder subjectAlternativeNames) throws GeneralSecurityException, OperatorCreationException, IOException { diff --git a/app/src/main/java/org/littleshoot/proxy/mitm/CertificateHelper.java b/app/src/main/java/org/littleshoot/proxy/mitm/CertificateHelper.java index a4358e2..fd823e2 100755 --- a/app/src/main/java/org/littleshoot/proxy/mitm/CertificateHelper.java +++ b/app/src/main/java/org/littleshoot/proxy/mitm/CertificateHelper.java @@ -1,5 +1,31 @@ package org.littleshoot.proxy.mitm; +import org.apache.commons.io.IOUtils; +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.X500NameBuilder; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.KeyPurposeId; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.asn1.x509.SubjectKeyIdentifier; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.bc.BcX509ExtensionUtils; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.math.BigInteger; @@ -30,46 +56,12 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; -import org.apache.commons.io.IOUtils; -import org.bouncycastle.asn1.ASN1EncodableVector; -import org.bouncycastle.asn1.ASN1InputStream; -import org.bouncycastle.asn1.ASN1Sequence; -import org.bouncycastle.asn1.DERSequence; -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x500.X500NameBuilder; -import org.bouncycastle.asn1.x500.style.BCStyle; -import org.bouncycastle.asn1.x509.BasicConstraints; -import org.bouncycastle.asn1.x509.Extension; -import org.bouncycastle.asn1.x509.KeyPurposeId; -import org.bouncycastle.asn1.x509.KeyUsage; -import org.bouncycastle.asn1.x509.SubjectKeyIdentifier; -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.X509v3CertificateBuilder; -import org.bouncycastle.cert.bc.BcX509ExtensionUtils; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.OperatorCreationException; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public final class CertificateHelper { - private static final Logger log = LoggerFactory.getLogger(CertificateHelper.class); - public static final String PROVIDER_NAME = BouncyCastleProvider.PROVIDER_NAME; - - static { - Security.addProvider(new BouncyCastleProvider()); - } - + private static final Logger log = LoggerFactory.getLogger(CertificateHelper.class); private static final String KEYGEN_ALGORITHM = "RSA"; - private static final String SECURE_RANDOM_ALGORITHM = "SHA1PRNG"; - /** * The signature algorithm starting with the message digest to use when * signing certificates. On 64-bit systems this should be set to SHA512, on @@ -78,29 +70,25 @@ public final class CertificateHelper { * http://crypto.stackexchange.com/questions/26336/sha512-faster-than-sha256 */ private static final String SIGNATURE_ALGORITHM = (is32BitJvm() ? "SHA256" : "SHA512") + "WithRSAEncryption"; - private static final int ROOT_KEYSIZE = 2048; - private static final int FAKE_KEYSIZE = 1024; - - /** The milliseconds of a day */ + /** + * The milliseconds of a day + */ private static final long ONE_DAY = 86400000L; - /** * Current time minus 1 year, just in case software clock goes back due to * time synchronization */ private static final Date NOT_BEFORE = new Date(System.currentTimeMillis() - ONE_DAY * 365); - /** * The maximum possible value in X.509 specification: 9999-12-31 23:59:59, * new Date(253402300799000L), but Apple iOS 8 fails with a certificate * expiration date grater than Mon, 24 Jan 6084 02:07:59 GMT (issue #6). - * + *

* Hundred years in the future from starting the proxy should be enough. */ private static final Date NOT_AFTER = new Date(System.currentTimeMillis() + ONE_DAY * 365 * 100); - /** * Enforce TLS 1.2 if available, since it's not default up to Java 8. *

@@ -120,7 +108,12 @@ public final class CertificateHelper { */ private static final String SSL_CONTEXT_FALLBACK_PROTOCOL = "TLSv1"; - private CertificateHelper() {} + static { + Security.addProvider(new BouncyCastleProvider()); + } + + private CertificateHelper() { + } public static KeyPair generateKeyPair(int keySize) throws NoSuchAlgorithmException, NoSuchProviderException { @@ -139,7 +132,7 @@ public static KeyPair generateKeyPair(int keySize) * if sun.arch.data.model explicitly indicates a 32-bit JVM. * * @return true if we can determine definitively that this is a 32-bit JVM, - * otherwise false + * otherwise false */ private static boolean is32BitJvm() { Integer bits = Integer.getInteger("sun.arch.data.model"); @@ -147,7 +140,7 @@ private static boolean is32BitJvm() { } public static KeyStore createRootCertificate(Authority authority, - String keyStoreType) throws NoSuchAlgorithmException, + String keyStoreType) throws NoSuchAlgorithmException, NoSuchProviderException, IOException, OperatorCreationException, CertificateException, KeyStoreException { @@ -189,7 +182,7 @@ public static KeyStore createRootCertificate(Authority authority, .getInstance(keyStoreType/* , PROVIDER_NAME */); result.load(null, null); result.setKeyEntry(authority.alias(), keyPair.getPrivate(), - authority.password(), new Certificate[] { cert }); + authority.password(), new Certificate[]{cert}); return result; } @@ -208,8 +201,8 @@ private static SubjectKeyIdentifier createSubjectKeyIdentifier(Key key) } public static KeyStore createServerCertificate(String commonName, - SubjectAlternativeNameHolder subjectAlternativeNames, - Authority authority, Certificate caCert, PrivateKey caPrivKey) + SubjectAlternativeNameHolder subjectAlternativeNames, + Authority authority, Certificate caCert, PrivateKey caPrivKey) throws NoSuchAlgorithmException, NoSuchProviderException, IOException, OperatorCreationException, CertificateException, InvalidKeyException, SignatureException, KeyStoreException { @@ -242,9 +235,9 @@ public static KeyStore createServerCertificate(String commonName, cert.verify(caCert.getPublicKey()); KeyStore result = KeyStore.getInstance(KeyStore.getDefaultType() - /* , PROVIDER_NAME */); + /* , PROVIDER_NAME */); result.load(null, null); - Certificate[] chain = { cert, caCert }; + Certificate[] chain = {cert, caCert}; result.setKeyEntry(authority.alias(), keyPair.getPrivate(), authority.password(), chain); @@ -266,24 +259,24 @@ public static TrustManager[] getTrustManagers(KeyStore keyStore) NoSuchProviderException { String trustManAlg = TrustManagerFactory.getDefaultAlgorithm(); TrustManagerFactory tmf = TrustManagerFactory.getInstance(trustManAlg - /* , PROVIDER_NAME */); + /* , PROVIDER_NAME */); tmf.init(keyStore); return tmf.getTrustManagers(); } public static KeyManager[] getKeyManagers(KeyStore keyStore, - Authority authority) throws NoSuchAlgorithmException, + Authority authority) throws NoSuchAlgorithmException, NoSuchProviderException, UnrecoverableKeyException, KeyStoreException { String keyManAlg = KeyManagerFactory.getDefaultAlgorithm(); KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManAlg - /* , PROVIDER_NAME */); + /* , PROVIDER_NAME */); kmf.init(keyStore, authority.password()); return kmf.getKeyManagers(); } public static SSLContext newClientContext(KeyManager[] keyManagers, - TrustManager[] trustManagers) throws NoSuchAlgorithmException, + TrustManager[] trustManagers) throws NoSuchAlgorithmException, KeyManagementException, NoSuchProviderException { SSLContext result = newSSLContext(); result.init(keyManagers, trustManagers, null); @@ -304,12 +297,12 @@ private static SSLContext newSSLContext() throws NoSuchAlgorithmException { try { log.debug("Using protocol {}", SSL_CONTEXT_PROTOCOL); return SSLContext.getInstance(SSL_CONTEXT_PROTOCOL - /* , PROVIDER_NAME */); + /* , PROVIDER_NAME */); } catch (NoSuchAlgorithmException e) { log.warn("Protocol {} not available, falling back to {}", SSL_CONTEXT_PROTOCOL, SSL_CONTEXT_FALLBACK_PROTOCOL); return SSLContext.getInstance(SSL_CONTEXT_FALLBACK_PROTOCOL - /* , PROVIDER_NAME */); + /* , PROVIDER_NAME */); } } diff --git a/app/src/main/java/org/littleshoot/proxy/mitm/CertificateSniffingMitmManager.java b/app/src/main/java/org/littleshoot/proxy/mitm/CertificateSniffingMitmManager.java index 0bdd0c5..4189e77 100755 --- a/app/src/main/java/org/littleshoot/proxy/mitm/CertificateSniffingMitmManager.java +++ b/app/src/main/java/org/littleshoot/proxy/mitm/CertificateSniffingMitmManager.java @@ -1,5 +1,9 @@ package org.littleshoot.proxy.mitm; +import org.littleshoot.proxy.MitmManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.security.cert.Certificate; import java.security.cert.X509Certificate; @@ -7,10 +11,6 @@ import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; -import org.littleshoot.proxy.MitmManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import io.netty.handler.codec.http.HttpRequest; /** @@ -39,14 +39,17 @@ public CertificateSniffingMitmManager(Authority authority) } } + @Override public SSLEngine serverSslEngine(String peerHost, int peerPort) { return sslEngineSource.newSslEngine(peerHost, peerPort); } + @Override public SSLEngine serverSslEngine() { return sslEngineSource.newSslEngine(); } + @Override public SSLEngine clientSslEngineFor(HttpRequest httpRequest, SSLSession serverSslSession) { try { X509Certificate upstreamCert = getCertificateFromSession(serverSslSession); diff --git a/app/src/main/java/org/littleshoot/proxy/mitm/SubjectAlternativeNameHolder.java b/app/src/main/java/org/littleshoot/proxy/mitm/SubjectAlternativeNameHolder.java index b24c750..7973e14 100755 --- a/app/src/main/java/org/littleshoot/proxy/mitm/SubjectAlternativeNameHolder.java +++ b/app/src/main/java/org/littleshoot/proxy/mitm/SubjectAlternativeNameHolder.java @@ -1,11 +1,5 @@ package org.littleshoot.proxy.mitm; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.x509.Extension; @@ -13,6 +7,12 @@ import org.bouncycastle.cert.CertIOException; import org.bouncycastle.cert.X509v3CertificateBuilder; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public class SubjectAlternativeNameHolder { private static final Pattern TAGS_PATTERN = Pattern.compile("[" diff --git a/app/src/main/res/drawable-v21/ic_info_black_24dp.xml b/app/src/main/res/drawable-v21/ic_info_black_24dp.xml index 34b8202..158e2a7 100644 --- a/app/src/main/res/drawable-v21/ic_info_black_24dp.xml +++ b/app/src/main/res/drawable-v21/ic_info_black_24dp.xml @@ -1,8 +1,8 @@ + android:viewportWidth="24.0" + android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable-v21/ic_menu_camera.xml b/app/src/main/res/drawable-v21/ic_menu_camera.xml index 0d9ea10..634fe92 100644 --- a/app/src/main/res/drawable-v21/ic_menu_camera.xml +++ b/app/src/main/res/drawable-v21/ic_menu_camera.xml @@ -1,8 +1,8 @@ + android:viewportWidth="24.0" + android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable-v21/ic_menu_gallery.xml b/app/src/main/res/drawable-v21/ic_menu_gallery.xml index 9e11b4a..10046e2 100644 --- a/app/src/main/res/drawable-v21/ic_menu_gallery.xml +++ b/app/src/main/res/drawable-v21/ic_menu_gallery.xml @@ -1,8 +1,8 @@ + android:viewportWidth="24.0" + android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable-v21/ic_menu_manage.xml b/app/src/main/res/drawable-v21/ic_menu_manage.xml index c1be60b..aeb047d 100644 --- a/app/src/main/res/drawable-v21/ic_menu_manage.xml +++ b/app/src/main/res/drawable-v21/ic_menu_manage.xml @@ -1,8 +1,8 @@ + android:viewportWidth="24.0" + android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable-v21/ic_menu_send.xml b/app/src/main/res/drawable-v21/ic_menu_send.xml index 00c668c..fdf1c90 100644 --- a/app/src/main/res/drawable-v21/ic_menu_send.xml +++ b/app/src/main/res/drawable-v21/ic_menu_send.xml @@ -1,8 +1,8 @@ + android:viewportWidth="24.0" + android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable-v21/ic_menu_share.xml b/app/src/main/res/drawable-v21/ic_menu_share.xml index a28fb9e..338d95a 100644 --- a/app/src/main/res/drawable-v21/ic_menu_share.xml +++ b/app/src/main/res/drawable-v21/ic_menu_share.xml @@ -1,8 +1,8 @@ + android:viewportWidth="24.0" + android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable-v21/ic_menu_slideshow.xml b/app/src/main/res/drawable-v21/ic_menu_slideshow.xml index 7c370ac..112a29d 100644 --- a/app/src/main/res/drawable-v21/ic_menu_slideshow.xml +++ b/app/src/main/res/drawable-v21/ic_menu_slideshow.xml @@ -1,8 +1,8 @@ + android:viewportWidth="24.0" + android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable-v21/ic_menu_webview.xml b/app/src/main/res/drawable-v21/ic_menu_webview.xml index a8bdeeb..0520282 100644 --- a/app/src/main/res/drawable-v21/ic_menu_webview.xml +++ b/app/src/main/res/drawable-v21/ic_menu_webview.xml @@ -5,5 +5,5 @@ android:viewportHeight="24.0"> + android:pathData="M12 10.9c-.61 0-1.1.49-1.1 1.1s.49 1.1 1.1 1.1c.61 0 1.1-.49 1.1-1.1s-.49-1.1-1.1-1.1zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm2.19 12.19L6 18l3.81-8.19L18 6l-3.81 8.19z" /> \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/ic_notifications_black_24dp.xml b/app/src/main/res/drawable-v21/ic_notifications_black_24dp.xml index e3400cf..81db489 100644 --- a/app/src/main/res/drawable-v21/ic_notifications_black_24dp.xml +++ b/app/src/main/res/drawable-v21/ic_notifications_black_24dp.xml @@ -1,8 +1,8 @@ + android:viewportWidth="24.0" + android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable-v21/ic_sync_black_24dp.xml b/app/src/main/res/drawable-v21/ic_sync_black_24dp.xml index 5a283aa..5ede6fb 100644 --- a/app/src/main/res/drawable-v21/ic_sync_black_24dp.xml +++ b/app/src/main/res/drawable-v21/ic_sync_black_24dp.xml @@ -1,8 +1,8 @@ + android:viewportWidth="24.0" + android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable/jz_toast_bg.xml b/app/src/main/res/drawable/jz_toast_bg.xml index 0da1ca3..cb78b93 100644 --- a/app/src/main/res/drawable/jz_toast_bg.xml +++ b/app/src/main/res/drawable/jz_toast_bg.xml @@ -2,8 +2,8 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/fragment_webview.xml b/app/src/main/res/layout-v21/fragment_webview.xml index 056fa53..6ed0c52 100644 --- a/app/src/main/res/layout-v21/fragment_webview.xml +++ b/app/src/main/res/layout-v21/fragment_webview.xml @@ -6,42 +6,42 @@ + android:layout_marginTop="-6.5dp" + android:layout_marginBottom="15dp" />