diff --git a/app/build.gradle b/app/build.gradle index 3500932f..94356ad7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 30 - buildToolsVersion '29.0.3' + buildToolsVersion '30.0.3' defaultConfig { minSdkVersion 15 @@ -90,7 +90,6 @@ dependencies { implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' implementation 'com.getbase:floatingactionbutton:1.10.1' implementation 'com.nispok:snackbar:2.11.0' - implementation 'com.github.aegnor:rencode-java:cb628e824e' implementation 'org.apache.openjpa:openjpa-lib:3.1.1' implementation 'net.iharder:base64:2.3.9' implementation('com.github.afollestad.material-dialogs:core:0.9.6.0@aar') { diff --git a/app/src/main/java/org/transdroid/core/gui/ServerPickerDialog.java b/app/src/main/java/org/transdroid/core/gui/ServerPickerDialog.java index dd889321..5a5b8d87 100644 --- a/app/src/main/java/org/transdroid/core/gui/ServerPickerDialog.java +++ b/app/src/main/java/org/transdroid/core/gui/ServerPickerDialog.java @@ -16,10 +16,11 @@ */ package org.transdroid.core.gui; -import android.app.AlertDialog; import android.app.Dialog; import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import org.transdroid.R; @@ -49,14 +50,17 @@ public class ServerPickerDialog extends DialogFragment { dialog.show(activity.getSupportFragmentManager(), "serverpicker"); } + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { String[] serverNames = getArguments().getStringArray("serverNames"); - return new AlertDialog.Builder(getActivity()).setTitle(R.string.navigation_pickserver) + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.navigation_pickserver) .setItems(serverNames, (dialog, which) -> { if (getActivity() != null && getActivity() instanceof TorrentsActivity) ((TorrentsActivity) getActivity()).switchServerAndAddFromIntent(which); - }).create(); + }) + .create(); } } diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java b/app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java index a51557de..e1dd374a 100644 --- a/app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java +++ b/app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java @@ -30,10 +30,10 @@ import android.text.Spannable; import android.text.SpannableString; import android.text.style.TypefaceSpan; +import androidx.appcompat.app.AlertDialog; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; -import com.afollestad.materialdialogs.MaterialDialog; import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache; import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator; import com.nostra13.universalimageloader.cache.memory.impl.UsingFreqLimitedMemoryCache; @@ -159,11 +159,11 @@ public class NavigationHelper { if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) { // Never asked again: show a dialog with an explanation activity.runOnUiThread(() -> - new MaterialDialog.Builder(context) - .content(R.string.permission_readtorrent) - .positiveText(android.R.string.ok) - .onPositive((dialog, which) -> - ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode)).show()); + new AlertDialog.Builder(context) + .setMessage(R.string.permission_readtorrent) + .setPositiveButton(android.R.string.ok, (dialog, which) -> + ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode)) + .show()); return false; } // Permission not granted (and we asked for it already before) diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssItemsFragment.java b/app/src/main/java/org/transdroid/core/gui/rss/RssItemsFragment.java index c6d66e21..e0e7963a 100644 --- a/app/src/main/java/org/transdroid/core/gui/rss/RssItemsFragment.java +++ b/app/src/main/java/org/transdroid/core/gui/rss/RssItemsFragment.java @@ -16,7 +16,6 @@ */ package org.transdroid.core.gui.rss; -import android.app.AlertDialog; import android.app.SearchManager; import android.content.ClipData; import android.content.ClipboardManager; @@ -33,6 +32,7 @@ import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; @@ -151,8 +151,10 @@ public class RssItemsFragment extends Fragment { final Item first = checked.get(0); if (itemId == R.id.action_showdetails) { // Show a dialog box with the RSS item description text - new AlertDialog.Builder(getActivity()).setMessage(first.getDescription()) - .setPositiveButton(R.string.action_close, null).show(); + new AlertDialog.Builder(getActivity()) + .setMessage(first.getDescription()) + .setPositiveButton(R.string.action_close, null) + .show(); } else if (itemId == R.id.action_openwebsite) { // Open the browser to show the website contained in the item's link tag Toast.makeText(getActivity(), getString(R.string.search_openingdetails, first.getTitle()), Toast.LENGTH_LONG).show(); diff --git a/app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java b/app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java index 6232ec0b..664b1549 100644 --- a/app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java +++ b/app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java @@ -18,11 +18,12 @@ package org.transdroid.core.gui.search; import android.annotation.SuppressLint; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.net.Uri; +import androidx.appcompat.app.AlertDialog; + import org.transdroid.R; import java.lang.ref.WeakReference; @@ -73,12 +74,15 @@ public class BarcodeHelper { } catch (Exception e) { // Can't start the bar code scanner, for example with a SecurityException or when ZXing is not present final WeakReference intentStartContext = new WeakReference<>(activity); - new AlertDialog.Builder(activity).setIcon(android.R.drawable.ic_dialog_alert) + new AlertDialog.Builder(activity) + .setIcon(android.R.drawable.ic_dialog_alert) .setMessage(activity.getString(R.string.search_barcodescannernotfound)) - .setPositiveButton(android.R.string.yes, (dialog, which) -> { + .setPositiveButton(android.R.string.ok, (dialog, which) -> { if (intentStartContext.get() != null) intentStartContext.get().startActivity(new Intent(Intent.ACTION_VIEW, SCANNER_MARKET_URI)); - }).setNegativeButton(android.R.string.no, null).show(); + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); } } diff --git a/app/src/main/java/org/transdroid/core/gui/search/FilePickerHelper.java b/app/src/main/java/org/transdroid/core/gui/search/FilePickerHelper.java index 8d5471d1..b82c9d71 100644 --- a/app/src/main/java/org/transdroid/core/gui/search/FilePickerHelper.java +++ b/app/src/main/java/org/transdroid/core/gui/search/FilePickerHelper.java @@ -18,11 +18,12 @@ package org.transdroid.core.gui.search; import android.annotation.SuppressLint; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.net.Uri; +import androidx.appcompat.app.AlertDialog; + import org.transdroid.R; import java.lang.ref.WeakReference; @@ -52,12 +53,15 @@ public class FilePickerHelper { } catch (Exception e2) { // Can't start the file manager, for example with a SecurityException or when IO File Manager is not present final WeakReference intentStartContext = new WeakReference<>(activity); - new AlertDialog.Builder(activity).setIcon(android.R.drawable.ic_dialog_alert) + new AlertDialog.Builder(activity) + .setIcon(android.R.drawable.ic_dialog_alert) .setMessage(activity.getString(R.string.search_filemanagernotfound)) - .setPositiveButton(android.R.string.yes, (dialog, which) -> { + .setPositiveButton(android.R.string.ok, (dialog, which) -> { if (intentStartContext.get() != null) intentStartContext.get().startActivity(new Intent(Intent.ACTION_VIEW, FILEMANAGER_MARKET_URI)); - }).setNegativeButton(android.R.string.no, null).show(); + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); } } } diff --git a/app/src/main/java/org/transdroid/core/gui/settings/MainSettingsActivity.java b/app/src/main/java/org/transdroid/core/gui/settings/MainSettingsActivity.java index 9f92ba46..94e20184 100644 --- a/app/src/main/java/org/transdroid/core/gui/settings/MainSettingsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/settings/MainSettingsActivity.java @@ -17,7 +17,6 @@ package org.transdroid.core.gui.settings; import android.annotation.TargetApi; -import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface.OnClickListener; import android.content.Intent; @@ -26,6 +25,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; +import androidx.appcompat.app.AlertDialog; import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceClickListener; @@ -272,7 +272,9 @@ public class MainSettingsActivity extends PreferenceCompatActivity { seedboxes[i + 1] = getString(R.string.pref_seedbox_addseedbox, SeedboxProvider.activeProviders()[i].getSettings().getName()); } seedboxes[seedboxes.length - 1] = getString(R.string.pref_seedbox_xirvikviaqr); - return new AlertDialog.Builder(this).setItems(seedboxes, onAddSeedbox).create(); + return new AlertDialog.Builder(this) + .setItems(seedboxes, onAddSeedbox) + .create(); } return null; } diff --git a/app/src/main/java/org/transdroid/core/gui/settings/RssfeedSettingsActivity.java b/app/src/main/java/org/transdroid/core/gui/settings/RssfeedSettingsActivity.java index 3fd5ff5e..e1b89fdb 100644 --- a/app/src/main/java/org/transdroid/core/gui/settings/RssfeedSettingsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/settings/RssfeedSettingsActivity.java @@ -17,12 +17,13 @@ package org.transdroid.core.gui.settings; import android.annotation.TargetApi; -import android.app.AlertDialog; import android.app.Dialog; import android.content.Intent; import android.os.Build; import android.os.Bundle; +import androidx.appcompat.app.AlertDialog; + import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.OptionsMenu; @@ -73,11 +74,14 @@ public class RssfeedSettingsActivity extends KeyBoundPreferencesActivity { @Override protected Dialog onCreateDialog(int id) { if (id == DIALOG_CONFIRMREMOVE) { - return new AlertDialog.Builder(this).setMessage(R.string.pref_confirmremove) + return new AlertDialog.Builder(this) + .setMessage(R.string.pref_confirmremove) .setPositiveButton(android.R.string.ok, (dialog, which) -> { ApplicationSettings_.getInstance_(RssfeedSettingsActivity.this).removeRssfeedSettings(key); finish(); - }).setNegativeButton(android.R.string.cancel, null).create(); + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); } return null; } diff --git a/app/src/main/java/org/transdroid/core/gui/settings/ServerSettingsActivity.java b/app/src/main/java/org/transdroid/core/gui/settings/ServerSettingsActivity.java index d774b9f4..b6eaadca 100644 --- a/app/src/main/java/org/transdroid/core/gui/settings/ServerSettingsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/settings/ServerSettingsActivity.java @@ -17,7 +17,6 @@ package org.transdroid.core.gui.settings; import android.annotation.TargetApi; -import android.app.AlertDialog; import android.app.Dialog; import android.content.Intent; import android.content.SharedPreferences; @@ -25,6 +24,7 @@ import android.os.Build; import android.os.Bundle; import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import androidx.preference.EditTextPreference; import androidx.preference.PreferenceManager; @@ -109,11 +109,14 @@ public class ServerSettingsActivity extends KeyBoundPreferencesActivity { @Override protected Dialog onCreateDialog(int id) { if (id == DIALOG_CONFIRMREMOVE) { - return new AlertDialog.Builder(this).setMessage(R.string.pref_confirmremove) + return new AlertDialog.Builder(this) + .setMessage(R.string.pref_confirmremove) .setPositiveButton(android.R.string.ok, (dialog, which) -> { ApplicationSettings_.getInstance_(ServerSettingsActivity.this).removeNormalServerSettings(key); finish(); - }).setNegativeButton(android.R.string.cancel, null).create(); + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); } return null; } diff --git a/app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java b/app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java index 059986f8..ec6538a8 100644 --- a/app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java @@ -17,7 +17,6 @@ package org.transdroid.core.gui.settings; import android.annotation.TargetApi; -import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; @@ -28,6 +27,7 @@ import android.os.Bundle; import android.text.TextUtils; import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference.OnPreferenceClickListener; import androidx.preference.PreferenceManager; @@ -263,7 +263,8 @@ public class SystemSettingsActivity extends PreferenceCompatActivity { SettingsPersistence.DEFAULT_SETTINGS_FILE.toString())) .setPositiveButton(R.string.pref_import_fromfile, importSettingsFromFile) .setNeutralButton(R.string.pref_import_fromqr, importSettingsFromQr) - .setNegativeButton(android.R.string.cancel, null).create(); + .setNegativeButton(android.R.string.cancel, null) + .create(); // @formatter:on case DIALOG_EXPORTSETTINGS: // @formatter:off @@ -276,7 +277,8 @@ public class SystemSettingsActivity extends PreferenceCompatActivity { SettingsPersistence.DEFAULT_SETTINGS_FILE.toString())) .setPositiveButton(R.string.pref_export_tofile, exportSettingsToFile) .setNeutralButton(R.string.pref_export_toqr, exportSettingsToQr) - .setNegativeButton(android.R.string.cancel, null).create(); + .setNegativeButton(android.R.string.cancel, null) + .create(); // @formatter:on } return null; diff --git a/app/src/main/java/org/transdroid/core/gui/settings/WebsearchSettingsActivity.java b/app/src/main/java/org/transdroid/core/gui/settings/WebsearchSettingsActivity.java index 3fe73fec..2e7a1174 100644 --- a/app/src/main/java/org/transdroid/core/gui/settings/WebsearchSettingsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/settings/WebsearchSettingsActivity.java @@ -17,12 +17,13 @@ package org.transdroid.core.gui.settings; import android.annotation.TargetApi; -import android.app.AlertDialog; import android.app.Dialog; import android.content.Intent; import android.os.Build; import android.os.Bundle; +import androidx.appcompat.app.AlertDialog; + import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.OptionsMenu; @@ -69,11 +70,14 @@ public class WebsearchSettingsActivity extends KeyBoundPreferencesActivity { @Override protected Dialog onCreateDialog(int id) { if (id == DIALOG_CONFIRMREMOVE) { - return new AlertDialog.Builder(this).setMessage(R.string.pref_confirmremove) + return new AlertDialog.Builder(this) + .setMessage(R.string.pref_confirmremove) .setPositiveButton(android.R.string.ok, (dialog, which) -> { ApplicationSettings_.getInstance_(WebsearchSettingsActivity.this).removeWebsearchSettings(key); finish(); - }).setNegativeButton(android.R.string.cancel, null).create(); + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); } return null; } diff --git a/app/src/main/java/se/dimovski/rencode/Rencode.java b/app/src/main/java/se/dimovski/rencode/Rencode.java new file mode 100644 index 00000000..e9e7ff74 --- /dev/null +++ b/app/src/main/java/se/dimovski/rencode/Rencode.java @@ -0,0 +1,29 @@ +package se.dimovski.rencode; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class Rencode { + + public static Object decode(byte[] data) throws IOException { + final InputStream is = new ByteArrayInputStream(data); + final RencodeInputStream inputStream = new RencodeInputStream(is); + + final Object decoded = inputStream.readObject(); + inputStream.close(); + + return decoded; + } + + public static byte[] encode(Object obj) throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final RencodeOutputStream output = new RencodeOutputStream(baos); + output.writeObject(obj); + final byte[] encoded = baos.toByteArray(); + output.close(); + return encoded; + } + +} diff --git a/app/src/main/java/se/dimovski/rencode/RencodeInputStream.java b/app/src/main/java/se/dimovski/rencode/RencodeInputStream.java new file mode 100644 index 00000000..965ecd78 --- /dev/null +++ b/app/src/main/java/se/dimovski/rencode/RencodeInputStream.java @@ -0,0 +1,405 @@ +package se.dimovski.rencode; + +import java.io.DataInput; +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +public class RencodeInputStream extends FilterInputStream implements DataInput { + /** + * The charset that is being used for {@link String}s. + */ + private final String charset; + + /** + * Whether or not all byte-Arrays should be decoded as {@link String}s. + */ + private final boolean decodeAsString; + + /** + * Creates a {@link RencodeInputStream} with the default encoding. + */ + public RencodeInputStream(InputStream in) { + this(in, Utils.UTF_8, false); + } + + /** + * Creates a {@link RencodeInputStream} with the given encoding. + */ + public RencodeInputStream(InputStream in, String charset) { + this(in, charset, false); + } + + /** + * Creates a {@link RencodeInputStream} with the default encoding. + */ + public RencodeInputStream(InputStream in, boolean decodeAsString) { + this(in, Utils.UTF_8, decodeAsString); + } + + /** + * Creates a {@link RencodeInputStream} with the given encoding. + */ + public RencodeInputStream(InputStream in, String charset, boolean decodeAsString) { + super(in); + + if (charset == null) { + throw new IllegalArgumentException("charset is null"); + } + + this.charset = charset; + this.decodeAsString = decodeAsString; + } + + /** + * Returns the charset that is used to decode {@link String}s. The default + * value is UTF-8. + */ + public String getCharset() { + return charset; + } + + /** + * Returns true if all byte-Arrays are being turned into {@link String}s. + */ + public boolean isDecodeAsString() { + return decodeAsString; + } + + /** + * Reads and returns an {@link Object}. + */ + public Object readObject() throws IOException { + int token = readToken(); + + return readObject(token); + } + + /** + * Reads and returns an {@link Object}. + */ + protected Object readObject(int token) throws IOException { + if (token == TypeCode.DICTIONARY) { + return readMap0(Object.class); + } else if (Utils.isFixedDictionary(token)) { + return readMap0(Object.class, token); + } else if (token == TypeCode.LIST) { + return readList0(Object.class); + } else if (Utils.isFixedList(token)) { + return readList0(Object.class, token); + } else if (Utils.isNumber(token)) { + return readNumber0(token); + } else if (token == TypeCode.FALSE || token == TypeCode.TRUE) { + return readBoolean0(token); + } else if (token == TypeCode.NULL) { + return null; + } else if (Utils.isDigit(token) || Utils.isFixedString(token)) { + return readString(token, charset); + } + + throw new IOException("Not implemented: " + token); + } + + /** + * Reads and returns a {@link Map}. + */ + public Map readMap() throws IOException { + return readMap(Object.class); + } + + /** + * Reads and returns a {@link Map}. + */ + public Map readMap(Class clazz) throws IOException { + int token = readToken(); + + if (token != TypeCode.DICTIONARY) { + throw new IOException(); + } + + return readMap0(clazz); + } + + private Map readMap0(Class clazz) throws IOException { + Map map = new TreeMap(); + int token = -1; + while ((token = readToken()) != TypeCode.END) { + readMapItem(clazz, token, map); + } + + return map; + } + + private Map readMap0(Class clazz, int token) throws IOException { + Map map = new TreeMap(); + + int count = token - TypeCode.EMBEDDED.DICT_START; + for (int i = 0; i < count; i++) { + readMapItem(clazz, readToken(), map); + } + + return map; + } + + private void readMapItem(Class clazz, int token, Map map) throws UnsupportedEncodingException, + IOException { + String key = readString(token, charset); + T value = clazz.cast(readObject()); + + map.put(key, value); + } + + public int readToken() throws IOException { + int token = super.read(); + if (token == -1) { + throw new EOFException(); + } + return token; + } + + /** + * Reads and returns a {@link List}. + */ + public List readList() throws IOException { + return readList(Object.class); + } + + /** + * Reads and returns a {@link List}. + */ + public List readList(Class clazz) throws IOException { + int token = readToken(); + + if (token != TypeCode.LIST) { + throw new IOException(); + } + + return readList0(clazz); + } + + private List readList0(Class clazz) throws IOException { + List list = new ArrayList(); + int token = -1; + while ((token = readToken()) != TypeCode.END) { + list.add(clazz.cast(readObject(token))); + } + return list; + } + + private List readList0(Class clazz, int token) throws IOException { + List list = new ArrayList(); + int length = token - TypeCode.EMBEDDED.LIST_START; + for (int i = 0; i < length; i++) { + list.add(clazz.cast(readObject())); + } + return list; + } + + public boolean readBoolean() throws IOException { + return readBoolean0(readToken()); + } + + public boolean readBoolean0(int token) throws IOException { + if (token == TypeCode.FALSE) { + return false; + } else if (token == TypeCode.TRUE) { + return true; + } + + throw new IOException(); + } + + public byte readByte() throws IOException { + return (byte) readToken(); + } + + public char readChar() throws IOException { + return (char) readToken(); + } + + public double readDouble() throws IOException { + return readNumber().doubleValue(); + } + + public float readFloat() throws IOException { + return readNumber().floatValue(); + } + + public void readFully(byte[] dst) throws IOException { + readFully(dst, 0, dst.length); + } + + public void readFully(byte[] dst, int off, int len) throws IOException { + int total = 0; + + while (total < len) { + int r = read(dst, total, len - total); + if (r == -1) { + throw new EOFException(); + } + + total += r; + } + } + + public int readInt() throws IOException { + return readNumber().intValue(); + } + + public String readLine() throws IOException { + return readString(); + } + + public long readLong() throws IOException { + return readNumber().longValue(); + } + + public short readShort() throws IOException { + return readNumber().shortValue(); + } + + public String readUTF() throws IOException { + return readString(Utils.UTF_8); + } + + public int readUnsignedByte() throws IOException { + return readByte() & 0xFF; + } + + public int readUnsignedShort() throws IOException { + return readShort() & 0xFFFF; + } + + /** + * Reads and returns a {@link Number}. + */ + public Number readNumber() throws IOException { + int token = readToken(); + + if (!Utils.isNumber(token)) { + throw new IOException(); + } + + return readNumber0(token); + } + + private Number readNumber0(int token) throws IOException { + switch (token) { + case TypeCode.BYTE: + return (int) readToBuffer(1).get(); + case TypeCode.SHORT: + return (int) readToBuffer(2).getShort(); + case TypeCode.INT: + return readToBuffer(4).getInt(); + case TypeCode.LONG: + return readToBuffer(8).getLong(); + case TypeCode.FLOAT: + return readToBuffer(4).getFloat(); + case TypeCode.DOUBLE: + return readToBuffer(8).getDouble(); + + case TypeCode.NUMBER: + return readNumber0(); + } + if (Utils.isNegativeFixedNumber(token)) { + return TypeCode.EMBEDDED.INT_NEG_START - 1 - token; + } else if (Utils.isPositiveFixedNumber(token)) { + return TypeCode.EMBEDDED.INT_POS_START + token; + } + + throw new IOException("Unknown number. TypeCode: " + token); + } + + private ByteBuffer readToBuffer(int count) throws IOException { + return ByteBuffer.wrap(readBytesFixed(count)); + } + + private Number readNumber0() throws IOException { + StringBuilder buffer = new StringBuilder(); + + boolean decimal = false; + + int token = -1; + while ((token = readToken()) != TypeCode.END) { + if (token == '.') { + decimal = true; + } + + buffer.append((char) token); + } + + try { + if (decimal) { + return new BigDecimal(buffer.toString()); + } else { + return new BigInteger(buffer.toString()); + } + } catch (NumberFormatException err) { + throw new IOException("NumberFormatException", err); + } + } + + public int skipBytes(int n) throws IOException { + return (int) skip(n); + } + + /** + * Reads and returns a byte-Array. + */ + public byte[] readBytes() throws IOException { + int token = readToken(); + + return readBytes(token); + } + + /** + * Reads and returns a {@link String}. + */ + public String readString() throws IOException { + return readString(charset); + } + + private String readString(String encoding) throws IOException { + return readString(readToken(), encoding); + } + + private String readString(int token, String charset) throws IOException { + if (Utils.isFixedString(token)) { + int length = token - TypeCode.EMBEDDED.STR_START; + return new String(readBytesFixed(length), charset); + } + return new String(readBytes(token), charset); + } + + private byte[] readBytes(int token) throws IOException { + int length = readLength(token); + return readBytesFixed(length); + } + + private byte[] readBytesFixed(int count) throws IOException { + byte[] data = new byte[count]; + readFully(data); + return data; + } + + private int readLength(int token) throws IOException { + StringBuilder buffer = new StringBuilder(); + buffer.append((char) token); + + while ((token = readToken()) != TypeCode.LENGTH_DELIM) { + + buffer.append((char) token); + } + + return Integer.parseInt(buffer.toString()); + } +} diff --git a/app/src/main/java/se/dimovski/rencode/RencodeOutputStream.java b/app/src/main/java/se/dimovski/rencode/RencodeOutputStream.java new file mode 100644 index 00000000..335da987 --- /dev/null +++ b/app/src/main/java/se/dimovski/rencode/RencodeOutputStream.java @@ -0,0 +1,321 @@ +package se.dimovski.rencode; + +import java.io.DataOutput; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Array; +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +public class RencodeOutputStream extends FilterOutputStream implements DataOutput { + + /** + * The {@link String} charset. + */ + private final String charset; + + /** + * Creates a {@link RencodeOutputStream} with the default charset. + */ + public RencodeOutputStream(OutputStream out) { + this(out, Utils.UTF_8); + } + + /** + * Creates a {@link RencodeOutputStream} with the given encoding. + */ + public RencodeOutputStream(OutputStream out, String charset) { + super(out); + + if (charset == null) { + throw new NullPointerException("charset"); + } + + this.charset = charset; + } + + /** + * Returns the charset that is used to encode {@link String}s. The default + * value is UTF-8. + */ + public String getCharset() { + return charset; + } + + /** + * Writes an {@link Object}. + */ + public void writeObject(Object value) throws IOException { + if (value == null) { + writeNull(); + } else if (value instanceof byte[]) { + writeBytes((byte[]) value); + } else if (value instanceof Boolean) { + writeBoolean((Boolean) value); + + } else if (value instanceof Character) { + writeChar((Character) value); + + } else if (value instanceof Number) { + writeNumber((Number) value); + + } else if (value instanceof String) { + writeString((String) value); + + } else if (value instanceof Collection) { + writeCollection((Collection) value); + + } else if (value instanceof Map) { + writeMap((Map) value); + + } else if (value instanceof Enum) { + writeEnum((Enum) value); + + } else if (value.getClass().isArray()) { + writeArray(value); + + } else { + writeCustom(value); + } + } + + /** + * Writes a null value + */ + public void writeNull() throws IOException { + write(TypeCode.NULL); + } + + /** + * Overwrite this method to write custom objects. The default implementation + * throws an {@link IOException}. + */ + protected void writeCustom(Object value) throws IOException { + throw new IOException("Cannot encode " + value); + } + + /** + * Writes the given byte-Array + */ + public void writeBytes(byte[] value) throws IOException { + writeBytes(value, 0, value.length); + } + + /** + * Writes the given byte-Array + */ + public void writeBytes(byte[] value, int offset, int length) throws IOException { + write(value, offset, length); + } + + /** + * Writes a boolean + */ + public void writeBoolean(boolean value) throws IOException { + write(value ? TypeCode.TRUE : TypeCode.FALSE); + } + + /** + * Writes a char + */ + public void writeChar(int value) throws IOException { + writeByte(value); + } + + /** + * Writes a byte + */ + public void writeByte(int value) throws IOException { + write(TypeCode.BYTE); + write(value); + } + + /** + * Writes a short + */ + public void writeShort(int value) throws IOException { + write(TypeCode.SHORT); + ByteBuffer buffer = ByteBuffer.allocate(Utils.SHORT_BYTES).putShort((short) value); + write(buffer.array()); + } + + /** + * Writes an int + */ + public void writeInt(int value) throws IOException { + write(TypeCode.INT); + ByteBuffer buffer = ByteBuffer.allocate(Utils.INTEGER_BYTES).putInt(value); + write(buffer.array()); + } + + /** + * Writes a long + */ + public void writeLong(long value) throws IOException { + write(TypeCode.LONG); + ByteBuffer buffer = ByteBuffer.allocate(Utils.LONG_BYTES).putLong(value); + write(buffer.array()); + } + + /** + * Writes a float + */ + public void writeFloat(float value) throws IOException { + write(TypeCode.FLOAT); + ByteBuffer buffer = ByteBuffer.allocate(Utils.FLOAT_BYTES).putFloat(value); + write(buffer.array()); + } + + /** + * Writes a double + */ + public void writeDouble(double value) throws IOException { + write(TypeCode.DOUBLE); + ByteBuffer buffer = ByteBuffer.allocate(Utils.DOUBLE_BYTES).putDouble(value); + write(buffer.array()); + } + + /** + * Writes a {@link Number} + */ + public void writeNumber(Number num) throws IOException { + if (num instanceof Float) { + writeFloat(num.floatValue()); + } else if (num instanceof Double) { + writeDouble(num.doubleValue()); + } + if (0 <= num.intValue() && num.intValue() < TypeCode.EMBEDDED.INT_POS_COUNT) { + write(TypeCode.EMBEDDED.INT_POS_START + num.intValue()); + } else if (-TypeCode.EMBEDDED.INT_NEG_COUNT <= num.intValue() && num.intValue() < 0) { + write(TypeCode.EMBEDDED.INT_NEG_START - 1 - num.intValue()); + } else if (Byte.MIN_VALUE <= num.intValue() && num.intValue() < Byte.MAX_VALUE) { + writeByte(num.byteValue()); + } else if (Short.MIN_VALUE <= num.intValue() && num.intValue() < Short.MAX_VALUE) { + writeShort(num.shortValue()); + } else if (Integer.MIN_VALUE <= num.longValue() && num.longValue() < Integer.MAX_VALUE) { + writeInt(num.intValue()); + } else if (Long.MIN_VALUE <= num.longValue() && num.longValue() < Long.MAX_VALUE) { + writeLong(num.longValue()); + } else { + String number = num.toString(); + write(TypeCode.NUMBER); + write(number.getBytes(charset)); + write(TypeCode.END); + } + } + + /** + * Writes a {@link String} + */ + public void writeString(String value) throws IOException { + int len = value.length(); + if (len < TypeCode.EMBEDDED.STR_COUNT) { + write(TypeCode.EMBEDDED.STR_START + len); + } else { + String lenString = Integer.toString(len); + writeBytes(lenString.getBytes(charset)); + write(TypeCode.LENGTH_DELIM); + } + + writeBytes(value.getBytes(charset)); + } + + /** + * Writes a {@link Collection}. + */ + public void writeCollection(Collection value) throws IOException { + boolean useEndToken = value.size() >= TypeCode.EMBEDDED.LIST_COUNT; + if (useEndToken) { + write(TypeCode.LIST); + } else { + write(TypeCode.EMBEDDED.LIST_START + value.size()); + } + + for (Object element : value) { + writeObject(element); + } + + if (useEndToken) { + write(TypeCode.END); + } + } + + /** + * Writes a {@link Map}. + */ + public void writeMap(Map map) throws IOException { + if (!(map instanceof SortedMap)) { + map = new TreeMap(map); + } + + boolean untilEnd = map.size() >= TypeCode.EMBEDDED.DICT_COUNT; + + if (untilEnd) { + write(TypeCode.DICTIONARY); + } else { + write(TypeCode.EMBEDDED.DICT_START + map.size()); + } + + for (Map.Entry entry : map.entrySet()) { + writeObject(entry.getKey()); + writeObject(entry.getValue()); + } + + if (untilEnd) { + write(TypeCode.END); + } + } + + /** + * Writes an {@link Enum}. + */ + public void writeEnum(Enum value) throws IOException { + writeString(value.name()); + } + + /** + * Writes an array + */ + public void writeArray(Object value) throws IOException { + int length = Array.getLength(value); + boolean useEndToken = length >= TypeCode.EMBEDDED.LIST_COUNT; + if (useEndToken) { + write(TypeCode.LIST); + } else { + write(TypeCode.EMBEDDED.LIST_START + length); + } + + for (int i = 0; i < length; i++) { + writeObject(Array.get(value, i)); + } + + if (useEndToken) { + write(TypeCode.END); + } + } + + /** + * Writes the given {@link String} + */ + public void writeBytes(String value) throws IOException { + writeString(value); + } + + /** + * Writes the given {@link String} + */ + public void writeChars(String value) throws IOException { + writeString(value); + } + + /** + * Writes an UTF encoded {@link String} + */ + public void writeUTF(String value) throws IOException { + writeBytes(value.getBytes(Utils.UTF_8)); + } +} diff --git a/app/src/main/java/se/dimovski/rencode/TypeCode.java b/app/src/main/java/se/dimovski/rencode/TypeCode.java new file mode 100644 index 00000000..966742a3 --- /dev/null +++ b/app/src/main/java/se/dimovski/rencode/TypeCode.java @@ -0,0 +1,45 @@ +package se.dimovski.rencode; + +public class TypeCode { + // The bencode 'typecodes' such as i, d, etc have been + // extended and relocated on the base-256 character set. + public static final char LIST = 59; + public static final char DICTIONARY = 60; + public static final char NUMBER = 61; + public static final char BYTE = 62; + public static final char SHORT = 63; + public static final char INT = 64; + public static final char LONG = 65; + public static final char FLOAT = 66; + public static final char DOUBLE = 44; + public static final char TRUE = 67; + public static final char FALSE = 68; + public static final char NULL = 69; + public static final char END = 127; + public static final char LENGTH_DELIM = ':'; + + /* + * TypeCodes with embedded values/lengths + */ + public static class EMBEDDED { + // Positive integers + public static final int INT_POS_START = 0; + public static final int INT_POS_COUNT = 44; + + // Negative integers + public static final int INT_NEG_START = 70; + public static final int INT_NEG_COUNT = 32; + + // Dictionaries + public static final int DICT_START = 102; + public static final int DICT_COUNT = 25; + + // Strings + public static final int STR_START = 128; + public static final int STR_COUNT = 64; + + // Lists + public static final int LIST_START = STR_START + STR_COUNT; + public static final int LIST_COUNT = 64; + } +} diff --git a/app/src/main/java/se/dimovski/rencode/Utils.java b/app/src/main/java/se/dimovski/rencode/Utils.java new file mode 100644 index 00000000..16d80f7a --- /dev/null +++ b/app/src/main/java/se/dimovski/rencode/Utils.java @@ -0,0 +1,63 @@ +package se.dimovski.rencode; + +public class Utils { + // Character Encodings + public final static String UTF_8 = "UTF-8"; + public final static String ISO_8859 = "ISO-8859-1"; + + // Byte-lengths for types + public static final int SHORT_BYTES = Short.SIZE / Byte.SIZE; + public static final int INTEGER_BYTES = Integer.SIZE / Byte.SIZE; + public static final int LONG_BYTES = Long.SIZE / Byte.SIZE; + public static final int FLOAT_BYTES = Float.SIZE / Byte.SIZE; + public static final int DOUBLE_BYTES = Double.SIZE / Byte.SIZE; + + // Maximum length of integer when written as base 10 string. + public static final int MAX_INT_LENGTH = 64; + + private static boolean tokenInRange(int token, int start, int count) { + return start <= token && token < (start + count); + } + + public static boolean isNumber(int token) { + switch (token) { + case TypeCode.NUMBER: + case TypeCode.BYTE: + case TypeCode.SHORT: + case TypeCode.INT: + case TypeCode.LONG: + case TypeCode.FLOAT: + case TypeCode.DOUBLE: + return true; + } + return isFixedNumber(token); + } + + public static boolean isFixedNumber(int token) { + return isPositiveFixedNumber(token) || isNegativeFixedNumber(token); + } + + public static boolean isPositiveFixedNumber(int token) { + return tokenInRange(token, TypeCode.EMBEDDED.INT_POS_START, TypeCode.EMBEDDED.INT_POS_COUNT); + } + + public static boolean isNegativeFixedNumber(int token) { + return tokenInRange(token, TypeCode.EMBEDDED.INT_NEG_START, TypeCode.EMBEDDED.INT_NEG_COUNT); + } + + public static boolean isFixedList(int token) { + return tokenInRange(token, TypeCode.EMBEDDED.LIST_START, TypeCode.EMBEDDED.LIST_COUNT); + } + + public static boolean isFixedDictionary(int token) { + return tokenInRange(token, TypeCode.EMBEDDED.DICT_START, TypeCode.EMBEDDED.DICT_COUNT); + } + + public static boolean isFixedString(int token) { + return tokenInRange(token, TypeCode.EMBEDDED.STR_START, TypeCode.EMBEDDED.STR_COUNT); + } + + public static boolean isDigit(int token) { + return '0' <= token && token <= '9'; + } +} diff --git a/build.gradle b/build.gradle index 4941eb6e..fc1dc4f8 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:7.0.2' } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053..7454180f 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac33e994..a0f7639f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index fbd7c515..c53aefaa 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index a9f778a7..ac1b06f9 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell