Merge "Fix NodeServlet Vulnerabilities"
[dmaap/datarouter.git] / datarouter-prov / src / main / java / org / onap / dmaap / datarouter / provisioning / InternalServlet.java
1 /*******************************************************************************
2  * ============LICENSE_START==================================================
3  * * org.onap.dmaap
4  * * ===========================================================================
5  * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  * * ===========================================================================
7  * * Licensed under the Apache License, Version 2.0 (the "License");
8  * * you may not use this file except in compliance with the License.
9  * * You may obtain a copy of the License at
10  * *
11  *  *      http://www.apache.org/licenses/LICENSE-2.0
12  * *
13  *  * Unless required by applicable law or agreed to in writing, software
14  * * distributed under the License is distributed on an "AS IS" BASIS,
15  * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * * See the License for the specific language governing permissions and
17  * * limitations under the License.
18  * * ============LICENSE_END====================================================
19  * *
20  * * ECOMP is a trademark and service mark of AT&T Intellectual Property.
21  * *
22  ******************************************************************************/
23
24
25 package org.onap.dmaap.datarouter.provisioning;
26
27 import java.io.ByteArrayOutputStream;
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.nio.file.FileStore;
32 import java.nio.file.FileSystem;
33 import java.nio.file.Files;
34 import java.nio.file.Path;
35 import java.nio.file.Paths;
36 import java.nio.file.StandardCopyOption;
37 import java.util.Properties;
38
39 import javax.servlet.http.HttpServletRequest;
40 import javax.servlet.http.HttpServletResponse;
41
42 import org.json.JSONArray;
43 import org.onap.dmaap.datarouter.provisioning.beans.EventLogRecord;
44 import org.onap.dmaap.datarouter.provisioning.beans.LogRecord;
45 import org.onap.dmaap.datarouter.provisioning.beans.Parameters;
46 import org.onap.dmaap.datarouter.provisioning.eelf.EelfMsgs;
47 import org.onap.dmaap.datarouter.provisioning.utils.DB;
48 import org.onap.dmaap.datarouter.provisioning.utils.LogfileLoader;
49 import org.onap.dmaap.datarouter.provisioning.utils.RLEBitSet;
50
51 import com.att.eelf.configuration.EELFLogger;
52 import com.att.eelf.configuration.EELFManager;
53
54 import static org.onap.dmaap.datarouter.provisioning.utils.HttpServletUtils.sendResponseError;
55
56 /**
57  * <p>
58  * This servlet handles requests to URLs under /internal on the provisioning server. These include:
59  * </p>
60  * <div class="contentContainer">
61  * <table class="packageSummary" border="0" cellpadding="3" cellspacing="0">
62  * <caption><span>URL Path Summary</span><span class="tabEnd">&nbsp;</span></caption>
63  * <tr>
64  * <th class="colFirst" width="15%">URL Path</th>
65  * <th class="colOne">Method</th>
66  * <th class="colLast">Purpose</th>
67  * </tr>
68  * <tr class="altColor">
69  * <td class="colFirst">/internal/prov</td>
70  * <td class="colOne">GET</td>
71  * <td class="colLast">used to GET a full JSON copy of the provisioning data.</td>
72  * </tr>
73  * <tr class="rowColor">
74  * <td class="colFirst">/internal/fetchProv</td>
75  * <td class="colOne">GET</td>
76  * <td class="colLast">used to signal to a standby POD that the provisioning data should be fetched from the active
77  * POD.</td>
78  * </tr>
79  * <tr class="altColor">
80  * <td class="colFirst" rowspan="2">/internal/logs</td>
81  * <td class="colOne">GET</td>
82  * <td class="colLast">used to GET an index of log files and individual logs for this provisioning server.</td>
83  * </tr>
84  * <tr class="altColor">
85  * <td class="colOne">POST</td>
86  * <td class="colLast">used to POST log files from the individual nodes to this provisioning server.</td>
87  * </tr>
88  * <tr class="rowColor">
89  * <td class="colFirst" rowspan="4">/internal/api</td>
90  * <td class="colOne">GET</td>
91  * <td class="colLast">used to GET an individual parameter value. The parameter name is specified by the path after
92  * /api/.</td>
93  * </tr>
94  * <tr class="rowColor">
95  * <td class="colOne">PUT</td>
96  * <td class="colLast">used to set an individual parameter value. The parameter name is specified by the path after
97  * /api/.</td>
98  * </tr>
99  * <tr class="rowColor">
100  * <td class="colOne">DELETE</td>
101  * <td class="colLast">used to remove an individual parameter value. The parameter name is specified by the path after
102  * /api/.</td>
103  * </tr>
104  * <tr class="rowColor">
105  * <td class="colOne">POST</td>
106  * <td class="colLast">used to create a new individual parameter value. The parameter name is specified by the path
107  * after /api/.</td>
108  * </tr>
109  * <tr class="altColor">
110  * <td class="colFirst">/internal/halt</td>
111  * <td class="colOne">GET</td>
112  * <td class="colLast">used to halt the server (must be accessed from 127.0.0.1).</td>
113  * </tr>
114  * <tr class="rowColor">
115  * <td class="colFirst" rowspan="2">/internal/drlogs</td>
116  * <td class="colOne">GET</td>
117  * <td class="colLast">used to get a list of DR log entries available for retrieval.
118  * Note: these are the actual data router log entries sent to the provisioning server by the nodes, not the provisioning
119  * server's internal logs (access via /internal/logs above). The range is returned as a list of record sequence
120  * numbers.</td>
121  * </tr>
122  * <tr class="rowColor">
123  * <td class="colOne">POST</td>
124  * <td class="colLast">used to retrieve specific log entries.
125  * The sequence numbers of the records to fetch are POST-ed; the records matching the sequence numbers are
126  * returned.</td>
127  * </tr>
128  * <tr class="altColor">
129  * <td class="colFirst">/internal/route/*</td>
130  * <td class="colOne">*</td>
131  * <td class="colLast">URLs under this path are handled via the {@link org.onap.dmaap.datarouter.provisioning.RouteServlet}</td>
132  * </tr>
133  * </table>
134  * </div>
135  * <p>
136  * Authorization to use these URLs is a little different than for other URLs on the provisioning server. For the most
137  * part, the IP address that the request comes from should be either:
138  * </p>
139  * <ol>
140  * <li>an IP address of a provisioning server, or</li>
141  * <li>the IP address of a node (to allow access to /internal/prov), or</li>
142  * <li>an IP address from the "<i>special subnet</i>" which is configured with
143  * the PROV_SPECIAL_SUBNET parameter.
144  * </ol>
145  * <p>
146  * In addition, requests to /internal/halt can ONLY come from localhost (127.0.0.1) on the HTTP port.
147  * </p>
148  * <p>
149  * All DELETE/GET/PUT/POST requests made to /internal/api on this servlet on the standby server are proxied to the
150  * active server (using the {@link ProxyServlet}) if it is up and reachable.
151  * </p>
152  *
153  * @author Robert Eby
154  * @version $Id: InternalServlet.java,v 1.23 2014/03/24 18:47:10 eby Exp $
155  */
156 @SuppressWarnings("serial")
157 public class InternalServlet extends ProxyServlet {
158
159     private static final Object lock = new Object();
160     private static Integer logseq = 0; // another piece of info to make log spool file names unique
161     //Adding EELF Logger Rally:US664892
162     private static EELFLogger eelflogger = EELFManager.getInstance()
163         .getLogger("org.onap.dmaap.datarouter.provisioning.InternalServlet");
164
165     /**
166      * Delete a parameter at the address /internal/api/&lt;parameter&gt;. See the <b>Internal API</b> document for
167      * details on how this method should be invoked.
168      */
169     @Override
170     public void doDelete(HttpServletRequest req, HttpServletResponse resp) {
171         setIpAndFqdnForEelf("doDelete");
172         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID, req.getHeader(BEHALF_HEADER), getIdFromPath(req) + "");
173         EventLogRecord elr = new EventLogRecord(req);
174         if (!isAuthorizedForInternal(req)) {
175             elr.setMessage("Unauthorized.");
176             elr.setResult(HttpServletResponse.SC_FORBIDDEN);
177             eventlogger.info(elr);
178             sendResponseError(resp, HttpServletResponse.SC_FORBIDDEN, "Unauthorized.", eventlogger);
179             return;
180         }
181
182         String path = req.getPathInfo();
183         if (path.startsWith("/api/")) {
184             if (isProxyOK(req) && isProxyServer()) {
185                 try {
186                     super.doDelete(req, resp);
187                 } catch (IOException ioe) {
188                     intlogger.error("IOException" + ioe.getMessage());
189                 }
190                 return;
191             }
192             String key = path.substring(5);
193             if (key.length() > 0) {
194                 Parameters param = Parameters.getParameter(key);
195                 if (param != null) {
196                     if (doDelete(param)) {
197                         elr.setResult(HttpServletResponse.SC_OK);
198                         eventlogger.info(elr);
199                         resp.setStatus(HttpServletResponse.SC_OK);
200                         provisioningDataChanged();
201                         provisioningParametersChanged();
202                     } else {
203                         // Something went wrong with the DELETE
204                         elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
205                         eventlogger.info(elr);
206                         sendResponseError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG, eventlogger);
207                     }
208                     return;
209                 }
210             }
211         }
212         sendResponseError(resp, HttpServletResponse.SC_NOT_FOUND, "Bad URL.", eventlogger);
213     }
214
215     /**
216      * Get some information (such as a parameter) underneath the /internal/ namespace. See the <b>Internal API</b>
217      * document for details on how this method should be invoked.
218      */
219     @Override
220     public void doGet(HttpServletRequest req, HttpServletResponse resp) {
221         setIpAndFqdnForEelf("doGet");
222         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID, req.getHeader(BEHALF_HEADER), getIdFromPath(req) + "");
223         String path = req.getPathInfo();
224         Properties props = (new DB()).getProperties();
225         if (path.equals("/halt") && !req.isSecure()) {
226             // request to halt the server - can ONLY come from localhost
227             String remote = req.getRemoteAddr();
228             if (remote.equals(props.getProperty("org.onap.dmaap.datarouter.provserver.localhost"))) {
229                 intlogger.info("PROV0009 Request to HALT received.");
230                 resp.setStatus(HttpServletResponse.SC_OK);
231                 Main.shutdown();
232             } else {
233                 intlogger.info("PROV0010 Disallowed request to HALT received from " + remote);
234                 resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
235             }
236             return;
237         }
238
239         EventLogRecord elr = new EventLogRecord(req);
240         if (!isAuthorizedForInternal(req)) {
241             elr.setMessage("Unauthorized.");
242             elr.setResult(HttpServletResponse.SC_FORBIDDEN);
243             eventlogger.info(elr);
244             sendResponseError(resp, HttpServletResponse.SC_FORBIDDEN, "Unauthorized.", eventlogger);
245             return;
246         }
247         if (path.equals("/fetchProv") && !req.isSecure()) {
248             // if request came from active_pod or standby_pod and it is not us, reload prov data
249             SynchronizerTask s = SynchronizerTask.getSynchronizer();
250             s.doFetch();
251             resp.setStatus(HttpServletResponse.SC_OK);
252             return;
253         }
254         if (path.equals("/prov")) {
255             if (isProxyOK(req) && isProxyServer()) {
256                 try {
257                     if (super.doGetWithFallback(req, resp)) {
258                         return;
259                     }
260                 } catch (IOException ioe) {
261                     intlogger.error("IOException" + ioe.getMessage());
262                 }
263                 // fall back to returning the local data if the remote is unreachable
264                 intlogger.info("Active server unavailable; falling back to local copy.");
265             }
266             Poker p = Poker.getPoker();
267             resp.setStatus(HttpServletResponse.SC_OK);
268             resp.setContentType(PROVFULL_CONTENT_TYPE2);
269             try {
270                 resp.getOutputStream().print(p.getProvisioningString());
271             } catch (IOException ioe) {
272                 intlogger.error("IOException" + ioe.getMessage());
273             }
274             return;
275         }
276         if (path.equals("/logs") || path.equals("/logs/")) {
277             resp.setStatus(HttpServletResponse.SC_OK);
278             resp.setContentType("application/json");
279             try {
280                 resp.getOutputStream().print(generateLogfileList().toString());
281             } catch (IOException ioe) {
282                 intlogger.error("IOException" + ioe.getMessage());
283             }
284             return;
285         }
286         if (path.startsWith("/logs/")) {
287             String logdir = props.getProperty("org.onap.dmaap.datarouter.provserver.accesslog.dir");
288             String logfile = path.substring(6);
289             if (logdir != null && logfile != null && logfile.indexOf('/') < 0) {
290                 File log = new File(logdir + "/" + logfile);
291                 if (log.exists() && log.isFile()) {
292                     resp.setStatus(HttpServletResponse.SC_OK);
293                     resp.setContentType("text/plain");
294                     Path logpath = Paths.get(log.getAbsolutePath());
295                     try {
296                         Files.copy(logpath, resp.getOutputStream());
297                     } catch (IOException ioe) {
298                         intlogger.error("IOException" + ioe.getMessage());
299                     }
300                     return;
301                 }
302             }
303             sendResponseError(resp, HttpServletResponse.SC_NO_CONTENT, "No file.", eventlogger);
304             return;
305         }
306         if (path.startsWith("/api/")) {
307             if (isProxyOK(req) && isProxyServer()) {
308                 try {
309                     super.doGet(req, resp);
310                 } catch (IOException ioe) {
311                     intlogger.error("IOException" + ioe.getMessage());
312                 }
313                 return;
314             }
315             String key = path.substring(5);
316             if (key.length() > 0) {
317                 Parameters param = Parameters.getParameter(key);
318                 if (param != null) {
319                     resp.setStatus(HttpServletResponse.SC_OK);
320                     resp.setContentType("text/plain");
321                     try {
322                         resp.getOutputStream().print(param.getValue() + "\n");
323                     } catch (IOException ioe) {
324                         intlogger.error("IOException" + ioe.getMessage());
325                     }
326                     return;
327                 }
328             }
329         }
330         if (path.equals("/drlogs") || path.equals("/drlogs/")) {
331             // Special POD <=> POD API to determine what log file records are loaded here
332             LogfileLoader lfl = LogfileLoader.getLoader();
333             resp.setStatus(HttpServletResponse.SC_OK);
334             resp.setContentType("text/plain");
335             try {
336                 resp.getOutputStream().print(lfl.getBitSet().toString());
337             } catch (IOException ioe) {
338                 intlogger.error("IOException" + ioe.getMessage());
339             }
340             return;
341         }
342         sendResponseError(resp, HttpServletResponse.SC_NOT_FOUND, "Bad URL.", eventlogger);
343     }
344
345     /**
346      * Modify a parameter at the address /internal/api/&lt;parameter&gt;. See the <b>Internal API</b> document for
347      * details on how this method should be invoked.
348      */
349     @Override
350     public void doPut(HttpServletRequest req, HttpServletResponse resp) {
351         setIpAndFqdnForEelf("doPut");
352         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID, req.getHeader(BEHALF_HEADER), getIdFromPath(req) + "");
353         EventLogRecord elr = new EventLogRecord(req);
354         if (!isAuthorizedForInternal(req)) {
355             elr.setMessage("Unauthorized.");
356             elr.setResult(HttpServletResponse.SC_FORBIDDEN);
357             eventlogger.info(elr);
358             sendResponseError(resp, HttpServletResponse.SC_FORBIDDEN, "Unauthorized.", eventlogger);
359             return;
360         }
361         String path = req.getPathInfo();
362         if (path.startsWith("/api/")) {
363             if (isProxyOK(req) && isProxyServer()) {
364                 try {
365                     super.doPut(req, resp);
366                 } catch (IOException ioe) {
367                     intlogger.error("IOException" + ioe.getMessage());
368                 }
369                 return;
370             }
371             String key = path.substring(5);
372             if (key.length() > 0) {
373                 Parameters param = Parameters.getParameter(key);
374                 if (param != null) {
375                     String t = catValues(req.getParameterValues("val"));
376                     param.setValue(t);
377                     if (doUpdate(param)) {
378                         elr.setResult(HttpServletResponse.SC_OK);
379                         eventlogger.info(elr);
380                         resp.setStatus(HttpServletResponse.SC_OK);
381                         provisioningDataChanged();
382                         provisioningParametersChanged();
383                     } else {
384                         // Something went wrong with the UPDATE
385                         elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
386                         eventlogger.info(elr);
387                         sendResponseError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG, eventlogger);
388                     }
389                     return;
390                 }
391             }
392         }
393         sendResponseError(resp, HttpServletResponse.SC_NOT_FOUND, "Bad URL.", eventlogger);
394     }
395
396     /**
397      * Create some new information (such as a parameter or log entries) underneath the /internal/ namespace. See the
398      * <b>Internal API</b> document for details on how this method should be invoked.
399      */
400     @SuppressWarnings("resource")
401     @Override
402     public void doPost(HttpServletRequest req, HttpServletResponse resp) {
403         setIpAndFqdnForEelf("doPost");
404         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF, req.getHeader(BEHALF_HEADER));
405         EventLogRecord elr = new EventLogRecord(req);
406         if (!isAuthorizedForInternal(req)) {
407             elr.setMessage("Unauthorized.");
408             elr.setResult(HttpServletResponse.SC_FORBIDDEN);
409             eventlogger.info(elr);
410             sendResponseError(resp, HttpServletResponse.SC_FORBIDDEN, "Unauthorized.", eventlogger);
411             return;
412         }
413
414         String path = req.getPathInfo();
415         if (path.startsWith("/api/")) {
416             if (isProxyOK(req) && isProxyServer()) {
417                 try {
418                     super.doPost(req, resp);
419                 } catch (IOException ioe) {
420                     intlogger.error("IOException" + ioe.getMessage());
421                 }
422                 return;
423             }
424             String key = path.substring(5);
425             if (key.length() > 0) {
426                 Parameters param = Parameters.getParameter(key);
427                 if (param == null) {
428                     String t = catValues(req.getParameterValues("val"));
429                     param = new Parameters(key, t);
430                     if (doInsert(param)) {
431                         elr.setResult(HttpServletResponse.SC_OK);
432                         eventlogger.info(elr);
433                         resp.setStatus(HttpServletResponse.SC_OK);
434                         provisioningDataChanged();
435                         provisioningParametersChanged();
436                     } else {
437                         // Something went wrong with the INSERT
438                         elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
439                         eventlogger.info(elr);
440                         sendResponseError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG, eventlogger);
441                     }
442                     return;
443                 }
444             }
445         }
446
447         if (path.equals("/logs") || path.equals("/logs/")) {
448             String ctype = req.getHeader("Content-Type");
449             if (ctype == null || !ctype.equals("text/plain")) {
450                 elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
451                 elr.setMessage("Bad media type: " + ctype);
452                 resp.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
453                 eventlogger.info(elr);
454                 return;
455             }
456             String spooldir = (new DB()).getProperties().getProperty("org.onap.dmaap.datarouter.provserver.spooldir");
457             String spoolname = String.format("%d-%d-", System.currentTimeMillis(), Thread.currentThread().getId());
458             synchronized (lock) {
459                 // perhaps unnecessary, but it helps make the name unique
460                 spoolname += logseq.toString();
461                 logseq++;
462             }
463             String encoding = req.getHeader("Content-Encoding");
464             if (encoding != null) {
465                 if (encoding.trim().equals("gzip")) {
466                     spoolname += ".gz";
467                 } else {
468                     elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
469                     resp.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
470                     eventlogger.info(elr);
471                     return;
472                 }
473             }
474             // Determine space available -- available space must be at least 5%
475             FileSystem fs = (Paths.get(spooldir)).getFileSystem();
476             long total = 0;
477             long avail = 0;
478             for (FileStore store : fs.getFileStores()) {
479                 try {
480                     total += store.getTotalSpace();
481                     avail += store.getUsableSpace();
482                 } catch (IOException ioe) {
483                     intlogger.error("IOException" + ioe.getMessage());
484                 }
485             }
486             try {
487                 fs.close();
488             } catch (Exception e) {
489             }
490             if (((avail * 100) / total) < 5) {
491                 elr.setResult(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
492                 resp.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
493                 eventlogger.info(elr);
494                 return;
495             }
496             Path tmppath = Paths.get(spooldir, spoolname);
497             Path donepath = Paths.get(spooldir, "IN." + spoolname);
498             try {
499                 Files.copy(req.getInputStream(), Paths.get(spooldir, spoolname), StandardCopyOption.REPLACE_EXISTING);
500                 Files.move(tmppath, donepath, StandardCopyOption.REPLACE_EXISTING);
501                 elr.setResult(HttpServletResponse.SC_CREATED);
502                 resp.setStatus(HttpServletResponse.SC_CREATED);
503                 eventlogger.info(elr);
504                 LogfileLoader.getLoader();    // This starts the logfile loader "task"
505             } catch (IOException ioe) {
506                 intlogger.error("IOException" + ioe.getMessage());
507             }
508             return;
509         }
510
511         if (path.equals("/drlogs") || path.equals("/drlogs/")) {
512             // Receive post request and generate log entries
513             String ctype = req.getHeader("Content-Type");
514             if (ctype == null || !ctype.equals("text/plain")) {
515                 elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
516                 elr.setMessage("Bad media type: " + ctype);
517                 resp.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
518                 eventlogger.info(elr);
519                 return;
520             }
521             try {
522                 InputStream is = req.getInputStream();
523                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
524                 int ch;
525                 while ((ch = is.read()) >= 0) {
526                     bos.write(ch);
527                 }
528                 RLEBitSet bs = new RLEBitSet(bos.toString());    // The set of records to retrieve
529                 elr.setResult(HttpServletResponse.SC_OK);
530                 resp.setStatus(HttpServletResponse.SC_OK);
531                 resp.setContentType("text/plain");
532                 LogRecord.printLogRecords(resp.getOutputStream(), bs);
533                 eventlogger.info(elr);
534             } catch (IOException ioe) {
535                 intlogger.error("IOException" + ioe.getMessage());
536             }
537             return;
538         }
539
540         elr.setResult(HttpServletResponse.SC_NOT_FOUND);
541         sendResponseError(resp, HttpServletResponse.SC_NOT_FOUND, "Bad URL.", eventlogger);
542         eventlogger.info(elr);
543     }
544
545     private String catValues(String[] v) {
546         StringBuilder sb = new StringBuilder();
547         if (v != null) {
548             String pfx = "";
549             for (String s : v) {
550                 sb.append(pfx);
551                 sb.append(s);
552                 pfx = "|";
553             }
554         }
555         return sb.toString();
556     }
557
558     private JSONArray generateLogfileList() {
559         JSONArray ja = new JSONArray();
560         Properties p = (new DB()).getProperties();
561         String s = p.getProperty("org.onap.dmaap.datarouter.provserver.accesslog.dir");
562         if (s != null) {
563             String[] dirs = s.split(",");
564             for (String dir : dirs) {
565                 File f = new File(dir);
566                 String[] list = f.list();
567                 if (list != null) {
568                     for (String s2 : list) {
569                         if (!s2.startsWith(".")) {
570                             ja.put(s2);
571                         }
572                     }
573                 }
574             }
575         }
576         return ja;
577     }
578 }