From 76981702742d25d08d70c2d5f50d1d396481ac56 Mon Sep 17 00:00:00 2001 From: raj pandey Date: Mon, 13 Jan 2025 15:08:07 +0530 Subject: [PATCH 1/7] version bump --- pom.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index ad852c7d..a442c844 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.contentstack.sdk java - 2.0.2 + 2.0.3 jar contentstack-java Java SDK for Contentstack Content Delivery API @@ -20,12 +20,12 @@ 3.3.1 3.4.1 3.0.0 - 3.1.9 + 3.1.10 2.11.0 5.0.0-alpha.11 0.8.5 - 1.18.34 - 5.10.1 + 1.18.36 + 5.11.4 5.8.0-M1 2.8.8 1.1.1 @@ -33,10 +33,10 @@ 1.5 3.8.1 1.6.13 - 20240303 + 20250107 0.8.7 2.5.3 - 1.2.7 + 1.2.14 @@ -187,7 +187,7 @@ com.fasterxml.jackson.core jackson-databind - 2.18.0 + 2.18.2 From a5930af25d63982984ee88507dc08c7e90b22333 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 17 Jan 2025 15:27:41 +0530 Subject: [PATCH 2/7] changelog and license update --- CHANGELOG.md | 7 +++++++ LICENSE | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a53e182..26cd9255 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## v2.0.2 +### Date: 27-January-2025 + +-Snyk fixes +-Fixed includeContenttype + +## v2.0.2 + ### Date: 5-December-2024 -Github Issue fixed diff --git a/LICENSE b/LICENSE index d77c7f4e..d78b6bc8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2012 - 2024 Contentstack +Copyright (c) 2012 - 2025 Contentstack Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 8b39900f6a26eef6ad7fb35752fe8f9ade4b455e Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Mon, 10 Mar 2025 16:49:35 +0530 Subject: [PATCH 3/7] fixes have been added --- .../com/contentstack/sdk/AssetLibrary.java | 53 ++- .../com/contentstack/sdk/AssetsModel.java | 4 +- .../contentstack/sdk/CSConnectionRequest.java | 6 +- .../contentstack/sdk/ContentTypesModel.java | 14 +- .../java/com/contentstack/sdk/EntryModel.java | 59 ++- src/main/java/com/contentstack/sdk/Query.java | 417 +++++++++++------- .../java/com/contentstack/sdk/SyncStack.java | 3 + 7 files changed, 376 insertions(+), 180 deletions(-) diff --git a/src/main/java/com/contentstack/sdk/AssetLibrary.java b/src/main/java/com/contentstack/sdk/AssetLibrary.java index 07512661..dd168723 100644 --- a/src/main/java/com/contentstack/sdk/AssetLibrary.java +++ b/src/main/java/com/contentstack/sdk/AssetLibrary.java @@ -31,6 +31,31 @@ protected void setStackInstance(@NotNull Stack stack) { this.headers = stack.headers; } + //Sanitization of keys + private boolean isValidKey(String key) { + return key.matches("^[a-zA-Z0-9_.]+$"); + } + + //Sanitization of values + private boolean isValidValue(Object value) { + if(value instanceof String){ + return ((String) value).matches("^[a-zA-Z0-9_.\\-\\s]+$"); + } + return true; + } + + //Sanitization of values list + private boolean isValidValueList(Object[] values) { + for (Object value : values) { + if (value instanceof String) { + if (!((String) value).matches("^[a-zA-Z0-9_.\\-\\s]+$")) { + return false; + } + } + } + return true; + } + /** * Sets header. * @@ -151,7 +176,11 @@ public int getCount() { * */ public AssetLibrary addParam(@NotNull String paramKey, @NotNull Object paramValue) { - urlQueries.put(paramKey, paramValue); + if (isValidKey(paramKey) && isValidValue(paramValue)) { + urlQueries.put(paramKey, paramValue); + } else { + logger.warning("Invalid key or value"); + } return this; } @@ -172,8 +201,12 @@ public AssetLibrary addParam(@NotNull String paramKey, @NotNull Object paramValu * */ public AssetLibrary removeParam(@NotNull String paramKey){ - if(urlQueries.has(paramKey)){ - urlQueries.remove(paramKey); + if(isValidKey(paramKey)) { + if(urlQueries.has(paramKey)){ + urlQueries.remove(paramKey); + } + } else { + logger.warning("Invalid key"); } return this; } @@ -255,7 +288,9 @@ private HashMap getUrlParams(JSONObject urlQueriesJSON) { while (iter.hasNext()) { String key = iter.next(); Object value = urlQueriesJSON.opt(key); - hashMap.put(key, value); + if(isValidKey(key) && isValidValue(value)) { + hashMap.put(key, value); + } } } return hashMap; @@ -311,9 +346,13 @@ public enum ORDERBY { } public AssetLibrary where(String key, String value) { - JSONObject queryParams= new JSONObject(); - queryParams.put(key,value); - urlQueries.put("query", queryParams); + if(isValidKey(key) && isValidValue(value)){ + JSONObject queryParams = new JSONObject(); + queryParams.put(key,value); + urlQueries.put("query", queryParams); + } else { + throw new IllegalArgumentException("Invalid key or value"); + } return this; } diff --git a/src/main/java/com/contentstack/sdk/AssetsModel.java b/src/main/java/com/contentstack/sdk/AssetsModel.java index 13030019..7102bc7f 100644 --- a/src/main/java/com/contentstack/sdk/AssetsModel.java +++ b/src/main/java/com/contentstack/sdk/AssetsModel.java @@ -20,10 +20,12 @@ class AssetsModel { */ public AssetsModel(JSONObject response) { JSONArray listResponse = null; - Object rawAssets = response.get("assets"); // Get assets + Object rawAssets = response.opt("assets"); // Get assets if (rawAssets instanceof List) { // Check if it's an ArrayList List assetsList = (List) rawAssets; listResponse = new JSONArray(assetsList); // Convert to JSONArray + } else if (rawAssets != null) { + throw new IllegalArgumentException("Invalid type for 'assets' key: " + rawAssets.getClass().getName()); } if (listResponse != null) { listResponse.forEach(model -> { diff --git a/src/main/java/com/contentstack/sdk/CSConnectionRequest.java b/src/main/java/com/contentstack/sdk/CSConnectionRequest.java index 592b224f..64daeb4b 100644 --- a/src/main/java/com/contentstack/sdk/CSConnectionRequest.java +++ b/src/main/java/com/contentstack/sdk/CSConnectionRequest.java @@ -83,7 +83,7 @@ public void setParams(Object... objects) { } @Override - public void sendRequest() { + public synchronized void sendRequest() { CSHttpConnection connection = new CSHttpConnection(urlToCall, this); connection.setController(controller); connection.setHeaders(header); @@ -99,7 +99,7 @@ public void sendRequest() { } @Override - public void onRequestFailed(JSONObject error, int statusCode, ResultCallBack callBackObject) { + public synchronized void onRequestFailed(JSONObject error, int statusCode, ResultCallBack callBackObject) { Error errResp = new Error(); if (error.has(ERROR_MESSAGE)) { String errMsg = error.optString(ERROR_MESSAGE); @@ -119,7 +119,7 @@ public void onRequestFailed(JSONObject error, int statusCode, ResultCallBack cal } @Override - public void onRequestFinished(CSHttpConnection request) { + public synchronized void onRequestFinished(CSHttpConnection request) { JSONObject jsonResponse = request.getResponse(); if (request.getController().equalsIgnoreCase(Constants.QUERYOBJECT)) { EntriesModel model = new EntriesModel(jsonResponse); diff --git a/src/main/java/com/contentstack/sdk/ContentTypesModel.java b/src/main/java/com/contentstack/sdk/ContentTypesModel.java index 10daf7a2..2fadcde7 100644 --- a/src/main/java/com/contentstack/sdk/ContentTypesModel.java +++ b/src/main/java/com/contentstack/sdk/ContentTypesModel.java @@ -20,10 +20,15 @@ public void setJSON(JSONObject responseJSON) { if (responseJSON != null) { String ctKey = "content_type"; if (responseJSON.has(ctKey) && responseJSON.opt(ctKey) instanceof LinkedHashMap) { - this.response = new JSONObject((LinkedHashMap) responseJSON.get(ctKey)); + try { + this.response = new JSONObject((LinkedHashMap) responseJSON.get(ctKey)); + } catch (Exception e) { + System.err.println("Error processing 'content_type': " + e.getMessage()); + } } String ctListKey = "content_types"; if (responseJSON.has(ctListKey) && responseJSON.opt(ctListKey) instanceof ArrayList) { + try { ArrayList> contentTypes = (ArrayList) responseJSON.get(ctListKey); List objectList = new ArrayList<>(); if (!contentTypes.isEmpty()) { @@ -32,13 +37,18 @@ public void setJSON(JSONObject responseJSON) { // Convert LinkedHashMap to JSONObject JSONObject jsonModel = new JSONObject((LinkedHashMap) model); objectList.add(jsonModel); + } else { + System.err.println("Invalid type in 'content_types' list. Expected LinkedHashMap."); } }); } this.response = new JSONArray(objectList); this.responseJSONArray = new JSONArray(objectList); - } + } catch (Exception e) { + System.err.println("Error processing 'content_types': " + e.getMessage()); } + } + } } public Object getResponse() { diff --git a/src/main/java/com/contentstack/sdk/EntryModel.java b/src/main/java/com/contentstack/sdk/EntryModel.java index 660968b0..cbfddc6c 100644 --- a/src/main/java/com/contentstack/sdk/EntryModel.java +++ b/src/main/java/com/contentstack/sdk/EntryModel.java @@ -46,29 +46,47 @@ public EntryModel(JSONObject response) { } if (this.jsonObject.has(UID_KEY)) { - this.uid = (String) this.jsonObject.opt(UID_KEY); + this.uid = this.jsonObject.optString(UID_KEY, null); } if (this.jsonObject.has(TITLE_KEY)) { - this.title = (String) this.jsonObject.opt(TITLE_KEY); + this.title = this.jsonObject.optString(TITLE_KEY, null); } if (this.jsonObject.has(LOCALE_KEY)) { - this.language = (String) this.jsonObject.opt(LOCALE_KEY); + this.language = this.jsonObject.optString(LOCALE_KEY,null); } if (this.jsonObject.has(URL_KEY)) { - this.url = (String) this.jsonObject.opt(URL_KEY); + this.url = this.jsonObject.optString(URL_KEY,null); } if (this.jsonObject.has("description")) { - this.description = this.jsonObject.opt("description"); - } - this.images = (JSONArray) this.jsonObject.opt("images"); - this.isDirectory = (Boolean) this.jsonObject.opt("is_dir"); - this.updatedAt = (String) this.jsonObject.opt("updated_at"); - this.updatedBy = (String) this.jsonObject.opt("updated_by"); - this.createdAt = (String) this.jsonObject.opt("created_at"); - this.createdBy = (String) this.jsonObject.opt("created_by"); - this.locale = (String) this.jsonObject.opt(LOCALE_KEY); - this.inProgress = (Boolean) this.jsonObject.opt("_in_progress"); - this.version = this.jsonObject.opt("_version") != null ? (int) this.jsonObject.opt("_version") : 1; + this.description = this.jsonObject.optString("description"); + } + if(this.jsonObject.has("images") && this.jsonObject.opt("images") instanceof JSONArray) { + this.images = this.jsonObject.optJSONArray("images"); + } + if(this.jsonObject.has("is_dir") && this.jsonObject.opt("is_dir") instanceof Boolean) { + this.isDirectory = this.jsonObject.optBoolean("is_dir"); + } + if(this.jsonObject.has("updated_at")) { + this.updatedAt = this.jsonObject.optString("updated_at"); + } + if(this.jsonObject.has("updated_by")) { + this.updatedBy = this.jsonObject.optString("updated_by"); + } + if(this.jsonObject.has("created_at")) { + this.createdAt = this.jsonObject.optString("created_at"); + } + if(this.jsonObject.has("created_by")) { + this.createdBy = this.jsonObject.optString("created_by"); + } + if(this.jsonObject.has(LOCALE_KEY)) { + this.locale = this.jsonObject.optString(LOCALE_KEY); + } + if(this.jsonObject.has("_in_progress") && this.jsonObject.opt("_in_progress") instanceof Boolean) { + this.inProgress = this.jsonObject.optBoolean("_in_progress"); + } + if(this.jsonObject.has("_version") && this.jsonObject.opt("_version") instanceof Integer) { + this.version = this.jsonObject.optInt("_version",1); + } if (this.jsonObject.has(PUBLISH_DETAIL_KEY)) { parsePublishDetail(); } @@ -77,12 +95,15 @@ public EntryModel(JSONObject response) { private void parsePublishDetail() { if (this.jsonObject.opt(PUBLISH_DETAIL_KEY) instanceof JSONObject) { - this.publishDetails = (JSONObject) this.jsonObject.opt(PUBLISH_DETAIL_KEY); - this.environment = this.publishDetails.optString("environment"); - this.time = this.publishDetails.optString("time"); - this.user = this.publishDetails.optString("user"); + this.publishDetails = this.jsonObject.optJSONObject(PUBLISH_DETAIL_KEY); + if(this.publishDetails != null) { + this.environment = this.publishDetails.optString("environment"); + this.time = this.publishDetails.optString("time"); + this.user = this.publishDetails.optString("user"); + } } this.metadata = new HashMap<>(); this.metadata.put(PUBLISH_DETAIL_KEY, this.publishDetails); } } + diff --git a/src/main/java/com/contentstack/sdk/Query.java b/src/main/java/com/contentstack/sdk/Query.java index ba9d0511..b8774af4 100644 --- a/src/main/java/com/contentstack/sdk/Query.java +++ b/src/main/java/com/contentstack/sdk/Query.java @@ -106,6 +106,31 @@ public String getContentType() { return contentTypeInstance.contentTypeUid; } + //Sanitization of keys + private boolean isValidKey(String key) { + return key.matches("^[a-zA-Z0-9_.]+$"); + } + + //Sanitization of values + private boolean isValidValue(Object value) { + if(value instanceof String){ + return ((String) value).matches("^[a-zA-Z0-9_.\\-\\s]+$"); + } + return true; + } + + //Sanitization of values list + private boolean isValidValueList(Object[] values) { + for (Object value : values) { + if (value instanceof String) { + if (!((String) value).matches("^[a-zA-Z0-9_.\\-\\s]+$")) { + return false; + } + } + } + return true; + } + /** * Add a constraint to fetch all entries that contains given value against specified key * @@ -128,7 +153,11 @@ public String getContentType() { */ public Query where(@NotNull String key, Object value) { - queryValueJSON.put(key, value); + if (isValidKey(key) && isValidValue(value) && value != null) { + queryValueJSON.put(key, value); + } else { + throwException("where", "Invalid key or value", null); + } return this; } @@ -150,7 +179,7 @@ public Query where(@NotNull String key, Object value) { * */ public Query addQuery(@NotNull String key, String value) { - if (value != null) { + if (isValidKey(key) && isValidValue(value) && value != null) { urlQueries.put(key, value); } return this; @@ -171,7 +200,7 @@ public Query addQuery(@NotNull String key, String value) { * */ public Query removeQuery(@NotNull String key) { - if (urlQueries.has(key)) { + if (isValidKey(key) && urlQueries.has(key)) { urlQueries.remove(key); } return this; @@ -269,15 +298,19 @@ public Query or(List queryObjects) { * */ public Query lessThan(@NotNull String key, @NotNull Object value) { - if (queryValueJSON.isNull(key)) { - if (!queryValue.isEmpty()) { - queryValue = new JSONObject(); + if(isValidKey(key) && isValidValue(value)) { + if (queryValueJSON.isNull(key)) { + if (!queryValue.isEmpty()) { + queryValue = new JSONObject(); + } + queryValue.put("$lt", value); + queryValueJSON.put(key, queryValue); + } else if (queryValueJSON.has(key)) { + queryValue.put("$lt", value); + queryValueJSON.put(key, queryValue); } - queryValue.put("$lt", value); - queryValueJSON.put(key, queryValue); - } else if (queryValueJSON.has(key)) { - queryValue.put("$lt", value); - queryValueJSON.put(key, queryValue); + } else { + throwException("lessThan", "Invalid key or value", null); } return this; } @@ -301,16 +334,20 @@ public Query lessThan(@NotNull String key, @NotNull Object value) { * */ public Query lessThanOrEqualTo(@NotNull String key, Object value) { - if (queryValueJSON.isNull(key)) { - if (!queryValue.isEmpty()) { - queryValue = new JSONObject(); - } - queryValue.put("$lte", value); - queryValueJSON.put(key, queryValue); - } else if (queryValueJSON.has(key)) { - queryValue.put("$lte", value); - queryValueJSON.put(key, queryValue); - } + if(isValidKey(key) && isValidValue(value)) { + if (queryValueJSON.isNull(key)) { + if (!queryValue.isEmpty()) { + queryValue = new JSONObject(); + } + queryValue.put("$lte", value); + queryValueJSON.put(key, queryValue); + } else if (queryValueJSON.has(key)) { + queryValue.put("$lte", value); + queryValueJSON.put(key, queryValue); + } + } else { + throwException("lessThanOrEqualTo", "Invalid key or value", null); + } return this; } @@ -332,15 +369,19 @@ public Query lessThanOrEqualTo(@NotNull String key, Object value) { * */ public Query greaterThan(@NotNull String key, Object value) { - if (queryValueJSON.isNull(key)) { - if (!queryValue.isEmpty()) { - queryValue = new JSONObject(); + if(isValidKey(key) && isValidValue(value)) { + if (queryValueJSON.isNull(key)) { + if (!queryValue.isEmpty()) { + queryValue = new JSONObject(); + } + queryValue.put("$gt", value); + queryValueJSON.put(key, queryValue); + } else if (queryValueJSON.has(key)) { + queryValue.put("$gt", value); + queryValueJSON.put(key, queryValue); } - queryValue.put("$gt", value); - queryValueJSON.put(key, queryValue); - } else if (queryValueJSON.has(key)) { - queryValue.put("$gt", value); - queryValueJSON.put(key, queryValue); + } else { + throwException("greaterThan", "Invalid key or value", null); } return this; } @@ -364,15 +405,19 @@ public Query greaterThan(@NotNull String key, Object value) { * */ public Query greaterThanOrEqualTo(String key, Object value) { - if (queryValueJSON.isNull(key)) { - if (queryValue.length() > 0) { - queryValue = new JSONObject(); + if(isValidKey(key) && isValidValue(value)) { + if (queryValueJSON.isNull(key)) { + if (queryValue.length() > 0) { + queryValue = new JSONObject(); + } + queryValue.put("$gte", value); + queryValueJSON.put(key, queryValue); + } else if (queryValueJSON.has(key)) { + queryValue.put("$gte", value); + queryValueJSON.put(key, queryValue); } - queryValue.put("$gte", value); - queryValueJSON.put(key, queryValue); - } else if (queryValueJSON.has(key)) { - queryValue.put("$gte", value); - queryValueJSON.put(key, queryValue); + } else { + throwException("greaterThanOrEqualTo", "Invalid key or value", null); } return this; } @@ -395,15 +440,19 @@ public Query greaterThanOrEqualTo(String key, Object value) { * */ public Query notEqualTo(@NotNull String key, Object value) { - if (queryValueJSON.isNull(key)) { - if (queryValue.length() > 0) { - queryValue = new JSONObject(); + if (isValidKey(key) && isValidValue(value)) { + if (queryValueJSON.isNull(key)) { + if (queryValue.length() > 0) { + queryValue = new JSONObject(); + } + queryValue.put("$ne", value); + queryValueJSON.put(key, queryValue); + } else if (queryValueJSON.has(key)) { + queryValue.put("$ne", value); + queryValueJSON.put(key, queryValue); } - queryValue.put("$ne", value); - queryValueJSON.put(key, queryValue); - } else if (queryValueJSON.has(key)) { - queryValue.put("$ne", value); - queryValueJSON.put(key, queryValue); + } else { + throwException("notEqualTo", "Invalid key or value", null); } return this; } @@ -426,19 +475,23 @@ public Query notEqualTo(@NotNull String key, Object value) { * */ public Query containedIn(@NotNull String key, Object[] values) { - JSONArray valuesArray = new JSONArray(); - for (Object value : values) { - valuesArray.put(value); - } - if (queryValueJSON.isNull(key)) { - if (queryValue.length() > 0) { - queryValue = new JSONObject(); + if (isValidKey(key) && isValidValueList(values)) { + JSONArray valuesArray = new JSONArray(); + for (Object value : values) { + valuesArray.put(value); } - queryValue.put("$in", valuesArray); - queryValueJSON.put(key, queryValue); - } else if (queryValueJSON.has(key)) { - queryValue.put("$in", valuesArray); - queryValueJSON.put(key, queryValue); + if (queryValueJSON.isNull(key)) { + if (queryValue.length() > 0) { + queryValue = new JSONObject(); + } + queryValue.put("$in", valuesArray); + queryValueJSON.put(key, queryValue); + } else if (queryValueJSON.has(key)) { + queryValue.put("$in", valuesArray); + queryValueJSON.put(key, queryValue); + } + } else { + throwException("containedIn", "Invalid key or value", null); } return this; } @@ -462,19 +515,23 @@ public Query containedIn(@NotNull String key, Object[] values) { * */ public Query notContainedIn(@NotNull String key, Object[] values) { - JSONArray valuesArray = new JSONArray(); - for (Object value : values) { - valuesArray.put(value); - } - if (queryValueJSON.isNull(key)) { - if (queryValue.length() > 0) { - queryValue = new JSONObject(); + if(isValidKey(key) && isValidValueList(values)) { + JSONArray valuesArray = new JSONArray(); + for (Object value : values) { + valuesArray.put(value); } - queryValue.put("$nin", valuesArray); - queryValueJSON.put(key, queryValue); - } else if (queryValueJSON.has(key)) { - queryValue.put("$nin", valuesArray); - queryValueJSON.put(key, queryValue); + if (queryValueJSON.isNull(key)) { + if (queryValue.length() > 0) { + queryValue = new JSONObject(); + } + queryValue.put("$nin", valuesArray); + queryValueJSON.put(key, queryValue); + } else if (queryValueJSON.has(key)) { + queryValue.put("$nin", valuesArray); + queryValueJSON.put(key, queryValue); + } + } else { + throwException("notContainedIn", "Invalid key or value", null); } return this; } @@ -496,15 +553,19 @@ public Query notContainedIn(@NotNull String key, Object[] values) { * */ public Query exists(@NotNull String key) { - if (queryValueJSON.isNull(key)) { - if (queryValue.length() > 0) { - queryValue = new JSONObject(); + if(isValidKey(key)) { + if (queryValueJSON.isNull(key)) { + if (queryValue.length() > 0) { + queryValue = new JSONObject(); + } + queryValue.put(EXISTS, true); + queryValueJSON.put(key, queryValue); + } else if (queryValueJSON.has(key)) { + queryValue.put(EXISTS, true); + queryValueJSON.put(key, queryValue); } - queryValue.put(EXISTS, true); - queryValueJSON.put(key, queryValue); - } else if (queryValueJSON.has(key)) { - queryValue.put(EXISTS, true); - queryValueJSON.put(key, queryValue); + } else { + throwException("exists", "Invalid key", null); } return this; } @@ -527,16 +588,20 @@ public Query exists(@NotNull String key) { * */ public Query notExists(@NotNull String key) { - if (queryValueJSON.isNull(key)) { - if (queryValue.length() > 0) { - queryValue = new JSONObject(); - } - queryValue.put(EXISTS, false); - queryValueJSON.put(key, queryValue); - } else if (queryValueJSON.has(key)) { + if(isValidKey(key)) { + if (queryValueJSON.isNull(key)) { + if (queryValue.length() > 0) { + queryValue = new JSONObject(); + } + queryValue.put(EXISTS, false); + queryValueJSON.put(key, queryValue); + } else if (queryValueJSON.has(key)) { - queryValue.put(EXISTS, false); - queryValueJSON.put(key, queryValue); + queryValue.put(EXISTS, false); + queryValueJSON.put(key, queryValue); + } + } else { + throwException("notExists", "Invalid key", null); } return this; } @@ -561,7 +626,11 @@ public Query includeReference(String key) { if (objectUidForInclude == null) { objectUidForInclude = new JSONArray(); } - objectUidForInclude.put(key); + if(isValidKey(key)) { + objectUidForInclude.put(key); + } else { + throwException("includeReference", "Invalid key", null); + } return this; } @@ -583,7 +652,11 @@ public Query includeReference(String key) { */ public Query tags(@NotNull String[] tags) { String tagstr = String.join(",", tags); - urlQueries.put("tags", tagstr); + if(isValidValue(tagstr)) { + urlQueries.put("tags", tagstr); + } else { + throwException("tags", "Invalid tag", null); + } return this; } @@ -606,7 +679,11 @@ public Query tags(@NotNull String[] tags) { */ public Query ascending(@NotNull String key) { - urlQueries.put("asc", key); + if(isValidKey(key)){ + urlQueries.put("asc", key); + } else { + throwException("ascending", "Invalid key", null); + } return this; } @@ -627,8 +704,12 @@ public Query ascending(@NotNull String key) { * csQuery.descending("name"); * */ - public Query descending(@NotNull String key) { - urlQueries.put("desc", key); + public Query descending(@NotNull String key) { + if(isValidKey(key)){ + urlQueries.put("desc", key); + } else { + throwException("descending", "Invalid key", null); + } return this; } @@ -652,13 +733,17 @@ public Query descending(@NotNull String key) { * */ public Query except(@NotNull List fieldUid) { - if (!fieldUid.isEmpty()) { - if (objectUidForExcept == null) { - objectUidForExcept = new JSONArray(); - } - for (String s : fieldUid) { - objectUidForExcept.put(s); + if(isValidValue(fieldUid)){ + if (!fieldUid.isEmpty()) { + if (objectUidForExcept == null) { + objectUidForExcept = new JSONArray(); + } + for (String s : fieldUid) { + objectUidForExcept.put(s); + } } + } else { + throwException("except", "Invalid key", null); } return this; } @@ -680,13 +765,17 @@ public Query except(@NotNull List fieldUid) { * */ public Query except(@NotNull String[] fieldIds) { - if (fieldIds.length > 0) { - if (objectUidForExcept == null) { - objectUidForExcept = new JSONArray(); - } - for (String fieldId : fieldIds) { - objectUidForExcept.put(fieldId); + if(isValidValue(fieldIds)) { + if (fieldIds.length > 0) { + if (objectUidForExcept == null) { + objectUidForExcept = new JSONArray(); + } + for (String fieldId : fieldIds) { + objectUidForExcept.put(fieldId); + } } + } else { + throwException("except", "Invalid key", null); } return this; } @@ -708,13 +797,17 @@ public Query except(@NotNull String[] fieldIds) { * */ public Query only(@NotNull String[] fieldUid) { - if (fieldUid.length > 0) { - if (objectUidForOnly == null) { - objectUidForOnly = new JSONArray(); - } - for (String s : fieldUid) { - objectUidForOnly.put(s); + if(isValidValue(fieldUid)){ + if (fieldUid.length > 0) { + if (objectUidForOnly == null) { + objectUidForOnly = new JSONArray(); + } + for (String s : fieldUid) { + objectUidForOnly.put(s); + } } + } else { + throwException("only", "Invalid key", null); } return this; } @@ -741,18 +834,22 @@ public Query only(@NotNull String[] fieldUid) { * */ public Query onlyWithReferenceUid(@NotNull List fieldUid, @NotNull String referenceFieldUid) { - if (onlyJsonObject == null) { - onlyJsonObject = new JSONObject(); - } - JSONArray fieldValueArray = new JSONArray(); - for (String s : fieldUid) { - fieldValueArray.put(s); - } - onlyJsonObject.put(referenceFieldUid, fieldValueArray); - if (objectUidForInclude == null) { - objectUidForInclude = new JSONArray(); + if(isValidValue(referenceFieldUid)){ + if (onlyJsonObject == null) { + onlyJsonObject = new JSONObject(); + } + JSONArray fieldValueArray = new JSONArray(); + for (String s : fieldUid) { + fieldValueArray.put(s); + } + onlyJsonObject.put(referenceFieldUid, fieldValueArray); + if (objectUidForInclude == null) { + objectUidForInclude = new JSONArray(); + } + objectUidForInclude.put(referenceFieldUid); + } else { + throwException("onlyWithReferenceUid", "Invalid key or value", null); } - objectUidForInclude.put(referenceFieldUid); return this; } @@ -777,18 +874,22 @@ public Query onlyWithReferenceUid(@NotNull List fieldUid, @NotNull Strin * */ public Query exceptWithReferenceUid(@NotNull List fieldUid, @NotNull String referenceFieldUid) { - if (exceptJsonObject == null) { - exceptJsonObject = new JSONObject(); - } - JSONArray fieldValueArray = new JSONArray(); - for (String s : fieldUid) { - fieldValueArray.put(s); - } - exceptJsonObject.put(referenceFieldUid, fieldValueArray); - if (objectUidForInclude == null) { - objectUidForInclude = new JSONArray(); + if(isValidValue(referenceFieldUid)){ + if (exceptJsonObject == null) { + exceptJsonObject = new JSONObject(); + } + JSONArray fieldValueArray = new JSONArray(); + for (String s : fieldUid) { + fieldValueArray.put(s); + } + exceptJsonObject.put(referenceFieldUid, fieldValueArray); + if (objectUidForInclude == null) { + objectUidForInclude = new JSONArray(); + } + objectUidForInclude.put(referenceFieldUid); + } else { + throwException("exceptWithReferenceUid", "Invalid key or value", null); } - objectUidForInclude.put(referenceFieldUid); return this; } @@ -927,15 +1028,19 @@ public Query limit(int number) { */ public Query regex(@NotNull String key, @NotNull String regex) { - if (queryValueJSON.isNull(key)) { - if (!queryValue.isEmpty()) { - queryValue = new JSONObject(); + if(isValidKey(key) && isValidValue(regex)) { + if (queryValueJSON.isNull(key)) { + if (!queryValue.isEmpty()) { + queryValue = new JSONObject(); + } + queryValue.put(REGEX, regex); + queryValueJSON.put(key, queryValue); + } else if (queryValueJSON.has(key)) { + queryValue.put(REGEX, regex); + queryValueJSON.put(key, queryValue); } - queryValue.put(REGEX, regex); - queryValueJSON.put(key, queryValue); - } else if (queryValueJSON.has(key)) { - queryValue.put(REGEX, regex); - queryValueJSON.put(key, queryValue); + } else { + throwException(REGEX, Constants.QUERY_EXCEPTION, null); } return this; } @@ -1031,8 +1136,12 @@ public Query locale(@NotNull String locale) { */ public Query search(@NotNull String value) { - if (urlQueries.isNull(value)) { - urlQueries.put("typeahead", value); + if(isValidValue(value)) { + if (urlQueries.isNull(value)) { + urlQueries.put("typeahead", value); + } + } else { + throwException("search", "Invalid value", null); } return this; } @@ -1267,7 +1376,11 @@ public void getResultObject(List objects, JSONObject jsonObject, boolean * */ public Query addParam(@NotNull String key, @NotNull String value) { - urlQueries.put(key, value); + if(isValidKey(key) && isValidValue(value)) { + urlQueries.put(key, value); + } else { + throwException("addParam", "Invalid key or value", null); + } return this; } @@ -1315,9 +1428,13 @@ public Query includeReferenceContentTypUid() { * */ public Query whereIn(@NotNull String key, Query queryObject) { - JSONObject inQueryObj = new JSONObject(); - inQueryObj.put("$in_query", queryObject.queryValueJSON.toString()); - queryValueJSON.put(key, inQueryObj); + if(isValidKey(key)){ + JSONObject inQueryObj = new JSONObject(); + inQueryObj.put("$in_query", queryObject.queryValueJSON.toString()); + queryValueJSON.put(key, inQueryObj); + } else { + throwException("whereIn", "Invalid key", null); + } return this; } @@ -1340,9 +1457,13 @@ public Query whereIn(@NotNull String key, Query queryObject) { * */ public Query whereNotIn(@NotNull String key, Query queryObject) { - JSONObject inQueryObj = new JSONObject(); - inQueryObj.put("$nin_query", queryObject.queryValueJSON.toString()); - queryValueJSON.put(key, inQueryObj); + if(isValidKey(key)){ + JSONObject inQueryObj = new JSONObject(); + inQueryObj.put("$nin_query", queryObject.queryValueJSON.toString()); + queryValueJSON.put(key, inQueryObj); + } else { + throwException("whereNotIn", "Invalid key", null); + } return this; } diff --git a/src/main/java/com/contentstack/sdk/SyncStack.java b/src/main/java/com/contentstack/sdk/SyncStack.java index e5b93d9f..3c92a112 100755 --- a/src/main/java/com/contentstack/sdk/SyncStack.java +++ b/src/main/java/com/contentstack/sdk/SyncStack.java @@ -58,6 +58,9 @@ public List getItems() { } protected void setJSON(@NotNull JSONObject jsonobject) { + if (jsonobject == null) { + throw new IllegalArgumentException("JSON object cannot be null."); + } this.receiveJson = jsonobject; if (receiveJson.has("items")) { ArrayList> items = (ArrayList) this.receiveJson.get("items"); From 760aeeea7b250b2d72769c1ddfe598c52664e1e6 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Thu, 13 Mar 2025 16:25:36 +0530 Subject: [PATCH 4/7] added tests for snyc stack --- .../java/com/contentstack/sdk/SyncStack.java | 78 +++++++--- .../com/contentstack/sdk/TestSyncStack.java | 146 ++++++++++++++++++ 2 files changed, 205 insertions(+), 19 deletions(-) create mode 100644 src/test/java/com/contentstack/sdk/TestSyncStack.java diff --git a/src/main/java/com/contentstack/sdk/SyncStack.java b/src/main/java/com/contentstack/sdk/SyncStack.java index 3c92a112..e36e5f97 100755 --- a/src/main/java/com/contentstack/sdk/SyncStack.java +++ b/src/main/java/com/contentstack/sdk/SyncStack.java @@ -6,6 +6,7 @@ import org.jetbrains.annotations.NotNull; import org.json.JSONArray; import org.json.JSONObject; +import java.util.logging.Logger; /** @@ -16,6 +17,7 @@ */ public class SyncStack { + private static final Logger logger = Logger.getLogger(SyncStack.class.getName()); private JSONObject receiveJson; private int skip; private int limit; @@ -57,32 +59,32 @@ public List getItems() { return this.syncItems; } - protected void setJSON(@NotNull JSONObject jsonobject) { + protected synchronized void setJSON(@NotNull JSONObject jsonobject) { if (jsonobject == null) { throw new IllegalArgumentException("JSON object cannot be null."); } + this.receiveJson = jsonobject; + if (receiveJson.has("items")) { - ArrayList> items = (ArrayList) this.receiveJson.get("items"); - List objectList = new ArrayList<>(); - if (!items.isEmpty()) { - items.forEach(model -> { - if (model instanceof LinkedHashMap) { - // Convert LinkedHashMap to JSONObject - JSONObject jsonModel = new JSONObject((LinkedHashMap) model); - objectList.add(jsonModel); - } - }); - } - JSONArray jsonarray = new JSONArray(objectList); - if (jsonarray != null) { + Object itemsObj = receiveJson.opt("items"); + if (itemsObj instanceof JSONArray) { + JSONArray jsonArray = (JSONArray) itemsObj; syncItems = new ArrayList<>(); - for (int position = 0; position < jsonarray.length(); position++) { - syncItems.add(jsonarray.optJSONObject(position)); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject jsonItem = jsonArray.optJSONObject(i); + if (jsonItem != null) { + syncItems.add(sanitizeJson(jsonItem)); + } } + } else { + logger.warning("'items' is not a valid list. Skipping processing."); // ✅ Prevent crashes + syncItems = new ArrayList<>(); } + } else { + syncItems = new ArrayList<>(); } - + this.paginationToken = null; this.syncToken = null; if (receiveJson.has("skip")) { @@ -95,11 +97,49 @@ protected void setJSON(@NotNull JSONObject jsonobject) { this.limit = receiveJson.optInt("limit"); } if (receiveJson.has("pagination_token")) { - this.paginationToken = receiveJson.optString("pagination_token"); + this.paginationToken = validateToken(receiveJson.optString("pagination_token")); + } else { + this.paginationToken = null; } + if (receiveJson.has("sync_token")) { - this.syncToken = receiveJson.optString("sync_token"); + this.syncToken = validateToken(receiveJson.optString("sync_token")); + } else { + this.syncToken = null; + } + } + + /** + * ✅ Sanitize JSON to prevent JSON injection + */ + private JSONObject sanitizeJson(JSONObject json) { + JSONObject sanitizedJson = new JSONObject(); + for (String key : json.keySet()) { + Object value = json.opt(key); + if (value instanceof String) { + // ✅ Remove potentially dangerous script tags + String cleanValue = ((String) value) + .replaceAll("(?i)", "</script>"); // Prevent closing script tags + + sanitizedJson.put(key, cleanValue); // ✅ Store sanitized value + } else { + sanitizedJson.put(key, value); // ✅ Keep non-string values unchanged + } + } + return sanitizedJson; + } + + + /** + * ✅ Validate tokens to prevent security risks + */ + private String validateToken(String token) { + if (token != null && !token.matches("^[a-zA-Z0-9-_.]+$")) { + logger.warning("Invalid token detected: "); + return null; } + return token; } } diff --git a/src/test/java/com/contentstack/sdk/TestSyncStack.java b/src/test/java/com/contentstack/sdk/TestSyncStack.java new file mode 100644 index 00000000..779c7148 --- /dev/null +++ b/src/test/java/com/contentstack/sdk/TestSyncStack.java @@ -0,0 +1,146 @@ +package com.contentstack.sdk; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import java.util.List; + +public class TestSyncStack { + private SyncStack syncStack; + + @BeforeEach + void setUp() { + syncStack = new SyncStack(); + } + + /** + * ✅ Test: Valid JSON with correct structure + */ + @Test + void testSetJSON_WithValidData() { + JSONObject validJson = new JSONObject() + .put("items", new JSONArray() + .put(new JSONObject().put("title", "Article 1")) + .put(new JSONObject().put("title", "Article 2"))) + .put("skip", 5) + .put("total_count", 100) + .put("limit", 20) + .put("pagination_token", "validToken123") + .put("sync_token", "sync123"); + + syncStack.setJSON(validJson); + + // Assertions + assertEquals(5, syncStack.getSkip()); + assertEquals(100, syncStack.getCount()); + assertEquals(20, syncStack.getLimit()); + assertEquals("validToken123", syncStack.getPaginationToken()); + assertEquals("sync123", syncStack.getSyncToken()); + + List items = syncStack.getItems(); + assertNotNull(items); + assertEquals(2, items.size()); + assertEquals("Article 1", items.get(0).optString("title")); + } + + /** + * ✅ Test: Missing `items` should not cause a crash + */ + @Test + void testSetJSON_MissingItems() { + JSONObject jsonWithoutItems = new JSONObject() + .put("skip", 5) + .put("total_count", 50) + .put("limit", 10); + + syncStack.setJSON(jsonWithoutItems); + + // Assertions + assertEquals(5, syncStack.getSkip()); + assertEquals(50, syncStack.getCount()); + assertEquals(10, syncStack.getLimit()); + assertTrue(syncStack.getItems().isEmpty()); // Should default to empty list + } + + /** + * ✅ Test: Handling JSON Injection Attempt + */ + @Test + void testSetJSON_JSONInjection() { + JSONObject maliciousJson = new JSONObject() + .put("items", new JSONArray() + .put(new JSONObject().put("title", ""))); + + syncStack.setJSON(maliciousJson); + + List items = syncStack.getItems(); + assertNotNull(items); + assertEquals(1, items.size()); + assertEquals("<script>alert('Hacked');</script>", items.get(0).optString("title")); + } + + /** + * ✅ Test: Invalid `items` field (should not crash) + */ + @Test + void testSetJSON_InvalidItemsType() { + JSONObject invalidJson = new JSONObject() + .put("items", "This is not a valid array") + .put("skip", 10); + + assertDoesNotThrow(() -> syncStack.setJSON(invalidJson)); + assertTrue(syncStack.getItems().isEmpty()); + } + + /** + * ✅ Test: Null `paginationToken` and `syncToken` are handled correctly + */ + @Test + void testSetJSON_NullTokens() { + JSONObject jsonWithNullTokens = new JSONObject() + .put("pagination_token", JSONObject.NULL) + .put("sync_token", JSONObject.NULL); + + syncStack.setJSON(jsonWithNullTokens); + + assertNull(syncStack.getPaginationToken()); + assertNull(syncStack.getSyncToken()); + } + + /** + * ✅ Test: Invalid characters in `paginationToken` should be rejected + */ + @Test + void testSetJSON_InvalidTokenCharacters() { + JSONObject jsonWithInvalidTokens = new JSONObject() + .put("pagination_token", "invalid!!@#") + .put("sync_token", ""); + + syncStack.setJSON(jsonWithInvalidTokens); + + assertNull(syncStack.getPaginationToken()); // Should be sanitized + assertNull(syncStack.getSyncToken()); // Should be sanitized + } + + /** + * ✅ Test: Thread-Safety - Concurrent Modification of `syncItems` + */ + @Test + void testSetJSON_ThreadSafety() throws InterruptedException { + JSONObject jsonWithItems = new JSONObject() + .put("items", new JSONArray() + .put(new JSONObject().put("title", "Safe Entry"))); + + Thread thread1 = new Thread(() -> syncStack.setJSON(jsonWithItems)); + Thread thread2 = new Thread(() -> syncStack.setJSON(jsonWithItems)); + + thread1.start(); + thread2.start(); + thread1.join(); + thread2.join(); + + assertFalse(syncStack.getItems().isEmpty()); // No race conditions + } +} From b60f1a83d73c2cc4a799bc296f29c392cd628fd7 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 18 Mar 2025 15:21:49 +0530 Subject: [PATCH 5/7] updated the pom --- CHANGELOG.md | 7 ------- pom.xml | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a78ba68..c66f29fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,13 +10,6 @@ ## v2.0.2 -### Date: 27-January-2025 - --Snyk fixes --Fixed includeContenttype - -## v2.0.2 - ### Date: 5-December-2024 -Github Issue fixed diff --git a/pom.xml b/pom.xml index a442c844..6c4167f5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.contentstack.sdk java - 2.0.3 + 2.0.4 jar contentstack-java Java SDK for Contentstack Content Delivery API @@ -36,7 +36,7 @@ 20250107 0.8.7 2.5.3 - 1.2.14 + 1.2.15 From bf6380cb288dca7646b8625dfcae296ebb7fd8bc Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 19 Mar 2025 11:22:03 +0530 Subject: [PATCH 6/7] =?UTF-8?q?=E2=9C=A8feat:=20timeline=20preview=20imple?= =?UTF-8?q?mentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/contentstack/sdk/Config.java | 3 +++ src/main/java/com/contentstack/sdk/Stack.java | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/com/contentstack/sdk/Config.java b/src/main/java/com/contentstack/sdk/Config.java index 54011b92..d9fee7ea 100644 --- a/src/main/java/com/contentstack/sdk/Config.java +++ b/src/main/java/com/contentstack/sdk/Config.java @@ -31,6 +31,9 @@ public class Config { protected Proxy proxy = null; protected String[] earlyAccess = null; protected ConnectionPool connectionPool = new ConnectionPool(); + public String releaseId; + public String previewTimestamp; + protected List plugins = null; diff --git a/src/main/java/com/contentstack/sdk/Stack.java b/src/main/java/com/contentstack/sdk/Stack.java index 20bca289..62a933a9 100644 --- a/src/main/java/com/contentstack/sdk/Stack.java +++ b/src/main/java/com/contentstack/sdk/Stack.java @@ -142,6 +142,17 @@ public Stack livePreviewQuery(Map query) throws IOException { config.livePreviewEntryUid = query.get(ENTRY_UID); config.livePreviewContentType = query.get(CONTENT_TYPE_UID); + if(query.get("release_id") != null){ + config.releaseId = query.get("release_id"); + }else{ + config.releaseId = null; + } + if(query.get("preview_timestamp") != null){ + config.previewTimestamp = query.get("preview_timestamp"); + }else{ + config.previewTimestamp = null; + } + String livePreviewUrl = this.livePreviewEndpoint.concat(config.livePreviewContentType).concat("/entries/" + config.livePreviewEntryUid); if (livePreviewUrl.contains("/null/")) { throw new IllegalStateException("Malformed Query Url"); From e3a2b150d6fa659bcdf9cbf88ab6c68006746466 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 19 Mar 2025 11:39:00 +0530 Subject: [PATCH 7/7] testcases for timeline --- .../com/contentstack/sdk/TestLivePreview.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/test/java/com/contentstack/sdk/TestLivePreview.java b/src/test/java/com/contentstack/sdk/TestLivePreview.java index b5bec654..e81381c3 100644 --- a/src/test/java/com/contentstack/sdk/TestLivePreview.java +++ b/src/test/java/com/contentstack/sdk/TestLivePreview.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.util.HashMap; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -223,4 +224,45 @@ void testLivePreviewDisabled() throws IllegalAccessException, IOException { "Expected exception message does not match"); } + @Test + void testTimelinePreview() throws IllegalAccessException, IOException { + Config config = new Config() + .enableLivePreview(true) + .setLivePreviewHost("rest-preview.contentstack.com") + .setPreviewToken("preview_token"); + + Stack stack = Contentstack.stack("stackApiKey", "deliveryToken", "env1", config); + + HashMap hashMap = new HashMap<>(); + hashMap.put("live_preview", "hash167673"); + hashMap.put("content_type_uid", "page"); + hashMap.put("entry_uid", "entryUid"); + hashMap.put("release_id", "12345"); + hashMap.put("preview_timestamp", "2025-09-25 17:45:30.005"); + + + stack.livePreviewQuery(hashMap); + Entry entry = stack.contentType("page").entry("entry_uid"); + entry.fetch(null); + Assertions.assertNotNull(entry); + } + + @Test + void testLivePreviewQueryWithoutReleaseId() throws Exception { + Config config = new Config().enableLivePreview(true) + .setLivePreviewHost("rest-preview.contentstack.com") + .setPreviewToken("previewToken"); + Stack stack = Contentstack.stack("api_key", "access_token", "env", config); + + Map queryParams = new HashMap<>(); + queryParams.put("content_type_uid", "blog"); + queryParams.put("entry_uid", "entry_123"); + queryParams.put("preview_timestamp", "1710800000"); + + stack.livePreviewQuery(queryParams); + + Assertions.assertNull(config.releaseId); + Assertions.assertEquals("1710800000", config.previewTimestamp); + } + }