1 //
2 //  WebContext.java
3 //  cldrtools
4 //
5 //  Created by Steven R. Loomis on 3/11/2005.
6 //  Copyright 2005-2012 IBM. All rights reserved.
7 //
8 package org.unicode.cldr.web;
9 
10 import java.io.IOException;
11 import java.io.PrintWriter;
12 import java.io.StringWriter;
13 import java.io.UnsupportedEncodingException;
14 import java.io.Writer;
15 import java.net.URLEncoder;
16 import java.sql.SQLException;
17 import java.util.Hashtable;
18 import java.util.Iterator;
19 import java.util.Map;
20 import java.util.Map.Entry;
21 import java.util.Set;
22 import java.util.TreeMap;
23 import java.util.Vector;
24 import java.util.concurrent.ConcurrentHashMap;
25 
26 import javax.servlet.RequestDispatcher;
27 import javax.servlet.ServletException;
28 import javax.servlet.ServletRequest;
29 import javax.servlet.ServletResponse;
30 import javax.servlet.http.Cookie;
31 import javax.servlet.http.HttpServletRequest;
32 import javax.servlet.http.HttpServletResponse;
33 import javax.servlet.http.HttpSession;
34 
35 import org.unicode.cldr.test.CheckCLDR;
36 import org.unicode.cldr.test.DisplayAndInputProcessor;
37 import org.unicode.cldr.test.HelpMessages;
38 import org.unicode.cldr.util.CLDRFile;
39 import org.unicode.cldr.util.CLDRLocale;
40 import org.unicode.cldr.util.Level;
41 import org.unicode.cldr.util.Organization;
42 import org.unicode.cldr.util.PathHeader;
43 import org.unicode.cldr.util.PathHeader.PageId;
44 import org.unicode.cldr.util.StandardCodes;
45 import org.unicode.cldr.web.CLDRProgressIndicator.CLDRProgressTask;
46 import org.unicode.cldr.web.SurveyAjax.AjaxType;
47 import org.unicode.cldr.web.SurveyMain.Phase;
48 import org.unicode.cldr.web.SurveyMain.UserLocaleStuff;
49 import org.unicode.cldr.web.UserRegistry.LogoutException;
50 import org.unicode.cldr.web.UserRegistry.User;
51 import org.w3c.dom.Document;
52 
53 import com.ibm.icu.dev.util.ElapsedTimer;
54 import com.ibm.icu.text.UnicodeSet;
55 import com.ibm.icu.util.ULocale;
56 
57 /**
58  * This is the per-client context passed to basically all functions it has
59  * print*() like functions, and so can be written to.
60  */
61 public class WebContext implements Cloneable, Appendable {
62     public static final String TMPL_PATH = "/WEB-INF/tmpl/";
63     public static java.util.logging.Logger logger = SurveyLog.logger;
64     // USER fields
65     public SurveyMain sm = null;
66     public Document doc[] = new Document[0];
67     private CLDRLocale locale = null;
68     public ULocale displayLocale = SurveyMain.TRANS_HINT_LOCALE;
69     public CLDRLocale docLocale[] = new CLDRLocale[0];
70     public CookieSession session = null;
71     public ElapsedTimer reqTimer = null;
72     public Hashtable<String, Object> temporaryStuff = new Hashtable<>();
73     public static final String CLDR_WEBCONTEXT = "cldr_webcontext";
74 
75     public static final String TARGET_ZOOMED = "CLDR-ST-ZOOMED";
76     public static final String TARGET_EXAMPLE = "CLDR-ST-EXAMPLE";
77     public static final String TARGET_DOCS = "CLDR-ST-DOCS";
78 
79     // private fields
80     protected Writer out = null;
81     private PrintWriter pw = null;
82     String outQuery = null;
83     TreeMap<String, String> outQueryMap = new TreeMap<>();
84     boolean dontCloseMe = false;
85     HttpServletRequest request;
86     HttpServletResponse response;
87 
88     /**
89      *
90      * @return the output PrintWriter
91      */
getOut()92     public PrintWriter getOut() {
93         return pw;
94     }
95 
96     /**
97      * Flush output content. This is useful when JSPs are mixed in with servlet
98      * code.
99      *
100      * @see java.io.PrintWriter#flush()
101      */
flush()102     public void flush() {
103         pw.flush();
104     }
105 
106     /**
107      * Return the parameter map of the underlying request.
108      *
109      * @return {@link ServletRequest#getParameterMap()}
110      */
getParameterMap()111     public Map<?, ?> getParameterMap() {
112         return request.getParameterMap();
113     }
114 
115     /**
116      * Construct a new WebContext from the servlet request and response. This is
117      * the normal constructor to use when a top level servlet or JSP spins up.
118      * Embedded JSPs should use fromRequest.
119      *
120      * @see #fromRequest(ServletRequest, ServletResponse, Writer)
121      */
WebContext(HttpServletRequest irq, HttpServletResponse irs)122     public WebContext(HttpServletRequest irq, HttpServletResponse irs) throws IOException {
123         setRequestResponse(irq, irs);
124         setStream(irs.getWriter());
125     }
126 
127     /**
128      * Internal function to setup the WebContext to point at a servlet req/resp.
129      * Also registers the WebContext with the Request.
130      *
131      * @param irq
132      * @param irs
133      * @throws IOException
134      */
setRequestResponse(HttpServletRequest irq, HttpServletResponse irs)135     protected void setRequestResponse(HttpServletRequest irq, HttpServletResponse irs) throws IOException {
136         request = irq;
137         response = irs;
138         // register us - only if another webcontext is not already registered.
139         if (request.getAttribute(CLDR_WEBCONTEXT) == null) {
140             request.setAttribute(CLDR_WEBCONTEXT, this);
141         }
142     }
143 
144     /**
145      * Change the output stream to a different writer. If it isn't a
146      * PrintWriter, it will be wrapped in one. The WebContext will assume it
147      * does not own the stream, and will not close it when done.
148      *
149      * @param w
150      */
setStream(Writer w)151     protected void setStream(Writer w) {
152         out = w;
153         if (out instanceof PrintWriter) {
154             pw = (PrintWriter) out;
155         } else {
156             pw = new PrintWriter(out, true);
157         }
158         dontCloseMe = true; // do not close the stream if the Response owns it.
159     }
160 
161     /**
162      * Extract (or create) a WebContext from a request/response. Call this from
163      * a .jsp which is embedded in survey tool to extract the WebContext object.
164      * The WebContext will have its output stream set to point to the request
165      * and response, so you can mix write calls from the JSP with ST calls.
166      *
167      * @param request
168      * @param response
169      * @param out
170      * @return the new WebContext, which was cloned from the one posted to the
171      *         Request
172      * @throws IOException
173      */
fromRequest(ServletRequest request, ServletResponse response, Writer out)174     public static JspWebContext fromRequest(ServletRequest request, ServletResponse response, Writer out) throws IOException {
175         WebContext ctx = (WebContext) request.getAttribute(CLDR_WEBCONTEXT);
176         if (ctx == null) {
177             throw new InternalError("WebContext: could not load fromRequest. Are you trying to load a JSP directly?");
178         }
179         JspWebContext subCtx = new JspWebContext(ctx); // clone the important
180         // fields..
181         subCtx.setRequestResponse((HttpServletRequest) request, // but use the
182             // req/resp of
183             // the current
184             // situation
185             (HttpServletResponse) response);
186         subCtx.setStream(out);
187         return subCtx;
188     }
189 
190     /**
191      * Copy one WebContext to another. This is useful when you wish to create a
192      * sub-context which has a different base URL (such as for processing a
193      * certain form or widget).
194      *
195      * @param other
196      *            the other WebContext to copy from
197      */
WebContext(WebContext other)198     public WebContext(WebContext other) {
199         if ((other instanceof URLWebContext) && !(this instanceof URLWebContext)) {
200             throw new InternalError("Can't slice a URLWebContext - use clone()");
201         }
202         init(other);
203     }
204 
205     /**
206      * get a field's value as a boolean
207      *
208      * @param x
209      *            field name
210      * @param def
211      *            default value if field is not found.
212      * @return the field value
213      */
fieldBool(String x, boolean def)214     boolean fieldBool(String x, boolean def) {
215         if (field(x).length() > 0) {
216             if (field(x).charAt(0) == 't') {
217                 return true;
218             } else {
219                 return false;
220             }
221         } else {
222             return def;
223         }
224     }
225 
226     /**
227      * get a field's value as an integer, or -1 if not found
228      *
229      * @param x
230      *            field name
231      * @return the field's value as an integer, or -1 if it was not found
232      */
fieldInt(String x)233     public final int fieldInt(String x) {
234         return fieldInt(x, -1);
235     }
236 
237     /**
238      * get a field's value, or the default
239      *
240      * @param x
241      *            field name
242      * @param def
243      *            default value
244      * @return the field's value as an integer, or the default value if the
245      *         field was not found.
246      */
fieldInt(String x, int def)247     public int fieldInt(String x, int def) {
248         String f;
249         if ((f = field(x)).length() > 0) {
250             try {
251                 return new Integer(f).intValue();
252             } catch (Throwable t) {
253                 return def;
254             }
255         } else {
256             return def;
257         }
258     }
259 
260     /**
261      * Return true if the field is present
262      *
263      * @param x
264      *            field name
265      * @return true if the field is present
266      */
hasField(String x)267     public boolean hasField(String x) {
268         return (request.getParameter(x) != null);
269     }
270 
271     /**
272      * return a field's value, else ""
273      *
274      * @param x
275      *            field name
276      * @return the field value, or else ""
277      */
field(String x)278     public final String field(String x) {
279         return field(x, "");
280     }
281 
282     /**
283      * return a field's value, else default
284      *
285      * @param x
286      *            field name
287      * @param def
288      *            default value
289      * @return the field's value as a string, otherwise the default
290      */
field(String x, String def)291     public String field(String x, String def) {
292         if (request == null) {
293             return def; // support testing
294         }
295 
296         String res = request.getParameter(x);
297         if (res == null) {
298             return def; // don't try to transcode null.
299         }
300         return decodeFieldString(res);
301     }
302 
303     /*
304      * Decode a single string from URL format into Unicode
305      *
306      * @param res UTF-8 'encoded' bytes (expanded to a string)
307      *
308      * @return Unicode string (will return 'res' if no high bits were detected)
309      */
decodeFieldString(String res)310     public static String decodeFieldString(String res) {
311         if (res == null)
312             return null;
313         byte asBytes[] = new byte[res.length()];
314         boolean wasHigh = false;
315         int n;
316         for (n = 0; n < res.length(); n++) {
317             asBytes[n] = (byte) (res.charAt(n) & 0x00FF);
318             // println(" n : " + (int)asBytes[n] + " .. ");
319             if (asBytes[n] < 0) {
320                 wasHigh = true;
321             }
322         }
323         if (wasHigh == false) {
324             return res; // no utf-8
325         } else {
326             // println("[ trying to decode on: " + res + "]");
327         }
328         try {
329             res = new String(asBytes, "UTF-8");
330         } catch (Throwable t) {
331             return res;
332         }
333 
334         return res;
335     }
336 
337     // preference api
338     /**
339      * get a preference's value as a boolean. defaults to false.
340      *
341      * @param x
342      *            pref name
343      * @return preference value (or false)
344      */
prefBool(String x)345     public boolean prefBool(String x) {
346         return prefBool(x, false);
347     }
348 
349     /**
350      * Get a preference's value as an integer. Defaults to 'def'
351      *
352      * @param x
353      *            field name
354      * @param def
355      *            default value
356      * @return the prefence's value
357      */
prefInt(String x, int def)358     int prefInt(String x, int def) {
359         String f;
360         if ((f = pref(x, "")).length() > 0) {
361             try {
362                 return new Integer(f).intValue();
363             } catch (Throwable t) {
364                 return def;
365             }
366         } else {
367             return def;
368         }
369     }
370 
371     /**
372      * Get a preference's value as an integer, or else -1
373      *
374      * @param x
375      *            field name
376      * @return preference value or -1
377      */
prefInt(String x)378     int prefInt(String x) {
379         return prefInt(x, -1);
380     }
381 
382     int codesPerPage = -1;
383 
384     /**
385      * Special preference: Number of codes to show on a single page
386      *
387      * @return The preferred value (minimum: 5)
388      * @see SurveyMain#CODES_PER_PAGE
389      */
prefCodesPerPage()390     int prefCodesPerPage() {
391         if (codesPerPage == -1) {
392             codesPerPage = prefInt(SurveyMain.PREF_CODES_PER_PAGE, SurveyMain.CODES_PER_PAGE);
393             codesPerPage = Math.max(codesPerPage, 5);
394         }
395         return codesPerPage;
396     }
397 
398     /**
399      * get a preference's value as a boolean. defaults to defVal.
400      *
401      * @param x
402      *            preference name
403      * @param defVal
404      *            default value
405      * @return the preference value
406      */
prefBool(String x, boolean defVal)407     boolean prefBool(String x, boolean defVal) {
408         if (session == null) {
409             return defVal;
410         }
411         boolean ret = fieldBool(x, session.prefGetBool(x, defVal));
412         session.prefPut(x, ret);
413         return ret;
414     }
415 
416     /**
417      * get a pref that is a string,
418      *
419      * @param x
420      *            the field name and pref name
421      * @return string preference value or "" if otherwise not found.
422      */
pref(String x)423     String pref(String x) {
424         return pref(x, "");
425     }
426 
427     /**
428      * get a pref that is a string,
429      *
430      * @param x
431      *            the field name and pref name
432      * @param def
433      *            default value
434      * @return pref value or def
435      */
pref(String x, String def)436     String pref(String x, String def) {
437         String ret = field(x, session.prefGet(x));
438         if (ret != null) {
439             session.prefPut(x, ret);
440         }
441         if ((ret == null) || (ret.length() <= 0)) {
442             ret = def;
443         }
444         return ret;
445     }
446 
447     /**
448      * Get the target keyword and value for an 'a href' HTML tag
449      *
450      * @param target
451      *            the target name to use
452      * @return the 'target=...' string - may be blank if the user has requested
453      *         no popups
454      */
atarget(String t)455     public String atarget(String t) {
456         if (prefBool(SurveyMain.PREF_NOPOPUPS)) {
457             return "";
458         } else {
459             return "target='" + t + "' ";
460         }
461     }
462 
463     /**
464      * Get the target keyword and value for an 'a href' HTML tag on
465      * TARGET_ZOOMED
466      *
467      * @return the 'target=...' string - may be blank if the user has requested
468      *         no popups
469      * @see #TARGET_ZOOMED
470      */
atarget()471     public String atarget() {
472         return atarget(TARGET_ZOOMED);
473     }
474 
475     /**
476      * Add a parameter to the output URL
477      *
478      * @param k
479      *            key
480      * @param v
481      *            value
482      */
addQuery(String k, String v)483     public void addQuery(String k, String v) {
484         outQueryMap.put(k, v);
485         if (outQuery == null) {
486             outQuery = k + "=" + v;
487         } else {
488             outQuery = outQuery + "&amp;" + k + "=" + v;
489         }
490     }
491 
492     /**
493      * Copy from request queries to output query
494      */
addAllParametersAsQuery()495     public void addAllParametersAsQuery() {
496         for (Entry<String, String[]> e : request.getParameterMap().entrySet()) {
497             addQuery(e.getKey(), e.getValue()[0]);
498         }
499     }
500 
501     /**
502      * Add a boolean parameter to the output URL as 't' or 'f'
503      *
504      * @param k
505      *            key
506      * @param v
507      *            value
508      */
addQuery(String k, boolean v)509     void addQuery(String k, boolean v) {
510         addQuery(k, v ? "t" : "f");
511     }
512 
513     /**
514      * Set a parameter on the output URL, replacing an existing value if any
515      *
516      * @param k
517      *            key
518      * @param v
519      *            value
520      */
setQuery(String k, String v)521     public void setQuery(String k, String v) {
522         if (outQueryMap.get(k) == null) { // if it wasn't there..
523             addQuery(k, v); // then do a simple append
524         } else {
525             // rebuild query string:
526             outQuery = null;
527             TreeMap<String, String> oldMap = outQueryMap;
528             oldMap.put(k, v); // replace
529             outQueryMap = new TreeMap<>();
530             for (Iterator<String> i = oldMap.keySet().iterator(); i.hasNext();) {
531                 String somek = i.next();
532                 addQuery(somek, oldMap.get(somek));
533             }
534         }
535     }
536 
537     /**
538      * Set a query from an integer
539      *
540      * @param k
541      * @param v
542      */
setQuery(String k, int v)543     public void setQuery(String k, int v) {
544         setQuery(k, new Integer(v).toString());
545     }
546 
547     /**
548      * Remove the specified key from the query. Has no effect if the field
549      * doesn't exist.
550      *
551      * @param k
552      *            key
553      */
removeQuery(String k)554     public void removeQuery(String k) {
555         if (outQueryMap.get(k) != null) { // if it was there..
556             // rebuild query string:
557             outQuery = null;
558             TreeMap<String, String> oldMap = outQueryMap;
559             oldMap.remove(k); // replace
560             outQueryMap = new TreeMap<>();
561             for (Iterator<String> i = oldMap.keySet().iterator(); i.hasNext();) {
562                 String somek = i.next();
563                 addQuery(somek, oldMap.get(somek));
564             }
565         }
566     }
567 
568     /**
569      * Return the output URL
570      *
571      * @return the output URL
572      */
url()573     public String url() {
574         if (outQuery == null) {
575             return base();
576         } else {
577             return base() + "?" + outQuery;
578         }
579     }
580 
581     /**
582      * Return the raw query string, or null
583      * @return
584      */
query()585     public String query() {
586         return outQuery;
587     }
588 
589     /**
590      * Returns the string that must be appended to the URL to start the next
591      * parameter - either ? or &amp;
592      *
593      * @return the connecting string
594      */
urlConnector()595     public final String urlConnector() {
596         return (url().indexOf('?') != -1) ? "&amp;" : "?";
597     }
598 
599     /**
600      * Get the base URL (servlet path)
601      *
602      * @return the servlet path in context
603      */
base()604     public String base() {
605         if (theServletPath == null) {
606             return context() + request.getServletPath();
607         } else {
608             return context() + theServletPath;
609         }
610     }
611 
vurl(CLDRLocale loc)612     public String vurl(CLDRLocale loc) {
613         return vurl(loc, null, null, null);
614     }
615 
616     /**
617      * Get the new '/v' viewing URL. Note that this will include a fragment, do NOT append to the result (pass in something in queryAppend)
618      * @param loc locale to view.
619      * @param page pageID to view. Example:  PageId.Africa (shouldn't be null- yet)
620      * @param strid strid to view. Example: "12345678" or null
621      * @param queryAppend  this will be appended as the query. Example: "?email=foo@bar"
622      * @return
623      */
vurl(CLDRLocale loc, PageId page, String strid, String queryAppend)624     public String vurl(CLDRLocale loc, PageId page, String strid, String queryAppend) {
625         StringBuilder sb = new StringBuilder(request.getContextPath());
626         return WebContext.appendContextVurl(sb, loc, page, strid, queryAppend).toString();
627     }
628 
appendContextVurl(StringBuilder sb, CLDRLocale loc, PageId page, String strid, String queryAppend)629     public static StringBuilder appendContextVurl(StringBuilder sb, CLDRLocale loc, PageId page, String strid, String queryAppend) {
630 
631         sb.append("/v");
632         if (queryAppend != null && !queryAppend.isEmpty()) {
633             sb.append(queryAppend);
634         }
635         sb.append('#'); // hash
636 
637         // locale
638         sb.append('/');
639         sb.append(loc.getBaseName());
640 
641         // page
642         sb.append('/');
643         if (page != null) {
644             sb.append(page.name());
645         }
646         if (strid != null && !strid.isEmpty()) {
647             sb.append('/');
648             sb.append(strid);
649         }
650 
651         return sb;
652     }
653 
redirectToVurl(String vurl)654     public void redirectToVurl(String vurl) {
655         println("<a class='vredirect' href='" + vurl + "'>Redirecting to " + vurl + "</a>");
656         println("<script>window.location=' " + vurl + "/'+window.location.hash.substring(1);</script>");
657     }
658 
setServletPath(String path)659     public void setServletPath(String path) {
660         theServletPath = path;
661     }
662 
663     private String theServletPath = null;
664 
665     /**
666      * Get the base URL for some request
667      *
668      * @param request
669      * @return base URL
670      */
base(HttpServletRequest request)671     public static String base(HttpServletRequest request) {
672         return contextBase(request) + request.getServletPath();
673     }
674 
675     /**
676      * The base not including /servlet
677      * @param request
678      * @return
679      */
contextBase(HttpServletRequest request)680     public static String contextBase(HttpServletRequest request) {
681         return schemeHostPort(request) + request.getContextPath();
682     }
683 
684     /**
685      * Get the context path
686      *
687      * @return the context path
688      */
context()689     public String context() {
690         return request.getContextPath();
691     }
692 
693     /**
694      * Get the context path for a certain resource
695      *
696      * @param s
697      *            resource URL
698      * @return the context path for the specified resource
699      */
context(String s)700     public String context(String s) {
701         if (request == null)
702             return s;
703         return context(request, s);
704     }
705 
706     /**
707      * Get the context path for a certain resource
708      *
709      * @param s
710      *            resource URL
711      * @return the context path for the specified resource
712      */
context(HttpServletRequest request, String s)713     public static String context(HttpServletRequest request, String s) {
714         if (request == null)
715             return "/" + s;
716         return request.getContextPath() + "/" + s;
717     }
718 
719     /**
720      * Get a link (HTML URL) to a JSP
721      *
722      * @param s
723      *            resource to link to
724      * @return the URL suitable for HTML
725      */
jspLink(String s)726     public String jspLink(String s) {
727         return context(s) + "?a=" + base()
728             + ((outQuery != null) ? ("&amp;" + outQuery) : ((session != null) ? ("&amp;s=" + session.id) : ""));
729     }
730 
731     /**
732      * Get a link (Text URL) to a JSP
733      *
734      * @param s
735      *            resource to link to
736      * @return the URL suitable for Text
737      */
jspUrl(String s)738     public String jspUrl(String s) {
739         return context(s) + "?a=" + base()
740             + ((outQuery != null) ? ("&" + outQuery) : ((session != null) ? ("&s=" + session.id) : ""));
741     }
742 
743     /**
744      * Output the full current output URL in hidden field format.
745      */
printUrlAsHiddenFields()746     void printUrlAsHiddenFields() {
747         for (Iterator<String> e = outQueryMap.keySet().iterator(); e.hasNext();) {
748             String k = e.next().toString();
749             String v = outQueryMap.get(k).toString();
750             print("<input type='hidden' name='" + k + "' value='" + v + "'/>");
751         }
752     }
753 
754     /**
755      * return the IP of the remote user. If they are behind a proxy, return the
756      * actual original URL.
757      *
758      * @return a URL
759      */
userIP()760     String userIP() {
761         return userIP(request);
762     }
763 
764     /**
765      * return the IP of the remote user given a request. If they are behind a
766      * proxy, return the actual original URL.
767      *
768      * @param request
769      *            the request to use
770      * @return a URL
771      */
userIP(HttpServletRequest request)772     public static String userIP(HttpServletRequest request) {
773         String ip = request.getHeader("x-forwarded-for");
774         if (ip == null || ip.length() == 0) {
775             ip = request.getRemoteAddr();
776         }
777         return ip;
778     }
779 
780     /**
781      * return the hostname of the web server
782      *
783      * @return the Server Name
784      */
serverName()785     String serverName() {
786         return request.getServerName();
787     }
788 
789     /**
790      * return the hostname of the web server given a request
791      *
792      * @return the Server name
793      */
serverName(HttpServletRequest request)794     static String serverName(HttpServletRequest request) {
795         return request.getServerName();
796     }
797 
798     /**
799      * Returns the host:port of the server
800      *
801      * @return the "host:port:
802      */
serverHostport()803     String serverHostport() {
804         return serverHostport(request);
805     }
806 
807     /**
808      * Returns the host:port of the server
809      *
810      * @param request
811      *            a specific request
812      * @return the "host:port:
813      */
serverHostport(HttpServletRequest request)814     static String serverHostport(HttpServletRequest request) {
815         int port = request.getServerPort();
816         String scheme = request.getScheme();
817         if (port == 80 && "http".equals(scheme)) {
818             return serverName(request);
819         } else if (port == 443 && "https".equals(scheme)) {
820             return serverName(request);
821         } else {
822             return serverName(request) + ":" + port;
823         }
824     }
825 
826     /**
827      * Returns the scheme://host:port
828      *
829      * @return the "scheme://host:port"
830      */
schemeHostPort()831     String schemeHostPort() {
832         return schemeHostPort(request);
833     }
834 
835     /**
836      * Returns the scheme://host:port
837      *
838      * @return the "scheme://host:port"
839      * @param request
840      *            the request portion
841      */
schemeHostPort(HttpServletRequest request)842     static String schemeHostPort(HttpServletRequest request) {
843         return request.getScheme() + "://" + serverHostport(request);
844     }
845 
846     /**
847      * Print out a line
848      *
849      * @param s
850      *            line to print
851      * @see PrintWriter#println(String)
852      */
println(String s)853     public final void println(String s) {
854         pw.println(s);
855     }
856 
857     /**
858      * @param s
859      * @see PrintWriter#print(String)
860      */
print(String s)861     public final void print(String s) {
862         pw.print(s);
863     }
864 
865     /**
866      * Print out a Throwable as HTML.
867      *
868      * @param t
869      *            throwable to print
870      */
print(Throwable t)871     void print(Throwable t) {
872         print("<pre style='border: 2px dashed red; margin: 1em; padding: 1'>" + t.toString() + "<br />");
873         StringWriter asString = new StringWriter();
874         if (t instanceof SQLException) {
875             println("SQL: " + DBUtils.unchainSqlException((SQLException) t));
876         } else {
877             t.printStackTrace(new PrintWriter(asString));
878         }
879         print(asString.toString());
880         print("</pre>");
881     }
882 
883     /**
884      * Send the user to another URL. Won't work if there was already some
885      * output.
886      *
887      * @param where
888      * @see HttpServletResponse#sendRedirect(String)
889      */
redirect(String where)890     void redirect(String where) {
891         try {
892             response.sendRedirect(where);
893             out.close();
894             close();
895         } catch (IOException ioe) {
896             throw new RuntimeException(ioe.toString() + " while redirecting to " + where);
897         }
898     }
899 
900     /**
901      * Close the stream. Normally not called directly, except in outermost
902      * processor.
903      *
904      * @throws IOException
905      */
close()906     void close() throws IOException {
907         if (!dontCloseMe) {
908             out.close();
909             out = null;
910         } else {
911             closeUserFile();
912         }
913     }
914 
915     // doc api
916 
917     /**
918      * the current processor
919      *
920      * @see DisplayAndInputProcessor
921      */
922     public DisplayAndInputProcessor processor = null;
923 
924     /**
925      * Set this context to be handling a certain locale
926      *
927      * @param l
928      *            locale to set
929      */
setLocale(CLDRLocale l)930     public void setLocale(CLDRLocale l) {
931         if (!SurveyMain.getLocalesSet().contains(l)) { // bogus
932             locale = null;
933             return;
934         }
935         locale = l;
936         // localeString = locale.getBaseName();
937         processor = new DisplayAndInputProcessor(l, false);
938         Vector<CLDRLocale> localesVector = new Vector<>();
939         for (CLDRLocale parents : locale.getParentIterator()) {
940             localesVector.add(parents);
941         }
942         docLocale = localesVector.toArray(docLocale);
943         // logger.info("NOT NOT NOT fetching locale: " + l.toString() +
944         // ", count: " + doc.length);
945     }
946 
947     /**
948      * Cached direction of this locale.
949      */
950     private HTMLDirection direction = null;
951 
952     /**
953      * Return the HTML direction of this locale, ltr or rtl. Returns ltr by
954      * default. TODO: should return display locale's directionality by default.
955      *
956      * @return directionality
957      */
getDirectionForLocale()958     public HTMLDirection getDirectionForLocale() {
959         if ((locale != null) && (direction == null)) {
960             direction = sm.getHTMLDirectionFor(getLocale());
961         }
962         if (direction == null) {
963             return HTMLDirection.LEFT_TO_RIGHT;
964         } else {
965             return direction;
966         }
967     }
968 
969     /**
970      * Return the current locale as a string. Deprecated, please use getLocale
971      * instead.
972      *
973      * @deprecated use getLocale().toString() -
974      * @see #getLocale()
975      */
976     @Deprecated
localeString()977     public final String localeString() {
978         if (locale == null) {
979             throw new InternalError("localeString is null, locale=" + locale);
980         }
981         return locale.toString();
982     }
983 
984     /**
985      * Print the coverage level for a certain locale.
986      */
showCoverageLevel()987     public void showCoverageLevel() {
988         String itsLevel = getEffectiveCoverageLevel();
989         String recLevel = getRecommendedCoverageLevel();
990         String def = getRequiredCoverageLevel();
991         if (def.equals(COVLEV_RECOMMENDED)) {
992             print("Coverage Level: <tt class='codebox'>" + itsLevel.toString() + "</tt><br>");
993         } else {
994             print("Coverage Level: <tt class='codebox'>" + def + "</tt>  (overriding <tt>" + itsLevel.toString() + "</tt>)<br>");
995         }
996         print("Recommended level: <tt class='codebox'>" + recLevel.toString() + "</tt><br>");
997         print("<ul><li>To change your default coverage level, see ");
998         SurveyMain.printMenu(this, "", "options", "My Options", SurveyMain.QUERY_DO);
999         println("</li></ul>");
1000     }
1001 
getEffectiveCoverageLevel(CLDRLocale locale)1002     public String getEffectiveCoverageLevel(CLDRLocale locale) {
1003         return getEffectiveCoverageLevel(locale.getBaseName());
1004     }
1005 
getEffectiveCoverageLevel(String locale)1006     public String getEffectiveCoverageLevel(String locale) {
1007         String level = getRequiredCoverageLevel();
1008         if ((level == null) || (level.equals(COVLEV_RECOMMENDED)) || (level.equals("default"))) {
1009             // fetch from org
1010             level = session.getOrgCoverageLevel(locale);
1011         }
1012         return level;
1013     }
1014 
getRecommendedCoverageLevel()1015     public String getRecommendedCoverageLevel() {
1016         String myOrg = session.getUserOrg();
1017         if ((myOrg == null) || !isCoverageOrganization(myOrg)) {
1018             return COVLEV_DEFAULT_RECOMMENDED_STRING;
1019         } else {
1020             CLDRLocale loc = getLocale();
1021             if (loc != null) {
1022                 Level lev = StandardCodes.make().getLocaleCoverageLevel(myOrg, loc.toString());
1023                 if (lev == Level.UNDETERMINED) {
1024                     lev = COVLEVEL_DEFAULT_RECOMMENDED;
1025                 }
1026                 return lev.toString();
1027             } else {
1028                 return COVLEVEL_DEFAULT_RECOMMENDED.toString();
1029             }
1030         }
1031     }
1032 
showCoverageSetting()1033     public String showCoverageSetting() {
1034         String rv = sm.showListSetting(this, SurveyMain.PREF_COVLEV, "Coverage", WebContext.PREF_COVLEV_LIST);
1035         return rv;
1036     }
1037 
showCoverageSettingForLocale()1038     public String showCoverageSettingForLocale() {
1039         String rv = sm.showListSetting(this, SurveyMain.PREF_COVLEV, "Coverage", WebContext.PREF_COVLEV_LIST,
1040             getRecommendedCoverageLevel());
1041         return rv;
1042     }
1043 
getCoverageSetting()1044     public String getCoverageSetting() {
1045         return sm.getListSetting(this, SurveyMain.PREF_COVLEV, WebContext.PREF_COVLEV_LIST, false);
1046     }
1047 
1048     public static final String COVLEV_RECOMMENDED = "default";
1049     public static final String PREF_COVLEV_LIST[] = { COVLEV_RECOMMENDED,
1050         Level.COMPREHENSIVE.toString(), Level.MODERN.toString(), Level.MODERATE.toString(), Level.BASIC.toString() };
1051 
1052     /**
1053      * The default level, if no organization is available.
1054      */
1055     public static final Level COVLEVEL_DEFAULT_RECOMMENDED = org.unicode.cldr.util.Level.MODERN;
1056     public static final String COVLEV_DEFAULT_RECOMMENDED_STRING = COVLEVEL_DEFAULT_RECOMMENDED.name().toLowerCase();
1057 
1058     /**
1059      * Is it an organization that participates in coverage?
1060      *
1061      * @param org the name of the organization
1062      * @return true if the organization participates in coverage, else false
1063      */
isCoverageOrganization(String org)1064     public static boolean isCoverageOrganization(String org) {
1065         return (org != null && StandardCodes.make().getLocaleCoverageOrganizationStrings().contains(org.toLowerCase()));
1066     }
1067 
1068     /**
1069      * Get a list of all locale types
1070      *
1071      * @return a list of locale types
1072      * @see StandardCodes#getLocaleCoverageOrganizations()
1073      */
getLocaleCoverageOrganizations()1074     static String[] getLocaleCoverageOrganizations() {
1075         Set<Organization> set = StandardCodes.make().getLocaleCoverageOrganizations();
1076         Organization[] orgArray = set.toArray(new Organization[0]);
1077         String[] stringArray = new String[orgArray.length];
1078         int i = 0;
1079         for (Organization org : orgArray) {
1080             stringArray[i++] = org.toString();
1081         }
1082         return stringArray;
1083         // There was ArrayStoreException here:
1084         // return StandardCodes.make().getLocaleCoverageOrganizations().toArray(new String[0]);
1085     }
1086 
1087     /**
1088      * Append the WebContext Options map to the specified map
1089      *
1090      * @return the map
1091      */
getOptionsMap()1092     public CheckCLDR.Options getOptionsMap() {
1093         String def = getRequiredCoverageLevel();
1094         String org = getEffectiveCoverageLevel();
1095 
1096         return new CheckCLDR.Options(getLocale(), SurveyMain.getTestPhase(), def, org);
1097     }
1098 
1099     /**
1100      * @return
1101      */
getEffectiveCoverageLevel()1102     public String getEffectiveCoverageLevel() {
1103         String org = getEffectiveCoverageLevel(getLocale().toString());
1104         return org;
1105     }
1106 
1107     /**
1108      * @return
1109      */
getRequiredCoverageLevel()1110     public String getRequiredCoverageLevel() {
1111         String def = sm.getListSetting(this, SurveyMain.PREF_COVLEV, WebContext.PREF_COVLEV_LIST, false);
1112         return def;
1113     }
1114 
1115     public enum LoadingShow {
1116         dontShowLoading, showLoading
1117     }
1118 
1119     private static final boolean CACHE_DATA_SECTION = false; // TESTING, not ready for use
1120 
1121     private static final Map<String, DataSection> dataSectionCache = CACHE_DATA_SECTION ? new ConcurrentHashMap<>() : null;
1122 
1123     /**
1124      * Get a DataSection
1125      *
1126      * @param prefix a string such as "//ldml"; or null
1127      * @param matcher the XPathMatcher (which is ... ?); or null
1128      * @param pageId the PageId, with a name such as "Generic" and a SectionId with a name such as "DateTime"; or null
1129      * @return the DataSection
1130      *
1131      * Called only by SurveyAjax.getRow, twice:
1132      *    ctx.getDataSection(null [prefix], null [matcher], pageId);
1133      *    ctx.getDataSection(baseXp [prefix], matcher, null [pageId]);
1134      *
1135      * TODO: as part of DataSection performance improvement, consider moving this code to a different
1136      * module, maybe DataSection itself, especially if we can make DataSection not be user-specific.
1137      * WebContext is user-specific, and even request-specific.
1138      *
1139      * Renamed 2019-05-15 from getSection (5 args) to getDataSection
1140      *
1141      * Reference: https://unicode-org.atlassian.net/browse/CLDR-12020
1142      */
getDataSection(String prefix, XPathMatcher matcher, PageId pageId)1143     public DataSection getDataSection(String prefix, XPathMatcher matcher, PageId pageId) {
1144 
1145         DataSection section = null;
1146         synchronized (this) {
1147             String cacheKey = null;
1148             if (CACHE_DATA_SECTION) {
1149                 cacheKey = locale.toString(); // not enough!
1150                 section = dataSectionCache.get(cacheKey);
1151             }
1152             if (section == null) {
1153                 CLDRProgressTask progress = sm.openProgress("Loading");
1154                 try {
1155                     progress.update("<span title='" + sm.xpt.getPrettyPath(prefix) + "'>" + locale + "</span>");
1156                     flush();
1157                     synchronized (session) {
1158                         section = DataSection.make(pageId, this /* ctx */, this.session, locale, prefix, matcher);
1159                     }
1160                 } finally {
1161                     progress.close(); // TODO: this can trigger "State Error: Closing an already-closed CLDRProgressIndicator"
1162                 }
1163                 if (section == null) {
1164                     throw new InternalError("No section.");
1165                 }
1166                 if (CACHE_DATA_SECTION) {
1167                     dataSectionCache.put(cacheKey, section);
1168                 }
1169             }
1170         }
1171         return section;
1172     }
1173 
1174     // Internal Utils
1175 
1176     static final HelpMessages surveyToolHelpMessages = new HelpMessages("test_help_messages.html");
1177     public static final String CAN_MODIFY = "canModify";
1178     public static final String DATA_SECTION = "DataSection";
1179     public static final String ZOOMED_IN = "zoomedIn";
1180     public static final String DATA_ROW = "DataRow";
1181     public static final String BASE_EXAMPLE = "baseExample";
1182     public static final String BASE_VALUE = "baseValue";
1183 
1184     /**
1185      * Print a link to help with a specified title
1186      *
1187      * @param what
1188      *            the help to link to
1189      * @param title
1190      *            the title of the help
1191      */
printHelpLink(String what, String title)1192     public void printHelpLink(String what, String title) {
1193         printHelpLink(what, title, true);
1194     }
1195 
1196     /**
1197      * @param what
1198      * @param title
1199      * @param doEdit
1200      * @deprecated editing is deprecated
1201      */
1202     @Deprecated
printHelpLink(String what, String title, boolean doEdit)1203     public void printHelpLink(String what, String title, boolean doEdit) {
1204         printHelpLink(what, title, doEdit, true);
1205     }
1206 
1207     /**
1208      * @deprecated editing is deprecated
1209      * @param what
1210      * @param title
1211      * @param doEdit
1212      * @param parens
1213      */
1214     @Deprecated
printHelpLink(String what, String title, boolean doEdit, boolean parens)1215     private void printHelpLink(String what, String title, boolean doEdit, boolean parens) {
1216         if (parens) {
1217             print("(");
1218         }
1219         print("<a href=\"" + (SurveyMain.CLDR_HELP_LINK) + what + "\">" + title + "</a>");
1220         if (parens) {
1221             println(")");
1222         }
1223     }
1224 
1225     /**
1226      * Get HTML for the 'modify' thing, with the hand icon
1227      *
1228      * @param message
1229      * @see #iconHtml(String, String)
1230      * @return HTML for the message
1231      */
modifyThing(String message)1232     public String modifyThing(String message) {
1233         return iconHtml("hand", message);
1234     }
1235 
1236     /**
1237      * get HTML for an icon with a certain message
1238      *
1239      * @param icon
1240      * @param message
1241      * @return the HTML for the icon and message
1242      */
iconHtml(String icon, String message)1243     public String iconHtml(String icon, String message) {
1244         return iconHtml(request, icon, message);
1245     }
1246 
iconHtml(HttpServletRequest request, String icon, String message)1247     public static String iconHtml(HttpServletRequest request, String icon, String message) {
1248         if (message == null) {
1249             message = "[" + icon + "]";
1250         }
1251         return "<img alt='[" + icon + "]' style='width: 16px; height: 16px; border: 0;' src='" + context(request, icon + ".png")
1252             + "' title='" + message + "' />";
1253     }
1254 
1255     /**
1256      * Clone (copy construct) the context
1257      *
1258      * @see #WebContext(WebContext)
1259      */
1260     @Override
clone()1261     public Object clone() {
1262         Object o;
1263         try {
1264             o = super.clone();
1265         } catch (CloneNotSupportedException e) {
1266             throw new InternalError();
1267         }
1268         WebContext n = (WebContext) o;
1269         n.init(this);
1270 
1271         return o;
1272     }
1273 
init(WebContext other)1274     private void init(WebContext other) {
1275         doc = other.doc;
1276         docLocale = other.docLocale;
1277         displayLocale = other.displayLocale;
1278         out = other.out;
1279         pw = other.pw;
1280         outQuery = other.outQuery;
1281         locale = other.locale;
1282         session = other.session;
1283         outQueryMap = (TreeMap<String, String>) other.outQueryMap.clone();
1284         dontCloseMe = true;
1285         request = other.request;
1286         response = other.response;
1287         sm = other.sm;
1288         processor = other.processor;
1289         temporaryStuff = other.temporaryStuff;
1290         theServletPath = other.theServletPath;
1291     }
1292 
1293     /**
1294      * Include a template fragment from /WEB-INF/tmpl
1295      *
1296      * @param filename
1297      */
includeFragment(String filename)1298     public void includeFragment(String filename) {
1299         try {
1300             WebContext.includeFragment(request, response, filename);
1301         } catch (Throwable t) {
1302             SurveyLog.logException(t, "Including template " + filename);
1303             this.println("<div class='ferrorbox'><B>Error</b> while including template <tt class='code'>" + filename
1304                 + "</tt>:<br>");
1305             this.print(t);
1306             this.println("</div>");
1307             System.err.println("While expanding " + TMPL_PATH + filename + ": " + t.toString());
1308             t.printStackTrace();
1309         }
1310     }
1311 
1312     /**
1313      * Include a template fragment from /WEB-INF/tmpl
1314      *
1315      * @param request
1316      * @param response
1317      * @param filename
1318      * @throws ServletException
1319      * @throws IOException
1320      *             , NullPointerException
1321      */
includeFragment(HttpServletRequest request, HttpServletResponse response, String filename)1322     public static void includeFragment(HttpServletRequest request, HttpServletResponse response, String filename)
1323         throws ServletException, IOException {
1324         RequestDispatcher dp = request.getRequestDispatcher(TMPL_PATH + filename);
1325         dp.include(request, response);
1326     }
1327 
1328     /**
1329      * Put something into the temporary (context, non session data) store
1330      *
1331      * @param string
1332      * @param object
1333      */
put(String string, Object object)1334     public void put(String string, Object object) {
1335         if (object == null) {
1336             temporaryStuff.remove(string);
1337         } else {
1338             temporaryStuff.put(string, object);
1339         }
1340     }
1341 
1342     /**
1343      * Get something from the temporary (context, non session data) store
1344      *
1345      * @param string
1346      * @return the object
1347      */
get(String string)1348     public Object get(String string) {
1349         return temporaryStuff.get(string);
1350     }
1351 
getString(String k)1352     public String getString(String k) {
1353         return (String) get(k);
1354     }
1355 
1356     /**
1357      * @return the CLDRLocale with which this WebContext currently pertains.
1358      * @see CLDRLocale
1359      */
getLocale()1360     public CLDRLocale getLocale() {
1361         return locale;
1362     }
1363 
1364     // Display Context Data
1365     protected Boolean canModify = null;
1366 
1367     /**
1368      * A direction, suitable for html 'dir=...'
1369      *
1370      * @author srl
1371      * @see getDirectionForLocale
1372      */
1373     public enum HTMLDirection {
1374         LEFT_TO_RIGHT("ltr"), RIGHT_TO_LEFT("rtl");
1375 
1376         private String str;
1377 
HTMLDirection(String str)1378         HTMLDirection(String str) {
1379             this.str = str;
1380         }
1381 
1382         @Override
toString()1383         public String toString() {
1384             return str;
1385         }
1386 
1387         /**
1388          * Convert a CLDR direction to an enum
1389          *
1390          * @param dir
1391          *            CLDR direction string
1392          * @return HTML direction enum
1393          */
fromCldr(String dir)1394         public static HTMLDirection fromCldr(String dir) {
1395             if (dir.equals("left-to-right")) {
1396                 return HTMLDirection.LEFT_TO_RIGHT;
1397             } else if (dir.equals("right-to-left")) {
1398                 return HTMLDirection.RIGHT_TO_LEFT;
1399             } else if (dir.equals("top-to-bottom")) {
1400                 return HTMLDirection.LEFT_TO_RIGHT; // !
1401             } else {
1402                 return HTMLDirection.LEFT_TO_RIGHT;
1403             }
1404         }
1405     }
1406 
1407     /**
1408      * Return true if the user can modify this locale
1409      *
1410      * @return true if the user can modify this locale
1411      */
canModify()1412     public Boolean canModify() {
1413         if (STFactory.isReadOnlyLocale(locale) || SurveyMain.phase() == Phase.READONLY)
1414             return (canModify = false);
1415         if (canModify == null) {
1416             if (session != null && session.user != null) {
1417                 canModify = UserRegistry.userCanModifyLocale(session.user, locale);
1418             } else {
1419                 canModify = false;
1420             }
1421         }
1422         return canModify;
1423     }
1424 
includeAjaxScript(AjaxType type)1425     public void includeAjaxScript(AjaxType type) {
1426         try {
1427             SurveyAjax.includeAjaxScript(request, response, type);
1428         } catch (Throwable t) {
1429             this.println("<div class='ferrorbox'><B>Error</b> while expanding ajax template ajax_" + type.name().toLowerCase()
1430                 + ".jsp:<br>");
1431             this.print(t);
1432             this.println("</div>");
1433             System.err.println("While expanding ajax: " + t.toString());
1434             t.printStackTrace();
1435         }
1436     }
1437 
getUserFile()1438     public SurveyMain.UserLocaleStuff getUserFile() {
1439         SurveyMain.UserLocaleStuff uf = peekUserFile();
1440         if (uf == null) {
1441             uf = sm.getUserFile(session, getLocale());
1442             put("UserFile", uf);
1443         }
1444         return uf;
1445     }
1446 
peekUserFile()1447     private UserLocaleStuff peekUserFile() {
1448         return (UserLocaleStuff) get("UserFile");
1449     }
1450 
closeUserFile()1451     public void closeUserFile() {
1452         UserLocaleStuff uf = (UserLocaleStuff) temporaryStuff.remove("UserFile");
1453         if (uf != null) {
1454             uf.close();
1455         }
1456     }
1457 
getCLDRFile()1458     public CLDRFile getCLDRFile() {
1459         return getUserFile().cldrfile;
1460     }
1461 
1462     /**
1463      * Get the user settings.
1464      *
1465      * @return
1466      */
settings()1467     UserSettings settings() {
1468         return session.settings();
1469     }
1470 
no_js_warning()1471     public void no_js_warning() {
1472         boolean no_js = prefBool(SurveyMain.PREF_NOJAVASCRIPT);
1473         if (!no_js) {
1474             WebContext nuCtx = (WebContext) clone();
1475             nuCtx.setQuery(SurveyMain.PREF_NOJAVASCRIPT, "t");
1476             println("<noscript><h1>");
1477             println(iconHtml("warn", "JavaScript disabled") + "JavaScript is disabled. Please enable JavaScript..");
1478             println("</h1></noscript>");
1479         }
1480     }
1481 
1482     /**
1483      * Return the ID of this user, or -1 (UserRegistry.NO_USER)
1484      *
1485      * @return user's id, or -1 (UserRegistry.NO_USER) if not found/not set
1486      */
userId()1487     public int userId() {
1488         if (session != null && session.user != null) {
1489             return session.user.id;
1490         } else {
1491             return UserRegistry.NO_USER;
1492         }
1493     }
1494 
user()1495     private User user() {
1496         if (session != null && session.user != null) {
1497             return session.user;
1498         } else {
1499             return null;
1500         }
1501     }
1502 
1503     /**
1504      * Get a certain cookie
1505      *
1506      * @param id
1507      * @return
1508      */
getCookie(String id)1509     public Cookie getCookie(String id) {
1510         return getCookie(request, id);
1511     }
1512 
getCookie(HttpServletRequest request, String id)1513     public static Cookie getCookie(HttpServletRequest request, String id) {
1514         if (request == null) return null;
1515         Cookie cooks[] = request.getCookies();
1516         if (cooks == null)
1517             return null;
1518         for (Cookie c : cooks) {
1519             if (c.getName().equals(id)) {
1520                 return c;
1521             }
1522         }
1523         return null;
1524     }
1525 
1526     /**
1527      * Get a cookie value or null
1528      *
1529      * @param id
1530      * @return
1531      */
getCookieValue(String id)1532     public String getCookieValue(String id) {
1533         Cookie c = getCookie(id);
1534         if (c != null) {
1535             return c.getValue();
1536         }
1537         return null;
1538     }
1539 
1540     /**
1541      * Set a cookie
1542      *
1543      * @param id
1544      * @param value
1545      * @param expiry
1546      */
addCookie(String id, String value, int expiry)1547     Cookie addCookie(String id, String value, int expiry) {
1548         Cookie c = new Cookie(id, value);
1549         c.setMaxAge(expiry);
1550         response.addCookie(c);
1551         return c;
1552     }
1553 
1554     @Override
append(CharSequence csq)1555     public Appendable append(CharSequence csq) throws IOException {
1556         pw.append(csq);
1557         return this;
1558     }
1559 
1560     @Override
append(char c)1561     public Appendable append(char c) throws IOException {
1562         pw.append(c);
1563         return this;
1564     }
1565 
1566     @Override
append(CharSequence csq, int start, int end)1567     public Appendable append(CharSequence csq, int start, int end) throws IOException {
1568         pw.append(csq, start, end);
1569         return this;
1570     }
1571 
1572     @Override
toString()1573     public String toString() {
1574         return "{ " + this.getClass().getName() + ": url=" + url() + ", ip=" + this.userIP() + ", user=" + this.user() + "}";
1575     }
1576 
1577     /**
1578      * @return
1579      */
getAlign()1580     public String getAlign() {
1581         String ourAlign = "left";
1582         if (getDirectionForLocale().equals(HTMLDirection.RIGHT_TO_LEFT)) {
1583             ourAlign = "right";
1584         }
1585         return ourAlign;
1586     }
1587 
1588     /**
1589      * @return
1590      */
getCanModify()1591     public boolean getCanModify() {
1592         boolean canModify = (UserRegistry.userCanModifyLocale(session.user, getLocale()));
1593         return canModify;
1594     }
1595 
1596     /**
1597      * @param locale
1598      * @return
1599      */
urlForLocale(CLDRLocale locale)1600     String urlForLocale(CLDRLocale locale) {
1601         return vurl(locale, null, null, null);
1602     }
1603 
getLocaleDisplayName(String loc)1604     public String getLocaleDisplayName(String loc) {
1605         return getLocaleDisplayName(CLDRLocale.getInstance(loc));
1606     }
1607 
getLocaleDisplayName(CLDRLocale instance)1608     public String getLocaleDisplayName(CLDRLocale instance) {
1609         return instance.getDisplayName();
1610     }
1611 
1612     /**
1613      * @return
1614      */
hasAdminPassword()1615     public boolean hasAdminPassword() {
1616         return field("dump").equals(SurveyMain.vap);
1617     }
1618 
hasTestPassword()1619     public boolean hasTestPassword() {
1620         return field("dump").equals(SurveyMain.testpw) || hasAdminPassword();
1621     }
1622 
1623     private static UnicodeSet csvSpecial = new UnicodeSet("[, \"]").freeze();
1624 
csvWrite(Writer out, String str)1625     public static final void csvWrite(Writer out, String str) throws IOException {
1626         str = str.trim();
1627         if (csvSpecial.containsSome(str)) {
1628             out.write('"');
1629             out.write(str.replaceAll("\"", "\"\""));
1630             out.write('"');
1631         } else {
1632             out.write(str);
1633         }
1634     }
1635 
1636     private boolean checkedPage = false;
1637     private PageId pageId = null;
1638 
getPageId()1639     public PageId getPageId() {
1640         if (!checkedPage) {
1641             String pageField = field(SurveyMain.QUERY_SECTION);
1642             pageId = getPageId(pageField);
1643             checkedPage = true;
1644         }
1645         return pageId;
1646     }
1647 
1648     /**
1649      * set the session. Only call this once.
1650      */
setSession()1651     public String setSession() {
1652         if (request == null && session != null) {
1653             return "using canned session"; // already set - for testing
1654         }
1655 
1656         if (this.session != null) return "Internal error - session already set.";
1657 
1658         CookieSession.checkForExpiredSessions(); // If a session has expired, remove it
1659 
1660         String message = null; // return message, explaining what happened with the session.
1661 
1662         String myNum = field(SurveyMain.QUERY_SESSION); // s
1663         String password = field(SurveyMain.QUERY_PASSWORD);
1664         if (password.isEmpty()) {
1665             password = field(SurveyMain.QUERY_PASSWORD_ALT);
1666         }
1667         boolean letmein = SurveyMain.vap.equals(field("letmein")); // using CLDR_VAP (admin) password
1668         String email = field(SurveyMain.QUERY_EMAIL);
1669         if ("admin@".equals(email) && SurveyMain.vap.equals(password)) {
1670             letmein = true;
1671         }
1672 
1673         // if there was an email/password in the cookie, use that.
1674         {
1675             String myEmail = getCookieValue(SurveyMain.QUERY_EMAIL);
1676             String myPassword = getCookieValue(SurveyMain.QUERY_PASSWORD);
1677             if (myEmail != null && (email == null || email.isEmpty())) {
1678                 email = myEmail;
1679                 if (myPassword != null && (password == null || password.isEmpty())) {
1680                     password = myPassword;
1681                 }
1682             } else {
1683                 if (myEmail != null && !myEmail.equals(email)) {
1684                     removeLoginCookies(request, response);
1685                 }
1686             }
1687         }
1688 
1689         User user = null;
1690         // if an email/password given, try to fetch a user
1691         try {
1692             user = CookieSession.sm.reg.get(password, email, userIP(), letmein);
1693         } catch (LogoutException e) {
1694             logout(); // failed login, so logout this session.
1695         }
1696         if (user != null) {
1697             user.touch(); // mark this user as active.
1698         }
1699 
1700         HttpSession httpSession = request.getSession(true); // create httpsession
1701 
1702         //boolean idFromSession = false; // did the id come from the httpsession? (and why do we care?)
1703 
1704         if (myNum.equals(SurveyMain.SURVEYTOOL_COOKIE_NONE)) { // "0"- for testing
1705             httpSession.removeAttribute(SurveyMain.SURVEYTOOL_COOKIE_SESSION);
1706         }
1707 
1708         // we just logged in- see if there's already a user session for this user..
1709         if (user != null) {
1710             session = CookieSession.retrieveUser(user.email); // is this user already logged in?
1711             if (session != null) {
1712                 if (null == CookieSession.retrieve(session.id)) { // double check- is the session still valid?
1713                     session = null; // don't allow dead sessions to show up
1714                     // via the user list.
1715                 }
1716             }
1717         }
1718 
1719         // Retreive a number from the httpSession if present
1720         if ((httpSession != null) && (session == null)) {
1721             String aNum = (String) httpSession.getAttribute(SurveyMain.SURVEYTOOL_COOKIE_SESSION);
1722             if (aNum != null) {
1723                 session = CookieSession.retrieve(aNum);
1724                 if (CookieSession.DEBUG_INOUT) {
1725                     System.err.println("From httpsession " + httpSession.getId() + " = ST session " + aNum + " = " + session);
1726                 }
1727             }
1728         }
1729 
1730         if (session != null && session.user != null && user != null && user.id != session.user.id) {
1731             session = null; // user was already logged in as 'session.user', replacing this with 'user'
1732         }
1733 
1734         if ((user == null) &&
1735             (hasField(SurveyMain.QUERY_PASSWORD) || hasField(SurveyMain.QUERY_EMAIL))) {
1736             if (CookieSession.DEBUG_INOUT) {
1737                 System.out.println("Logging out - mySession=" + session + ",user=" + user + ", and had em/pw");
1738             }
1739             logout(); // zap cookies if some id/pw failed to work
1740         }
1741 
1742         if (session == null && user == null) {
1743             session = CookieSession.checkForAbuseFrom(userIP(), SurveyMain.BAD_IPS, request.getHeader("User-Agent"));
1744             if (session != null) {
1745                 println("<h1>Note: Your IP, " + userIP() + " has been throttled for making " + SurveyMain.BAD_IPS.get(userIP())
1746                     + " connections. Try turning on cookies, or obeying the 'META ROBOTS' tag.</h1>");
1747                 flush();
1748                 session = null;
1749                 return "Bad IP.";
1750             }
1751         }
1752 
1753         if (CookieSession.DEBUG_INOUT) System.out.println("Session Now=" + session + ", user=" + user);
1754 
1755         // guest?
1756         if (letmein || (user != null && UserRegistry.userIsTC(user))) {
1757             // allow in administrator or TC.
1758         } else if ((user != null) && (session == null)) { // user trying to log in-
1759             if (CookieSession.tooManyUsers()) {
1760                 System.err.println("Refused login for " + email + " from " + userIP() + " - too many users ( " + CookieSession.getUserCount() + ")");
1761                 return "We are swamped with about " + CookieSession.getUserCount()
1762                     + " people using the SurveyTool right now! Please try back in a little while.";
1763             }
1764         } else if (session == null || (session.user == null)) { // guest user
1765             if (CookieSession.tooManyGuests()) {
1766                 if (session != null) {
1767                     System.err.println("Logged-out guest  " + session.id + " from " + userIP() + " - too many users ( " + CookieSession.getUserCount() + ")");
1768                     session.remove(); // remove guests at this point
1769                     session = null;
1770                 }
1771                 logout(); // clear session cookie
1772                 return "We have too many people browsing the CLDR Data on the Survey Tool. Please try again later when the load has gone down.";
1773             }
1774         }
1775 
1776         // New up a session, if we don't already have one.
1777         if (session == null) {
1778             session = CookieSession.newSession(user == null, userIP(), httpSession.getId());
1779             if (!myNum.equals(SurveyMain.SURVEYTOOL_COOKIE_NONE)) {
1780                 // ctx.println("New session: " + mySession.id + "<br>");
1781             }
1782         }
1783 
1784         // should we add "&s=.#####" to the URL?
1785         if (httpSession.isNew()) { // If it's a new session..
1786             addQuery(SurveyMain.QUERY_SESSION + "__", session.id);
1787         } else {
1788             // ctx.println("['s' suppressed]");
1789         }
1790 
1791         // store the session id in the HttpSession
1792         httpSession.setAttribute(SurveyMain.SURVEYTOOL_COOKIE_SESSION, session.id);
1793         httpSession.setMaxInactiveInterval(-1); // never expire
1794 
1795         if (user != null) {
1796             session.setUser(user); // this will replace any existing session by this user.
1797             session.user.ip = userIP();
1798         } else {
1799             if ((email != null) && (email.length() > 0) && (session.user == null)) {
1800                 String encodedEmail;
1801                 try {
1802                     encodedEmail = URLEncoder.encode(email, "UTF-8");
1803                 } catch (UnsupportedEncodingException e) {
1804                     // The server doesn't support UTF-8?  (Should never happen)
1805                     throw new RuntimeException(e);
1806                 }
1807                 message = iconHtml("stop", "failed login") + "login failed. <a href='"
1808                     + request.getContextPath() + "/reset.jsp"
1809                     + "?email=" + encodedEmail
1810                     + "&s=" + session.id
1811                     + "' id='notselected'>recover password?</a><br>";
1812             }
1813         }
1814         // processs the 'remember me'
1815         if (session != null && session.user != null) {
1816             if (hasField(SurveyMain.QUERY_SAVE_COOKIE)) {
1817                 if (SurveyMain.isUnofficial()) {
1818                     System.out.println("Remembering user: " + session.user);
1819                 }
1820                 loginRemember(session.user);
1821             }
1822         }
1823         return message;
1824     }
1825 
1826     /**
1827      * Logout this ctx
1828      */
logout()1829     public void logout() {
1830         logout(request, response);
1831     }
1832 
1833     /**
1834      * Logout this req/response (zap cookie)
1835      * Zaps http session
1836      * @param request
1837      * @param response
1838      */
logout(HttpServletRequest request, HttpServletResponse response)1839     public static void logout(HttpServletRequest request, HttpServletResponse response) {
1840         HttpSession session = request.getSession(false); // dont create
1841         if (session != null) {
1842             String sessionId = (String) session.getAttribute(SurveyMain.SURVEYTOOL_COOKIE_SESSION);
1843             if (CookieSession.DEBUG_INOUT) {
1844                 System.err.println("logout() of session " + session.getId() + " and cookieSession " + sessionId);
1845             }
1846             if (sessionId != null) {
1847                 CookieSession sess = CookieSession.retrieveWithoutTouch(sessionId);
1848                 if (sess != null) {
1849                     sess.remove(); // forcibly remove session
1850                 }
1851             }
1852             session.removeAttribute(SurveyMain.SURVEYTOOL_COOKIE_SESSION);
1853         }
1854         removeLoginCookies(request, response);
1855     }
1856 
1857     /**
1858      * @param request
1859      * @param response
1860      */
removeLoginCookies(HttpServletRequest request, HttpServletResponse response)1861     public static void removeLoginCookies(HttpServletRequest request, HttpServletResponse response) {
1862         Cookie c0 = WebContext.getCookie(request, SurveyMain.QUERY_EMAIL);
1863         if (c0 != null) { // only zap extant cookies
1864             c0.setValue("");
1865             c0.setMaxAge(0);
1866             response.addCookie(c0);
1867         }
1868         Cookie c1 = WebContext.getCookie(request, SurveyMain.QUERY_PASSWORD);
1869         if (c1 != null) {
1870             c1.setValue("");
1871             c1.setMaxAge(0);
1872             response.addCookie(c1);
1873         }
1874     }
1875 
getPageId(String pageField)1876     public static PageId getPageId(String pageField) {
1877         if (pageField != null && !pageField.isEmpty()) {
1878             try {
1879                 return PathHeader.PageId.forString(pageField);
1880             } catch (Exception e) {
1881                 // ignore.
1882             }
1883         }
1884         return null;
1885     }
1886 
1887     /**
1888      * Remember this login (adds cookie to ctx.response )
1889      */
loginRemember(User user)1890     public void loginRemember(User user) {
1891         addCookie(SurveyMain.QUERY_EMAIL, user.email, SurveyMain.TWELVE_WEEKS);
1892         addCookie(SurveyMain.QUERY_PASSWORD, user.password, SurveyMain.TWELVE_WEEKS);
1893     }
1894 }
1895