-
Notifications
You must be signed in to change notification settings - Fork 5
fix: XSS vulnerability in autocomplete endpoints #280
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
86e373c
05f1f20
6c151b2
af4fece
89bfcad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -27,7 +27,6 @@ | |||||||||||||
| * CARLOS has no affiliation with OSCAR or McMaster University. | ||||||||||||||
| */ | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| package io.github.carlos_emr.carlos.commn.web; | ||||||||||||||
|
|
||||||||||||||
| import java.util.ArrayList; | ||||||||||||||
|
|
@@ -40,7 +39,7 @@ | |||||||||||||
|
|
||||||||||||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||||||||||||
|
|
||||||||||||||
| import org.apache.commons.text.StringEscapeUtils; | ||||||||||||||
| import org.owasp.encoder.Encode; | ||||||||||||||
| import org.apache.commons.lang3.StringUtils; | ||||||||||||||
| import io.github.carlos_emr.carlos.commn.dao.DemographicCustDao; | ||||||||||||||
| import io.github.carlos_emr.carlos.commn.dao.DemographicDao; | ||||||||||||||
|
|
@@ -50,6 +49,7 @@ | |||||||||||||
| import io.github.carlos_emr.carlos.utility.LoggedInInfo; | ||||||||||||||
| import io.github.carlos_emr.carlos.utility.MiscUtils; | ||||||||||||||
| import io.github.carlos_emr.carlos.utility.SpringUtils; | ||||||||||||||
| import io.github.carlos_emr.carlos.managers.SecurityInfoManager; | ||||||||||||||
|
|
||||||||||||||
| import io.github.carlos_emr.OscarProperties; | ||||||||||||||
| import io.github.carlos_emr.carlos.prescript.data.RxProviderData; | ||||||||||||||
|
|
@@ -61,109 +61,133 @@ | |||||||||||||
| import com.opensymphony.xwork2.ActionSupport; | ||||||||||||||
| import org.apache.struts2.ServletActionContext; | ||||||||||||||
|
|
||||||||||||||
| @SuppressWarnings("deprecation") | ||||||||||||||
| public class SearchDemographicAutoComplete2Action extends ActionSupport { | ||||||||||||||
| HttpServletRequest request = ServletActionContext.getRequest(); | ||||||||||||||
| HttpServletResponse response = ServletActionContext.getResponse(); | ||||||||||||||
| private static final ObjectMapper objectMapper = new ObjectMapper(); | ||||||||||||||
| private SecurityInfoManager securityInfoManager = SpringUtils.getBean(SecurityInfoManager.class); | ||||||||||||||
|
|
||||||||||||||
| public String execute() throws Exception { | ||||||||||||||
| LoggedInInfo loggedInInfo = LoggedInInfo.getLoggedInInfoFromSession(request); | ||||||||||||||
|
|
||||||||||||||
| if (!securityInfoManager.hasPrivilege(loggedInInfo, "_demographic", SecurityInfoManager.READ, null)) { | ||||||||||||||
| throw new RuntimeException("missing required security object (_demographic)"); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| public String execute() throws Exception { | ||||||||||||||
| String providerNo = LoggedInInfo.getLoggedInInfoFromSession(request).getLoggedInProviderNo(); | ||||||||||||||
| String providerNo = loggedInInfo.getLoggedInProviderNo(); | ||||||||||||||
|
|
||||||||||||||
| boolean outOfDomain = false; | ||||||||||||||
| if (request.getParameter("outofdomain") != null && request.getParameter("outofdomain").equals("true")) { | ||||||||||||||
| outOfDomain = true; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| DemographicDao demographicDao = (DemographicDao) SpringUtils.getBean(DemographicDao.class); | ||||||||||||||
| String searchStr = request.getParameter("demographicKeyword"); | ||||||||||||||
|
|
||||||||||||||
| if (searchStr == null) { | ||||||||||||||
| searchStr = request.getParameter("query"); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (searchStr == null) { | ||||||||||||||
| searchStr = request.getParameter("name"); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (searchStr == null) { | ||||||||||||||
| searchStr = request.getParameter("term"); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| boolean activeOnly = false; | ||||||||||||||
| activeOnly = request.getParameter("activeOnly") != null && request.getParameter("activeOnly").equalsIgnoreCase("true"); | ||||||||||||||
| boolean jqueryJSON = request.getParameter("jqueryJSON") != null && request.getParameter("jqueryJSON").equalsIgnoreCase("true"); | ||||||||||||||
| activeOnly = request.getParameter("activeOnly") != null | ||||||||||||||
| && request.getParameter("activeOnly").equalsIgnoreCase("true"); | ||||||||||||||
| boolean jqueryJSON = request.getParameter("jqueryJSON") != null | ||||||||||||||
| && request.getParameter("jqueryJSON").equalsIgnoreCase("true"); | ||||||||||||||
| RxProviderData rx = new RxProviderData(); | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| List<Demographic> list = null; | ||||||||||||||
|
|
||||||||||||||
| // Handle null or empty search string | ||||||||||||||
| if (searchStr == null || searchStr.trim().isEmpty()) { | ||||||||||||||
| response.setContentType("text/x-json"); | ||||||||||||||
| response.getWriter().write("{\"results\":[]}"); | ||||||||||||||
| return null; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (searchStr.length() == 8 && searchStr.matches("([0-9]*)")) { | ||||||||||||||
| list = demographicDao.searchDemographicByDOB(searchStr.substring(0, 4) + "-" + searchStr.substring(4, 6) + "-" + searchStr.substring(6, 8), 100, 0, providerNo, outOfDomain); | ||||||||||||||
| list = demographicDao.searchDemographicByDOB( | ||||||||||||||
| searchStr.substring(0, 4) + "-" + searchStr.substring(4, 6) + "-" + searchStr.substring(6, 8), 100, | ||||||||||||||
| 0, providerNo, outOfDomain); | ||||||||||||||
|
Comment on lines
116
to
+119
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. searchstr null dereference • searchStr is read from multiple request parameters but can still remain null if none are present. • The code then calls searchStr.length() without a null guard, which can throw a NullPointerException and return a 500 error. • This violates the edge-case handling requirement for null/empty inputs. Agent Prompt
|
||||||||||||||
| } else if (activeOnly) { | ||||||||||||||
| OscarProperties props = OscarProperties.getInstance(); | ||||||||||||||
| String pstatus = props.getProperty("inactive_statuses", "IN, DE, IC, ID, MO, FI"); | ||||||||||||||
| pstatus = pstatus.replaceAll("'", "").replaceAll("\\s", ""); | ||||||||||||||
| List<String> stati = Arrays.asList(pstatus.split(",")); | ||||||||||||||
|
|
||||||||||||||
| list = demographicDao.searchDemographicByNameAndNotStatus(searchStr, stati, 100, 0, providerNo, outOfDomain); | ||||||||||||||
| list = demographicDao.searchDemographicByNameAndNotStatus(searchStr, stati, 100, 0, providerNo, | ||||||||||||||
| outOfDomain); | ||||||||||||||
| if (list.size() == 100) { | ||||||||||||||
| MiscUtils.getLogger().warn("More results exists than returned"); | ||||||||||||||
| } | ||||||||||||||
| } else { | ||||||||||||||
| list = demographicDao.searchDemographicByName(searchStr, 100, 0, providerNo, outOfDomain); | ||||||||||||||
| if (list.size() == 100) { | ||||||||||||||
| MiscUtils.getLogger().warn("More results exists than returned"); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| List<HashMap<String, String>> secondList = new ArrayList<HashMap<String, String>>(); | ||||||||||||||
| for (Demographic demo : list) { | ||||||||||||||
| HashMap<String, String> h = new HashMap<String, String>(); | ||||||||||||||
| h.put("fomattedDob", demo.getFormattedDob()); | ||||||||||||||
| h.put("formattedName", StringEscapeUtils.escapeJava(demo.getFormattedName().replaceAll("\"", "\\\""))); | ||||||||||||||
| String encodedDob = Encode.forHtml(demo.getFormattedDob()); | ||||||||||||||
| h.put("formattedDob", encodedDob); | ||||||||||||||
| h.put("fomattedDob", encodedDob); // Legacy key for backward compatibility | ||||||||||||||
| h.put("formattedName", Encode.forHtml(demo.getFormattedName())); | ||||||||||||||
| h.put("demographicNo", String.valueOf(demo.getDemographicNo())); | ||||||||||||||
| h.put("status", demo.getPatientStatus()); | ||||||||||||||
|
|
||||||||||||||
| h.put("status", Encode.forHtml(demo.getPatientStatus())); | ||||||||||||||
|
|
||||||||||||||
| Provider p = rx.getProvider(demo.getProviderNo()); | ||||||||||||||
| if (demo.getProviderNo() != null) { | ||||||||||||||
| h.put("providerNo", demo.getProviderNo()); | ||||||||||||||
| } | ||||||||||||||
| if (p.getSurname() != null && p.getFirstName() != null) { | ||||||||||||||
| h.put("providerName", p.getSurname() + ", " + p.getFirstName()); | ||||||||||||||
| if (p != null && p.getSurname() != null && p.getFirstName() != null) { | ||||||||||||||
| h.put("providerName", Encode.forHtml(p.getSurname() + ", " + p.getFirstName())); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (OscarProperties.getInstance().isPropertyActive("workflow_enhance")) { | ||||||||||||||
| h.put("nextAppointment", AppointmentUtil.getNextAppointment(demo.getDemographicNo() + "")); | ||||||||||||||
| DemographicCustDao demographicCustDao = (DemographicCustDao) SpringUtils.getBean(DemographicCustDao.class); | ||||||||||||||
| String nextAppt = AppointmentUtil.getNextAppointment(demo.getDemographicNo() + ""); | ||||||||||||||
| h.put("nextAppointment", nextAppt != null ? Encode.forHtml(nextAppt) : ""); | ||||||||||||||
| DemographicCustDao demographicCustDao = (DemographicCustDao) SpringUtils | ||||||||||||||
| .getBean(DemographicCustDao.class); | ||||||||||||||
| DemographicCust demographicCust = demographicCustDao.find(demo.getDemographicNo()); | ||||||||||||||
|
|
||||||||||||||
| if (demographicCust != null) { | ||||||||||||||
| String cust1 = StringUtils.trimToNull(demographicCust.getNurse()); | ||||||||||||||
| String cust2 = StringUtils.trimToNull(demographicCust.getResident()); | ||||||||||||||
| String cust4 = StringUtils.trimToNull(demographicCust.getMidwife()); | ||||||||||||||
| if (cust1 != null) { | ||||||||||||||
| h.put("cust1", cust1); | ||||||||||||||
| p = rx.getProvider(cust1); | ||||||||||||||
| h.put("cust1Name", p.getSurname() + ", " + p.getFirstName()); | ||||||||||||||
| if (p != null && p.getSurname() != null && p.getFirstName() != null) { | ||||||||||||||
| h.put("cust1Name", Encode.forHtml(p.getSurname() + ", " + p.getFirstName())); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| if (cust2 != null) { | ||||||||||||||
| h.put("cust2", cust2); | ||||||||||||||
| p = rx.getProvider(cust2); | ||||||||||||||
| h.put("cust2Name", p.getSurname() + ", " + p.getFirstName()); | ||||||||||||||
| if (p != null && p.getSurname() != null && p.getFirstName() != null) { | ||||||||||||||
| h.put("cust2Name", Encode.forHtml(p.getSurname() + ", " + p.getFirstName())); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| if (cust4 != null) { | ||||||||||||||
| h.put("cust4", cust4); | ||||||||||||||
| p = rx.getProvider(cust4); | ||||||||||||||
| h.put("cust4Name", p.getSurname() + ", " + p.getFirstName()); | ||||||||||||||
| if (p != null && p.getSurname() != null && p.getFirstName() != null) { | ||||||||||||||
| h.put("cust4Name", Encode.forHtml(p.getSurname() + ", " + p.getFirstName())); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
Check notice on line 188 in src/main/java/io/github/carlos_emr/carlos/commn/web/SearchDemographicAutoComplete2Action.java
|
||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| secondList.add(h); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -181,24 +205,25 @@ | |||||||||||||
|
|
||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| private String formatJSON(List<HashMap<String, String>> info) { | ||||||||||||||
| StringBuilder json = new StringBuilder("["); | ||||||||||||||
|
|
||||||||||||||
| HashMap<String, String> record; | ||||||||||||||
| int size = info.size(); | ||||||||||||||
| for (int idx = 0; idx < size; ++idx) { | ||||||||||||||
| record = info.get(idx); | ||||||||||||||
| json.append("{\"label\":\"" + record.get("formattedName") + " " + record.get("fomattedDob") + " (" + record.get("status") + ")\",\"value\":\"" + record.get("demographicNo") + "\""); | ||||||||||||||
| json.append(",\"providerNo\":\"" + record.get("providerNo") + "\",\"provider\":\"" + record.get("providerName") + "\",\"nextAppt\":\"" + record.get("nextAppointment") + "\","); | ||||||||||||||
| json.append("\"formattedName\":\"" + record.get("formattedName") + "\"}"); | ||||||||||||||
|
|
||||||||||||||
| if (idx < size - 1) { | ||||||||||||||
| json.append(","); | ||||||||||||||
| } | ||||||||||||||
| private String formatJSON(List<HashMap<String, String>> info) | ||||||||||||||
| throws com.fasterxml.jackson.core.JsonProcessingException { | ||||||||||||||
| List<java.util.LinkedHashMap<String, String>> results = new ArrayList<>(); | ||||||||||||||
| for (HashMap<String, String> record : info) { | ||||||||||||||
| java.util.LinkedHashMap<String, String> node = new java.util.LinkedHashMap<>(); | ||||||||||||||
| String formattedName = record.get("formattedName") != null ? record.get("formattedName") : ""; | ||||||||||||||
| String formattedDob = record.get("formattedDob") != null ? record.get("formattedDob") : ""; | ||||||||||||||
| String status = record.get("status") != null ? record.get("status") : ""; | ||||||||||||||
|
Comment on lines
+213
to
+215
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These null checks can be simplified by using
Suggested change
|
||||||||||||||
| node.put("label", formattedName + " " + formattedDob + " (" + status + ")"); | ||||||||||||||
| node.put("value", record.get("demographicNo")); | ||||||||||||||
| node.put("providerNo", record.get("providerNo")); | ||||||||||||||
| node.put("provider", record.get("providerName")); | ||||||||||||||
| node.put("nextAppt", record.get("nextAppointment")); | ||||||||||||||
| node.put("formattedName", formattedName); | ||||||||||||||
| node.put("formattedDob", formattedDob); | ||||||||||||||
| node.put("fomattedDob", formattedDob); // Legacy key for backward compatibility | ||||||||||||||
| results.add(node); | ||||||||||||||
| } | ||||||||||||||
| json.append("]"); | ||||||||||||||
|
|
||||||||||||||
| return json.toString(); | ||||||||||||||
| return objectMapper.writeValueAsString(results); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| } | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,10 +27,8 @@ | |
| * CARLOS has no affiliation with OSCAR or McMaster University. | ||
| */ | ||
|
|
||
|
|
||
| package io.github.carlos_emr.carlos.commn.web; | ||
|
|
||
| import java.util.Hashtable; | ||
| import java.util.List; | ||
|
|
||
| import javax.servlet.http.HttpServletRequest; | ||
|
|
@@ -40,8 +38,10 @@ | |
| import com.fasterxml.jackson.databind.node.ObjectNode; | ||
|
|
||
| import io.github.carlos_emr.carlos.utility.SpringUtils; | ||
| import io.github.carlos_emr.carlos.utility.LoggedInInfo; | ||
| import io.github.carlos_emr.carlos.managers.SecurityInfoManager; | ||
|
|
||
| import io.github.carlos_emr.carlos.providers.data.ProviderData; | ||
| import org.owasp.encoder.Encode; | ||
| import io.github.carlos_emr.carlos.commn.dao.ProviderDataDao; | ||
|
|
||
| /** | ||
|
|
@@ -50,14 +50,22 @@ | |
| import com.opensymphony.xwork2.ActionSupport; | ||
| import org.apache.struts2.ServletActionContext; | ||
|
|
||
| @SuppressWarnings("deprecation") | ||
| public class SearchProviderAutoComplete2Action extends ActionSupport { | ||
| HttpServletRequest request = ServletActionContext.getRequest(); | ||
| HttpServletResponse response = ServletActionContext.getResponse(); | ||
|
|
||
|
|
||
| private static final ObjectMapper objectMapper = new ObjectMapper(); | ||
| private SecurityInfoManager securityInfoManager = SpringUtils.getBean(SecurityInfoManager.class); | ||
| private ProviderDataDao providerDataDao = SpringUtils.getBean(ProviderDataDao.class); | ||
|
|
||
| public String execute() throws Exception { | ||
| LoggedInInfo loggedInInfo = LoggedInInfo.getLoggedInInfoFromSession(request); | ||
|
|
||
| if (!securityInfoManager.hasPrivilege(loggedInInfo, "_provider", SecurityInfoManager.READ, null)) { | ||
| throw new RuntimeException("missing required security object (_provider)"); | ||
| } | ||
|
|
||
| if ("labSearch".equals(request.getParameter("method"))) { | ||
| return labSearch(); | ||
|
Comment on lines
62
to
70
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check Struts2 DMI configuration
echo "=== Checking for Struts2 DMI configuration ==="
rg -n 'struts.enable.DynamicMethodInvocation' --type=properties --type=xml --type=conf -C 2
echo -e "\n=== Checking for struts2 XML config files ==="
fd -e "xml" | grep -i struts | head -20
echo -e "\n=== Looking for SearchProviderAutoComplete configuration ==="
rg -n 'SearchProviderAutoComplete' --type=xml -C 5Repository: carlos-emr/carlos Length of output: 6541 🏁 Script executed: #!/bin/bash
# Read the full SearchProviderAutoComplete2Action.java file
echo "=== Full SearchProviderAutoComplete2Action.java content ==="
fd -name "SearchProviderAutoComplete2Action.java" -type fRepository: carlos-emr/carlos Length of output: 368 🏁 Script executed: #!/bin/bash
# Find the file and read it
find . -name "SearchProviderAutoComplete2Action.java" -type f 2>/dev/null | head -1 | xargs wc -lRepository: carlos-emr/carlos Length of output: 157 🏁 Script executed: #!/bin/bash
# Read the full SearchProviderAutoComplete2Action.java file
cat -n ./src/main/java/io/github/carlos_emr/carlos/commn/web/SearchProviderAutoComplete2Action.javaRepository: carlos-emr/carlos Length of output: 6796 🏁 Script executed: #!/bin/bash
# Check for DMI configuration in properties files and constants
echo "=== Checking for DMI in struts configuration ==="
rg -n 'DynamicMethodInvocation' --type=java --type=xml
echo ""
echo "=== Checking struts.properties or application.properties ==="
fd "\.properties$" | xargs rg -n 'struts.enable' 2>/dev/null || echo "No struts.enable properties found"
echo ""
echo "=== Checking for default struts configuration ==="
rg -n 'struts\.' src/main/resources/ 2>/dev/null | head -20 || echo "No src/main/resources found"Repository: carlos-emr/carlos Length of output: 344 🏁 Script executed: #!/bin/bash
# Check if there's a struts properties file or Spring XML configuration
find . -name "*.properties" -o -name "*.xml" | grep -E "(struts|spring)" | grep -v "test" | grep -v "TrackingRegexp"Repository: carlos-emr/carlos Length of output: 303 Make The - public String labSearch() throws Exception {
+ private String labSearch() throws Exception {🤖 Prompt for AI Agents
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @claude, this still requires authentication checks and the nature of this platform does largely treat authentication as circle of care, but this is something to look at further in general as the code base matures. Does this allow for bypass of authentication check? I don't think so? Please make a issue ticket for this for follow up.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ask❓@claude, this still requires authentication checks and the nature of this platform does largely treat authentication as circle of care, but this is something to look at further in general as the code base matures. Does this allow for bypass of authentication check? I don't think so? Please make a issue ticket for this for follow up. Answer:Does this allow a bypass of the authentication/authorization check? Why it could be bypassableIn if (!securityInfoManager.hasPrivilege(loggedInInfo, "_provider", SecurityInfoManager.READ, null)) {
throw new RuntimeException("missing required security object (_provider)");
}However, If DMI is disabled (common in hardened setups), then this direct invocation path generally won’t work, and you’re right that it likely does not bypass authz in practice. But because this is security-sensitive and configuration can drift over time (or differ across deployments), it’s still worth addressing as defense-in-depth. Recommended follow-up (low-risk hardening)Any one of these would close the potential gap:
Option (1) is simplest and aligns with CodeRabbit’s suggestion, provided there are no external callers (and since Struts uses reflection, “external caller” here includes the framework via DMI). Follow-up issue ticket to createTitle:
|
||
| } | ||
|
|
@@ -69,8 +77,41 @@ public String execute() throws Exception { | |
| searchStr = request.getParameter("name"); | ||
| } | ||
|
|
||
| List provList = ProviderData.searchProvider(searchStr, true); | ||
| Hashtable d = new Hashtable(); | ||
| // Handle null or empty search string | ||
| if (searchStr == null || searchStr.trim().isEmpty()) { | ||
| response.setContentType("text/x-json"); | ||
| response.getWriter().write("{\"results\":[]}"); | ||
| return null; | ||
| } | ||
|
|
||
| // Parse search string for firstName and lastName (replaces deprecated | ||
| // ProviderData.searchProvider) | ||
| String firstName = null; | ||
| String lastName; | ||
| if (searchStr.indexOf(",") != -1) { | ||
| String[] array = searchStr.split(",", -1); | ||
| lastName = array[0].trim(); | ||
| firstName = array.length > 1 ? array[1].trim() : ""; | ||
| } else { | ||
| lastName = searchStr.trim(); | ||
| } | ||
|
|
||
| // Use the DAO directly with the newer model class | ||
| List<io.github.carlos_emr.carlos.commn.model.ProviderData> providers = providerDataDao.findByName(firstName, | ||
| lastName, true); | ||
|
|
||
| // Convert to the expected map format for JSON response | ||
| List<java.util.Map<String, String>> provList = new java.util.ArrayList<>(); | ||
| for (io.github.carlos_emr.carlos.commn.model.ProviderData p : providers) { | ||
| java.util.Map<String, String> result = new java.util.HashMap<>(); | ||
| result.put("providerNo", p.getId()); | ||
| result.put("firstName", p.getFirstName() != null ? Encode.forHtml(p.getFirstName()) : ""); | ||
| result.put("lastName", p.getLastName() != null ? Encode.forHtml(p.getLastName()) : ""); | ||
| result.put("ohipNo", p.getOhipNo() != null ? Encode.forHtml(p.getOhipNo()) : ""); | ||
| provList.add(result); | ||
| } | ||
|
|
||
| java.util.HashMap<String, Object> d = new java.util.HashMap<>(); | ||
| d.put("results", provList); | ||
|
|
||
| response.setContentType("text/x-json"); | ||
|
|
@@ -83,12 +124,22 @@ public String execute() throws Exception { | |
| public String labSearch() throws Exception { | ||
|
|
||
| String searchStr = request.getParameter("term"); | ||
|
|
||
| // Handle null or empty search string | ||
| if (searchStr == null || searchStr.trim().isEmpty()) { | ||
| response.setContentType("text/x-json"); | ||
| response.getWriter().write("[]"); | ||
| return null; | ||
| } | ||
|
|
||
| String firstName, lastName; | ||
|
|
||
| if (searchStr.indexOf(",") != -1) { | ||
| String[] searchParams = searchStr.split(",", -1); | ||
| //note - the -1 is added because split discards a last empty string by default, so "smith,".split(",") returns ["smith"], not ["smith",""]. | ||
| //adding the -1 causes split to return the 2 element array in this situation, to avoid an index out of bounds error when setting the firstName | ||
| // note - the -1 is added because split discards a last empty string by default, | ||
| // so "smith,".split(",") returns ["smith"], not ["smith",""]. | ||
| // adding the -1 causes split to return the 2 element array in this situation, | ||
| // to avoid an index out of bounds error when setting the firstName | ||
| lastName = searchParams[0].trim(); | ||
| firstName = searchParams[1].trim(); | ||
| } else { | ||
|
|
@@ -97,21 +148,21 @@ public String labSearch() throws Exception { | |
| } | ||
|
|
||
| ProviderDataDao providerDataDao = SpringUtils.getBean(ProviderDataDao.class); | ||
| List<io.github.carlos_emr.carlos.commn.model.ProviderData> provList = providerDataDao.findByName(firstName, lastName, true); | ||
| StringBuilder searchResults = new StringBuilder("["); | ||
| int idx = 0; | ||
| List<io.github.carlos_emr.carlos.commn.model.ProviderData> provList = providerDataDao.findByName(firstName, | ||
| lastName, true); | ||
| List<java.util.LinkedHashMap<String, String>> searchResults = new java.util.ArrayList<>(); | ||
|
|
||
| for (io.github.carlos_emr.carlos.commn.model.ProviderData provData : provList) { | ||
| searchResults.append("{\"label\":\"" + provData.getLastName() + ", " + provData.getFirstName() + "\",\"value\":\"" + provData.getId() + "\"}"); | ||
| if (idx < provList.size() - 1) { | ||
| searchResults.append(","); | ||
| } | ||
| ++idx; | ||
| java.util.LinkedHashMap<String, String> node = new java.util.LinkedHashMap<>(); | ||
| String provLastName = provData.getLastName() != null ? Encode.forHtml(provData.getLastName()) : ""; | ||
| String provFirstName = provData.getFirstName() != null ? Encode.forHtml(provData.getFirstName()) : ""; | ||
| node.put("label", provLastName + ", " + provFirstName); | ||
| node.put("value", provData.getId()); | ||
| searchResults.add(node); | ||
| } | ||
|
|
||
| searchResults.append("]"); | ||
| response.setContentType("text/x-json"); | ||
| response.getWriter().write(searchResults.toString()); | ||
| response.getWriter().write(objectMapper.writeValueAsString(searchResults)); | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.