├── .gitignore ├── LICENSE.txt ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── grid.json │ ├── java │ └── org │ │ └── fly │ │ ├── android │ │ └── localvpn │ │ │ ├── LocalVPN.java │ │ │ ├── LocalVPNService.java │ │ │ ├── Packet.java │ │ │ ├── TCPInput.java │ │ │ ├── TCPOutput.java │ │ │ ├── UDPInput.java │ │ │ ├── UDPOutput.java │ │ │ ├── contract │ │ │ ├── IFirewall.java │ │ │ ├── TcpIO.java │ │ │ └── UdpIO.java │ │ │ ├── firewall │ │ │ ├── Dns.java │ │ │ ├── Firewall.java │ │ │ ├── Grid.java │ │ │ ├── Http.java │ │ │ └── Other.java │ │ │ ├── store │ │ │ ├── Block.java │ │ │ ├── TCB.java │ │ │ └── UDB.java │ │ │ └── structs │ │ │ ├── BufferUtils.java │ │ │ ├── HttpUtils.java │ │ │ ├── IoBuffer.java │ │ │ ├── IoUtils.java │ │ │ ├── Jacksonable.java │ │ │ └── LRUCache.java │ │ └── protocol │ │ ├── Protocol.java │ │ ├── contract │ │ ├── IFactory.java │ │ ├── IFactoryThrowing.java │ │ └── IHandler.java │ │ ├── dns │ │ ├── content │ │ │ ├── Dns.java │ │ │ └── Label.java │ │ ├── request │ │ │ └── Request.java │ │ └── response │ │ │ └── Response.java │ │ ├── exception │ │ ├── PtrException.java │ │ ├── RequestException.java │ │ └── ResponseException.java │ │ ├── http │ │ ├── Constant.java │ │ ├── content │ │ │ ├── ContentType.java │ │ │ ├── Cookie.java │ │ │ └── CookieHandler.java │ │ ├── request │ │ │ ├── Method.java │ │ │ └── Request.java │ │ ├── response │ │ │ ├── ChunkedOutputStream.java │ │ │ ├── IStatus.java │ │ │ ├── Response.java │ │ │ └── Status.java │ │ ├── sockets │ │ │ ├── DefaultServerSocketFactory.java │ │ │ └── SecureServerSocketFactory.java │ │ └── tempfiles │ │ │ ├── DefaultTempFile.java │ │ │ ├── DefaultTempFileManager.java │ │ │ ├── DefaultTempFileManagerFactory.java │ │ │ ├── ITempFile.java │ │ │ └── ITempFileManager.java │ │ └── resources │ │ └── META-INF │ │ └── protocol │ │ ├── default-mimetypes.properties │ │ └── mimetypes.properties │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ └── activity_local_vpn.xml │ ├── menu │ └── menu_local_vpn.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libs └── core-1.0.0.jar └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | .idea/ 4 | .idea_modules/ 5 | *.iws 6 | *.iml 7 | *.ipr 8 | .DS_Store 9 | /build 10 | target/ 11 | */target/ 12 | core 13 | # Ignore Gradle GUI config 14 | gradle-app.setting 15 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 16 | !gradle-wrapper.jar 17 | # Cache of project 18 | .gradletasknamecache 19 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 20 | # gradle/wrapper/gradle-wrapper.properties -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LocalVPN 2 | A packet interceptor for Android built on top of VpnService 3 | 4 | License: Apache v2.0 5 | 6 | Early alpha, will eat your cat! 7 | 8 | 9 | Android环境中,捕获、修改所有HTTP数据包、DNS包的。源代码中实现了捕获所有TCP、UDP。 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | */target/ -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | defaultConfig { 7 | applicationId "org.fly.android.localvpn" 8 | minSdkVersion 19 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation 'com.android.support:appcompat-v7:28.0.0' 25 | implementation 'com.squareup.okhttp3:okhttp:3.12.0' 26 | implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version:'2.9.9' 27 | implementation group: 'commons-codec', name: 'commons-codec', version: '1.12' 28 | 29 | 30 | } 31 | 32 | 33 | tasks.withType(JavaCompile) { 34 | options.encoding = "UTF-8" 35 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/i069076/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/assets/grid.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns": { 3 | /*"neverssl.com": { 4 | "A": [ 5 | "127.0.0.1" 6 | "IP2" 7 | ] 8 | "AAAA": [ 9 | "ipv6" 10 | ] 11 | }*/ 12 | }, 13 | "http": { 14 | ".*?neverssl.com/online.*?": { 15 | "GET": "Inject Result", 16 | //"POST": "Result", 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/LocalVPN.java: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2015, Mohamed Naufal 3 | ** 4 | ** Licensed under the Apache License, Version 2.0 (the "License"); 5 | ** you may not use this file except in compliance with the License. 6 | ** You may obtain a copy of the License at 7 | ** 8 | ** http://www.apache.org/licenses/LICENSE-2.0 9 | ** 10 | ** Unless required by applicable law or agreed to in writing, software 11 | ** distributed under the License is distributed on an "AS IS" BASIS, 12 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | ** See the License for the specific language governing permissions and 14 | ** limitations under the License. 15 | */ 16 | 17 | package org.fly.android.localvpn; 18 | 19 | import android.app.Activity; 20 | import android.content.BroadcastReceiver; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.content.IntentFilter; 24 | import android.content.res.AssetManager; 25 | import android.net.VpnService; 26 | import android.os.Bundle; 27 | import android.support.v4.content.LocalBroadcastManager; 28 | import android.util.Log; 29 | import android.view.View; 30 | import android.widget.Button; 31 | 32 | import org.fly.android.localvpn.firewall.Firewall; 33 | 34 | import java.io.BufferedReader; 35 | import java.io.File; 36 | import java.io.IOException; 37 | import java.io.InputStream; 38 | import java.io.InputStreamReader; 39 | 40 | 41 | public class LocalVPN extends Activity 42 | { 43 | private static final String TAG = LocalVPN.class.getSimpleName(); 44 | 45 | public static final int BUFFER_SIZE = 16384; 46 | 47 | private static final int VPN_REQUEST_CODE = 0x0F; 48 | 49 | private boolean waitingForVPNStart; 50 | 51 | private BroadcastReceiver vpnStateReceiver = new BroadcastReceiver() 52 | { 53 | @Override 54 | public void onReceive(Context context, Intent intent) 55 | { 56 | if (LocalVPNService.BROADCAST_VPN_STATE.equals(intent.getAction())) 57 | { 58 | if (intent.getBooleanExtra("running", false)) 59 | waitingForVPNStart = false; 60 | } 61 | } 62 | }; 63 | 64 | public static String readAssetFile(AssetManager mgr, String path) { 65 | String contents = ""; 66 | InputStream is = null; 67 | BufferedReader reader = null; 68 | try { 69 | is = mgr.open(path); 70 | reader = new BufferedReader(new InputStreamReader(is)); 71 | contents = reader.readLine(); 72 | String line = null; 73 | while ((line = reader.readLine()) != null) { 74 | contents += '\n' + line; 75 | } 76 | } catch (final Exception e) { 77 | e.printStackTrace(); 78 | } finally { 79 | if (is != null) { 80 | try { 81 | is.close(); 82 | } catch (IOException ignored) { 83 | } 84 | } 85 | if (reader != null) { 86 | try { 87 | reader.close(); 88 | } catch (IOException ignored) { 89 | } 90 | } 91 | } 92 | return contents; 93 | } 94 | 95 | @Override 96 | protected void onCreate(Bundle savedInstanceState) 97 | { 98 | super.onCreate(savedInstanceState); 99 | 100 | setContentView(R.layout.activity_local_vpn); 101 | final Button vpnButton = findViewById(R.id.vpn); 102 | vpnButton.setOnClickListener(new View.OnClickListener() 103 | { 104 | @Override 105 | public void onClick(View v) 106 | { 107 | startVPN(); 108 | } 109 | }); 110 | 111 | 112 | Firewall.createTable(readAssetFile(getAssets(), "grid.json")); 113 | 114 | final Button httpButton = findViewById(R.id.okhttp); 115 | 116 | httpButton.setOnClickListener(new View.OnClickListener() 117 | { 118 | @Override 119 | public void onClick(View v) 120 | { 121 | 122 | } 123 | }); 124 | 125 | Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { 126 | @Override 127 | public void uncaughtException(Thread t, Throwable e) { 128 | Log.e(TAG, e.getMessage(), e); 129 | 130 | } 131 | }); 132 | 133 | waitingForVPNStart = false; 134 | LocalBroadcastManager.getInstance(this).registerReceiver(vpnStateReceiver, 135 | new IntentFilter(LocalVPNService.BROADCAST_VPN_STATE)); 136 | } 137 | 138 | private void startVPN() 139 | { 140 | Intent vpnIntent = VpnService.prepare(this); 141 | if (vpnIntent != null) 142 | startActivityForResult(vpnIntent, VPN_REQUEST_CODE); 143 | else 144 | onActivityResult(VPN_REQUEST_CODE, RESULT_OK, null); 145 | } 146 | 147 | @Override 148 | protected void onActivityResult(int requestCode, int resultCode, Intent data) 149 | { 150 | super.onActivityResult(requestCode, resultCode, data); 151 | if (requestCode == VPN_REQUEST_CODE && resultCode == RESULT_OK) 152 | { 153 | waitingForVPNStart = true; 154 | startService(new Intent(this, LocalVPNService.class)); 155 | enableButton(false); 156 | } 157 | } 158 | 159 | @Override 160 | protected void onResume() { 161 | super.onResume(); 162 | 163 | enableButton(!waitingForVPNStart && !LocalVPNService.isRunning()); 164 | } 165 | 166 | private void enableButton(boolean enable) 167 | { 168 | final Button vpnButton = findViewById(R.id.vpn); 169 | if (enable) 170 | { 171 | vpnButton.setEnabled(true); 172 | vpnButton.setText(R.string.start_vpn); 173 | } 174 | else 175 | { 176 | vpnButton.setEnabled(false); 177 | vpnButton.setText(R.string.stop_vpn); 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/LocalVPNService.java: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2015, Mohamed Naufal 3 | ** 4 | ** Licensed under the Apache License, Version 2.0 (the "License"); 5 | ** you may not use this file except in compliance with the License. 6 | ** You may obtain a copy of the License at 7 | ** 8 | ** http://www.apache.org/licenses/LICENSE-2.0 9 | ** 10 | ** Unless required by applicable law or agreed to in writing, software 11 | ** distributed under the License is distributed on an "AS IS" BASIS, 12 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | ** See the License for the specific language governing permissions and 14 | ** limitations under the License. 15 | */ 16 | 17 | package org.fly.android.localvpn; 18 | 19 | import android.content.Context; 20 | import android.content.Intent; 21 | import android.net.VpnService; 22 | import android.os.ParcelFileDescriptor; 23 | import android.support.v4.content.LocalBroadcastManager; 24 | import android.util.Log; 25 | 26 | import java.io.Closeable; 27 | import java.io.FileDescriptor; 28 | import java.io.FileInputStream; 29 | import java.io.FileOutputStream; 30 | import java.io.IOException; 31 | import java.nio.ByteBuffer; 32 | import java.nio.channels.FileChannel; 33 | import java.nio.channels.Selector; 34 | import java.util.concurrent.ConcurrentLinkedQueue; 35 | import java.util.concurrent.ExecutorService; 36 | import java.util.concurrent.Executors; 37 | 38 | public class LocalVPNService extends VpnService 39 | { 40 | private static final String TAG = LocalVPNService.class.getSimpleName(); 41 | private static final String VPN_ADDRESS = "10.0.0.2"; // Only IPv4 support for now 42 | private static final String VPN_ROUTE = "0.0.0.0"; // Intercept everything 43 | 44 | public static final String BROADCAST_VPN_STATE = "org.fly.android.localvpn.VPN_STATE"; 45 | 46 | private static boolean isRunning = false; 47 | 48 | private ParcelFileDescriptor vpnInterface = null; 49 | 50 | private ConcurrentLinkedQueue deviceToNetworkUDPQueue; 51 | private ConcurrentLinkedQueue deviceToNetworkTCPQueue; 52 | private ConcurrentLinkedQueue networkToDeviceQueue; 53 | private ExecutorService executorService; 54 | 55 | private Selector udpSelector; 56 | private Selector tcpSelector; 57 | 58 | @Override 59 | public void onCreate() 60 | { 61 | super.onCreate(); 62 | isRunning = true; 63 | setupVPN(); 64 | try 65 | { 66 | udpSelector = Selector.open(); 67 | tcpSelector = Selector.open(); 68 | deviceToNetworkUDPQueue = new ConcurrentLinkedQueue<>(); 69 | deviceToNetworkTCPQueue = new ConcurrentLinkedQueue<>(); 70 | networkToDeviceQueue = new ConcurrentLinkedQueue<>(); 71 | 72 | executorService = Executors.newFixedThreadPool(5); 73 | executorService.submit(new UDPInput(networkToDeviceQueue, udpSelector)); 74 | executorService.submit(new UDPOutput(deviceToNetworkUDPQueue, networkToDeviceQueue, udpSelector, this)); 75 | executorService.submit(new TCPInput(networkToDeviceQueue, tcpSelector)); 76 | executorService.submit(new TCPOutput(deviceToNetworkTCPQueue, networkToDeviceQueue, tcpSelector, this)); 77 | executorService.submit(new VPNRunnable( 78 | this, 79 | vpnInterface.getFileDescriptor(), 80 | deviceToNetworkUDPQueue, 81 | deviceToNetworkTCPQueue, 82 | networkToDeviceQueue 83 | )); 84 | LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(BROADCAST_VPN_STATE).putExtra("running", true)); 85 | Log.i(TAG, "Started"); 86 | } 87 | catch (IOException e) 88 | { 89 | // TODO: Here and elsewhere, we should explicitly notify the user of any errors 90 | // and suggest that they stop the service, since we can't do it ourselves 91 | Log.e(TAG, "Error starting service", e); 92 | cleanup(); 93 | } 94 | } 95 | 96 | private void setupVPN() 97 | { 98 | if (vpnInterface == null) 99 | { 100 | Builder builder = new Builder(); 101 | builder.setMtu(Packet.MUTE_SIZE); 102 | 103 | builder.addAddress(VPN_ADDRESS, 32); 104 | builder.addRoute(VPN_ROUTE, 0); 105 | builder.addDnsServer("223.5.5.5"); 106 | builder.addDnsServer("8.8.8.8"); 107 | 108 | vpnInterface = builder 109 | .setSession(getString(R.string.app_name)) 110 | //.setConfigureIntent() 111 | .establish(); 112 | } 113 | } 114 | 115 | @Override 116 | public int onStartCommand(Intent intent, int flags, int startId) 117 | { 118 | return START_STICKY; 119 | } 120 | 121 | public static boolean isRunning() 122 | { 123 | return isRunning; 124 | } 125 | 126 | @Override 127 | public void onDestroy() 128 | { 129 | super.onDestroy(); 130 | isRunning = false; 131 | executorService.shutdownNow(); 132 | cleanup(); 133 | Log.i(TAG, "Stopped"); 134 | } 135 | 136 | private void cleanup() 137 | { 138 | deviceToNetworkTCPQueue = null; 139 | deviceToNetworkUDPQueue = null; 140 | networkToDeviceQueue = null; 141 | closeResources(udpSelector, tcpSelector, vpnInterface); 142 | } 143 | 144 | // TODO: Move this to a "utils" class for reuse 145 | private static void closeResources(Closeable... resources) 146 | { 147 | for (Closeable resource : resources) 148 | { 149 | try 150 | { 151 | resource.close(); 152 | } 153 | catch (IOException e) 154 | { 155 | // Ignore 156 | } 157 | } 158 | } 159 | 160 | private static class VPNRunnable implements Runnable 161 | { 162 | private static final String TAG = VPNRunnable.class.getSimpleName(); 163 | 164 | private Context context; 165 | private FileDescriptor vpnFileDescriptor; 166 | 167 | private ConcurrentLinkedQueue deviceToNetworkUDPQueue; 168 | private ConcurrentLinkedQueue deviceToNetworkTCPQueue; 169 | private ConcurrentLinkedQueue networkToDeviceQueue; 170 | 171 | public VPNRunnable(Context context, 172 | FileDescriptor vpnFileDescriptor, 173 | ConcurrentLinkedQueue deviceToNetworkUDPQueue, 174 | ConcurrentLinkedQueue deviceToNetworkTCPQueue, 175 | ConcurrentLinkedQueue networkToDeviceQueue) 176 | { 177 | this.context = context; 178 | this.vpnFileDescriptor = vpnFileDescriptor; 179 | this.deviceToNetworkUDPQueue = deviceToNetworkUDPQueue; 180 | this.deviceToNetworkTCPQueue = deviceToNetworkTCPQueue; 181 | this.networkToDeviceQueue = networkToDeviceQueue; 182 | } 183 | 184 | private void waitUntilPrepared() { 185 | while (prepare(context) != null) { 186 | try { 187 | Thread.sleep(100); 188 | } catch (InterruptedException e) { 189 | 190 | } 191 | } 192 | } 193 | 194 | @Override 195 | public void run() 196 | { 197 | waitUntilPrepared(); 198 | 199 | Log.i(TAG, "Started"); 200 | 201 | FileChannel vpnInput = new FileInputStream(vpnFileDescriptor).getChannel(); 202 | FileChannel vpnOutput = new FileOutputStream(vpnFileDescriptor).getChannel(); 203 | 204 | try 205 | { 206 | ByteBuffer bufferToNetwork = null; 207 | boolean dataSent = true; 208 | boolean dataReceived; 209 | while (!Thread.interrupted()) 210 | { 211 | if (dataSent) 212 | bufferToNetwork = ByteBuffer.allocate(LocalVPN.BUFFER_SIZE); 213 | else 214 | bufferToNetwork.clear(); 215 | 216 | // TODO: Block when not connected 217 | int readBytes = vpnInput.read(bufferToNetwork); 218 | if (readBytes > 0) 219 | { 220 | dataSent = true; 221 | bufferToNetwork.flip(); //read mode 222 | Packet packet = new Packet(bufferToNetwork, Packet.TYPE.SEND); 223 | if (packet.isUDP()) 224 | { 225 | deviceToNetworkUDPQueue.offer(packet); 226 | } 227 | else if (packet.isTCP()) 228 | { 229 | deviceToNetworkTCPQueue.offer(packet); 230 | } 231 | else 232 | { 233 | Log.w(TAG, "Unknown packet type"); 234 | Log.w(TAG, packet.ip4Header.toString()); 235 | dataSent = false; 236 | } 237 | } 238 | else 239 | { 240 | dataSent = false; 241 | } 242 | 243 | ByteBuffer bufferFromNetwork = networkToDeviceQueue.poll(); 244 | if (bufferFromNetwork != null) 245 | { 246 | bufferFromNetwork.flip(); 247 | 248 | while (bufferFromNetwork.hasRemaining()) 249 | vpnOutput.write(bufferFromNetwork); 250 | 251 | dataReceived = true; 252 | 253 | //bufferFromNetwork.clear(); 254 | } 255 | else 256 | { 257 | dataReceived = false; 258 | } 259 | 260 | // TODO: Sleep-looping is not very battery-friendly, consider blocking instead 261 | // Confirm if throughput with ConcurrentQueue is really higher compared to BlockingQueue 262 | if (!dataSent && !dataReceived) 263 | Thread.sleep(5); 264 | } 265 | } 266 | catch (InterruptedException e) 267 | { 268 | Log.i(TAG, "Stopping"); 269 | } 270 | catch (IOException e) 271 | { 272 | Log.w(TAG, e.toString(), e); 273 | } 274 | finally 275 | { 276 | closeResources(vpnInput, vpnOutput); 277 | } 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/TCPInput.java: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2015, Mohamed Naufal 3 | ** 4 | ** Licensed under the Apache License, Version 2.0 (the "License"); 5 | ** you may not use this file except in compliance with the License. 6 | ** You may obtain a copy of the License at 7 | ** 8 | ** http://www.apache.org/licenses/LICENSE-2.0 9 | ** 10 | ** Unless required by applicable law or agreed to in writing, software 11 | ** distributed under the License is distributed on an "AS IS" BASIS, 12 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | ** See the License for the specific language governing permissions and 14 | ** limitations under the License. 15 | */ 16 | 17 | package org.fly.android.localvpn; 18 | 19 | import android.util.Log; 20 | 21 | import org.fly.android.localvpn.contract.TcpIO; 22 | import org.fly.android.localvpn.store.TCB; 23 | import org.fly.android.localvpn.store.TCB.TCBStatus; 24 | 25 | import java.io.IOException; 26 | import java.nio.ByteBuffer; 27 | import java.nio.channels.SelectionKey; 28 | import java.nio.channels.Selector; 29 | import java.nio.channels.SocketChannel; 30 | import java.util.Iterator; 31 | import java.util.Set; 32 | import java.util.concurrent.ConcurrentLinkedQueue; 33 | 34 | public class TCPInput extends TcpIO implements Runnable 35 | { 36 | private static final String TAG = TCPInput.class.getSimpleName(); 37 | 38 | public TCPInput(ConcurrentLinkedQueue outputQueue, Selector selector) 39 | { 40 | this.outputQueue = outputQueue; 41 | this.selector = selector; 42 | } 43 | 44 | @Override 45 | public void run() 46 | { 47 | try 48 | { 49 | Log.d(TAG, "Started"); 50 | while (!Thread.interrupted()) 51 | { 52 | int readyChannels = selector.select(); 53 | 54 | if (readyChannels == 0) { 55 | Thread.sleep(10); 56 | continue; 57 | } 58 | 59 | Set keys = selector.selectedKeys(); 60 | Iterator keyIterator = keys.iterator(); 61 | 62 | while (keyIterator.hasNext() && !Thread.interrupted()) 63 | { 64 | SelectionKey key = keyIterator.next(); 65 | if (key.isValid()) 66 | { 67 | // Selector 已連接 68 | if (key.isConnectable()) 69 | processConnect(key, keyIterator); 70 | // Selector 回執的數據 71 | else if (key.isReadable()) 72 | processInput(key, keyIterator); 73 | } 74 | } 75 | } 76 | } 77 | catch (InterruptedException e) 78 | { 79 | Log.i(TAG, "Stopping"); 80 | } 81 | catch (IOException e) 82 | { 83 | Log.w(TAG, e.toString(), e); 84 | } 85 | } 86 | 87 | private void processConnect(SelectionKey key, Iterator keyIterator) 88 | { 89 | TCB tcb = (TCB) key.attachment(); 90 | Packet referencePacket = tcb.referencePacket; 91 | try 92 | { 93 | if (tcb.channel.finishConnect()) 94 | { 95 | keyIterator.remove(); 96 | tcb.status = TCBStatus.SYN_RECEIVED; 97 | 98 | // TODO: Set MSS for receiving larger packets from the device 99 | ByteBuffer responseBuffer = ByteBuffer.allocate(LocalVPN.BUFFER_SIZE); 100 | referencePacket.generateTCPBuffer(responseBuffer, (byte) (Packet.TCPHeader.SYN | Packet.TCPHeader.ACK), 101 | tcb, 0); 102 | outputQueue.offer(responseBuffer); 103 | 104 | tcb.incrementSeq();// SYN counts as a byte 105 | 106 | key.interestOps(SelectionKey.OP_READ); 107 | } 108 | } 109 | catch (IOException e) 110 | { 111 | Log.e(TAG, "Connection error: " + tcb.ipAndPort, e); 112 | ByteBuffer responseBuffer = ByteBuffer.allocate(LocalVPN.BUFFER_SIZE); 113 | referencePacket.generateTCPBuffer(responseBuffer, (byte) Packet.TCPHeader.RST, 0, tcb.myAcknowledgementNum, 0); 114 | outputQueue.offer(responseBuffer); 115 | TCB.closeTCB(tcb); 116 | } 117 | } 118 | 119 | private void processInput(SelectionKey key, Iterator keyIterator) 120 | { 121 | keyIterator.remove(); 122 | ByteBuffer receiveBuffer = ByteBuffer.allocate(LocalVPN.BUFFER_SIZE); 123 | // Leave space for the header 124 | receiveBuffer.position(HEADER_SIZE); 125 | 126 | TCB tcb = (TCB) key.attachment(); 127 | synchronized (tcb) 128 | { 129 | Packet referencePacket = tcb.referencePacket; 130 | SocketChannel inputChannel = (SocketChannel) key.channel(); 131 | int readBytes; 132 | try 133 | { 134 | readBytes = inputChannel.read(receiveBuffer); 135 | } 136 | catch (Exception e) 137 | { 138 | Log.e(TAG, "Network read error: " + tcb.ipAndPort, e); 139 | referencePacket.generateTCPBuffer(receiveBuffer, (byte) Packet.TCPHeader.RST, 0, tcb.myAcknowledgementNum, 0); 140 | outputQueue.offer(receiveBuffer); 141 | TCB.closeTCB(tcb); 142 | return; 143 | } 144 | 145 | if (readBytes == -1) 146 | { 147 | // End of stream, stop waiting until we push more data 148 | key.interestOps(0); 149 | tcb.waitingForNetworkData = false; 150 | 151 | if (tcb.status != TCBStatus.CLOSE_WAIT) 152 | { 153 | //receiveBuffer.clear(); 154 | return; 155 | } 156 | 157 | tcb.status = TCBStatus.LAST_ACK; 158 | referencePacket.generateTCPBuffer(receiveBuffer, (byte) Packet.TCPHeader.FIN, tcb, 0); 159 | 160 | tcb.incrementSeq(); // FIN counts as a byte 161 | 162 | } 163 | else 164 | { 165 | // XXX: We should ideally be splitting segments by MTU/MSS, but this seems to work without 166 | referencePacket.generateTCPBuffer(receiveBuffer, (byte) (Packet.TCPHeader.PSH | Packet.TCPHeader.ACK), 167 | tcb, readBytes); 168 | 169 | tcb.incrementSeq(readBytes);// Next sequence number 170 | 171 | receiveBuffer.position(HEADER_SIZE + readBytes); 172 | } 173 | } 174 | outputQueue.offer(receiveBuffer); 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/TCPOutput.java: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2015, Mohamed Naufal 3 | ** 4 | ** Licensed under the Apache License, Version 2.0 (the "License"); 5 | ** you may not use this file except in compliance with the License. 6 | ** You may obtain a copy of the License at 7 | ** 8 | ** http://www.apache.org/licenses/LICENSE-2.0 9 | ** 10 | ** Unless required by applicable law or agreed to in writing, software 11 | ** distributed under the License is distributed on an "AS IS" BASIS, 12 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | ** See the License for the specific language governing permissions and 14 | ** limitations under the License. 15 | */ 16 | 17 | package org.fly.android.localvpn; 18 | 19 | import android.util.Log; 20 | 21 | import org.fly.android.localvpn.Packet.TCPHeader; 22 | import org.fly.android.localvpn.contract.TcpIO; 23 | import org.fly.android.localvpn.store.TCB; 24 | import org.fly.android.localvpn.store.TCB.TCBStatus; 25 | 26 | import java.io.IOException; 27 | import java.net.InetAddress; 28 | import java.net.InetSocketAddress; 29 | import java.nio.ByteBuffer; 30 | import java.nio.channels.SelectionKey; 31 | import java.nio.channels.Selector; 32 | import java.nio.channels.SocketChannel; 33 | import java.util.LinkedList; 34 | import java.util.concurrent.ConcurrentLinkedQueue; 35 | 36 | public class TCPOutput extends TcpIO implements Runnable 37 | { 38 | private static final String TAG = TCPOutput.class.getSimpleName(); 39 | 40 | private LocalVPNService vpnService; 41 | 42 | public TCPOutput(ConcurrentLinkedQueue inputQueue, ConcurrentLinkedQueue outputQueue, 43 | Selector selector, LocalVPNService vpnService) throws IOException 44 | { 45 | this.inputQueue = inputQueue; 46 | this.outputQueue = outputQueue; 47 | this.selector = selector; 48 | this.vpnService = vpnService; 49 | } 50 | 51 | @Override 52 | public void run() 53 | { 54 | Log.i(TAG, "Started"); 55 | try 56 | { 57 | Thread currentThread = Thread.currentThread(); 58 | while (true) 59 | { 60 | Packet currentPacket; 61 | // TODO: Block when not connected 62 | do 63 | { 64 | currentPacket = inputQueue.poll(); 65 | if (currentPacket != null) 66 | break; 67 | Thread.sleep(5); 68 | } while (!currentThread.isInterrupted()); 69 | 70 | if (currentThread.isInterrupted()) 71 | break; 72 | 73 | ByteBuffer payloadBuffer = currentPacket.backingBuffer; 74 | currentPacket.backingBuffer = null; 75 | ByteBuffer responseBuffer = ByteBuffer.allocate(LocalVPN.BUFFER_SIZE); 76 | 77 | InetAddress destinationAddress = currentPacket.ip4Header.destinationAddress; 78 | 79 | TCPHeader tcpHeader = currentPacket.tcpHeader; 80 | int destinationPort = tcpHeader.destinationPort; 81 | 82 | String ipAndPort = currentPacket.getKey(); 83 | TCB tcb = TCB.getTCB(ipAndPort); 84 | 85 | if (tcb == null) // 握手1 86 | initializeConnection(ipAndPort, destinationAddress, destinationPort, 87 | currentPacket, tcpHeader, responseBuffer); 88 | else if (tcpHeader.isSYN()) // 同步序列号 89 | processDuplicateSYN(tcb, tcpHeader, responseBuffer); 90 | else if (tcpHeader.isRST()) // 連接丟失 91 | closeCleanly(tcb, responseBuffer); 92 | else if (tcpHeader.isFIN()) // 揮手1 93 | processFIN(tcb, tcpHeader, responseBuffer); 94 | else if (tcpHeader.isACK()) 95 | processACK(tcb, tcpHeader, payloadBuffer, responseBuffer); 96 | 97 | // XXX: cleanup later 98 | if (responseBuffer.position() == 0) { 99 | //responseBuffer.clear(); 100 | } 101 | //payloadBuffer.clear(); 102 | } 103 | } 104 | catch (InterruptedException e) 105 | { 106 | Log.i(TAG, "Stopping"); 107 | } 108 | catch (IOException e) 109 | { 110 | Log.e(TAG, e.toString(), e); 111 | } 112 | catch (Exception e) 113 | { 114 | Log.e(TAG, e.toString(), e); 115 | } 116 | finally 117 | { 118 | Log.d(TAG, "Close all tcb"); 119 | TCB.closeAll(); 120 | } 121 | } 122 | 123 | private void initializeConnection(String ipAndPort, InetAddress destinationAddress, int destinationPort, 124 | Packet currentPacket, TCPHeader tcpHeader, ByteBuffer responseBuffer) 125 | throws IOException 126 | { 127 | currentPacket.swapSourceAndDestination(); 128 | if (tcpHeader.isSYN()) 129 | { 130 | SocketChannel outputChannel = SocketChannel.open(); 131 | outputChannel.configureBlocking(false); 132 | vpnService.protect(outputChannel.socket()); 133 | 134 | TCB tcb = new TCB( 135 | ipAndPort, 136 | tcpHeader, 137 | outputChannel, 138 | currentPacket); 139 | 140 | TCB.putTCB(ipAndPort, tcb); 141 | 142 | try 143 | { 144 | Log.d(TAG, "Connect: " + tcb.getIpAndPort()); 145 | 146 | outputChannel.connect(new InetSocketAddress(destinationAddress, destinationPort)); 147 | 148 | //由于上面是异步的, 不可能这么快返回连接成功, 连接成功会触发selector的事件驱动 149 | //但是本地连接会快速的返回 150 | if (outputChannel.finishConnect()) 151 | { 152 | tcb.status = TCBStatus.SYN_RECEIVED; 153 | // TODO: Set MSS for receiving larger packets from the device 154 | currentPacket.generateTCPBuffer(responseBuffer, (byte) (TCPHeader.SYN | TCPHeader.ACK), 155 | tcb, 0); 156 | 157 | tcb.incrementSeq();// SYN counts as a byte 158 | } 159 | else 160 | { 161 | // register to selector 162 | tcb.status = TCBStatus.SYN_SENT; 163 | selector.wakeup(); 164 | tcb.selectionKey = outputChannel.register(selector, SelectionKey.OP_CONNECT, tcb); 165 | return; 166 | } 167 | } 168 | catch (IOException e) 169 | { 170 | Log.e(TAG, "Connection error: " + ipAndPort, e); 171 | currentPacket.generateTCPBuffer(responseBuffer, (byte) TCPHeader.RST, 0, tcb.myAcknowledgementNum, 0); 172 | TCB.closeTCB(tcb); 173 | } 174 | } 175 | else 176 | { 177 | currentPacket.generateTCPBuffer(responseBuffer, (byte) TCPHeader.RST, 178 | 0, tcpHeader.sequenceNumber + 1, 0); 179 | } 180 | outputQueue.offer(responseBuffer); 181 | } 182 | 183 | /** 184 | * 客戶端發SYN 185 | * 如果在SYN_SENT状态下,则记录新的序列号 186 | * 其它状态,表示有问题 187 | * @param tcb 188 | * @param tcpHeader 189 | * @param responseBuffer 190 | */ 191 | private void processDuplicateSYN(TCB tcb, TCPHeader tcpHeader, ByteBuffer responseBuffer) 192 | { 193 | synchronized (tcb) 194 | { 195 | if (tcb.status == TCBStatus.SYN_SENT) 196 | { 197 | tcb.myAcknowledgementNum = tcpHeader.sequenceNumber + 1; 198 | return; 199 | } 200 | } 201 | sendRST(tcb, 1, responseBuffer); 202 | } 203 | 204 | /** 205 | * 兩次揮手 206 | * @param tcb 207 | * @param tcpHeader 208 | * @param responseBuffer 209 | */ 210 | private void processFIN(TCB tcb, TCPHeader tcpHeader, ByteBuffer responseBuffer) 211 | { 212 | synchronized (tcb) 213 | { 214 | Packet referencePacket = tcb.referencePacket; 215 | 216 | //服务器(VPN)收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。 217 | tcb.incrementReplyAck(tcpHeader); 218 | 219 | // 客戶端揮手1, 回復ACK 220 | if (tcb.waitingForNetworkData) 221 | { 222 | tcb.status = TCBStatus.CLOSE_WAIT; 223 | referencePacket.generateTCPBuffer(responseBuffer, (byte) TCPHeader.ACK, 224 | tcb, 0); 225 | } 226 | // 客戶端揮手2,3,回復FIN+ACK 227 | else 228 | { 229 | tcb.status = TCBStatus.LAST_ACK; 230 | referencePacket.generateTCPBuffer(responseBuffer, (byte) (TCPHeader.FIN | TCPHeader.ACK), 231 | tcb, 0); 232 | 233 | tcb.incrementSeq(); // FIN counts as a byte 234 | } 235 | } 236 | outputQueue.offer(responseBuffer); 237 | } 238 | 239 | /** 240 | * ACK 241 | * ACK + PSH 242 | * 243 | * @param tcb 244 | * @param tcpHeader 245 | * @param payloadBuffer 246 | * @param responseBuffer 247 | * @throws IOException 248 | */ 249 | private void processACK(TCB tcb, TCPHeader tcpHeader, ByteBuffer payloadBuffer, ByteBuffer responseBuffer) throws IOException 250 | { 251 | int payloadSize = payloadBuffer.limit() - payloadBuffer.position(); 252 | 253 | synchronized (tcb) 254 | { 255 | SocketChannel outputChannel = tcb.channel; 256 | 257 | // 客戶端握手3 ACK 258 | if (tcb.status == TCBStatus.SYN_RECEIVED) 259 | { 260 | tcb.status = TCBStatus.ESTABLISHED; 261 | 262 | // 註冊READ的Selector 263 | selector.wakeup(); 264 | tcb.selectionKey = outputChannel.register(selector, SelectionKey.OP_READ, tcb); 265 | tcb.waitingForNetworkData = true; 266 | } 267 | // 客戶端最後回復的ACK,揮手4 268 | else if (tcb.status == TCBStatus.LAST_ACK) 269 | { 270 | closeCleanly(tcb, responseBuffer); 271 | return; 272 | } 273 | 274 | // 空ACK 可以不用轉發給remote, 因為空ACK是手机和VPN的确认包,理论上需要验证seq,VPN->Remote的通讯依赖于channel 275 | if (payloadSize == 0) return; // Empty ACK, ignore 276 | 277 | // 給selector 添加一個OP_READ監聽狀態 278 | if (!tcb.waitingForNetworkData) 279 | { 280 | selector.wakeup(); 281 | tcb.selectionKey.interestOps(SelectionKey.OP_READ); 282 | tcb.waitingForNetworkData = true; 283 | } 284 | 285 | // 筛选数据,经过处理了之后再次转发 286 | // Forward to sendToRemote server 287 | try 288 | { 289 | LinkedList byteBuffers = tcb.filter(payloadBuffer); 290 | if (byteBuffers != null) 291 | { 292 | ByteBuffer buff; 293 | while ((buff = byteBuffers.poll()) != null) 294 | sendToRemote(tcb, buff); 295 | } 296 | } 297 | catch (IOException e) 298 | { 299 | Log.e(TAG, "TCP Network write error: " + tcb.ipAndPort, e); 300 | sendRST(tcb, payloadSize, responseBuffer); 301 | return; 302 | } 303 | catch (Exception e) 304 | { 305 | Log.e(TAG, e.getMessage(), e); 306 | } 307 | 308 | // TODO: We don't expect out-of-order packets, but verify 309 | // 回復給客戶端收到哪個ACK 310 | tcb.incrementReplyAck(tcpHeader, payloadSize); 311 | 312 | Packet referencePacket = tcb.referencePacket; 313 | referencePacket.generateTCPBuffer(responseBuffer, (byte) TCPHeader.ACK, tcb, 0); 314 | outputQueue.offer(responseBuffer); 315 | 316 | // response before send to remote 317 | LinkedList byteBuffers = tcb.getResponse(); 318 | ByteBuffer buff; 319 | while ((buff = byteBuffers.poll()) != null) 320 | sendToClient(tcb, buff); 321 | 322 | } 323 | 324 | } 325 | 326 | private void sendRST(TCB tcb, int prevPayloadSize, ByteBuffer buffer) 327 | { 328 | tcb.referencePacket.generateTCPBuffer(buffer, (byte) TCPHeader.RST, 0, tcb.myAcknowledgementNum + prevPayloadSize, 0); 329 | 330 | outputQueue.offer(buffer); 331 | TCB.closeTCB(tcb); 332 | } 333 | 334 | private void closeCleanly(TCB tcb, ByteBuffer buffer) 335 | { 336 | //buffer.clear(); 337 | 338 | TCB.closeTCB(tcb); 339 | } 340 | 341 | } 342 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/UDPInput.java: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2015, Mohamed Naufal 3 | ** 4 | ** Licensed under the Apache License, Version 2.0 (the "License"); 5 | ** you may not use this file except in compliance with the License. 6 | ** You may obtain a copy of the License at 7 | ** 8 | ** http://www.apache.org/licenses/LICENSE-2.0 9 | ** 10 | ** Unless required by applicable law or agreed to in writing, software 11 | ** distributed under the License is distributed on an "AS IS" BASIS, 12 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | ** See the License for the specific language governing permissions and 14 | ** limitations under the License. 15 | */ 16 | 17 | package org.fly.android.localvpn; 18 | 19 | import android.util.Log; 20 | 21 | import org.fly.android.localvpn.contract.UdpIO; 22 | import org.fly.android.localvpn.store.UDB; 23 | import org.fly.protocol.dns.content.Dns; 24 | 25 | import java.io.IOException; 26 | import java.nio.ByteBuffer; 27 | import java.nio.channels.DatagramChannel; 28 | import java.nio.channels.SelectionKey; 29 | import java.nio.channels.Selector; 30 | import java.util.Iterator; 31 | import java.util.Set; 32 | import java.util.concurrent.ConcurrentLinkedQueue; 33 | 34 | public class UDPInput extends UdpIO implements Runnable 35 | { 36 | private static final String TAG = UDPInput.class.getSimpleName(); 37 | 38 | public UDPInput(ConcurrentLinkedQueue outputQueue, Selector selector) 39 | { 40 | this.outputQueue = outputQueue; 41 | this.selector = selector; 42 | } 43 | 44 | @Override 45 | public void run() 46 | { 47 | try 48 | { 49 | Log.i(TAG, "Started"); 50 | while (!Thread.interrupted()) 51 | { 52 | int readyChannels = selector.select(); 53 | 54 | if (readyChannels == 0) { 55 | Thread.sleep(5); 56 | continue; 57 | } 58 | 59 | Set keys = selector.selectedKeys(); 60 | Iterator keyIterator = keys.iterator(); 61 | 62 | while (keyIterator.hasNext() && !Thread.interrupted()) 63 | { 64 | SelectionKey key = keyIterator.next(); 65 | UDB udb = (UDB)key.attachment(); 66 | if (key.isValid() && key.isReadable()) 67 | { 68 | keyIterator.remove(); 69 | 70 | ByteBuffer receiveBuffer = ByteBuffer.allocate(LocalVPN.BUFFER_SIZE); 71 | // Leave space for the header 72 | receiveBuffer.position(HEADER_SIZE); 73 | 74 | DatagramChannel inputChannel = (DatagramChannel) key.channel(); 75 | // XXX: We should handle any IOExceptions here immediately, 76 | // but that probably won't happen with UDP 77 | ByteBuffer buffer = ByteBuffer.allocate(LocalVPN.BUFFER_SIZE); 78 | int readBytes = inputChannel.read(buffer); 79 | buffer.flip(); 80 | 81 | try { 82 | if (udb.getFirewall().getProtocol() instanceof org.fly.android.localvpn.firewall.Dns) 83 | { 84 | Dns dns = new Dns(buffer.duplicate()); 85 | 86 | for (Dns.Record record: dns.getAnswers() 87 | ) { 88 | Log.d(TAG, "DNS Recive: --- " + record.getName() + ": " + record.getData()); 89 | } 90 | } 91 | } catch (Exception e) 92 | { 93 | 94 | } 95 | 96 | receiveBuffer.put(buffer); 97 | Packet referencePacket = udb.referencePacket; 98 | referencePacket.generateUDPBuffer(receiveBuffer, readBytes); 99 | receiveBuffer.position(HEADER_SIZE + readBytes); 100 | 101 | outputQueue.offer(receiveBuffer); 102 | } 103 | } 104 | } 105 | } 106 | catch (InterruptedException e) 107 | { 108 | Log.i(TAG, "Stopping"); 109 | } 110 | catch (IOException e) 111 | { 112 | Log.w(TAG, e.toString(), e); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/UDPOutput.java: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2015, Mohamed Naufal 3 | ** 4 | ** Licensed under the Apache License, Version 2.0 (the "License"); 5 | ** you may not use this file except in compliance with the License. 6 | ** You may obtain a copy of the License at 7 | ** 8 | ** http://www.apache.org/licenses/LICENSE-2.0 9 | ** 10 | ** Unless required by applicable law or agreed to in writing, software 11 | ** distributed under the License is distributed on an "AS IS" BASIS, 12 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | ** See the License for the specific language governing permissions and 14 | ** limitations under the License. 15 | */ 16 | 17 | package org.fly.android.localvpn; 18 | 19 | import android.util.Log; 20 | 21 | import org.fly.android.localvpn.contract.UdpIO; 22 | import org.fly.android.localvpn.store.UDB; 23 | 24 | import java.io.IOException; 25 | import java.net.InetAddress; 26 | import java.net.InetSocketAddress; 27 | import java.nio.ByteBuffer; 28 | import java.nio.channels.DatagramChannel; 29 | import java.nio.channels.SelectionKey; 30 | import java.nio.channels.Selector; 31 | import java.util.LinkedList; 32 | import java.util.concurrent.ConcurrentLinkedQueue; 33 | 34 | public class UDPOutput extends UdpIO implements Runnable 35 | { 36 | private static final String TAG = UDPOutput.class.getSimpleName(); 37 | 38 | private LocalVPNService vpnService; 39 | 40 | public UDPOutput(ConcurrentLinkedQueue inputQueue, 41 | ConcurrentLinkedQueue outputQueue, 42 | Selector selector, 43 | LocalVPNService vpnService) 44 | { 45 | this.inputQueue = inputQueue; 46 | this.outputQueue = outputQueue; 47 | this.selector = selector; 48 | this.vpnService = vpnService; 49 | } 50 | 51 | @Override 52 | public void run() 53 | { 54 | Log.i(TAG, "Started"); 55 | try 56 | { 57 | Thread currentThread = Thread.currentThread(); 58 | while (true) 59 | { 60 | Packet currentPacket; 61 | // TODO: Block when not connected 62 | do 63 | { 64 | currentPacket = inputQueue.poll(); 65 | if (currentPacket != null) 66 | break; 67 | Thread.sleep(10); 68 | } while (!currentThread.isInterrupted()); 69 | 70 | if (currentThread.isInterrupted()) 71 | break; 72 | 73 | InetAddress destinationAddress = currentPacket.ip4Header.destinationAddress; 74 | int destinationPort = currentPacket.udpHeader.destinationPort; 75 | 76 | String ipAndPort = currentPacket.getKey(); 77 | 78 | UDB udb = UDB.getUDB(ipAndPort); 79 | 80 | if (udb == null) { 81 | 82 | DatagramChannel outputChannel = DatagramChannel.open(); 83 | 84 | udb = new UDB(ipAndPort, outputChannel, currentPacket); 85 | 86 | vpnService.protect(outputChannel.socket()); 87 | 88 | try 89 | { 90 | outputChannel.connect(new InetSocketAddress(destinationAddress, destinationPort)); 91 | } 92 | catch (IOException e) 93 | { 94 | Log.e(TAG, "Connection error: " + ipAndPort, e); 95 | 96 | UDB.closeUDB(udb); 97 | 98 | //currentPacket.backingBuffer.clear(); 99 | continue; 100 | } 101 | outputChannel.configureBlocking(false); 102 | currentPacket.swapSourceAndDestination(); // 交换源和目标 103 | 104 | selector.wakeup(); 105 | outputChannel.register(selector, SelectionKey.OP_READ, udb); 106 | 107 | UDB.putUDB(ipAndPort, udb); 108 | } 109 | 110 | try 111 | { 112 | ByteBuffer payloadBuffer = currentPacket.backingBuffer; 113 | 114 | LinkedList byteBuffers = udb.filter(payloadBuffer); 115 | if (byteBuffers != null) 116 | { 117 | ByteBuffer buff; 118 | while ((buff = byteBuffers.poll()) != null) 119 | sendToRemote(udb, buff); 120 | } 121 | 122 | } 123 | catch (IOException e) 124 | { 125 | Log.e(TAG, "UDP Network write error: " + ipAndPort, e); 126 | 127 | UDB.closeUDB(udb); 128 | } 129 | 130 | //currentPacket.backingBuffer.clear(); 131 | 132 | // response before send to remote 133 | LinkedList byteBuffers = udb.getResponse(); 134 | ByteBuffer buff; 135 | while ((buff = byteBuffers.poll()) != null) 136 | sendToClient(udb, buff); 137 | 138 | } 139 | } 140 | catch (InterruptedException e) 141 | { 142 | Log.i(TAG, "Stopping"); 143 | } 144 | catch (IOException e) 145 | { 146 | Log.i(TAG, e.toString(), e); 147 | } 148 | catch (Exception e) 149 | { 150 | Log.e(TAG, e.toString(), e); 151 | } 152 | finally 153 | { 154 | Log.d(TAG, "Close all udb"); 155 | 156 | UDB.closeAll(); 157 | } 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/contract/IFirewall.java: -------------------------------------------------------------------------------- 1 | package org.fly.android.localvpn.contract; 2 | 3 | import org.fly.protocol.exception.RequestException; 4 | import org.fly.protocol.exception.ResponseException; 5 | 6 | import java.io.IOException; 7 | import java.nio.ByteBuffer; 8 | import java.util.LinkedList; 9 | 10 | public interface IFirewall { 11 | LinkedList write(ByteBuffer readableBuffer) throws IOException, RequestException, ResponseException; 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/contract/TcpIO.java: -------------------------------------------------------------------------------- 1 | package org.fly.android.localvpn.contract; 2 | 3 | import org.fly.android.localvpn.LocalVPN; 4 | import org.fly.android.localvpn.Packet; 5 | import org.fly.android.localvpn.store.TCB; 6 | 7 | import java.io.IOException; 8 | import java.nio.ByteBuffer; 9 | import java.nio.channels.Selector; 10 | import java.util.concurrent.ConcurrentLinkedQueue; 11 | 12 | public abstract class TcpIO { 13 | 14 | protected static final int HEADER_SIZE = Packet.IP4_HEADER_SIZE + Packet.TCP_HEADER_SIZE; 15 | 16 | protected ConcurrentLinkedQueue inputQueue; 17 | protected ConcurrentLinkedQueue outputQueue; 18 | protected Selector selector; 19 | 20 | /** 21 | * 发送数据给远端 22 | * 23 | * @param tcb 24 | * @param remoteBuffer 25 | * @throws IOException 26 | */ 27 | public void sendToRemote(TCB tcb, ByteBuffer remoteBuffer) throws IOException 28 | { 29 | // 轉發數據給remote 30 | while (remoteBuffer.hasRemaining()) { 31 | tcb.channel.write(remoteBuffer); 32 | } 33 | } 34 | 35 | /** 36 | * 回复本地客户端的数据 37 | * 38 | * @param tcb 39 | * @param replyBuffer 40 | */ 41 | public void sendToClient(TCB tcb, ByteBuffer replyBuffer) 42 | { 43 | replyBuffer.flip(); 44 | Packet referencePacket = tcb.referencePacket; 45 | 46 | int readBytes; 47 | //按照MTU分割 48 | while ((readBytes = Math.min(replyBuffer.remaining(), Packet.MUTE_SIZE - HEADER_SIZE)) > 0) 49 | { 50 | ByteBuffer segmentBuffer = ByteBuffer.allocate(LocalVPN.BUFFER_SIZE); 51 | 52 | segmentBuffer.position(HEADER_SIZE); 53 | 54 | byte[] bytes = new byte[readBytes]; 55 | replyBuffer.get(bytes); 56 | segmentBuffer.put(bytes); 57 | 58 | referencePacket.generateTCPBuffer(segmentBuffer, (byte) (Packet.TCPHeader.PSH | Packet.TCPHeader.ACK), 59 | tcb, readBytes); 60 | 61 | tcb.incrementSeq(readBytes); // Next sequence number 62 | segmentBuffer.position(HEADER_SIZE + readBytes); 63 | 64 | outputQueue.offer(segmentBuffer); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/contract/UdpIO.java: -------------------------------------------------------------------------------- 1 | package org.fly.android.localvpn.contract; 2 | 3 | import org.fly.android.localvpn.LocalVPN; 4 | import org.fly.android.localvpn.Packet; 5 | import org.fly.android.localvpn.store.UDB; 6 | 7 | import java.io.IOException; 8 | import java.nio.ByteBuffer; 9 | import java.nio.channels.Selector; 10 | import java.util.concurrent.ConcurrentLinkedQueue; 11 | 12 | public abstract class UdpIO { 13 | 14 | protected static final int HEADER_SIZE = Packet.IP4_HEADER_SIZE + Packet.UDP_HEADER_SIZE; 15 | protected Selector selector; 16 | protected ConcurrentLinkedQueue outputQueue; 17 | protected ConcurrentLinkedQueue inputQueue; 18 | 19 | /** 20 | * 发送数据给远端 21 | * 22 | * @param udb 23 | * @param remoteBuffer 24 | * @throws IOException 25 | */ 26 | public void sendToRemote(UDB udb, ByteBuffer remoteBuffer) throws IOException 27 | { 28 | // 轉發數據給remote 29 | while (remoteBuffer.hasRemaining()) { 30 | udb.channel.write(remoteBuffer); 31 | } 32 | } 33 | 34 | /** 35 | * 回复本地客户端的数据 36 | * 37 | * @param udb 38 | * @param replyBuffer 39 | */ 40 | public void sendToClient(UDB udb, ByteBuffer replyBuffer) 41 | { 42 | replyBuffer.flip(); 43 | Packet referencePacket = udb.referencePacket; 44 | 45 | int readBytes; 46 | //按照MTU分割 47 | while ((readBytes = Math.min(replyBuffer.remaining(), Packet.MUTE_SIZE - HEADER_SIZE)) > 0) 48 | { 49 | ByteBuffer segmentBuffer = ByteBuffer.allocate(LocalVPN.BUFFER_SIZE); 50 | 51 | segmentBuffer.position(HEADER_SIZE); 52 | 53 | byte[] bytes = new byte[readBytes]; 54 | replyBuffer.get(bytes); 55 | segmentBuffer.put(bytes); 56 | 57 | referencePacket.generateUDPBuffer(segmentBuffer, readBytes); 58 | 59 | segmentBuffer.position(HEADER_SIZE + readBytes); 60 | 61 | outputQueue.offer(segmentBuffer); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/firewall/Dns.java: -------------------------------------------------------------------------------- 1 | package org.fly.android.localvpn.firewall; 2 | 3 | import org.fly.android.localvpn.LocalVPN; 4 | import org.fly.android.localvpn.contract.IFirewall; 5 | import org.fly.protocol.dns.request.Request; 6 | import org.fly.protocol.dns.response.Response; 7 | import org.fly.protocol.exception.RequestException; 8 | import org.fly.protocol.exception.ResponseException; 9 | 10 | import java.io.IOException; 11 | import java.nio.BufferUnderflowException; 12 | import java.nio.ByteBuffer; 13 | import java.util.LinkedList; 14 | import java.util.List; 15 | 16 | public class Dns implements IFirewall { 17 | 18 | private Firewall firewall; 19 | 20 | public Dns(Firewall firewall) { 21 | this.firewall = firewall; 22 | } 23 | 24 | private static Request getRequest(ByteBuffer byteBuffer) 25 | { 26 | Request request = new Request(); 27 | 28 | try { 29 | request.read(byteBuffer); 30 | } catch (Exception e) 31 | { 32 | return null; 33 | } 34 | 35 | return request; 36 | } 37 | 38 | public static boolean maybe(ByteBuffer readableBuffer) 39 | { 40 | Request.Header header = new Request.Header(); 41 | 42 | try 43 | { 44 | header.read(readableBuffer.duplicate()); 45 | 46 | if (!header.isQuery() /*|| !header.isQueryDomain() */|| header.getQdCount() <= 0) 47 | return false; 48 | 49 | } catch (BufferUnderflowException e) 50 | { 51 | return false; 52 | } 53 | 54 | return getRequest(readableBuffer.duplicate()) != null; 55 | } 56 | 57 | @Override 58 | public LinkedList write(ByteBuffer readableBuffer) throws IOException, RequestException, ResponseException { 59 | 60 | Request request = getRequest(readableBuffer); 61 | 62 | if (request == null || request.getHeader().getQdCount() <= 0) 63 | 64 | System.out.println("Invalid DNS"); 65 | 66 | for (Request.Query record: request.getQuestions() 67 | ) { 68 | System.out.println("DNS: -- " + firewall.getBlock().getIpAndPort() + " " + record.getName() + "-" + record.getType()); 69 | } 70 | 71 | Request.Query record = request.getQuestions().get(0); 72 | 73 | List table = Firewall.getFilter().matchDns(record.getName(), record.getType()); 74 | 75 | if (table != null) 76 | { 77 | LinkedList linkedList = new LinkedList<>(); 78 | 79 | try { 80 | ByteBuffer out = ByteBuffer.allocate(LocalVPN.BUFFER_SIZE); 81 | Response response = Response.create(request.getHeader().getId(), record.getName(), table.get(0), record.getType(), 10); 82 | 83 | for (int i = 1; i < table.size() ; i++) 84 | response.addAnswer(new org.fly.protocol.dns.content.Dns.Record(record.getName(), record.getType(), table.get(i), 10)); 85 | 86 | if (request.getHeader().getRd() > 0) 87 | { 88 | response.getHeader().setRa(1); 89 | response.getHeader().setRd(1); 90 | } 91 | 92 | response.write(out); 93 | linkedList.add(out); 94 | 95 | firewall.drop(); 96 | 97 | } catch (Exception e) 98 | { 99 | e.printStackTrace(); 100 | firewall.accept(); 101 | return null; 102 | } 103 | 104 | return linkedList; 105 | } 106 | 107 | firewall.accept(); 108 | 109 | return null; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/firewall/Firewall.java: -------------------------------------------------------------------------------- 1 | package org.fly.android.localvpn.firewall; 2 | 3 | import android.util.Log; 4 | 5 | import org.fly.android.localvpn.LocalVPN; 6 | import org.fly.android.localvpn.Packet; 7 | import org.fly.android.localvpn.contract.IFirewall; 8 | import org.fly.android.localvpn.store.Block; 9 | import org.fly.protocol.exception.RequestException; 10 | import org.fly.protocol.exception.ResponseException; 11 | import org.fly.protocol.http.request.Method; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.nio.ByteBuffer; 16 | import java.nio.charset.StandardCharsets; 17 | import java.util.LinkedList; 18 | import java.util.List; 19 | import java.util.Timer; 20 | import java.util.concurrent.locks.ReadWriteLock; 21 | import java.util.concurrent.locks.ReentrantReadWriteLock; 22 | 23 | public class Firewall { 24 | 25 | private static final String TAG = Firewall.class.getSimpleName(); 26 | private static Filter filter; 27 | 28 | private static final int PROTOCOL_GRID = 0xa101; 29 | 30 | public static void createTable(String config) { 31 | filter = new Filter(config); 32 | } 33 | 34 | private enum Status { 35 | ACCEPT, // 放行 36 | DROP, // 丢包 37 | INCOMPLETE, //包不完整 38 | } 39 | 40 | private LinkedList session = new LinkedList<>(); 41 | private LinkedList response = new LinkedList<>(); 42 | 43 | private Status status = Status.INCOMPLETE; 44 | private IFirewall protocol = null; 45 | private static Other other = new Other(); 46 | private long count = 0; 47 | private final Packet.IP4Header.TransportProtocol transportProtocol; 48 | private Block block; 49 | 50 | public Firewall(Packet.IP4Header.TransportProtocol transportProtocol, Block block) { 51 | 52 | this.transportProtocol = transportProtocol; 53 | this.block = block; 54 | } 55 | 56 | public boolean isAccept() { 57 | return status == Status.ACCEPT; 58 | } 59 | 60 | public boolean isDrop() { 61 | return status == Status.DROP; 62 | } 63 | 64 | public LinkedList getSession() { 65 | return session; 66 | } 67 | 68 | public LinkedList getResponse() { 69 | return response; 70 | } 71 | 72 | public void clear() 73 | { 74 | ByteBuffer buffer; 75 | while((buffer = session.poll()) != null) { 76 | //buffer.clear(); 77 | } 78 | 79 | session.clear(); 80 | } 81 | 82 | public void write(ByteBuffer byteBuffer) { 83 | 84 | ++count; 85 | 86 | ByteBuffer buffer = ByteBuffer.allocate(LocalVPN.BUFFER_SIZE); 87 | 88 | while (byteBuffer.hasRemaining()) 89 | buffer.put(byteBuffer); 90 | 91 | buffer.flip(); 92 | 93 | session.add(buffer.duplicate()); 94 | 95 | handle(buffer.duplicate()); 96 | } 97 | 98 | private String cacheToString() 99 | { 100 | StringBuilder stringBuilder = new StringBuilder(); 101 | for (ByteBuffer buffer : session 102 | ) 103 | stringBuilder.append(StandardCharsets.US_ASCII.decode(buffer.duplicate())); 104 | 105 | return stringBuilder.toString(); 106 | } 107 | 108 | public void accept() 109 | { 110 | status = Status.ACCEPT; 111 | } 112 | 113 | public void drop() 114 | { 115 | status = Status.DROP; 116 | } 117 | 118 | private void handle(ByteBuffer readableBuffer) { 119 | 120 | // 第一个包就可以判断出是什么协议 121 | // HTTP中,如果MTU短到 GET / 都无法一个包的场景, 就放行吧 122 | if (protocol == null) 123 | { 124 | if (transportProtocol == Packet.IP4Header.TransportProtocol.TCP 125 | && Http.maybe(readableBuffer)) 126 | protocol = new Http(this); 127 | else if (transportProtocol == Packet.IP4Header.TransportProtocol.UDP 128 | && Dns.maybe(readableBuffer)) 129 | protocol = new Dns(this); 130 | else 131 | protocol = other; 132 | } 133 | 134 | // 未知协议,直接放行 135 | if (protocol.equals(other)) { 136 | accept(); 137 | return; 138 | } 139 | 140 | try 141 | { 142 | LinkedList results = protocol.write(readableBuffer); 143 | 144 | if (results != null) 145 | response.addAll(results); 146 | 147 | } 148 | catch (IOException | RequestException | ResponseException e) 149 | { 150 | protocol = other; 151 | accept(); 152 | 153 | Log.e(TAG, e.getMessage(), e); 154 | } 155 | } 156 | 157 | public static Filter getFilter() { 158 | return filter; 159 | } 160 | 161 | public Status getStatus() { 162 | return status; 163 | } 164 | 165 | public IFirewall getProtocol() { 166 | return protocol; 167 | } 168 | 169 | public long getCount() { 170 | return count; 171 | } 172 | 173 | public Block getBlock() { 174 | return block; 175 | } 176 | 177 | static class Filter { 178 | private Grid grid; 179 | private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); 180 | private Timer timer; 181 | 182 | public Filter(String config) { 183 | try { 184 | Grid grid = Grid.fromJson(Grid.class, config); 185 | setGrid(grid); 186 | } catch (IOException e) 187 | { 188 | Log.e(TAG, e.toString(), e); 189 | } 190 | 191 | } 192 | 193 | void setGrid(Grid grid) 194 | { 195 | readWriteLock.writeLock().lock(); 196 | this.grid = grid; 197 | grid.init(); 198 | readWriteLock.writeLock().unlock(); 199 | } 200 | 201 | public String matchHttp(String url, Method method) 202 | { 203 | if (grid == null) 204 | return null; 205 | 206 | readWriteLock.readLock().lock(); 207 | 208 | try { 209 | String result; 210 | 211 | result = grid.matchHttp(url, method); 212 | return result; 213 | 214 | } catch (Exception e) { 215 | e.printStackTrace(); 216 | } finally { 217 | readWriteLock.readLock().unlock(); 218 | } 219 | 220 | return null; 221 | } 222 | 223 | public List matchDns(String domain, org.fly.protocol.dns.content.Dns.TYPE type) 224 | { 225 | if (grid == null) 226 | return null; 227 | 228 | readWriteLock.readLock().lock(); 229 | 230 | try { 231 | List list; 232 | 233 | list = grid.matchDns(domain, type); 234 | return list; 235 | 236 | } catch (Exception e) { 237 | e.printStackTrace(); 238 | } finally 239 | { 240 | readWriteLock.readLock().unlock(); 241 | } 242 | 243 | return null; 244 | } 245 | } 246 | } 247 | 248 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/firewall/Grid.java: -------------------------------------------------------------------------------- 1 | package org.fly.android.localvpn.firewall; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | 5 | import org.fly.android.localvpn.structs.Jacksonable; 6 | import org.fly.protocol.http.request.Method; 7 | 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | class Grid extends Jacksonable { 16 | 17 | public Map dns = new HashMap<>(); 18 | public Map http = new HashMap<>(); 19 | 20 | public void init() 21 | { 22 | for (Map.Entry entry: dns.entrySet() 23 | ) { 24 | entry.getValue().pattern = Pattern.compile(entry.getKey(), Pattern.CASE_INSENSITIVE); 25 | } 26 | 27 | for (Map.Entry entry: http.entrySet() 28 | ) { 29 | entry.getValue().pattern = Pattern.compile(entry.getKey(), Pattern.CASE_INSENSITIVE); 30 | } 31 | } 32 | 33 | List matchDns(String domain, org.fly.protocol.dns.content.Dns.TYPE type) 34 | { 35 | for(Map.Entry entry: dns.entrySet()) 36 | { 37 | Matcher matcher = entry.getValue().pattern.matcher(domain); 38 | if (matcher.find()) 39 | { 40 | switch (type) 41 | { 42 | case A: 43 | return entry.getValue() == null || entry.getValue().A.isEmpty() ? null : entry.getValue().A; 44 | case AAAA: 45 | return entry.getValue() == null || entry.getValue().AAAA.isEmpty() ? null : entry.getValue().AAAA; 46 | case CNAME: 47 | return entry.getValue() == null || entry.getValue().CNAME.isEmpty() ? null : entry.getValue().CNAME; 48 | } 49 | } 50 | } 51 | 52 | return null; 53 | } 54 | 55 | String matchHttp(String url, Method method) 56 | { 57 | for(Map.Entry entry: http.entrySet()) 58 | { 59 | Matcher matcher = entry.getValue().pattern.matcher(url); 60 | if (matcher.find()) 61 | { 62 | switch (method) 63 | { 64 | case POST: 65 | return entry.getValue() == null || entry.getValue().POST.isEmpty() ? null : entry.getValue().POST; 66 | case GET: 67 | return entry.getValue() == null || entry.getValue().GET.isEmpty() ? null : entry.getValue().GET; 68 | case PUT: 69 | return entry.getValue() == null || entry.getValue().PUT.isEmpty() ? null : entry.getValue().PUT; 70 | case DELETE: 71 | return entry.getValue() == null || entry.getValue().DELETE.isEmpty() ? null : entry.getValue().DELETE; 72 | } 73 | } 74 | } 75 | return null; 76 | } 77 | 78 | static class Dns { 79 | @JsonIgnore 80 | Pattern pattern; 81 | public List A = new ArrayList<>(); 82 | public List AAAA = new ArrayList<>(); 83 | public List CNAME = new ArrayList<>(); 84 | } 85 | 86 | static class Http { 87 | @JsonIgnore 88 | Pattern pattern; 89 | public String POST = null; 90 | public String GET = null; 91 | public String PUT = null; 92 | public String DELETE = null; 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/firewall/Http.java: -------------------------------------------------------------------------------- 1 | package org.fly.android.localvpn.firewall; 2 | 3 | import android.util.Log; 4 | 5 | import org.apache.commons.codec.binary.StringUtils; 6 | import org.fly.android.localvpn.contract.IFirewall; 7 | import org.fly.protocol.exception.RequestException; 8 | import org.fly.protocol.exception.ResponseException; 9 | import org.fly.protocol.http.request.Method; 10 | import org.fly.protocol.http.request.Request; 11 | import org.fly.protocol.http.response.Response; 12 | 13 | import java.io.IOException; 14 | import java.nio.ByteBuffer; 15 | import java.nio.charset.StandardCharsets; 16 | import java.util.LinkedList; 17 | import java.util.regex.Matcher; 18 | import java.util.regex.Pattern; 19 | 20 | public class Http implements IFirewall { 21 | 22 | private static final String TAG = Http.class.getSimpleName(); 23 | 24 | private static final Pattern inputPattern = Pattern.compile("\\$\\{input\\.([a-z0-9_\\.]*)\\}", Pattern.CASE_INSENSITIVE); 25 | 26 | private Request request = null; 27 | 28 | private Firewall firewall; 29 | 30 | public Http(Firewall firewall) { 31 | this.firewall = firewall; 32 | } 33 | 34 | public static boolean maybe(ByteBuffer readableBuffer) 35 | { 36 | String str = StandardCharsets.US_ASCII.decode(readableBuffer.duplicate()).toString(); 37 | if (!str.contains(" ")) 38 | return false; 39 | 40 | String[] startLine = str.split("\\s+"); 41 | 42 | //因为网址可能会比较长,所以只检查了Method 和 网址的关键字 43 | return Method.lookup(startLine[0]) != null && (startLine[1].startsWith("/") || startLine[1].contains("://")); 44 | } 45 | 46 | @Override 47 | public LinkedList write(ByteBuffer readableBuffer) throws IOException, RequestException, ResponseException { 48 | if (null == request) 49 | request = new Request(); 50 | 51 | LinkedList results = new LinkedList<>(); 52 | 53 | // 依次写入 54 | request.write(readableBuffer.duplicate()); 55 | 56 | String table = null; 57 | String url = null; 58 | //等待头完成 59 | if (request.isHeaderComplete()) 60 | { 61 | url = request.getUrl(); 62 | table = Firewall.getFilter().matchHttp(url, request.getMethod()); 63 | 64 | if (table != null) 65 | firewall.drop(); 66 | else 67 | firewall.accept(); 68 | } 69 | 70 | // 包体结束, 清除httpRequest等待通道复用 71 | if (request.isBodyComplete()) 72 | { 73 | Log.d(TAG, "HTTP -- " + firewall.getBlock().getIpAndPort() + " " + request.getMethod() + ": " + url); 74 | Log.d(TAG, request.getHeaderRaw()); 75 | 76 | if (table != null) 77 | { 78 | table = parse(request, table); 79 | 80 | if (table.startsWith("HTTP/1.1 ") && (table.contains("\r\n\r\n") || table.contains("\n\n"))) 81 | { 82 | results.add(StringUtils.getByteBufferUtf8(table)); 83 | } else { 84 | Response response = Response.newFixedLengthResponse(table); 85 | 86 | ByteBuffer buffer = ByteBuffer.allocateDirect(table.length() + Request.BUFF_SIZE); 87 | response.send(buffer); 88 | 89 | results.add(buffer); 90 | } 91 | } 92 | 93 | request = null; 94 | } 95 | 96 | return results; 97 | } 98 | 99 | private String parse(Request request, String content) { 100 | 101 | if (content.contains("${input.")) 102 | { 103 | Matcher matcher = inputPattern.matcher(content); 104 | 105 | StringBuffer buffer = new StringBuffer(); 106 | while(matcher.find()){ 107 | String value = request.input(matcher.group(1)); 108 | if (value == null) continue; 109 | 110 | matcher.appendReplacement(buffer, value); 111 | } 112 | 113 | matcher.appendTail(buffer); 114 | 115 | return buffer.toString(); 116 | } 117 | 118 | return content; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/firewall/Other.java: -------------------------------------------------------------------------------- 1 | package org.fly.android.localvpn.firewall; 2 | 3 | import org.fly.android.localvpn.contract.IFirewall; 4 | import org.fly.protocol.exception.RequestException; 5 | import org.fly.protocol.exception.ResponseException; 6 | 7 | import java.io.IOException; 8 | import java.nio.ByteBuffer; 9 | import java.util.LinkedList; 10 | 11 | public class Other implements IFirewall { 12 | 13 | @Override 14 | public LinkedList write(ByteBuffer byteBuffer) throws IOException, RequestException, ResponseException { 15 | return null; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/store/Block.java: -------------------------------------------------------------------------------- 1 | package org.fly.android.localvpn.store; 2 | 3 | import org.fly.android.localvpn.Packet; 4 | import org.fly.android.localvpn.firewall.Firewall; 5 | 6 | import java.nio.ByteBuffer; 7 | import java.util.LinkedList; 8 | 9 | public abstract class Block { 10 | public String ipAndPort; 11 | public Packet referencePacket; 12 | protected Firewall firewall; 13 | 14 | protected static final int MAX_CACHE_SIZE = 50; // XXX: Is this ideal? 15 | 16 | protected abstract void closeChannel(); 17 | 18 | public Firewall getFirewall() { 19 | return firewall; 20 | } 21 | 22 | public LinkedList filter(ByteBuffer byteBuffer) 23 | { 24 | firewall.write(byteBuffer); 25 | 26 | // 允许转发 27 | if (firewall.isAccept()) 28 | return firewall.getSession(); 29 | // 丢弃包 30 | else if (firewall.isDrop()) 31 | firewall.clear(); 32 | 33 | return null; 34 | } 35 | 36 | public String getIpAndPort() { 37 | return ipAndPort; 38 | } 39 | 40 | public Packet getReferencePacket() { 41 | return referencePacket; 42 | } 43 | 44 | public LinkedList getResponse() { 45 | return firewall.getResponse(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/store/TCB.java: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2015, Mohamed Naufal 3 | ** 4 | ** Licensed under the Apache License, Version 2.0 (the "License"); 5 | ** you may not use this file except in compliance with the License. 6 | ** You may obtain a copy of the License at 7 | ** 8 | ** http://www.apache.org/licenses/LICENSE-2.0 9 | ** 10 | ** Unless required by applicable law or agreed to in writing, software 11 | ** distributed under the License is distributed on an "AS IS" BASIS, 12 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | ** See the License for the specific language governing permissions and 14 | ** limitations under the License. 15 | */ 16 | 17 | package org.fly.android.localvpn.store; 18 | 19 | import android.util.Log; 20 | 21 | import org.fly.android.localvpn.Packet; 22 | import org.fly.android.localvpn.firewall.Firewall; 23 | import org.fly.android.localvpn.structs.LRUCache; 24 | 25 | import java.io.IOException; 26 | import java.nio.channels.SelectionKey; 27 | import java.nio.channels.SocketChannel; 28 | import java.util.Iterator; 29 | import java.util.Map; 30 | import java.util.Random; 31 | 32 | /** 33 | * Transmission Control Block 34 | */ 35 | public class TCB extends Block 36 | { 37 | private static final String TAG = TCB.class.getSimpleName(); 38 | 39 | // TCP has more states, but we need only these 40 | public enum TCBStatus 41 | { 42 | //第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,客戶端进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers) 43 | SYN_SENT, 44 | //第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时服務器也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; 45 | SYN_RECEIVED, 46 | //第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。 47 | ESTABLISHED, 48 | //当某端后发送FIN,對方端回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。 49 | CLOSE_WAIT, 50 | //已經 FIN - ACK 過的狀態 51 | LAST_ACK, 52 | } 53 | 54 | public long mySequenceNum, theirSequenceNum; 55 | public long myAcknowledgementNum, theirAcknowledgementNum; 56 | 57 | public TCBStatus status; 58 | 59 | public SocketChannel channel; 60 | public boolean waitingForNetworkData; 61 | public SelectionKey selectionKey; 62 | 63 | private static LRUCache tcbCache = 64 | new LRUCache<>(MAX_CACHE_SIZE, new LRUCache.CleanupCallback() 65 | { 66 | @Override 67 | public void cleanup(Map.Entry eldest) 68 | { 69 | eldest.getValue().closeChannel(); 70 | } 71 | }); 72 | 73 | public static TCB getTCB(String ipAndPort) 74 | { 75 | synchronized (tcbCache) 76 | { 77 | return tcbCache.get(ipAndPort); 78 | } 79 | } 80 | 81 | public static void putTCB(String ipAndPort, TCB tcb) 82 | { 83 | synchronized (tcbCache) 84 | { 85 | tcbCache.put(ipAndPort, tcb); 86 | } 87 | } 88 | 89 | public TCB(String ipAndPort, 90 | Packet.TCPHeader tcpHeader, 91 | SocketChannel channel, 92 | Packet referencePacket 93 | ) 94 | { 95 | this(ipAndPort, 96 | new Random().nextInt(Short.MAX_VALUE + 1), 97 | tcpHeader.sequenceNumber, 98 | tcpHeader.sequenceNumber + 1, 99 | tcpHeader.acknowledgementNumber, 100 | channel, 101 | referencePacket); 102 | } 103 | 104 | public TCB(String ipAndPort, 105 | long mySequenceNum, 106 | long theirSequenceNum, 107 | long myAcknowledgementNum, 108 | long theirAcknowledgementNum, 109 | SocketChannel channel, 110 | Packet referencePacket) 111 | { 112 | this.ipAndPort = ipAndPort; 113 | 114 | this.mySequenceNum = mySequenceNum; 115 | this.theirSequenceNum = theirSequenceNum; 116 | this.myAcknowledgementNum = myAcknowledgementNum; 117 | this.theirAcknowledgementNum = theirAcknowledgementNum; 118 | 119 | this.channel = channel; 120 | this.referencePacket = referencePacket; 121 | 122 | firewall = new Firewall(Packet.IP4Header.TransportProtocol.TCP, this); 123 | } 124 | 125 | public void incrementReplyAck(Packet.TCPHeader tcpHeader, int payloadSize) 126 | { 127 | myAcknowledgementNum = tcpHeader.sequenceNumber + payloadSize; 128 | theirAcknowledgementNum = tcpHeader.acknowledgementNumber; 129 | } 130 | 131 | public void incrementReplyAck(Packet.TCPHeader tcpHeader) 132 | { 133 | incrementReplyAck(tcpHeader, 1); 134 | } 135 | 136 | public void incrementSeq(int size) 137 | { 138 | mySequenceNum += size; 139 | 140 | mySequenceNum = mySequenceNum & 0xffffffffL; 141 | 142 | /*if (mySequenceNum > 0xffffffffL) // 2 ** 32 - 1 143 | mySequenceNum %= 0x100000000L; // 2 ** 32*/ 144 | } 145 | 146 | public int getRemainSeq() 147 | { 148 | return (int)(0xffffffffL - mySequenceNum); 149 | } 150 | 151 | public void incrementSeq() 152 | { 153 | incrementSeq(1); 154 | } 155 | 156 | public static void closeTCB(TCB tcb) 157 | { 158 | Log.d(TAG, "Close Connection:" + tcb.getIpAndPort()); 159 | 160 | tcb.closeChannel(); 161 | synchronized (tcbCache) 162 | { 163 | tcbCache.remove(tcb.ipAndPort); 164 | } 165 | } 166 | 167 | public static void closeAll() 168 | { 169 | synchronized (tcbCache) 170 | { 171 | Iterator> it = tcbCache.entrySet().iterator(); 172 | while (it.hasNext()) 173 | { 174 | it.next().getValue().closeChannel(); 175 | it.remove(); 176 | } 177 | } 178 | } 179 | 180 | protected void closeChannel() 181 | { 182 | try 183 | { 184 | firewall.clear(); 185 | channel.close(); 186 | } 187 | catch (IOException e) 188 | { 189 | // Ignore 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/store/UDB.java: -------------------------------------------------------------------------------- 1 | package org.fly.android.localvpn.store; 2 | 3 | import android.util.Log; 4 | 5 | import org.fly.android.localvpn.Packet; 6 | import org.fly.android.localvpn.firewall.Firewall; 7 | import org.fly.android.localvpn.structs.LRUCache; 8 | 9 | import java.io.IOException; 10 | import java.nio.channels.DatagramChannel; 11 | import java.util.Iterator; 12 | import java.util.Map; 13 | 14 | /** 15 | * User Datagram Block 16 | */ 17 | public class UDB extends Block 18 | { 19 | private static final String TAG = UDB.class.getSimpleName(); 20 | 21 | public DatagramChannel channel; 22 | 23 | private static LRUCache udpCache = 24 | new LRUCache<>(MAX_CACHE_SIZE, new LRUCache.CleanupCallback() 25 | { 26 | @Override 27 | public void cleanup(Map.Entry eldest) 28 | { 29 | eldest.getValue().closeChannel(); 30 | } 31 | }); 32 | 33 | public static UDB getUDB(String ipAndPort) 34 | { 35 | synchronized (udpCache) 36 | { 37 | return udpCache.get(ipAndPort); 38 | } 39 | } 40 | 41 | public static void putUDB(String ipAndPort, UDB udb) 42 | { 43 | synchronized (udpCache) 44 | { 45 | udpCache.put(ipAndPort, udb); 46 | } 47 | } 48 | 49 | public UDB(String ipAndPort, DatagramChannel channel, Packet referencePacket) { 50 | this.ipAndPort = ipAndPort; 51 | this.channel = channel; 52 | this.referencePacket = referencePacket; 53 | firewall = new Firewall(Packet.IP4Header.TransportProtocol.UDP, this); 54 | } 55 | 56 | public static void closeUDB(UDB udb) 57 | { 58 | Log.d(TAG, "Close Connection:" + udb.getIpAndPort()); 59 | 60 | udb.closeChannel(); 61 | 62 | synchronized (udpCache) 63 | { 64 | udpCache.remove(udb.ipAndPort); 65 | } 66 | } 67 | 68 | public static void closeAll() 69 | { 70 | synchronized (udpCache) 71 | { 72 | Iterator> it = udpCache.entrySet().iterator(); 73 | while (it.hasNext()) 74 | { 75 | it.next().getValue().closeChannel(); 76 | it.remove(); 77 | } 78 | } 79 | } 80 | 81 | protected void closeChannel() 82 | { 83 | try 84 | { 85 | channel.close(); 86 | } 87 | catch (IOException e) 88 | { 89 | // Ignore 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/structs/BufferUtils.java: -------------------------------------------------------------------------------- 1 | package org.fly.android.localvpn.structs; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | public class BufferUtils { 6 | // --------------------------ByteBuffer Byte--------------------------------- 7 | 8 | public static short getUnsignedByte(ByteBuffer bb) { 9 | return ((short) (bb.get() & 0xff)); 10 | } 11 | 12 | public static void putUnsignedByte(ByteBuffer bb, int value) { 13 | bb.put((byte) (value & 0xff)); 14 | } 15 | 16 | public static short getUnsignedByte(ByteBuffer bb, int position) { 17 | return ((short) (bb.get(position) & (short) 0xff)); 18 | } 19 | 20 | public static void putUnsignedByte(ByteBuffer bb, int position, int value) { 21 | bb.put(position, (byte) (value & 0xff)); 22 | } 23 | 24 | // --------------------------ByteBuffer Short--------------------------------- 25 | 26 | public static int getUnsignedShort(ByteBuffer bb) { 27 | return (bb.getShort() & 0xffff); 28 | } 29 | 30 | public static void putUnsignedShort(ByteBuffer bb, int value) { 31 | bb.putShort((short) (value & 0xffff)); 32 | } 33 | 34 | public static int getUnsignedShort(ByteBuffer bb, int position) { 35 | return (bb.getShort(position) & 0xffff); 36 | } 37 | 38 | public static void putUnsignedShort(ByteBuffer bb, int position, int value) { 39 | bb.putShort(position, (short) (value & 0xffff)); 40 | } 41 | 42 | // --------------------------ByteBuffer Int--------------------------------- 43 | 44 | 45 | public static long getUnsignedInt(ByteBuffer bb) { 46 | return ((long) bb.getInt() & 0xffffffffL); 47 | } 48 | 49 | public static void putUnsignedInt(ByteBuffer bb, long value) { 50 | bb.putInt((int) (value & 0xffffffffL)); 51 | } 52 | 53 | public static long getUnsignedInt(ByteBuffer bb, int position) { 54 | return ((long) bb.getInt(position) & 0xffffffffL); 55 | } 56 | 57 | public static void putUnsignedInt(ByteBuffer bb, int position, long value) { 58 | bb.putInt(position, (int) (value & 0xffffffffL)); 59 | } 60 | 61 | // --------------------------IoBuffer Byte--------------------------------- 62 | 63 | public static short getUnsignedByte(IoBuffer bb) { 64 | return ((short) (bb.get() & 0xff)); 65 | } 66 | 67 | public static void putUnsignedByte(IoBuffer bb, int value) { 68 | bb.put((byte) (value & 0xff)); 69 | } 70 | 71 | public static short getUnsignedByte(IoBuffer bb, int position) { 72 | return ((short) (bb.get(position) & (short) 0xff)); 73 | } 74 | 75 | public static void putUnsignedByte(IoBuffer bb, int position, int value) { 76 | bb.put(position, (byte) (value & 0xff)); 77 | } 78 | 79 | // --------------------------IoBuffer Short--------------------------------- 80 | 81 | public static int getUnsignedShort(IoBuffer bb) { 82 | return (bb.getShort() & 0xffff); 83 | } 84 | 85 | public static void putUnsignedShort(IoBuffer bb, int value) { 86 | bb.putShort((short) (value & 0xffff)); 87 | } 88 | 89 | public static int getUnsignedShort(IoBuffer bb, int position) { 90 | return (int)(bb.getShort(position) & 0xffff); 91 | } 92 | 93 | public static void putUnsignedShort(IoBuffer bb, int position, int value) { 94 | bb.putShort(position, (short) (value & 0xffff)); 95 | } 96 | 97 | // --------------------------IoBuffer Int--------------------------------- 98 | 99 | public static long getUnsignedInt(IoBuffer bb) { 100 | return ((long) bb.getInt() & 0xffffffffL); 101 | } 102 | 103 | public static void putUnsignedInt(IoBuffer bb, long value) { 104 | bb.putInt((int) (value & 0xffffffffL)); 105 | } 106 | 107 | public static long getUnsignedInt(IoBuffer bb, int position) { 108 | return ((long) bb.getInt(position) & 0xffffffffL); 109 | } 110 | 111 | public static void putUnsignedInt(IoBuffer bb, int position, long value) { 112 | bb.putInt(position, (int) (value & 0xffffffffL)); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/structs/HttpUtils.java: -------------------------------------------------------------------------------- 1 | package org.fly.android.localvpn.structs; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.UnsupportedEncodingException; 6 | import java.net.URL; 7 | import java.net.URLDecoder; 8 | import java.util.ArrayList; 9 | import java.util.Enumeration; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Properties; 14 | import java.util.StringTokenizer; 15 | 16 | public class HttpUtils { 17 | /** 18 | * Decode percent encoded String values. 19 | * 20 | * @param str 21 | * the percent encoded String 22 | * @return expanded form of the input, for example "foo%20bar" becomes 23 | * "foo bar" 24 | */ 25 | public static String decodePercent(String str) throws UnsupportedEncodingException { 26 | return URLDecoder.decode(str, "UTF8"); 27 | } 28 | 29 | /** 30 | * Decode parameters from a URL, handing the case where a single parameter 31 | * name might have been supplied several times, by return lists of values. 32 | * In general these lists will contain a single element. 33 | * 34 | * @param queryString 35 | * a query string pulled from the URL. 36 | * @return a map of String (parameter name) to 37 | * List<String> (a list of the values supplied). 38 | */ 39 | protected static Map> decodeParameters(String queryString) throws UnsupportedEncodingException { 40 | Map> params = new HashMap<>(); 41 | if (queryString != null) { 42 | StringTokenizer st = new StringTokenizer(queryString, "&"); 43 | while (st.hasMoreTokens()) { 44 | String e = st.nextToken(); 45 | int sep = e.indexOf('='); 46 | String propertyName = sep >= 0 ? decodePercent(e.substring(0, sep)).trim() : decodePercent(e).trim(); 47 | if (!params.containsKey(propertyName)) { 48 | params.put(propertyName, new ArrayList()); 49 | } 50 | String propertyValue = sep >= 0 ? decodePercent(e.substring(sep + 1)) : null; 51 | if (propertyValue != null) { 52 | params.get(propertyName).add(propertyValue); 53 | } 54 | } 55 | } 56 | return params; 57 | } 58 | 59 | protected static Map MIME_TYPES; 60 | 61 | public static Map mimeTypes() { 62 | if (MIME_TYPES == null) { 63 | MIME_TYPES = new HashMap<>(); 64 | 65 | try { 66 | loadMimeTypes(MIME_TYPES, "META-INF/nanohttpd/default-mimetypes.properties"); 67 | loadMimeTypes(MIME_TYPES, "META-INF/nanohttpd/mimetypes.properties"); 68 | } catch (IOException e) 69 | { 70 | e.printStackTrace(); 71 | } 72 | } 73 | return MIME_TYPES; 74 | } 75 | 76 | @SuppressWarnings({ 77 | "unchecked", 78 | "rawtypes" 79 | }) 80 | private static void loadMimeTypes(Map result, String resourceName) throws IOException { 81 | 82 | Enumeration resources = HttpUtils.class.getClassLoader().getResources(resourceName); 83 | while (resources.hasMoreElements()) { 84 | URL url = resources.nextElement(); 85 | Properties properties = new Properties(); 86 | InputStream stream = url.openStream(); 87 | properties.load(stream); 88 | 89 | result.putAll((Map) properties); 90 | } 91 | } 92 | 93 | /** 94 | * Get MIME type from file name extension, if possible 95 | * 96 | * @param uri 97 | * the string representing a file 98 | * @return the connected mime/type 99 | */ 100 | public static String getMimeTypeForFile(String uri) { 101 | int dot = uri.lastIndexOf('.'); 102 | String mime = null; 103 | if (dot >= 0) { 104 | mime = mimeTypes().get(uri.substring(dot + 1).toLowerCase()); 105 | } 106 | return mime == null ? "application/octet-stream" : mime; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/structs/IoUtils.java: -------------------------------------------------------------------------------- 1 | package org.fly.android.localvpn.structs; 2 | 3 | import java.io.Closeable; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.net.ServerSocket; 7 | import java.net.Socket; 8 | import java.nio.charset.Charset; 9 | import java.nio.charset.StandardCharsets; 10 | import java.text.DecimalFormat; 11 | 12 | import okio.BufferedSink; 13 | import okio.BufferedSource; 14 | import okio.Okio; 15 | 16 | public class IoUtils { 17 | 18 | private final static Charset utf8 = StandardCharsets.UTF_8; 19 | 20 | public static String getFileSize(long size) { 21 | if (size <= 0) 22 | return "0"; 23 | 24 | final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" }; 25 | int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); 26 | 27 | return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; 28 | } 29 | 30 | public static String read(File file, Charset charset) throws IOException 31 | { 32 | BufferedSource source = Okio.buffer(Okio.source(file)); 33 | String str = source.readString(charset); 34 | source.close(); 35 | return str; 36 | } 37 | 38 | public static byte[] readBytes(File file) throws IOException 39 | { 40 | BufferedSource source = Okio.buffer(Okio.source(file)); 41 | byte[] bytes = source.readByteArray(); 42 | source.close(); 43 | 44 | return bytes; 45 | } 46 | 47 | public static String readJson(File file) throws IOException 48 | { 49 | return readUtf8(file); 50 | } 51 | 52 | public static String readUtf8(File file) throws IOException 53 | { 54 | return read(file, utf8); 55 | } 56 | 57 | public static void write(File file, String content, Charset charset) throws IOException 58 | { 59 | BufferedSink sink = Okio.buffer(Okio.sink(file)); 60 | sink.writeString(content, charset); 61 | sink.close(); 62 | } 63 | 64 | public static void writeUtf8(File file, String content) throws IOException 65 | { 66 | write(file, content, utf8); 67 | } 68 | 69 | public static void write(File file, byte[] bytes) throws IOException 70 | { 71 | BufferedSink sink = Okio.buffer(Okio.sink(file)); 72 | sink.write(bytes); 73 | sink.close(); 74 | } 75 | 76 | public static void append(File file, String content, Charset charset) throws IOException 77 | { 78 | BufferedSink sink = Okio.buffer(Okio.appendingSink(file)); 79 | sink.writeString(content, charset); 80 | sink.close(); 81 | } 82 | 83 | public static void appendUtf8(File file, String content) throws IOException 84 | { 85 | append(file, content, utf8); 86 | } 87 | 88 | public static final void safeClose(Object closeable) throws IOException, IllegalArgumentException { 89 | 90 | if (closeable != null) { 91 | if (closeable instanceof Closeable) { 92 | ((Closeable) closeable).close(); 93 | } else if (closeable instanceof Socket) { 94 | ((Socket) closeable).close(); 95 | } else if (closeable instanceof ServerSocket) { 96 | ((ServerSocket) closeable).close(); 97 | } else { 98 | throw new IllegalArgumentException("Unknown object to close"); 99 | } 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/structs/Jacksonable.java: -------------------------------------------------------------------------------- 1 | package org.fly.android.localvpn.structs; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.core.type.TypeReference; 6 | import com.fasterxml.jackson.databind.DeserializationFeature; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.fasterxml.jackson.databind.SerializationFeature; 9 | 10 | import org.apache.commons.codec.binary.StringUtils; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | public class Jacksonable { 18 | 19 | public static class Builder{ 20 | public static ObjectMapper makeAdapter() 21 | { 22 | ObjectMapper objectMapper = new ObjectMapper(); 23 | objectMapper.enable(JsonParser.Feature.IGNORE_UNDEFINED); 24 | objectMapper.enable(JsonParser.Feature.ALLOW_COMMENTS); 25 | objectMapper.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES); 26 | objectMapper.enable(JsonParser.Feature.ALLOW_TRAILING_COMMA); 27 | objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); 28 | objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); 29 | return objectMapper; 30 | } 31 | 32 | public static String toJson(Object o) 33 | { 34 | return toJson(makeAdapter(), o); 35 | } 36 | 37 | public static String toJson(ObjectMapper objectMapper, Object o) 38 | { 39 | try { 40 | return objectMapper.writeValueAsString(o); 41 | } catch (JsonProcessingException e) 42 | { 43 | e.printStackTrace(); 44 | } 45 | 46 | return null; 47 | } 48 | 49 | public static void toJson(File file, Object o) throws IOException 50 | { 51 | toJson(makeAdapter(), file, o); 52 | } 53 | 54 | public static void toJson(ObjectMapper objectMapper, File file, Object o) throws IOException 55 | { 56 | objectMapper.writeValue(file, o); 57 | } 58 | 59 | public static Map jsonToMap(ObjectMapper objectMapper, String json) 60 | { 61 | if (json != null && !json.isEmpty()) { 62 | 63 | try { 64 | return objectMapper.readValue(json, new TypeReference>(){}); 65 | } catch (IOException e) 66 | { 67 | e.printStackTrace(); 68 | return null; 69 | } 70 | } 71 | 72 | return null; 73 | } 74 | 75 | public static Map jsonToMap(String json) 76 | { 77 | return jsonToMap(makeAdapter(), json); 78 | } 79 | 80 | public static List> jsonToRecords(String json) 81 | { 82 | return jsonToRecords(makeAdapter(), json); 83 | } 84 | 85 | public static List> jsonToRecords(ObjectMapper objectMapper, String json) 86 | { 87 | if (json != null && !json.isEmpty()) { 88 | 89 | try { 90 | return objectMapper.readValue(json, new TypeReference>>(){}); 91 | } catch (IOException e) 92 | { 93 | e.printStackTrace(); 94 | return null; 95 | } 96 | } 97 | 98 | return null; 99 | } 100 | 101 | public static List jsonToList(String json){ 102 | return jsonToList(makeAdapter(), json); 103 | } 104 | 105 | public static List jsonToList(ObjectMapper objectMapper, String json) 106 | { 107 | if (json != null && !json.isEmpty()) { 108 | try { 109 | return objectMapper.readValue(json, new TypeReference>(){}); 110 | } catch (IOException e) 111 | { 112 | e.printStackTrace(); 113 | return null; 114 | } 115 | } 116 | 117 | return null; 118 | } 119 | 120 | } 121 | 122 | public static T fromJson(ObjectMapper objectMapper, final Class clazz, String json) throws IOException 123 | { 124 | return objectMapper.readValue(json, clazz); 125 | } 126 | 127 | public static T fromJson(final Class clazz, String json) throws IOException 128 | { 129 | return fromJson(Builder.makeAdapter(), clazz, json); 130 | } 131 | 132 | public static T fromJson(ObjectMapper objectMapper, final Class clazz, byte[] json) throws IOException 133 | { 134 | return fromJson(objectMapper, clazz, StringUtils.newStringUtf8(json)); 135 | } 136 | 137 | public static T fromJson(final Class clazz, byte[] json) throws IOException 138 | { 139 | return fromJson(clazz, StringUtils.newStringUtf8(json)); 140 | } 141 | 142 | public static T fromJson(ObjectMapper objectMapper, final Class clazz, File file) throws IOException 143 | { 144 | return objectMapper.readValue(file, clazz); 145 | } 146 | 147 | public static T fromJson(final Class clazz, File file) throws IOException 148 | { 149 | return fromJson(Builder.makeAdapter(), clazz, file); 150 | } 151 | 152 | public String toJson(ObjectMapper objectMapper) 153 | { 154 | 155 | return Builder.toJson(objectMapper, this); 156 | } 157 | 158 | public String toJson() 159 | { 160 | return toJson(Builder.makeAdapter()); 161 | } 162 | 163 | public void toJson(ObjectMapper objectMapper, File file) throws Exception 164 | { 165 | Builder.toJson(objectMapper, file, this); 166 | } 167 | 168 | public void toJson(File file) throws Exception 169 | { 170 | toJson(Builder.makeAdapter(), file); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/android/localvpn/structs/LRUCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2015, Mohamed Naufal 3 | ** 4 | ** Licensed under the Apache License, Version 2.0 (the "License"); 5 | ** you may not use this file except in compliance with the License. 6 | ** You may obtain a copy of the License at 7 | ** 8 | ** http://www.apache.org/licenses/LICENSE-2.0 9 | ** 10 | ** Unless required by applicable law or agreed to in writing, software 11 | ** distributed under the License is distributed on an "AS IS" BASIS, 12 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | ** See the License for the specific language governing permissions and 14 | ** limitations under the License. 15 | */ 16 | 17 | package org.fly.android.localvpn.structs; 18 | 19 | import java.util.LinkedHashMap; 20 | import java.util.Map; 21 | 22 | public class LRUCache extends LinkedHashMap 23 | { 24 | private int maxSize; 25 | private CleanupCallback callback; 26 | 27 | public LRUCache(int maxSize, CleanupCallback callback) 28 | { 29 | super(maxSize + 1, 1, true); 30 | 31 | this.maxSize = maxSize; 32 | this.callback = callback; 33 | } 34 | 35 | @Override 36 | protected boolean removeEldestEntry(Entry eldest) 37 | { 38 | if (size() > maxSize) 39 | { 40 | callback.cleanup(eldest); 41 | return true; 42 | } 43 | return false; 44 | } 45 | 46 | public interface CleanupCallback 47 | { 48 | void cleanup(Entry eldest); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/Protocol.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol; 2 | 3 | public enum Protocol { 4 | NONE, 5 | HTTP, 6 | DNS, 7 | OTHER, 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/contract/IFactory.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.contract; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | /** 37 | * Represents a simple factory 38 | * 39 | * @author LordFokas 40 | * @param 41 | * The Type of object to create 42 | */ 43 | public interface IFactory { 44 | 45 | T create(); 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/contract/IFactoryThrowing.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.contract; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | /** 37 | * Represents a factory that can throw an exception instead of actually creating 38 | * an object 39 | * 40 | * @author LordFokas 41 | * @param 42 | * The Type of object to create 43 | * @param 44 | * The base Type of exceptions that can be thrown 45 | */ 46 | public interface IFactoryThrowing { 47 | 48 | T create() throws E; 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/contract/IHandler.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.contract; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | /** 37 | * Defines a generic handler that returns an object of type O when given an 38 | * object of type I. 39 | * 40 | * @author LordFokas 41 | * @param 42 | * The input type. 43 | * @param 44 | * The output type. 45 | */ 46 | public interface IHandler { 47 | 48 | public O handle(I input); 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/dns/content/Label.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.dns.content; 2 | 3 | import org.fly.android.localvpn.structs.BufferUtils; 4 | import org.fly.protocol.exception.PtrException; 5 | 6 | import java.nio.ByteBuffer; 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public class Label { 14 | 15 | public final static String DOT = "."; 16 | 17 | ByteBuffer byteBuffer; 18 | Map labelPositions = new HashMap<>(); 19 | 20 | public Label(ByteBuffer byteBuffer) { 21 | this.byteBuffer = byteBuffer; 22 | } 23 | 24 | public List readLabels() throws PtrException 25 | { 26 | return readLabels(-1); 27 | } 28 | 29 | public List readLabels(int offset) throws PtrException 30 | { 31 | if (offset != -1) 32 | byteBuffer.position(offset); 33 | 34 | List labels = new ArrayList<>(); 35 | 36 | while(true) 37 | { 38 | byte ch = byteBuffer.get(); 39 | if (isEndOfLabel(ch)) 40 | { 41 | if (!labels.isEmpty()) 42 | { 43 | // insert to labelPositions 44 | findLabelsPtr(labels, byteBuffer.position() - 1 - join(labels).length()); 45 | } 46 | break; 47 | } 48 | else if (isPtr(ch)) 49 | { 50 | byteBuffer.position(byteBuffer.position() - 1); 51 | int ptr = BufferUtils.getUnsignedShort(byteBuffer); 52 | ptr = getPtrOffset(ptr); 53 | 54 | byteBuffer.mark(); 55 | 56 | labels = readLabels(ptr); 57 | 58 | byteBuffer.reset(); 59 | break; 60 | } else { 61 | int len = ch & 0xff; 62 | byte[] bytes = new byte[len]; 63 | byteBuffer.get(bytes, 0, len); 64 | labels.add(new String(bytes)); 65 | } 66 | } 67 | return labels; 68 | } 69 | 70 | public void writeLabels(List labels, int offset) throws PtrException 71 | { 72 | if (offset != -1) 73 | byteBuffer.position(offset); 74 | 75 | int ptr = findLabelsPtr(labels, byteBuffer.position()); 76 | 77 | if (ptr == -1) { 78 | for (String label: labels 79 | ) { 80 | BufferUtils.putUnsignedByte(byteBuffer, label.length()); 81 | byteBuffer.put(label.getBytes()); 82 | } 83 | byteBuffer.put((byte)0); 84 | 85 | } else { 86 | BufferUtils.putUnsignedShort(byteBuffer, ptr); 87 | } 88 | } 89 | 90 | public void writeLabels(String domain, int offset) throws PtrException 91 | { 92 | writeLabels(Arrays.asList(domain.split("\\" + DOT)), offset); 93 | } 94 | 95 | public void writeLabels(String domain) throws PtrException 96 | { 97 | writeLabels(domain, -1); 98 | } 99 | 100 | public void writeLabels(List labels) throws PtrException 101 | { 102 | writeLabels(labels, -1); 103 | } 104 | 105 | protected int findLabelsPtr(List labels, int currentOffset) throws PtrException 106 | { 107 | String key = join(labels); 108 | 109 | if (labelPositions.containsKey(key)) 110 | { 111 | return getOffsetPtr(labelPositions.get(key)); 112 | } else { 113 | labelPositions.put(key, currentOffset); 114 | } 115 | 116 | return -1; 117 | } 118 | 119 | protected boolean isEndOfLabel(byte ch) 120 | { 121 | return ch == '\0'; 122 | } 123 | 124 | protected static boolean isPtr(byte ch){ 125 | return (ch & 0xc0) == 0xc0; // 1100 0000 126 | } 127 | 128 | public static int getPtrOffset(int ptr) 129 | { 130 | return ptr & 0x3fff; // 0011 1111 1111 1111 131 | } 132 | 133 | public static int getOffsetPtr(int offset) throws PtrException 134 | { 135 | if (offset > 0x3fff) { // 0011 1111 1111 1111 136 | throw new PtrException("Ptr offset must <= 0x3fff."); 137 | } 138 | 139 | return offset | 0xc000; //1100 0000 0000 0000 140 | } 141 | 142 | public static String join(CharSequence separator, List list) 143 | { 144 | StringBuilder s = new StringBuilder(); 145 | for(String r : list){ 146 | if (s.length() != 0) 147 | s.append(separator); 148 | s.append(r); 149 | } 150 | return s.toString(); 151 | } 152 | 153 | public static String join(List list) 154 | { 155 | return join(DOT, list); 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/dns/request/Request.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.dns.request; 2 | 3 | import org.fly.protocol.dns.content.Dns; 4 | 5 | public class Request extends Dns { 6 | 7 | public Request() { 8 | } 9 | 10 | public Request(String domain, TYPE type) { 11 | header.setId(generateId()); 12 | header.setRd(1); 13 | 14 | Query question = new Query(domain, type); 15 | addQuestion(question); 16 | } 17 | 18 | public static Request create(String domain, TYPE type) 19 | { 20 | return new Request(domain, type); 21 | } 22 | 23 | public static Request create(String domain) 24 | { 25 | return new Request(domain, TYPE.A); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/dns/response/Response.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.dns.response; 2 | 3 | import org.fly.protocol.dns.content.Dns; 4 | 5 | public class Response extends Dns { 6 | 7 | public Response() { 8 | } 9 | 10 | public Response(int id, String domain, String value, TYPE type, int ttl) { 11 | header.setId(id); 12 | header.setQr(1); 13 | header.setOpcode(OPCODE.QUERY); 14 | header.setRd(1); 15 | header.setRcode(RCODE.OK); 16 | 17 | Query question = new Query(domain, type); 18 | addQuestion(question); 19 | 20 | Record answer = new Record(domain, type, value, ttl); 21 | addAnswer(answer); 22 | } 23 | 24 | public static Response create(int id, String domain, String value, Dns.TYPE type, int ttl) 25 | { 26 | return new Response(id, domain, value, type, ttl); 27 | } 28 | 29 | public static Response create(int id, String domain, String value) 30 | { 31 | return create(id, domain, value, TYPE.A, 600); 32 | } 33 | 34 | public static Response create(int id, String domain, String value, int ttl) 35 | { 36 | return create(id, domain, value, TYPE.A, ttl); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/exception/PtrException.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.exception; 2 | 3 | import android.annotation.TargetApi; 4 | 5 | public class PtrException extends Exception { 6 | public PtrException() { 7 | } 8 | 9 | public PtrException(String message) { 10 | super(message); 11 | } 12 | 13 | public PtrException(String message, Throwable cause) { 14 | super(message, cause); 15 | } 16 | 17 | public PtrException(Throwable cause) { 18 | super(cause); 19 | } 20 | 21 | @TargetApi(24) 22 | public PtrException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 23 | super(message, cause, enableSuppression, writableStackTrace); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/exception/RequestException.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.exception; 2 | 3 | import android.annotation.TargetApi; 4 | 5 | public class RequestException extends Exception { 6 | 7 | public RequestException() { 8 | } 9 | 10 | public RequestException(String message) { 11 | super(message); 12 | } 13 | 14 | public RequestException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | public RequestException(Throwable cause) { 19 | super(cause); 20 | } 21 | 22 | @TargetApi(24) 23 | public RequestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 24 | super(message, cause, enableSuppression, writableStackTrace); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/exception/ResponseException.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.exception; 2 | 3 | import org.fly.protocol.http.response.Status; 4 | 5 | public final class ResponseException extends Exception { 6 | 7 | private static final long serialVersionUID = 6569838532917408380L; 8 | 9 | private final Status status; 10 | 11 | public ResponseException(Status status, String message) { 12 | super(message); 13 | this.status = status; 14 | } 15 | 16 | public ResponseException(Status status, String message, Exception e) { 17 | super(message, e); 18 | this.status = status; 19 | } 20 | 21 | public Status getStatus() { 22 | return this.status; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/Constant.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public class Constant { 6 | public static final String CONTENT_DISPOSITION_REGEX = "([ |\t]*Content-Disposition[ |\t]*:)(.*)"; 7 | 8 | public static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern.compile(CONTENT_DISPOSITION_REGEX, Pattern.CASE_INSENSITIVE); 9 | 10 | public static final String CONTENT_TYPE_REGEX = "([ |\t]*content-type[ |\t]*:)(.*)"; 11 | 12 | public static final Pattern CONTENT_TYPE_PATTERN = Pattern.compile(CONTENT_TYPE_REGEX, Pattern.CASE_INSENSITIVE); 13 | 14 | public static final String CONTENT_DISPOSITION_ATTRIBUTE_REGEX = "[ |\t]*([a-zA-Z]*)[ |\t]*=[ |\t]*['|\"]([^\"^']*)['|\"]"; 15 | 16 | public static final Pattern CONTENT_DISPOSITION_ATTRIBUTE_PATTERN = Pattern.compile(CONTENT_DISPOSITION_ATTRIBUTE_REGEX); 17 | 18 | private static final String REQUEST_START_LINE = "^([\\S]+)\\s+([a-zA-Z]+://|/).*"; 19 | 20 | public static final Pattern REQUEST_START_LINE_PATTERN = Pattern.compile(REQUEST_START_LINE); 21 | 22 | /** 23 | * Maximum time to wait on Socket.getInputStream().read() (in milliseconds) 24 | * This is required as the Keep-Alive HTTP connections would otherwise block 25 | * the socket reading thread forever (or as long the browser is open). 26 | */ 27 | public static final int SOCKET_READ_TIMEOUT = 5000; 28 | 29 | /** 30 | * Common MIME type for dynamic content: plain text 31 | */ 32 | public static final String MIME_PLAINTEXT = "text/plain"; 33 | 34 | /** 35 | * Common MIME type for dynamic content: html 36 | */ 37 | public static final String MIME_HTML = "text/html"; 38 | 39 | /** 40 | * Pseudo-Parameter to use to store the actual query string in the 41 | * parameters map for later re-processing. 42 | */ 43 | private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING"; 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/content/ContentType.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http.content; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | import java.util.regex.Matcher; 37 | import java.util.regex.Pattern; 38 | 39 | public class ContentType { 40 | 41 | private static final String ASCII_ENCODING = "US-ASCII"; 42 | 43 | private static final String MULTIPART_FORM_DATA_HEADER = "multipart/form-data"; 44 | 45 | private static final String CONTENT_REGEX = "[ |\t]*([^/^ ^;^,]+/[^ ^;^,]+)"; 46 | 47 | private static final Pattern MIME_PATTERN = Pattern.compile(CONTENT_REGEX, Pattern.CASE_INSENSITIVE); 48 | 49 | private static final String CHARSET_REGEX = "[ |\t]*(charset)[ |\t]*=[ |\t]*['|\"]?([^\"^'^;^,]*)['|\"]?"; 50 | 51 | private static final Pattern CHARSET_PATTERN = Pattern.compile(CHARSET_REGEX, Pattern.CASE_INSENSITIVE); 52 | 53 | private static final String BOUNDARY_REGEX = "[ |\t]*(boundary)[ |\t]*=[ |\t]*['|\"]?([^\"^'^;^,]*)['|\"]?"; 54 | 55 | private static final Pattern BOUNDARY_PATTERN = Pattern.compile(BOUNDARY_REGEX, Pattern.CASE_INSENSITIVE); 56 | 57 | private final String contentTypeHeader; 58 | 59 | private final String contentType; 60 | 61 | private final String encoding; 62 | 63 | private final String boundary; 64 | 65 | public ContentType(String contentTypeHeader) { 66 | this.contentTypeHeader = contentTypeHeader; 67 | if (contentTypeHeader != null) { 68 | contentType = getDetailFromContentHeader(contentTypeHeader, MIME_PATTERN, "", 1); 69 | encoding = getDetailFromContentHeader(contentTypeHeader, CHARSET_PATTERN, null, 2); 70 | } else { 71 | contentType = ""; 72 | encoding = "UTF-8"; 73 | } 74 | if (MULTIPART_FORM_DATA_HEADER.equalsIgnoreCase(contentType)) { 75 | boundary = getDetailFromContentHeader(contentTypeHeader, BOUNDARY_PATTERN, null, 2); 76 | } else { 77 | boundary = null; 78 | } 79 | } 80 | 81 | private String getDetailFromContentHeader(String contentTypeHeader, Pattern pattern, String defaultValue, int group) { 82 | Matcher matcher = pattern.matcher(contentTypeHeader); 83 | return matcher.find() ? matcher.group(group) : defaultValue; 84 | } 85 | 86 | public String getContentTypeHeader() { 87 | return contentTypeHeader; 88 | } 89 | 90 | public String getContentType() { 91 | return contentType; 92 | } 93 | 94 | public String getEncoding() { 95 | return encoding == null ? ASCII_ENCODING : encoding; 96 | } 97 | 98 | public String getBoundary() { 99 | return boundary; 100 | } 101 | 102 | public boolean isMultipart() { 103 | return MULTIPART_FORM_DATA_HEADER.equalsIgnoreCase(contentType); 104 | } 105 | 106 | public ContentType tryUTF8() { 107 | if (encoding == null) { 108 | return new ContentType(this.contentTypeHeader + "; charset=UTF-8"); 109 | } 110 | return this; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/content/Cookie.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http.content; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | import java.text.SimpleDateFormat; 37 | import java.util.Calendar; 38 | import java.util.Locale; 39 | import java.util.TimeZone; 40 | 41 | /** 42 | * A simple cookie representation. This is old code and is flawed in many ways. 43 | * 44 | * @author LordFokas 45 | */ 46 | public class Cookie { 47 | 48 | public static String getHTTPTime(int days) { 49 | Calendar calendar = Calendar.getInstance(); 50 | SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); 51 | dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); 52 | calendar.add(Calendar.DAY_OF_MONTH, days); 53 | return dateFormat.format(calendar.getTime()); 54 | } 55 | 56 | private final String n, v, e; 57 | 58 | public Cookie(String name, String value) { 59 | this(name, value, 30); 60 | } 61 | 62 | public Cookie(String name, String value, int numDays) { 63 | this.n = name; 64 | this.v = value; 65 | this.e = getHTTPTime(numDays); 66 | } 67 | 68 | public Cookie(String name, String value, String expires) { 69 | this.n = name; 70 | this.v = value; 71 | this.e = expires; 72 | } 73 | 74 | public String getHTTPHeader() { 75 | String fmt = "%s=%s; expires=%s"; 76 | return String.format(fmt, this.n, this.v, this.e); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/content/CookieHandler.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http.content; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | import org.fly.protocol.http.response.Response; 37 | 38 | import java.util.ArrayList; 39 | import java.util.HashMap; 40 | import java.util.Iterator; 41 | import java.util.Map; 42 | 43 | /** 44 | * Provides rudimentary support for cookies. Doesn't support 'path', 'secure' 45 | * nor 'httpOnly'. Feel free to improve it and/or add unsupported features. This 46 | * is old code and it's flawed in many ways. 47 | * 48 | * @author LordFokas 49 | */ 50 | public class CookieHandler implements Iterable { 51 | 52 | private final HashMap cookies = new HashMap(); 53 | 54 | private final ArrayList queue = new ArrayList(); 55 | 56 | public CookieHandler(Map httpHeaders) { 57 | String raw = httpHeaders.get("cookie"); 58 | if (raw != null) { 59 | String[] tokens = raw.split(";"); 60 | for (String token : tokens) { 61 | String[] data = token.trim().split("="); 62 | if (data.length == 2) { 63 | this.cookies.put(data[0], data[1]); 64 | } 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * Set a cookie with an expiration date from a month ago, effectively 71 | * deleting it on the client side. 72 | * 73 | * @param name 74 | * The cookie name. 75 | */ 76 | public void delete(String name) { 77 | set(name, "-delete-", -30); 78 | } 79 | 80 | @Override 81 | public Iterator iterator() { 82 | return this.cookies.keySet().iterator(); 83 | } 84 | 85 | /** 86 | * Read a cookie from the HTTP Headers. 87 | * 88 | * @param name 89 | * The cookie's name. 90 | * @return The cookie's value if it exists, null otherwise. 91 | */ 92 | public String read(String name) { 93 | return this.cookies.get(name); 94 | } 95 | 96 | public void set(Cookie cookie) { 97 | this.queue.add(cookie); 98 | } 99 | 100 | /** 101 | * Sets a cookie. 102 | * 103 | * @param name 104 | * The cookie's name. 105 | * @param value 106 | * The cookie's value. 107 | * @param expires 108 | * How many days until the cookie expires. 109 | */ 110 | public void set(String name, String value, int expires) { 111 | this.queue.add(new Cookie(name, value, Cookie.getHTTPTime(expires))); 112 | } 113 | 114 | /** 115 | * Internally used by the webserver to add all queued cookies into the 116 | * Response's HTTP Headers. 117 | * 118 | * @param response 119 | * The Response object to which headers the queued cookies will 120 | * be added. 121 | */ 122 | public void unloadQueue(Response response) { 123 | for (Cookie cookie : this.queue) { 124 | response.addCookieHeader(cookie.getHTTPHeader()); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/request/Method.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http.request; 2 | 3 | /** 4 | * HTTP Request methods, with the ability to decode a String back 5 | * to its enum value. 6 | */ 7 | public enum Method { 8 | GET, 9 | PUT, 10 | POST, 11 | DELETE, 12 | HEAD, 13 | OPTIONS, 14 | TRACE, 15 | CONNECT, 16 | PATCH, 17 | PROPFIND, 18 | PROPPATCH, 19 | MKCOL, 20 | MOVE, 21 | COPY, 22 | LOCK, 23 | UNLOCK, 24 | NOTIFY, 25 | SUBSCRIBE; 26 | 27 | public static Method lookup(String method) { 28 | if (method == null) 29 | return null; 30 | 31 | try { 32 | return valueOf(method); 33 | } catch (IllegalArgumentException e) { 34 | // TODO: Log it? 35 | return null; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/response/ChunkedOutputStream.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http.response; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | import java.io.FilterOutputStream; 37 | import java.io.IOException; 38 | import java.io.OutputStream; 39 | 40 | /** 41 | * Output stream that will automatically send every write to the wrapped 42 | * OutputStream according to chunked transfer: 43 | * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1 44 | */ 45 | public class ChunkedOutputStream extends FilterOutputStream { 46 | 47 | public ChunkedOutputStream(OutputStream out) { 48 | super(out); 49 | } 50 | 51 | @Override 52 | public void write(int b) throws IOException { 53 | byte[] data = { 54 | (byte) b 55 | }; 56 | write(data, 0, 1); 57 | } 58 | 59 | @Override 60 | public void write(byte[] b) throws IOException { 61 | write(b, 0, b.length); 62 | } 63 | 64 | @Override 65 | public void write(byte[] b, int off, int len) throws IOException { 66 | if (len == 0) 67 | return; 68 | out.write(String.format("%x\r\n", len).getBytes()); 69 | out.write(b, off, len); 70 | out.write("\r\n".getBytes()); 71 | } 72 | 73 | public void finish() throws IOException { 74 | out.write("0\r\n\r\n".getBytes()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/response/IStatus.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http.response; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | public interface IStatus { 37 | 38 | String getDescription(); 39 | 40 | int getRequestStatus(); 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/response/Response.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http.response; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | 37 | import org.fly.android.localvpn.structs.IoUtils; 38 | import org.fly.protocol.exception.ResponseException; 39 | import org.fly.protocol.http.Constant; 40 | import org.fly.protocol.http.content.ContentType; 41 | import org.fly.protocol.http.request.Method; 42 | import org.fly.protocol.http.tempfiles.DefaultTempFileManagerFactory; 43 | import org.fly.protocol.http.tempfiles.ITempFileManager; 44 | 45 | import java.io.BufferedWriter; 46 | import java.io.ByteArrayInputStream; 47 | import java.io.ByteArrayOutputStream; 48 | import java.io.Closeable; 49 | import java.io.IOException; 50 | import java.io.InputStream; 51 | import java.io.OutputStream; 52 | import java.io.OutputStreamWriter; 53 | import java.io.PrintWriter; 54 | import java.io.UnsupportedEncodingException; 55 | import java.nio.ByteBuffer; 56 | import java.nio.charset.Charset; 57 | import java.nio.charset.CharsetEncoder; 58 | import java.text.SimpleDateFormat; 59 | import java.util.ArrayList; 60 | import java.util.Date; 61 | import java.util.HashMap; 62 | import java.util.List; 63 | import java.util.Locale; 64 | import java.util.Map; 65 | import java.util.Map.Entry; 66 | import java.util.TimeZone; 67 | import java.util.zip.GZIPOutputStream; 68 | 69 | /** 70 | * HTTP sendToClient. Return one of these from serve(). 71 | */ 72 | public class Response implements Closeable { 73 | 74 | /** 75 | * HTTP status code after processing, e.g. "200 OK", Status.OK 76 | */ 77 | private IStatus status; 78 | 79 | /** 80 | * MIME type of content, e.g. "text/html" 81 | */ 82 | private String mimeType; 83 | 84 | /** 85 | * Data of the sendToClient, may be null. 86 | */ 87 | private InputStream data; 88 | 89 | private long contentLength; 90 | 91 | private ITempFileManager tempFileManager = new DefaultTempFileManagerFactory().create(); 92 | 93 | /** 94 | * Headers for the HTTP sendToClient. Use addHeader() to add lines. the 95 | * lowercase map is automatically kept up to date. 96 | */ 97 | @SuppressWarnings("serial") 98 | private final Map header = new HashMap() { 99 | 100 | public String put(String key, String value) { 101 | lowerCaseHeader.put(key == null ? key : key.toLowerCase(), value); 102 | return super.put(key, value); 103 | }; 104 | }; 105 | 106 | /** 107 | * copy of the header map with all the keys lowercase for faster searching. 108 | */ 109 | private final Map lowerCaseHeader = new HashMap(); 110 | 111 | /** 112 | * The request method that spawned this sendToClient. 113 | */ 114 | private Method requestMethod; 115 | 116 | /** 117 | * Use chunkedTransfer 118 | */ 119 | private boolean chunkedTransfer; 120 | 121 | private boolean keepAlive; 122 | 123 | private List cookieHeaders; 124 | 125 | private GzipUsage gzipUsage = GzipUsage.DEFAULT; 126 | 127 | private static enum GzipUsage { 128 | DEFAULT, 129 | ALWAYS, 130 | NEVER; 131 | } 132 | 133 | /** 134 | * Creates a fixed length sendToClient if totalBytes>=0, otherwise chunked. 135 | */ 136 | @SuppressWarnings({ 137 | "rawtypes", 138 | "unchecked" 139 | }) 140 | protected Response(IStatus status, String mimeType, InputStream data, long totalBytes) { 141 | this.status = status; 142 | this.mimeType = mimeType; 143 | if (data == null) { 144 | this.data = new ByteArrayInputStream(new byte[0]); 145 | this.contentLength = 0L; 146 | } else { 147 | this.data = data; 148 | this.contentLength = totalBytes; 149 | } 150 | this.chunkedTransfer = this.contentLength < 0; 151 | this.keepAlive = true; 152 | this.cookieHeaders = new ArrayList(10); 153 | } 154 | 155 | @Override 156 | public void close() throws IOException { 157 | if (this.data != null) { 158 | this.data.close(); 159 | } 160 | } 161 | 162 | /** 163 | * Adds a cookie header to the list. Should not be called manually, this is 164 | * an internal utility. 165 | */ 166 | public void addCookieHeader(String cookie) { 167 | cookieHeaders.add(cookie); 168 | } 169 | 170 | /** 171 | * Should not be called manually. This is an internally utility for JUnit 172 | * test purposes. 173 | * 174 | * @return All unloaded cookie headers. 175 | */ 176 | public List getCookieHeaders() { 177 | return cookieHeaders; 178 | } 179 | 180 | /** 181 | * Adds given line to the header. 182 | */ 183 | public void addHeader(String name, String value) { 184 | this.header.put(name, value); 185 | } 186 | 187 | /** 188 | * Indicate to close the connection after the Response has been sent. 189 | * 190 | * @param close 191 | * {@code true} to hint connection closing, {@code false} to let 192 | * connection be closed by client. 193 | */ 194 | public void closeConnection(boolean close) { 195 | if (close) 196 | this.header.put("connection", "close"); 197 | else 198 | this.header.remove("connection"); 199 | } 200 | 201 | /** 202 | * @return {@code true} if connection is to be closed after this Response 203 | * has been sent. 204 | */ 205 | public boolean isCloseConnection() { 206 | return "close".equals(getHeader("connection")); 207 | } 208 | 209 | public InputStream getData() { 210 | return this.data; 211 | } 212 | 213 | public String getHeader(String name) { 214 | return this.lowerCaseHeader.get(name.toLowerCase()); 215 | } 216 | 217 | public String getMimeType() { 218 | return this.mimeType; 219 | } 220 | 221 | public Method getRequestMethod() { 222 | return this.requestMethod; 223 | } 224 | 225 | public IStatus getStatus() { 226 | return this.status; 227 | } 228 | 229 | public void setKeepAlive(boolean useKeepAlive) { 230 | this.keepAlive = useKeepAlive; 231 | } 232 | 233 | /** 234 | * Short content Response 235 | * 236 | * @param byteBuffer 237 | * @throws IOException 238 | */ 239 | public void send(ByteBuffer byteBuffer) throws IOException, ResponseException 240 | { 241 | if (contentLength == -1 || contentLength > byteBuffer.remaining()) 242 | throw new ResponseException(Status.PAYLOAD_TOO_LARGE, "The response had a large body, or was chunked. Please use a OutStream instead of this"); 243 | 244 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 245 | send(outputStream); 246 | 247 | byteBuffer.put(outputStream.toByteArray()); 248 | } 249 | 250 | /** 251 | * Sends given sendToClient to the socket. 252 | */ 253 | public void send(OutputStream outputStream) throws IOException { 254 | SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US); 255 | gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT")); 256 | 257 | 258 | if (this.status == null) { 259 | throw new Error("sendResponse(): Status can't be null."); 260 | } 261 | PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream, new ContentType(this.mimeType).getEncoding())), false); 262 | pw.append("HTTP/1.1 ").append(this.status.getDescription()).append(" \r\n"); 263 | if (this.mimeType != null) { 264 | printHeader(pw, "Content-Type", this.mimeType); 265 | } 266 | if (getHeader("date") == null) { 267 | printHeader(pw, "Date", gmtFrmt.format(new Date())); 268 | } 269 | for (Entry entry : this.header.entrySet()) { 270 | printHeader(pw, entry.getKey(), entry.getValue()); 271 | } 272 | for (String cookieHeader : this.cookieHeaders) { 273 | printHeader(pw, "Set-Cookie", cookieHeader); 274 | } 275 | if (getHeader("connection") == null) { 276 | printHeader(pw, "Connection", (this.keepAlive ? "keep-alive" : "close")); 277 | } 278 | if (getHeader("content-length") != null) { 279 | setUseGzip(false); 280 | } 281 | if (useGzipWhenAccepted()) { 282 | printHeader(pw, "Content-Encoding", "gzip"); 283 | setChunkedTransfer(true); 284 | } 285 | long pending = this.data != null ? this.contentLength : 0; 286 | if (this.requestMethod != Method.HEAD && this.chunkedTransfer) { 287 | printHeader(pw, "Transfer-Encoding", "chunked"); 288 | } else if (!useGzipWhenAccepted()) { 289 | pending = sendContentLengthHeaderIfNotAlreadyPresent(pw, pending); 290 | } 291 | pw.append("\r\n"); 292 | pw.flush(); 293 | sendBodyWithCorrectTransferAndEncoding(outputStream, pending); 294 | outputStream.flush(); 295 | IoUtils.safeClose(this.data); 296 | 297 | } 298 | 299 | @SuppressWarnings("static-method") 300 | protected void printHeader(PrintWriter pw, String key, String value) { 301 | pw.append(key).append(": ").append(value).append("\r\n"); 302 | } 303 | 304 | protected long sendContentLengthHeaderIfNotAlreadyPresent(PrintWriter pw, long defaultSize) throws NumberFormatException { 305 | String contentLengthString = getHeader("content-length"); 306 | long size = defaultSize; 307 | if (contentLengthString != null) { 308 | size = Long.parseLong(contentLengthString); 309 | }else{ 310 | pw.print("Content-Length: " + size + "\r\n"); 311 | } 312 | return size; 313 | } 314 | 315 | private void sendBodyWithCorrectTransferAndEncoding(OutputStream outputStream, long pending) throws IOException { 316 | if (this.requestMethod != Method.HEAD && this.chunkedTransfer) { 317 | ChunkedOutputStream chunkedOutputStream = new ChunkedOutputStream(outputStream); 318 | sendBodyWithCorrectEncoding(chunkedOutputStream, -1); 319 | chunkedOutputStream.finish(); 320 | } else { 321 | sendBodyWithCorrectEncoding(outputStream, pending); 322 | } 323 | } 324 | 325 | private void sendBodyWithCorrectEncoding(OutputStream outputStream, long pending) throws IOException { 326 | if (useGzipWhenAccepted()) { 327 | GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream); 328 | sendBody(gzipOutputStream, -1); 329 | gzipOutputStream.finish(); 330 | } else { 331 | sendBody(outputStream, pending); 332 | } 333 | } 334 | 335 | /** 336 | * Sends the body to the specified OutputStream. The pending parameter 337 | * limits the maximum amounts of bytes sent unless it is -1, in which case 338 | * everything is sent. 339 | * 340 | * @param outputStream 341 | * the OutputStream to send data to 342 | * @param pending 343 | * -1 to send everything, otherwise sets a max limit to the 344 | * number of bytes sent 345 | * @throws IOException 346 | * if something goes wrong while sending the data. 347 | */ 348 | private void sendBody(OutputStream outputStream, long pending) throws IOException { 349 | long BUFFER_SIZE = 16 * 1024; 350 | byte[] buff = new byte[(int) BUFFER_SIZE]; 351 | boolean sendEverything = pending == -1; 352 | while (pending > 0 || sendEverything) { 353 | long bytesToRead = sendEverything ? BUFFER_SIZE : Math.min(pending, BUFFER_SIZE); 354 | int read = this.data.read(buff, 0, (int) bytesToRead); 355 | if (read <= 0) { 356 | break; 357 | } 358 | try { 359 | outputStream.write(buff, 0, read); 360 | } catch (Exception e) { 361 | if(this.data != null) { 362 | this.data.close(); 363 | } 364 | } 365 | if (!sendEverything) { 366 | pending -= read; 367 | } 368 | } 369 | } 370 | 371 | public void setChunkedTransfer(boolean chunkedTransfer) { 372 | this.chunkedTransfer = chunkedTransfer; 373 | } 374 | 375 | public void setData(InputStream data) { 376 | this.data = data; 377 | } 378 | 379 | public void setMimeType(String mimeType) { 380 | this.mimeType = mimeType; 381 | } 382 | 383 | public void setRequestMethod(Method requestMethod) { 384 | this.requestMethod = requestMethod; 385 | } 386 | 387 | public void setStatus(IStatus status) { 388 | this.status = status; 389 | } 390 | 391 | /** 392 | * Create a sendToClient with unknown length (using HTTP 1.1 chunking). 393 | */ 394 | public static Response newChunkedResponse(IStatus status, String mimeType, InputStream data) { 395 | return new Response(status, mimeType, data, -1); 396 | } 397 | 398 | public static Response newFixedLengthResponse(IStatus status, String mimeType, byte[] data) { 399 | return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(data), data.length); 400 | } 401 | 402 | /** 403 | * Create a sendToClient with known length. 404 | */ 405 | public static Response newFixedLengthResponse(IStatus status, String mimeType, InputStream data, long totalBytes) { 406 | return new Response(status, mimeType, data, totalBytes); 407 | } 408 | 409 | /** 410 | * Create a text sendToClient with known length. 411 | */ 412 | public static Response newFixedLengthResponse(IStatus status, String mimeType, String txt) throws UnsupportedEncodingException { 413 | ContentType contentType = new ContentType(mimeType); 414 | if (txt == null) { 415 | return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(new byte[0]), 0); 416 | } else { 417 | byte[] bytes; 418 | 419 | CharsetEncoder newEncoder = Charset.forName(contentType.getEncoding()).newEncoder(); 420 | if (!newEncoder.canEncode(txt)) { 421 | contentType = contentType.tryUTF8(); 422 | } 423 | bytes = txt.getBytes(contentType.getEncoding()); 424 | 425 | return newFixedLengthResponse(status, contentType.getContentTypeHeader(), new ByteArrayInputStream(bytes), bytes.length); 426 | } 427 | } 428 | 429 | /** 430 | * Create a text sendToClient with known length. 431 | */ 432 | public static Response newFixedLengthResponse(String msg) throws UnsupportedEncodingException { 433 | return newFixedLengthResponse(Status.OK, Constant.MIME_HTML, msg); 434 | } 435 | 436 | public Response setUseGzip(boolean useGzip) { 437 | gzipUsage = useGzip ? GzipUsage.ALWAYS : GzipUsage.NEVER; 438 | return this; 439 | } 440 | 441 | // If a Gzip usage has been enforced, use it. 442 | // Else decide whether or not to use Gzip. 443 | public boolean useGzipWhenAccepted() { 444 | if (gzipUsage == GzipUsage.DEFAULT) 445 | return getMimeType() != null && (getMimeType().toLowerCase().contains("text/") || getMimeType().toLowerCase().contains("/json")); 446 | else 447 | return gzipUsage == GzipUsage.ALWAYS; 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/response/Status.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http.response; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | /** 37 | * Some HTTP sendToClient status codes 38 | */ 39 | public enum Status implements IStatus { 40 | SWITCH_PROTOCOL(101, "Switching Protocols"), 41 | 42 | OK(200, "OK"), 43 | CREATED(201, "Created"), 44 | ACCEPTED(202, "Accepted"), 45 | NO_CONTENT(204, "No Content"), 46 | PARTIAL_CONTENT(206, "Partial Content"), 47 | MULTI_STATUS(207, "Multi-Status"), 48 | 49 | REDIRECT(301, "Moved Permanently"), 50 | /** 51 | * Many user agents mishandle 302 in ways that violate the RFC1945 spec 52 | * (i.e., redirect a POST to a GET). 303 and 307 were added in RFC2616 to 53 | * address this. You should prefer 303 and 307 unless the calling user agent 54 | * does not support 303 and 307 functionality 55 | */ 56 | @Deprecated 57 | FOUND(302, "Found"), 58 | REDIRECT_SEE_OTHER(303, "See Other"), 59 | NOT_MODIFIED(304, "Not Modified"), 60 | TEMPORARY_REDIRECT(307, "Temporary Redirect"), 61 | 62 | BAD_REQUEST(400, "Bad Request"), 63 | UNAUTHORIZED(401, "Unauthorized"), 64 | FORBIDDEN(403, "Forbidden"), 65 | NOT_FOUND(404, "Not Found"), 66 | METHOD_NOT_ALLOWED(405, "Method Not Allowed"), 67 | NOT_ACCEPTABLE(406, "Not Acceptable"), 68 | REQUEST_TIMEOUT(408, "Request Timeout"), 69 | CONFLICT(409, "Conflict"), 70 | GONE(410, "Gone"), 71 | LENGTH_REQUIRED(411, "Length Required"), 72 | PRECONDITION_FAILED(412, "Precondition Failed"), 73 | PAYLOAD_TOO_LARGE(413, "Payload Too Large"), 74 | UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"), 75 | RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"), 76 | EXPECTATION_FAILED(417, "Expectation Failed"), 77 | TOO_MANY_REQUESTS(429, "Too Many Requests"), 78 | 79 | INTERNAL_ERROR(500, "Internal Server Error"), 80 | NOT_IMPLEMENTED(501, "Not Implemented"), 81 | SERVICE_UNAVAILABLE(503, "Service Unavailable"), 82 | UNSUPPORTED_HTTP_VERSION(505, "HTTP Version Not Supported"); 83 | 84 | private final int requestStatus; 85 | 86 | private final String description; 87 | 88 | Status(int requestStatus, String description) { 89 | this.requestStatus = requestStatus; 90 | this.description = description; 91 | } 92 | 93 | public static org.fly.protocol.http.response.Status lookup(int requestStatus) { 94 | for (org.fly.protocol.http.response.Status status : org.fly.protocol.http.response.Status.values()) { 95 | if (status.getRequestStatus() == requestStatus) { 96 | return status; 97 | } 98 | } 99 | return null; 100 | } 101 | 102 | @Override 103 | public String getDescription() { 104 | return "" + this.requestStatus + " " + this.description; 105 | } 106 | 107 | @Override 108 | public int getRequestStatus() { 109 | return this.requestStatus; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/sockets/DefaultServerSocketFactory.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http.sockets; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | import org.fly.protocol.contract.IFactoryThrowing; 37 | 38 | import java.io.IOException; 39 | import java.net.ServerSocket; 40 | 41 | /** 42 | * Creates a normal ServerSocket for TCP connections 43 | */ 44 | public class DefaultServerSocketFactory implements IFactoryThrowing { 45 | 46 | @Override 47 | public ServerSocket create() throws IOException { 48 | return new ServerSocket(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/sockets/SecureServerSocketFactory.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http.sockets; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | import org.fly.protocol.contract.IFactoryThrowing; 37 | 38 | import java.io.IOException; 39 | import java.net.ServerSocket; 40 | 41 | import javax.net.ssl.SSLServerSocket; 42 | import javax.net.ssl.SSLServerSocketFactory; 43 | 44 | /** 45 | * Creates a new SSLServerSocket 46 | */ 47 | public class SecureServerSocketFactory implements IFactoryThrowing { 48 | 49 | private SSLServerSocketFactory sslServerSocketFactory; 50 | 51 | private String[] sslProtocols; 52 | 53 | public SecureServerSocketFactory(SSLServerSocketFactory sslServerSocketFactory, String[] sslProtocols) { 54 | this.sslServerSocketFactory = sslServerSocketFactory; 55 | this.sslProtocols = sslProtocols; 56 | } 57 | 58 | @Override 59 | public ServerSocket create() throws IOException { 60 | SSLServerSocket ss = null; 61 | ss = (SSLServerSocket) this.sslServerSocketFactory.createServerSocket(); 62 | if (this.sslProtocols != null) { 63 | ss.setEnabledProtocols(this.sslProtocols); 64 | } else { 65 | ss.setEnabledProtocols(ss.getSupportedProtocols()); 66 | } 67 | ss.setUseClientMode(false); 68 | ss.setWantClientAuth(false); 69 | ss.setNeedClientAuth(false); 70 | return ss; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/tempfiles/DefaultTempFile.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http.tempfiles; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | import org.fly.android.localvpn.structs.IoUtils; 37 | 38 | import java.io.File; 39 | import java.io.FileOutputStream; 40 | import java.io.IOException; 41 | import java.io.OutputStream; 42 | 43 | /** 44 | * Default strategy for creating and cleaning up temporary files. 45 | *

