diff --git a/.github/workflows/dependency-submission.yml b/.github/workflows/dependency-submission.yml new file mode 100644 index 0000000000..9d3978b13b --- /dev/null +++ b/.github/workflows/dependency-submission.yml @@ -0,0 +1,23 @@ +name: Automatic Dependency Submission + +on: + push: + branches: [ 'master' ] + workflow_dispatch: + +permissions: + contents: write + +jobs: + dependency-submission: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 17 + - name: Generate and submit dependency graph + uses: gradle/actions/dependency-submission@v4 diff --git a/README.md b/README.md index 5528c3f389..eee90ab779 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ The core [Termux](https://github.com/termux/termux-app) app comes with the follo ## Installation -Latest version is `v0.118.1`. +Latest version is `v0.118.3`. **NOTICE: It is highly recommended that you update to `v0.118.0` or higher ASAP for various bug fixes, including a critical world-readable vulnerability reported [here](https://termux.github.io/general/2022/02/15/termux-apps-vulnerability-disclosures.html). See [below](#google-play-store-experimental-branch) for information regarding Termux on Google Play.** diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..479f15cb81 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1 @@ +Check https://termux.dev/security for info on Termux security policies and how to report vulnerabilities. diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java index cd38163116..542611784d 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java @@ -495,6 +495,9 @@ public void checkForFontAndColors() { try { File colorsFile = TermuxConstants.TERMUX_COLOR_PROPERTIES_FILE; File fontFile = TermuxConstants.TERMUX_FONT_FILE; + File italicFontFile = TermuxConstants.TERMUX_ITALIC_FONT_FILE; + File boldFontFile = TermuxConstants.TERMUX_BOLD_FONT_FILE; + File boldItalicFontFile = TermuxConstants.TERMUX_BOLD_ITALIC_FONT_FILE; final Properties props = new Properties(); if (colorsFile.isFile()) { @@ -511,7 +514,16 @@ public void checkForFontAndColors() { updateBackgroundColor(); final Typeface newTypeface = (fontFile.exists() && fontFile.length() > 0) ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE; - mActivity.getTerminalView().setTypeface(newTypeface); + final Typeface newItalicTypeface = (italicFontFile.exists() && italicFontFile.length() > 0) ? Typeface.createFromFile(italicFontFile) : newTypeface; + final Typeface newBoldTypeface = (boldFontFile.exists() && boldFontFile.length() > 0) ? Typeface.createFromFile(boldFontFile) : newTypeface; + final Typeface newBoldItalicTypeface; + if (boldItalicFontFile.exists() && boldItalicFontFile.length() > 0) { + newBoldItalicTypeface = Typeface.createFromFile(boldItalicFontFile); + } else { + newBoldItalicTypeface = newBoldTypeface != newTypeface ? newBoldTypeface : newItalicTypeface; + } + + mActivity.getTerminalView().setTypefaces(newTypeface, newItalicTypeface, newBoldTypeface, newBoldItalicTypeface); } catch (Exception e) { Logger.logStackTraceWithMessage(LOG_TAG, "Error in checkForFontAndColors()", e); } diff --git a/jitpack.yml b/jitpack.yml index 6a9b67d8bf..900c8ffd65 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,2 +1,4 @@ +jdk: + - openjdk11 env: JITPACK_NDK_VERSION: "21.1.6352462" diff --git a/terminal-emulator/src/main/java/com/termux/terminal/TerminalEmulator.java b/terminal-emulator/src/main/java/com/termux/terminal/TerminalEmulator.java index 4e5a833c39..b0be6f3440 100644 --- a/terminal-emulator/src/main/java/com/termux/terminal/TerminalEmulator.java +++ b/terminal-emulator/src/main/java/com/termux/terminal/TerminalEmulator.java @@ -83,6 +83,10 @@ public final class TerminalEmulator { private static final int ESC_APC = 20; /** Escape processing: "ESC _" or Application Program Command (APC), followed by Escape. */ private static final int ESC_APC_ESCAPE = 21; + /** Escape processing: ESC [ */ + private static final int ESC_CSI_UNSUPPORTED_PARAMETER_BYTE = 22; + /** Escape processing: ESC [ */ + private static final int ESC_CSI_UNSUPPORTED_INTERMEDIATE_BYTE = 23; /** The number of parameter arguments including colon separated sub-parameters. */ private static final int MAX_ESCAPE_PARAMETERS = 32; @@ -658,6 +662,10 @@ public void processCodePoint(int b) { case ESC_CSI: doCsi(b); break; + case ESC_CSI_UNSUPPORTED_PARAMETER_BYTE: + case ESC_CSI_UNSUPPORTED_INTERMEDIATE_BYTE: + doCsiUnsupportedParameterOrIntermediateByte(b); + break; case ESC_CSI_EXCLAMATION: if (b == 'p') { // Soft terminal reset (DECSTR, http://vt100.net/docs/vt510-rm/DECSTR). reset(); @@ -1059,6 +1067,37 @@ private int nextTabStop(int numTabs) { return mRightMargin - 1; } + /** + * Process byte while in the {@link #ESC_CSI_UNSUPPORTED_PARAMETER_BYTE} or + * {@link #ESC_CSI_UNSUPPORTED_INTERMEDIATE_BYTE} escape state. + * + * Parse unsupported parameter, intermediate and final bytes but ignore them. + * + * > For Control Sequence Introducer, ... the ESC [ is followed by + * > - any number (including none) of "parameter bytes" in the range 0x30–0x3F (ASCII 0–9:;<=>?), + * > - then by any number of "intermediate bytes" in the range 0x20–0x2F (ASCII space and !"#$%&'()*+,-./), + * > - then finally by a single "final byte" in the range 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~). + * + * - https://en.wikipedia.org/wiki/ANSI_escape_code#Control_Sequence_Introducer_commands + * - https://invisible-island.net/xterm/ecma-48-parameter-format.html#section5.4 + */ + private void doCsiUnsupportedParameterOrIntermediateByte(int b) { + if (mEscapeState == ESC_CSI_UNSUPPORTED_PARAMETER_BYTE && b >= 0x30 && b <= 0x3F) { + // Supported `0–9:;>?` or unsupported `<=` parameter byte after an + // initial unsupported parameter byte in `doCsi()`, or a sequential parameter byte. + continueSequence(ESC_CSI_UNSUPPORTED_PARAMETER_BYTE); + } else if (b >= 0x20 && b <= 0x2F) { + // Optional intermediate byte `!"#$%&'()*+,-./` after parameter or intermediate byte. + continueSequence(ESC_CSI_UNSUPPORTED_INTERMEDIATE_BYTE); + } else if (b >= 0x40 && b <= 0x7E) { + // Final byte `@A–Z[\]^_`a–z{|}~` after parameter or intermediate byte. + // Calling `unknownSequence()` would log an error with only a final byte, so ignore it for now. + finishSequence(); + } else { + unknownSequence(b); + } + } + /** Process byte while in the {@link #ESC_CSI_QUESTIONMARK} escape state. */ private void doCsiQuestionMark(int b) { switch (b) { @@ -1656,12 +1695,16 @@ private void doCsi(int b) { } mCursorCol = newCol; break; - case '?': // Esc [ ? -- start of a private mode set + case '?': // Esc [ ? -- start of a private parameter byte continueSequence(ESC_CSI_QUESTIONMARK); break; - case '>': // "Esc [ >" -- + case '>': // "Esc [ >" -- start of a private parameter byte continueSequence(ESC_CSI_BIGGERTHAN); break; + case '<': // "Esc [ <" -- start of a private parameter byte + case '=': // "Esc [ =" -- start of a private parameter byte + continueSequence(ESC_CSI_UNSUPPORTED_PARAMETER_BYTE); + break; case '`': // Horizontal position absolute (HPA - http://www.vt100.net/docs/vt510-rm/HPA). setCursorColRespectingOriginMode(getArg0(1) - 1); break; diff --git a/terminal-view/src/main/java/com/termux/view/TerminalRenderer.java b/terminal-view/src/main/java/com/termux/view/TerminalRenderer.java index a4bef7d37c..e0dc78d756 100644 --- a/terminal-view/src/main/java/com/termux/view/TerminalRenderer.java +++ b/terminal-view/src/main/java/com/termux/view/TerminalRenderer.java @@ -4,6 +4,7 @@ import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.Typeface; +import android.util.Log; import com.termux.terminal.TerminalBuffer; import com.termux.terminal.TerminalEmulator; @@ -17,9 +18,15 @@ * Saves font metrics, so needs to be recreated each time the typeface or font size changes. */ public final class TerminalRenderer { + boolean uniqueItalicTypeface; + boolean uniqueBoldTypeface; + boolean uniqueBoldItalicTypeface; final int mTextSize; final Typeface mTypeface; + final Typeface mItalicTypeface; + final Typeface mBoldTypeface; + final Typeface mBoldItalicTypeface; private final Paint mTextPaint = new Paint(); /** The width of a single mono spaced character obtained by {@link Paint#measureText(String)} on a single 'X'. */ @@ -30,16 +37,24 @@ public final class TerminalRenderer { private final int mFontAscent; /** The {@link #mFontLineSpacing} + {@link #mFontAscent}. */ final int mFontLineSpacingAndAscent; - private final float[] asciiMeasures = new float[127]; - public TerminalRenderer(int textSize, Typeface typeface) { + public TerminalRenderer(int textSize, Typeface typeface, Typeface italicTypeface, Typeface boldTypeface, Typeface boldItalicTypeface) { mTextSize = textSize; mTypeface = typeface; + mItalicTypeface = italicTypeface; + mBoldTypeface = boldTypeface; + mBoldItalicTypeface = boldItalicTypeface; + + uniqueItalicTypeface = !mItalicTypeface.equals(mTypeface); + uniqueBoldTypeface = !mBoldTypeface.equals(mTypeface); + uniqueBoldItalicTypeface = !mBoldItalicTypeface.equals(mTypeface) && !mBoldItalicTypeface.equals(mItalicTypeface) && !mBoldItalicTypeface.equals(mBoldTypeface); + mTextPaint.setTypeface(typeface); mTextPaint.setAntiAlias(true); mTextPaint.setTextSize(textSize); + typeface.getStyle(); mFontLineSpacing = (int) Math.ceil(mTextPaint.getFontSpacing()); mFontAscent = (int) Math.ceil(mTextPaint.ascent()); @@ -168,6 +183,37 @@ private void drawTextRun(Canvas canvas, char[] text, int[] palette, float y, int final boolean strikeThrough = (effect & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0; final boolean dim = (effect & TextStyle.CHARACTER_ATTRIBUTE_DIM) != 0; + + if (italic && bold) { + mTextPaint.setTypeface(mBoldItalicTypeface); + if (mBoldItalicTypeface.equals(mBoldTypeface) && uniqueBoldTypeface) { + mTextPaint.setFakeBoldText(false); + mTextPaint.setTextSkewX(-0.35f); + } else if (mBoldItalicTypeface.equals(mItalicTypeface) && uniqueItalicTypeface){ + mTextPaint.setFakeBoldText(true); + mTextPaint.setTextSkewX(uniqueItalicTypeface ? 0.f : -0.35f); + } else { + mTextPaint.setFakeBoldText(false); + mTextPaint.setTextSkewX(0.f); + } + } else if (italic) { + mTextPaint.setTypeface(mItalicTypeface); + mTextPaint.setFakeBoldText(false); + mTextPaint.setTextSkewX(uniqueItalicTypeface ? 0.f : -0.35f); + } else if (bold) { + mTextPaint.setTypeface(mBoldTypeface); + mTextPaint.setFakeBoldText(!uniqueBoldTypeface); + mTextPaint.setTextSkewX(0.f); + } else { + mTextPaint.setTypeface(mTypeface); + mTextPaint.setFakeBoldText(false); + mTextPaint.setTextSkewX(0.f); + } + + final float fontWidth = mTextPaint.measureText("X"); + final int fontAscent = (int) Math.ceil(mTextPaint.ascent()); + final int fontLineSpacingAndAscent = (int) Math.ceil(mTextPaint.getFontSpacing()) + fontAscent; + if ((foreColor & 0xff000000) != 0xff000000) { // Let bold have bright colors if applicable (one of the first 8): if (bold && foreColor >= 0 && foreColor < 8) foreColor += 8; @@ -186,10 +232,10 @@ private void drawTextRun(Canvas canvas, char[] text, int[] palette, float y, int backColor = tmp; } - float left = startColumn * mFontWidth; - float right = left + runWidthColumns * mFontWidth; + float left = startColumn * fontWidth; + float right = left + runWidthColumns * fontWidth; - mes = mes / mFontWidth; + mes = mes / fontWidth; boolean savedMatrix = false; if (Math.abs(mes - runWidthColumns) > 0.01) { canvas.save(); @@ -202,12 +248,12 @@ private void drawTextRun(Canvas canvas, char[] text, int[] palette, float y, int if (backColor != palette[TextStyle.COLOR_INDEX_BACKGROUND]) { // Only draw non-default background. mTextPaint.setColor(backColor); - canvas.drawRect(left, y - mFontLineSpacingAndAscent + mFontAscent, right, y, mTextPaint); + canvas.drawRect(left, y - fontLineSpacingAndAscent + fontAscent, right, y, mTextPaint); } if (cursor != 0) { mTextPaint.setColor(cursor); - float cursorHeight = mFontLineSpacingAndAscent - mFontAscent; + float cursorHeight = fontLineSpacingAndAscent - fontAscent; if (cursorStyle == TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE) cursorHeight /= 4.; else if (cursorStyle == TerminalEmulator.TERMINAL_CURSOR_STYLE_BAR) right -= ((right - left) * 3) / 4.; canvas.drawRect(left, y - cursorHeight, right, y, mTextPaint); @@ -226,14 +272,12 @@ private void drawTextRun(Canvas canvas, char[] text, int[] palette, float y, int foreColor = 0xFF000000 + (red << 16) + (green << 8) + blue; } - mTextPaint.setFakeBoldText(bold); mTextPaint.setUnderlineText(underline); - mTextPaint.setTextSkewX(italic ? -0.35f : 0.f); mTextPaint.setStrikeThruText(strikeThrough); mTextPaint.setColor(foreColor); // The text alignment is the default Paint.Align.LEFT. - canvas.drawTextRun(text, startCharIndex, runWidthChars, startCharIndex, runWidthChars, left, y - mFontLineSpacingAndAscent, false, mTextPaint); + canvas.drawTextRun(text, startCharIndex, runWidthChars, startCharIndex, runWidthChars, left, y - fontLineSpacingAndAscent, false, mTextPaint); } if (savedMatrix) canvas.restore(); diff --git a/terminal-view/src/main/java/com/termux/view/TerminalView.java b/terminal-view/src/main/java/com/termux/view/TerminalView.java index 435c102515..54db872e63 100644 --- a/terminal-view/src/main/java/com/termux/view/TerminalView.java +++ b/terminal-view/src/main/java/com/termux/view/TerminalView.java @@ -512,12 +512,16 @@ public void onContextMenuClosed(Menu menu) { * @param textSize the new font size, in density-independent pixels. */ public void setTextSize(int textSize) { - mRenderer = new TerminalRenderer(textSize, mRenderer == null ? Typeface.MONOSPACE : mRenderer.mTypeface); + if (mRenderer == null) { + mRenderer = new TerminalRenderer(textSize,Typeface.MONOSPACE, Typeface.MONOSPACE, Typeface.MONOSPACE, Typeface.MONOSPACE); + } else { + mRenderer = new TerminalRenderer(textSize, mRenderer.mTypeface, mRenderer.mItalicTypeface, mRenderer.mBoldTypeface, mRenderer.mBoldItalicTypeface); + } updateSize(); } - public void setTypeface(Typeface newTypeface) { - mRenderer = new TerminalRenderer(mRenderer.mTextSize, newTypeface); + public void setTypefaces(Typeface newTypeface, Typeface newItalicTypeFace, Typeface newBoldTypeface, Typeface newBoldItalicTypeface) { + mRenderer = new TerminalRenderer(mRenderer.mTextSize, newTypeface, newItalicTypeFace, newBoldTypeface, newBoldItalicTypeface); updateSize(); invalidate(); } diff --git a/termux-shared/build.gradle b/termux-shared/build.gradle index 7c3cfcc71d..30e9536282 100644 --- a/termux-shared/build.gradle +++ b/termux-shared/build.gradle @@ -3,6 +3,7 @@ apply plugin: 'maven-publish' android { compileSdkVersion project.properties.compileSdkVersion.toInteger() + ndkVersion = System.getenv("JITPACK_NDK_VERSION") ?: project.properties.ndkVersion dependencies { implementation "androidx.appcompat:appcompat:1.3.1" @@ -14,7 +15,7 @@ android { implementation "io.noties.markwon:ext-strikethrough:$markwonVersion" implementation "io.noties.markwon:linkify:$markwonVersion" implementation "io.noties.markwon:recycler:$markwonVersion" - implementation "org.lsposed.hiddenapibypass:hiddenapibypass:5.0" + implementation "org.lsposed.hiddenapibypass:hiddenapibypass:6.1" // Do not increment version higher than 1.0.0-alpha09 since it will break ViewUtils and needs to be looked into // noinspection GradleDependency diff --git a/termux-shared/src/main/java/com/termux/shared/models/ReportInfo.java b/termux-shared/src/main/java/com/termux/shared/models/ReportInfo.java index 0f70fb5623..3e0ad6863a 100644 --- a/termux-shared/src/main/java/com/termux/shared/models/ReportInfo.java +++ b/termux-shared/src/main/java/com/termux/shared/models/ReportInfo.java @@ -1,5 +1,7 @@ package com.termux.shared.models; +import androidx.annotation.Keep; + import com.termux.shared.markdown.MarkdownUtils; import com.termux.shared.android.AndroidUtils; @@ -10,6 +12,25 @@ */ public class ReportInfo implements Serializable { + /** + * Explicitly define `serialVersionUID` to prevent exceptions on deserialization. + * + * Like when calling `Bundle.getSerializable()` on Android. + * `android.os.BadParcelableException: Parcelable encountered IOException reading a Serializable object` (name = ) + * `java.io.InvalidClassException: ; local class incompatible` + * + * The `@Keep` annotation is necessary to prevent the field from being removed by proguard when + * app is compiled, even if its kept during library compilation. + * + * **See Also:** + * - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/version.html#a6678 + * - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/class.html#a4100 + */ + @Keep + private static final long serialVersionUID = 1L; + + + /** The user action that was being processed for which the report was generated. */ public final String userAction; /** The internal app component that sent the report. */ diff --git a/termux-shared/src/main/java/com/termux/shared/models/TextIOInfo.java b/termux-shared/src/main/java/com/termux/shared/models/TextIOInfo.java index df9993afcb..b688c398b3 100644 --- a/termux-shared/src/main/java/com/termux/shared/models/TextIOInfo.java +++ b/termux-shared/src/main/java/com/termux/shared/models/TextIOInfo.java @@ -3,6 +3,7 @@ import android.graphics.Color; import android.graphics.Typeface; +import androidx.annotation.Keep; import androidx.annotation.NonNull; import com.termux.shared.activities.TextIOActivity; @@ -19,6 +20,25 @@ */ public class TextIOInfo implements Serializable { + /** + * Explicitly define `serialVersionUID` to prevent exceptions on deserialization. + * + * Like when calling `Bundle.getSerializable()` on Android. + * `android.os.BadParcelableException: Parcelable encountered IOException reading a Serializable object` (name = ) + * `java.io.InvalidClassException: ; local class incompatible` + * + * The `@Keep` annotation is necessary to prevent the field from being removed by proguard when + * app is compiled, even if its kept during library compilation. + * + * **See Also:** + * - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/version.html#a6678 + * - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/class.html#a4100 + */ + @Keep + private static final long serialVersionUID = 1L; + + + public static final int GENERAL_DATA_SIZE_LIMIT_IN_BYTES = 1000; public static final int LABEL_SIZE_LIMIT_IN_BYTES = 4000; public static final int TEXT_SIZE_LIMIT_IN_BYTES = 100000 - GENERAL_DATA_SIZE_LIMIT_IN_BYTES - LABEL_SIZE_LIMIT_IN_BYTES; // < 100KB diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java index 534936ffca..8885034908 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java @@ -772,6 +772,16 @@ public final class TermuxConstants { /** Termux app and Termux:Styling font.ttf file */ public static final File TERMUX_FONT_FILE = new File(TERMUX_FONT_FILE_PATH); + /** Termux app and Termux:Styling font-italic.ttf file path */ + public static final String TERMUX_ITALIC_FONT_FILE_PATH = TERMUX_DATA_HOME_DIR_PATH + "/font-italic.ttf"; + /** Termux app and Termux:Styling font-italic.ttf file */ + public static final File TERMUX_ITALIC_FONT_FILE = new File(TERMUX_ITALIC_FONT_FILE_PATH); + /** Termux app and Termux:Styling font-bold.ttf file */ + public static final String TERMUX_BOLD_FONT_FILE_PATH = TERMUX_DATA_HOME_DIR_PATH + "/font-bold.ttf"; + /** Termux app and Termux:Styling font-bold.ttf file */ + public static final File TERMUX_BOLD_FONT_FILE = new File(TERMUX_BOLD_FONT_FILE_PATH); + public static final String TERMUX_BOLD_ITALIC_FONT_FILE_PATH = TERMUX_DATA_HOME_DIR_PATH + "/font-bold-italic.ttf"; + public static final File TERMUX_BOLD_ITALIC_FONT_FILE = new File(TERMUX_BOLD_ITALIC_FONT_FILE_PATH); /** Termux app and plugins crash log file path */ public static final String TERMUX_CRASH_LOG_FILE_PATH = TERMUX_HOME_DIR_PATH + "/crash_log.md"; // Default: "/data/data/com.termux/files/home/crash_log.md"