From 1ae5157a6bb1b74ee7df984f8841b963a5a7bc10 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 23 Oct 2025 12:35:39 +0200 Subject: [PATCH 1/4] fix: json syntax exception Signed-off-by: alperozturk --- .../sso/aidl/ParcelFileDescriptorUtil.java | 4 ++- .../android/sso/api/NextcloudAPI.java | 30 ++++++++++++++++++- .../android/sso/api/TestRetrofitAPI.java | 30 +++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/com/nextcloud/android/sso/aidl/ParcelFileDescriptorUtil.java b/lib/src/main/java/com/nextcloud/android/sso/aidl/ParcelFileDescriptorUtil.java index 37e49bf3..0c560761 100644 --- a/lib/src/main/java/com/nextcloud/android/sso/aidl/ParcelFileDescriptorUtil.java +++ b/lib/src/main/java/com/nextcloud/android/sso/aidl/ParcelFileDescriptorUtil.java @@ -18,6 +18,8 @@ public class ParcelFileDescriptorUtil { private ParcelFileDescriptorUtil() { } + public static final int BUFFER_SIZE = 1024; + public static ParcelFileDescriptor pipeFrom(InputStream inputStream, IThreadListener listener) throws IOException { ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); @@ -61,7 +63,7 @@ static class TransferThread extends Thread { @Override public void run() { - byte[] buf = new byte[1024]; + byte[] buf = new byte[BUFFER_SIZE]; int len; try { diff --git a/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java b/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java index 1b31c989..31ef0c3a 100644 --- a/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java +++ b/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java @@ -23,6 +23,7 @@ import com.nextcloud.android.sso.exceptions.SSOException; import com.nextcloud.android.sso.model.SingleSignOnAccount; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -129,7 +130,7 @@ public T convertStreamToTargetEntity(InputStream inputStream, Type targetEnt final T result; try (InputStream os = inputStream; Reader targetReader = new InputStreamReader(os)) { - if (targetEntity == EmptyResponse.class) { + if (targetEntity == EmptyResponse.class || isReaderContainsEmptyResponse(targetReader)) { //noinspection unchecked result = (T) EMPTY_RESPONSE; } else { @@ -148,6 +149,33 @@ public T convertStreamToTargetEntity(InputStream inputStream, Type targetEnt return result; } + private static final int PEEK_LIMIT = 64; + public boolean isReaderContainsEmptyResponse(Reader reader) throws IOException { + if (!(reader instanceof BufferedReader)) { + reader = new BufferedReader(reader); + } + reader.mark(PEEK_LIMIT); + + try { + int c; + int count = 0; + + while ((c = reader.read()) != -1 && count < PEEK_LIMIT) { + if (c != '\u0000' && !Character.isWhitespace(c)) { + // Found a non-null, non-whitespace character (e.g., '{', '[', '"', 'a', '<'...) + return false; + } + count++; + } + + // all characters seen were '\u0000' or whitespace. + return true; + + } finally { + reader.reset(); + } + } + /** * The InputStreams needs to be closed after reading from it * diff --git a/lib/src/test/java/com/nextcloud/android/sso/api/TestRetrofitAPI.java b/lib/src/test/java/com/nextcloud/android/sso/api/TestRetrofitAPI.java index 295371eb..cfd8047a 100644 --- a/lib/src/test/java/com/nextcloud/android/sso/api/TestRetrofitAPI.java +++ b/lib/src/test/java/com/nextcloud/android/sso/api/TestRetrofitAPI.java @@ -13,6 +13,7 @@ import static junit.framework.TestCase.assertTrue; import static junit.framework.TestCase.fail; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -34,6 +35,8 @@ import org.mockito.junit.MockitoRule; import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; @@ -538,4 +541,31 @@ public void testEnqueue() throws InterruptedException { assertTrue(successful); } + @Test + public void testValidJson() throws IOException { + String json = "{\"name\": \"value\", \"type\": \"value_2\"}"; + Reader reader = new StringReader(json); + assertFalse(nextcloudApiMock.isReaderContainsEmptyResponse(reader)); + } + + @Test + public void testValidXml() throws IOException { + String xml = "UserUser2"; + Reader reader = new StringReader(xml); + assertFalse(nextcloudApiMock.isReaderContainsEmptyResponse(reader)); + } + + @Test + public void testOnlyNulls() throws IOException { + String nulls = "\u0000\u0000\u0000\u0000\u0000 \n\t"; // nulls + whitespace + Reader reader = new StringReader(nulls); + assertTrue(nextcloudApiMock.isReaderContainsEmptyResponse(reader)); + } + + @Test + public void testWhitespaceOnly() throws IOException { + String whitespace = " \n\t "; + Reader reader = new StringReader(whitespace); + assertTrue(nextcloudApiMock.isReaderContainsEmptyResponse(reader)); + } } From 1e992c2b21a22b449300d02a3f0410c2f0cbb0cc Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 23 Oct 2025 12:43:17 +0200 Subject: [PATCH 2/4] fix: json syntax exception Signed-off-by: alperozturk --- .../nextcloud/android/sso/aidl/ParcelFileDescriptorUtil.java | 4 ++-- .../main/java/com/nextcloud/android/sso/api/NextcloudAPI.java | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/com/nextcloud/android/sso/aidl/ParcelFileDescriptorUtil.java b/lib/src/main/java/com/nextcloud/android/sso/aidl/ParcelFileDescriptorUtil.java index 0c560761..b6358300 100644 --- a/lib/src/main/java/com/nextcloud/android/sso/aidl/ParcelFileDescriptorUtil.java +++ b/lib/src/main/java/com/nextcloud/android/sso/aidl/ParcelFileDescriptorUtil.java @@ -16,9 +16,9 @@ public class ParcelFileDescriptorUtil { - private ParcelFileDescriptorUtil() { } + private static final int BUFFER_SIZE = 1024; - public static final int BUFFER_SIZE = 1024; + private ParcelFileDescriptorUtil() { } public static ParcelFileDescriptor pipeFrom(InputStream inputStream, IThreadListener listener) throws IOException { diff --git a/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java b/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java index 31ef0c3a..4e1a7b1e 100644 --- a/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java +++ b/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java @@ -38,8 +38,8 @@ public class NextcloudAPI implements AutoCloseable { + private static final int PEEK_LIMIT = 64; private static final String TAG = NextcloudAPI.class.getCanonicalName(); - private static final EmptyResponse EMPTY_RESPONSE = new EmptyResponse(); private final NetworkRequest networkRequest; @@ -149,7 +149,6 @@ public T convertStreamToTargetEntity(InputStream inputStream, Type targetEnt return result; } - private static final int PEEK_LIMIT = 64; public boolean isReaderContainsEmptyResponse(Reader reader) throws IOException { if (!(reader instanceof BufferedReader)) { reader = new BufferedReader(reader); From a0e6f7ae2c391a82a921e1d4930c411640966bad Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 23 Oct 2025 13:05:42 +0200 Subject: [PATCH 3/4] fix: new tests Signed-off-by: alperozturk --- .../nextcloud/android/sso/api/NextcloudAPI.java | 15 ++++++++------- .../android/sso/api/TestRetrofitAPI.java | 4 +++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java b/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java index 4e1a7b1e..5dd016d7 100644 --- a/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java +++ b/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java @@ -150,16 +150,14 @@ public T convertStreamToTargetEntity(InputStream inputStream, Type targetEnt } public boolean isReaderContainsEmptyResponse(Reader reader) throws IOException { - if (!(reader instanceof BufferedReader)) { - reader = new BufferedReader(reader); - } - reader.mark(PEEK_LIMIT); + Reader r = reader instanceof BufferedReader ? reader : new BufferedReader(reader); + r.mark(PEEK_LIMIT); try { int c; int count = 0; - while ((c = reader.read()) != -1 && count < PEEK_LIMIT) { + while ((c = r.read()) != -1 && count < PEEK_LIMIT) { if (c != '\u0000' && !Character.isWhitespace(c)) { // Found a non-null, non-whitespace character (e.g., '{', '[', '"', 'a', '<'...) return false; @@ -169,9 +167,12 @@ public boolean isReaderContainsEmptyResponse(Reader reader) throws IOException { // all characters seen were '\u0000' or whitespace. return true; - } finally { - reader.reset(); + try { + r.reset(); + } catch (IOException e) { + // Ignore + } } } diff --git a/lib/src/test/java/com/nextcloud/android/sso/api/TestRetrofitAPI.java b/lib/src/test/java/com/nextcloud/android/sso/api/TestRetrofitAPI.java index cfd8047a..25924937 100644 --- a/lib/src/test/java/com/nextcloud/android/sso/api/TestRetrofitAPI.java +++ b/lib/src/test/java/com/nextcloud/android/sso/api/TestRetrofitAPI.java @@ -11,10 +11,10 @@ */ package com.nextcloud.android.sso.api; -import static junit.framework.TestCase.assertTrue; import static junit.framework.TestCase.fail; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; @@ -78,6 +78,8 @@ public void setUp() throws Exception { lenient().when(nextcloudApiMock.getGson()).thenReturn(new GsonBuilder().create()); lenient().when(nextcloudApiMock.performRequestObservableV2(any(), any())).thenReturn(Observable.empty()); lenient().when(nextcloudApiMock.performNetworkRequestV2(any())).thenReturn(new com.nextcloud.android.sso.api.Response(null, null)); + lenient().when(nextcloudApiMock.isReaderContainsEmptyResponse(any())) + .thenCallRealMethod(); mApi = new NextcloudRetrofitApiBuilder(nextcloudApiMock, mApiEndpoint).create(API.class); } From f52f118305d0eb27993f943035dcfe5a28c60ba6 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 23 Oct 2025 13:19:47 +0200 Subject: [PATCH 4/4] fix: parsing Signed-off-by: alperozturk --- .../android/sso/api/NextcloudAPI.java | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java b/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java index 5dd016d7..65efe674 100644 --- a/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java +++ b/lib/src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java @@ -128,50 +128,51 @@ public T convertStreamToTargetEntity(InputStream inputStream, Type targetEnt ensureTypeNotVoid(targetEntity); final T result; - try (InputStream os = inputStream; - Reader targetReader = new InputStreamReader(os)) { - if (targetEntity == EmptyResponse.class || isReaderContainsEmptyResponse(targetReader)) { + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + reader.mark(PEEK_LIMIT); + boolean empty = isReaderContainsEmptyResponse(reader); + reader.reset(); + + if (targetEntity == EmptyResponse.class || empty) { //noinspection unchecked - result = (T) EMPTY_RESPONSE; - } else { - result = gson.fromJson(targetReader, targetEntity); - if (result == null) { - if (targetEntity == Object.class) { - //noinspection unchecked - return (T) EMPTY_RESPONSE; - } else { - throw new IllegalStateException("Could not instantiate \"" + - targetEntity + "\", because response was null."); - } + return (T) EMPTY_RESPONSE; + } + + result = gson.fromJson(reader, targetEntity); + + if (result == null) { + if (targetEntity == Object.class) { + //noinspection unchecked + return (T) EMPTY_RESPONSE; + } else { + throw new IllegalStateException("Could not instantiate \"" + + targetEntity + "\", because response was null."); } } } + return result; } public boolean isReaderContainsEmptyResponse(Reader reader) throws IOException { - Reader r = reader instanceof BufferedReader ? reader : new BufferedReader(reader); - r.mark(PEEK_LIMIT); - + reader.mark(PEEK_LIMIT); try { int c; int count = 0; - - while ((c = r.read()) != -1 && count < PEEK_LIMIT) { + while ((c = reader.read()) != -1 && count < PEEK_LIMIT) { if (c != '\u0000' && !Character.isWhitespace(c)) { - // Found a non-null, non-whitespace character (e.g., '{', '[', '"', 'a', '<'...) + // Found meaningful character return false; } count++; } - - // all characters seen were '\u0000' or whitespace. return true; } finally { try { - r.reset(); - } catch (IOException e) { - // Ignore + reader.reset(); + } catch (Exception e) { + // Ignored } } }