46 | *

47 | * By default, files are created by File.createTempFile() in the 48 | * directory specified. 49 | *

50 | */ 51 | public class DefaultTempFile implements ITempFile { 52 | 53 | private final File file; 54 | 55 | private final OutputStream fstream; 56 | 57 | public DefaultTempFile(File tempdir) throws IOException { 58 | this.file = File.createTempFile("NanoHTTPD-", "", tempdir); 59 | this.fstream = new FileOutputStream(this.file); 60 | } 61 | 62 | @Override 63 | public void delete() throws Exception { 64 | IoUtils.safeClose(this.fstream); 65 | if (!this.file.delete()) { 66 | throw new Exception("could not delete temporary file: " + this.file.getAbsolutePath()); 67 | } 68 | } 69 | 70 | @Override 71 | public String getName() { 72 | return this.file.getAbsolutePath(); 73 | } 74 | 75 | @Override 76 | public OutputStream open() throws Exception { 77 | return this.fstream; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/tempfiles/DefaultTempFileManager.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http.tempfiles; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | import java.io.File; 37 | import java.util.ArrayList; 38 | import java.util.List; 39 | 40 | /** 41 | * Default strategy for creating and cleaning up temporary files. 42 | *

43 | *

44 | * This class stores its files in the standard location (that is, wherever 45 | * java.io.tmpdir points to). Files are added to an internal list, 46 | * and deleted when no longer needed (that is, when clear() is 47 | * invoked at the end of processing a request). 48 | *

49 | */ 50 | public class DefaultTempFileManager implements ITempFileManager { 51 | 52 | private final File tmpdir; 53 | 54 | private final List tempFiles; 55 | 56 | public DefaultTempFileManager() { 57 | this.tmpdir = new File(System.getProperty("java.io.tmpdir")); 58 | if (!tmpdir.exists()) { 59 | tmpdir.mkdirs(); 60 | } 61 | this.tempFiles = new ArrayList(); 62 | } 63 | 64 | @Override 65 | public void clear() { 66 | for (ITempFile file : this.tempFiles) { 67 | try { 68 | file.delete(); 69 | } catch (Exception ignored) { 70 | 71 | } 72 | } 73 | this.tempFiles.clear(); 74 | } 75 | 76 | @Override 77 | public ITempFile createTempFile(String filename_hint) throws Exception { 78 | DefaultTempFile tempFile = new DefaultTempFile(this.tmpdir); 79 | this.tempFiles.add(tempFile); 80 | return tempFile; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/tempfiles/DefaultTempFileManagerFactory.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http.tempfiles; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | import org.fly.protocol.contract.IFactory; 37 | 38 | /** 39 | * Default strategy for creating and cleaning up temporary files. 40 | */ 41 | public class DefaultTempFileManagerFactory implements IFactory { 42 | 43 | @Override 44 | public ITempFileManager create() { 45 | return new DefaultTempFileManager(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/tempfiles/ITempFile.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http.tempfiles; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | import java.io.OutputStream; 37 | 38 | /** 39 | * A temp file. 40 | *

41 | *

42 | * Temp files are responsible for managing the actual temporary storage and 43 | * cleaning themselves up when no longer needed. 44 | *

45 | */ 46 | public interface ITempFile { 47 | 48 | public void delete() throws Exception; 49 | 50 | public String getName(); 51 | 52 | public OutputStream open() throws Exception; 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/http/tempfiles/ITempFileManager.java: -------------------------------------------------------------------------------- 1 | package org.fly.protocol.http.tempfiles; 2 | 3 | /* 4 | * #%L 5 | * NanoHttpd-Core 6 | * %% 7 | * Copyright (C) 2012 - 2016 nanohttpd 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without modification, 10 | * are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * 3. Neither the name of the nanohttpd nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 31 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 32 | * OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * #L% 34 | */ 35 | 36 | /** 37 | * Temp file manager. 38 | *

39 | *

40 | * Temp file managers are created 1-to-1 with incoming requests, to create and 41 | * cleanup temporary files created as a result of handling the request. 42 | *

43 | */ 44 | public interface ITempFileManager { 45 | 46 | void clear(); 47 | 48 | public ITempFile createTempFile(String filename_hint) throws Exception; 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/resources/META-INF/protocol/default-mimetypes.properties: -------------------------------------------------------------------------------- 1 | #default mime types for nanohttpd, use META-INF/mimetypes.properties for user defined mimetypes 2 | css=text/css 3 | htm=text/html 4 | html=text/html 5 | xml=text/xml 6 | java=text/x-java-source, text/java 7 | md=text/plain 8 | txt=text/plain 9 | asc=text/plain 10 | gif=image/gif 11 | jpg=image/jpeg 12 | jpeg=image/jpeg 13 | png=image/png 14 | svg=image/svg+xml 15 | mp3=audio/mpeg 16 | m3u=audio/mpeg-url 17 | mp4=video/mp4 18 | ogv=video/ogg 19 | flv=video/x-flv 20 | mov=video/quicktime 21 | swf=application/x-shockwave-flash 22 | js=application/javascript 23 | pdf=application/pdf 24 | doc=application/msword 25 | ogg=application/x-ogg 26 | zip=application/octet-stream 27 | exe=application/octet-stream 28 | class=application/octet-stream 29 | m3u8=application/vnd.apple.mpegurl 30 | ts=video/mp2t -------------------------------------------------------------------------------- /app/src/main/java/org/fly/protocol/resources/META-INF/protocol/mimetypes.properties: -------------------------------------------------------------------------------- 1 | #mime types for nanohttpd, use a file like this for user defined mimetypes -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fly-studio/android-capture/c805a2078d85b34449df6977c2bb97ebee7c2ae7/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fly-studio/android-capture/c805a2078d85b34449df6977c2bb97ebee7c2ae7/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fly-studio/android-capture/c805a2078d85b34449df6977c2bb97ebee7c2ae7/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fly-studio/android-capture/c805a2078d85b34449df6977c2bb97ebee7c2ae7/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_local_vpn.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 |