Skip to content

Commit cb9ebaa

Browse files
Merge pull request #1713 from nextcloud/bugfix/xml-exception-parser
BugFix - XML Exception Parser
2 parents 8e561c7 + 3c3f433 commit cb9ebaa

7 files changed

Lines changed: 207 additions & 113 deletions

File tree

library/src/androidTest/java/com/owncloud/android/ExceptionParserIT.java

Lines changed: 0 additions & 96 deletions
This file was deleted.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Nextcloud Android Library
3+
*
4+
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: MIT
6+
*/
7+
8+
package com.owncloud.android
9+
10+
import com.owncloud.android.lib.common.utils.responseFormat.ResponseFormat
11+
import com.owncloud.android.lib.common.utils.responseFormat.ResponseFormatDetector
12+
import junit.framework.TestCase.assertEquals
13+
import org.junit.Test
14+
15+
class ResponseFormatDetectorTests {
16+
@Test
17+
fun testJsonDetection() {
18+
val json = """{ "name": "Alice", "age": 30 }"""
19+
assertEquals(ResponseFormat.JSON, ResponseFormatDetector.detectFormat(json))
20+
}
21+
22+
@Test
23+
fun testJsonArrayDetection() {
24+
val jsonArray = """[{"name": "Alice"}, {"name": "Bob"}]"""
25+
assertEquals(ResponseFormat.JSON, ResponseFormatDetector.detectFormat(jsonArray))
26+
}
27+
28+
@Test
29+
fun testXmlDetection() {
30+
val xml = """<person><name>Alice</name><age>30</age></person>"""
31+
assertEquals(ResponseFormat.XML, ResponseFormatDetector.detectFormat(xml))
32+
}
33+
34+
@Test
35+
fun testInvalidFormat() {
36+
val invalid = "Just a plain string"
37+
assertEquals(ResponseFormat.UNKNOWN, ResponseFormatDetector.detectFormat(invalid))
38+
}
39+
40+
@Test
41+
fun testEmptyString() {
42+
val empty = ""
43+
assertEquals(ResponseFormat.UNKNOWN, ResponseFormatDetector.detectFormat(empty))
44+
}
45+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Nextcloud Android Library
3+
*
4+
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: MIT
6+
*/
7+
package com.owncloud.android
8+
9+
import com.owncloud.android.lib.common.operations.XMLExceptionParser
10+
import org.junit.Assert
11+
import org.junit.Test
12+
import java.io.ByteArrayInputStream
13+
14+
class XMLExceptionParserTests {
15+
@Test
16+
fun testVirusException() {
17+
val virusException =
18+
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
19+
"<d:error xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\">\n" +
20+
" <s:exception>OCA\\DAV\\Connector\\Sabre\\Exception\\UnsupportedMediaType" +
21+
"</s:exception>\n" +
22+
" <s:message>Virus Eicar-Test-Signature is detected in the file. " +
23+
"Upload cannot be completed.</s:message>\n" +
24+
"</d:error>"
25+
26+
val inputStream = ByteArrayInputStream(virusException.toByteArray())
27+
val xmlParser = XMLExceptionParser(inputStream)
28+
29+
Assert.assertTrue(xmlParser.isVirusException)
30+
Assert.assertFalse(xmlParser.isInvalidCharacterException)
31+
}
32+
33+
@Test
34+
fun testInvalidCharacterException() {
35+
val virusException =
36+
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
37+
"<d:error xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\">\n" +
38+
" <s:exception>OC\\Connector\\Sabre\\Exception\\InvalidPath</s:exception>\n" +
39+
" <s:message>Wrong Path</s:message>\n" +
40+
"</d:error>"
41+
42+
val inputStream = ByteArrayInputStream(virusException.toByteArray())
43+
val xmlParser = XMLExceptionParser(inputStream)
44+
45+
Assert.assertTrue(xmlParser.isInvalidCharacterException)
46+
Assert.assertFalse(xmlParser.isVirusException)
47+
}
48+
49+
@Test
50+
fun testInvalidCharacterUploadException() {
51+
val virusException =
52+
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
53+
"<d:error xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\">\n" +
54+
" <s:exception>OCP\\Files\\InvalidPathException</s:exception>\n" +
55+
" <s:message>Wrong Path</s:message>\n" +
56+
"</d:error>"
57+
58+
val inputStream = ByteArrayInputStream(virusException.toByteArray())
59+
val xmlParser = XMLExceptionParser(inputStream)
60+
61+
Assert.assertTrue(xmlParser.isInvalidCharacterException)
62+
Assert.assertFalse(xmlParser.isVirusException)
63+
}
64+
65+
@Test
66+
fun testEmptyString() {
67+
val emptyString = ""
68+
69+
val inputStream = ByteArrayInputStream(emptyString.toByteArray())
70+
val xmlParser = XMLExceptionParser(inputStream)
71+
72+
Assert.assertFalse(xmlParser.isVirusException)
73+
Assert.assertFalse(xmlParser.isInvalidCharacterException)
74+
}
75+
}

library/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
3131
import com.owncloud.android.lib.common.network.CertificateCombinedException;
3232
import com.owncloud.android.lib.common.utils.Log_OC;
33+
import com.owncloud.android.lib.common.utils.responseFormat.ResponseFormat;
34+
import com.owncloud.android.lib.common.utils.responseFormat.ResponseFormatDetector;
3335
import com.owncloud.android.lib.resources.files.CreateLocalFileException;
3436

3537
import org.apache.commons.httpclient.ConnectTimeoutException;
@@ -235,10 +237,13 @@ public RemoteOperationResult(boolean success, String bodyResponse, int httpCode)
235237
switch (httpCode) {
236238
case HttpStatus.SC_BAD_REQUEST:
237239
try {
238-
InputStream is = new ByteArrayInputStream(bodyResponse.getBytes());
239-
ExceptionParser xmlParser = new ExceptionParser(is);
240-
if (xmlParser.isInvalidCharacterException()) {
241-
mCode = ResultCode.INVALID_CHARACTER_DETECT_IN_SERVER;
240+
if (!bodyResponse.isEmpty() &&
241+
ResponseFormatDetector.INSTANCE.detectFormat(bodyResponse) == ResponseFormat.XML) {
242+
InputStream is = new ByteArrayInputStream(bodyResponse.getBytes());
243+
XMLExceptionParser xmlParser = new XMLExceptionParser(is);
244+
if (xmlParser.isInvalidCharacterException()) {
245+
mCode = ResultCode.INVALID_CHARACTER_DETECT_IN_SERVER;
246+
}
242247
}
243248
} catch (Exception e) {
244249
mCode = ResultCode.UNHANDLED_HTTP_CODE;
@@ -336,9 +341,11 @@ public RemoteOperationResult(boolean success, HttpMethod httpMethod) {
336341
try {
337342
String bodyResponse = httpMethod.getResponseBodyAsString();
338343

339-
if (bodyResponse != null && bodyResponse.length() > 0) {
344+
if (bodyResponse != null
345+
&& !bodyResponse.isEmpty() &&
346+
ResponseFormatDetector.INSTANCE.detectFormat(bodyResponse) == ResponseFormat.XML) {
340347
InputStream is = new ByteArrayInputStream(bodyResponse.getBytes());
341-
ExceptionParser xmlParser = new ExceptionParser(is);
348+
XMLExceptionParser xmlParser = new XMLExceptionParser(is);
342349

343350
if (xmlParser.isInvalidCharacterException()) {
344351
mCode = ResultCode.INVALID_CHARACTER_DETECT_IN_SERVER;

library/src/main/java/com/owncloud/android/lib/common/operations/ExceptionParser.java renamed to library/src/main/java/com/owncloud/android/lib/common/operations/XMLExceptionParser.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
*
2525
* @author masensio, tobiaskaminsky
2626
*/
27-
public class ExceptionParser {
28-
private static final String TAG = ExceptionParser.class.getSimpleName();
27+
public class XMLExceptionParser {
28+
private static final String TAG = XMLExceptionParser.class.getSimpleName();
2929

3030
private static final String INVALID_PATH_EXCEPTION_STRING = "OC\\Connector\\Sabre\\Exception\\InvalidPath";
3131
private static final String INVALID_PATH_EXCEPTION_UPLOAD_STRING = "OCP\\Files\\InvalidPathException";
@@ -46,25 +46,20 @@ public class ExceptionParser {
4646
/**
4747
* Parse is as an Invalid Path Exception
4848
*
49-
* @param is InputStream xml
50-
* @return if The exception is an Invalid Char Exception
51-
* @throws IOException
49+
* @param inputStream InputStream xml
5250
*/
53-
public ExceptionParser(InputStream is) throws IOException {
54-
try {
55-
// XMLPullParser
51+
public XMLExceptionParser(InputStream inputStream) {
52+
try (inputStream) {
5653
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
5754
factory.setNamespaceAware(true);
5855

5956
XmlPullParser parser = Xml.newPullParser();
6057
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
61-
parser.setInput(is, null);
58+
parser.setInput(inputStream, null);
6259
parser.nextTag();
6360
readError(parser);
6461
} catch (Exception e) {
6562
Log_OC.e(TAG, "Error parsing exception: " + e.getMessage());
66-
} finally {
67-
is.close();
6863
}
6964
}
7065

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Nextcloud Android Library
3+
*
4+
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: MIT
6+
*/
7+
8+
package com.owncloud.android.lib.common.utils.responseFormat
9+
10+
enum class ResponseFormat {
11+
JSON,
12+
XML,
13+
UNKNOWN
14+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Nextcloud Android Library
3+
*
4+
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: MIT
6+
*/
7+
8+
package com.owncloud.android.lib.common.utils.responseFormat
9+
10+
import com.owncloud.android.lib.common.utils.Log_OC
11+
import org.json.JSONArray
12+
import org.json.JSONException
13+
import org.json.JSONObject
14+
import java.io.ByteArrayInputStream
15+
import javax.xml.parsers.DocumentBuilderFactory
16+
17+
object ResponseFormatDetector {
18+
private const val TAG = "ResponseFormatDetector"
19+
20+
fun detectFormat(input: String): ResponseFormat =
21+
when {
22+
isJSON(input) -> ResponseFormat.JSON
23+
isXML(input) -> ResponseFormat.XML
24+
else -> ResponseFormat.UNKNOWN
25+
}
26+
27+
private fun isJSON(input: String): Boolean =
28+
try {
29+
JSONObject(input)
30+
true
31+
} catch (e: JSONException) {
32+
try {
33+
Log_OC.i(TAG, "Info it's not JSONObject: $e")
34+
JSONArray(input)
35+
true
36+
} catch (e: JSONException) {
37+
Log_OC.e(TAG, "Exception it's not JSONArray: $e")
38+
false
39+
}
40+
}
41+
42+
@Suppress("TooGenericExceptionCaught")
43+
private fun isXML(input: String): Boolean =
44+
try {
45+
val factory = DocumentBuilderFactory.newInstance()
46+
val builder = factory.newDocumentBuilder()
47+
val stream = ByteArrayInputStream(input.toByteArray())
48+
builder.parse(stream)
49+
true
50+
} catch (e: Exception) {
51+
Log_OC.e(TAG, "Exception isXML: $e")
52+
false
53+
}
54+
}

0 commit comments

Comments
 (0)