| // Copyright (C) 2008 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.server; |
| |
| import com.google.gerrit.client.Gerrit; |
| import com.google.gerrit.client.SignInDialog; |
| import com.google.gerrit.client.SignInDialog.Mode; |
| import com.google.gerrit.client.account.SignInResult; |
| import com.google.gerrit.client.openid.DiscoveryResult; |
| import com.google.gerrit.client.openid.OpenIdUtil; |
| import com.google.gerrit.client.reviewdb.Account; |
| import com.google.gerrit.client.reviewdb.AccountExternalId; |
| import com.google.gerrit.client.reviewdb.AccountExternalIdAccess; |
| import com.google.gerrit.client.reviewdb.ReviewDb; |
| import com.google.gerrit.client.rpc.Common; |
| import com.google.gwt.user.server.rpc.RPCServletUtils; |
| import com.google.gwtjsonrpc.server.JsonServlet; |
| import com.google.gwtjsonrpc.server.ValidToken; |
| import com.google.gwtjsonrpc.server.XsrfException; |
| import com.google.gwtorm.client.OrmException; |
| import com.google.gwtorm.client.Transaction; |
| |
| import com.dyuproject.openid.Constants; |
| import com.dyuproject.openid.DefaultDiscovery; |
| import com.dyuproject.openid.DiffieHellmanAssociation; |
| import com.dyuproject.openid.OpenIdContext; |
| import com.dyuproject.openid.OpenIdUser; |
| import com.dyuproject.openid.RelyingParty; |
| import com.dyuproject.openid.SimpleHttpConnector; |
| import com.dyuproject.openid.UrlEncodedParameterMap; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.StringWriter; |
| import java.net.UnknownHostException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.List; |
| import java.util.zip.GZIPOutputStream; |
| |
| import javax.servlet.ServletConfig; |
| import javax.servlet.ServletException; |
| import javax.servlet.http.Cookie; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| /** Handles the <code>/login</code> URL for web based single-sign-on. */ |
| public class OpenIdLoginServlet extends HttpServlet { |
| private static final Logger log = |
| LoggerFactory.getLogger(OpenIdLoginServlet.class); |
| private static final int LASTID_AGE = 365 * 24 * 60 * 60; // seconds |
| private static final String AX_SCHEMA = "http://openid.net/srv/ax/1.0"; |
| private static final String OMODE_CANCEL = "cancel"; |
| private static final String GMODE_CHKCOOKIE = "gerrit.chkcookie"; |
| private static final String GMODE_SETCOOKIE = "gerrit.setcookie"; |
| |
| private RelyingParty relyingParty; |
| private GerritServer server; |
| private Document pleaseSetCookieDoc; |
| |
| @Override |
| public void init(final ServletConfig config) throws ServletException { |
| super.init(config); |
| |
| try { |
| server = GerritServer.getInstance(); |
| } catch (OrmException e) { |
| throw new ServletException("Cannot load GerritServer", e); |
| } catch (XsrfException e) { |
| throw new ServletException("Cannot load GerritServer", e); |
| } |
| |
| try { |
| final OpenIdContext context = new OpenIdContext(); |
| context.setAssociation(new DiffieHellmanAssociation()); |
| context.setDiscovery(new DefaultDiscovery()); |
| context.setHttpConnector(new SimpleHttpConnector()); |
| relyingParty = new RelyingParty(context, new GerritOpenIdUserManager()); |
| } catch (Throwable e) { |
| log.error("Cannot setup RelyingParty", e); |
| throw new ServletException("Cannot setup RelyingParty", e); |
| } |
| |
| final String scHtmlName = "com/google/gerrit/public/SetCookie.html"; |
| pleaseSetCookieDoc = HtmlDomUtil.parseFile(scHtmlName); |
| if (pleaseSetCookieDoc == null) { |
| log.error("No " + scHtmlName + " in CLASSPATH"); |
| throw new ServletException("No " + scHtmlName + " in CLASSPATH"); |
| } |
| } |
| |
| @Override |
| public void doGet(final HttpServletRequest req, final HttpServletResponse rsp) |
| throws IOException { |
| doPost(req, rsp); |
| } |
| |
| @Override |
| public void doPost(final HttpServletRequest req, final HttpServletResponse rsp) |
| throws IOException { |
| try { |
| doAuth(req, rsp); |
| } catch (Exception e) { |
| getServletContext().log("Unexpected error during authentication", e); |
| callback(req, rsp, SignInResult.CANCEL); |
| } |
| } |
| |
| private void doAuth(final HttpServletRequest req, |
| final HttpServletResponse rsp) throws Exception { |
| if (false) { |
| System.out.println(req.getMethod() + " /login"); |
| for (final Enumeration e = req.getParameterNames(); e.hasMoreElements();) { |
| final String n = (String) e.nextElement(); |
| for (final String v : req.getParameterValues(n)) { |
| System.out.println(" " + n + "=" + v); |
| } |
| } |
| System.out.println(); |
| } |
| |
| final String mode = req.getParameter(Constants.OPENID_MODE); |
| if (OMODE_CANCEL.equals(mode)) { |
| // Provider wants us to cancel the attempt. |
| // |
| callback(req, rsp, SignInResult.CANCEL); |
| return; |
| } else if (GMODE_CHKCOOKIE.equals(mode)) { |
| modeChkSetCookie(req, rsp, true); |
| return; |
| } else if (GMODE_SETCOOKIE.equals(mode)) { |
| modeChkSetCookie(req, rsp, false); |
| return; |
| } |
| |
| final OpenIdUser user; |
| try { |
| user = relyingParty.discover(req); |
| } catch (UnknownHostException u) { |
| // The remote host described in the OpenID doesn't exist, so we |
| // can't try to perform discovery against it. |
| // |
| callback(req, rsp, SignInResult.CANCEL); |
| return; |
| } |
| if (user == null) { |
| // User isn't known, no provider is known. |
| // |
| callback(req, rsp, SignInResult.CANCEL); |
| return; |
| } |
| |
| if (user.isAssociated() && RelyingParty.isAuthResponse(req)) { |
| if (!relyingParty.verifyAuth(user, req, rsp)) { |
| // Failed verification... re-authenticate. |
| // |
| callback(req, rsp, SignInResult.CANCEL); |
| return; |
| } |
| |
| // Authentication was successful. |
| // |
| String fullname = req.getParameter("openid.sreg.fullname"); |
| String email = req.getParameter("openid.sreg.email"); |
| for (int i = 1;; i++) { |
| final String nskey = "openid.ns.ext" + i; |
| final String nsval = req.getParameter(nskey); |
| if (nsval == null) { |
| break; |
| } |
| |
| final String ext = "openid.ext" + i + "."; |
| if (AX_SCHEMA.equals(nsval) |
| && "fetch_response".equals(req.getParameter(ext + "mode"))) { |
| final String ax_fname = req.getParameter(ext + "value.fname"); |
| final String ax_email = req.getParameter(ext + "value.email"); |
| if (fullname == null && ax_fname != null) { |
| fullname = ax_fname; |
| } |
| if (email == null && ax_email != null) { |
| email = ax_email; |
| } |
| break; |
| } |
| } |
| |
| initializeAccount(req, rsp, user, fullname, email); |
| return; |
| } |
| |
| if (!relyingParty.getOpenIdContext().getAssociation().associate(user, |
| relyingParty.getOpenIdContext())) { |
| // Failed association. Try again. |
| // |
| callback(req, rsp, SignInResult.CANCEL); |
| return; |
| } |
| |
| // Authenticate user through his/her OpenID provider |
| // |
| final String realm = serverUrl(req); |
| final UrlEncodedParameterMap retTo = |
| new UrlEncodedParameterMap(req.getRequestURL().toString()); |
| save(retTo, req, OpenIdUtil.P_SIGNIN_CB); |
| save(retTo, req, OpenIdUtil.P_SIGNIN_MODE); |
| save(retTo, req, OpenIdUtil.P_REMEMBERID); |
| final UrlEncodedParameterMap auth; |
| |
| auth = RelyingParty.getAuthUrlMap(user, realm, realm, retTo.toString()); |
| auth.put("openid.ns.ext1", AX_SCHEMA); |
| final String ext1 = "openid.ext1."; |
| auth.put(ext1 + "mode", "fetch_request"); |
| auth.put(ext1 + "type.fname", "http://example.com/schema/fullname"); |
| auth.put(ext1 + "type.email", "http://schema.openid.net/contact/email"); |
| auth.put(ext1 + "required", "email"); |
| auth.put(ext1 + "if_available", "fname"); |
| |
| auth.put("openid.sreg.optional", "fullname,email"); |
| auth.put("openid.ns.sreg", "http://openid.net/extensions/sreg/1.1"); |
| |
| relyingParty.getOpenIdUserManager().saveUser(user, req, rsp); |
| sendJson(req, rsp, new DiscoveryResult(true, auth.getUrl(), auth), req |
| .getParameter(OpenIdUtil.P_DISCOVERY_CB)); |
| } |
| |
| private static void save(UrlEncodedParameterMap b, HttpServletRequest r, |
| String n) { |
| final String v = r.getParameter(n); |
| if (v != null) { |
| b.put(n, v); |
| } |
| } |
| |
| private void initializeAccount(final HttpServletRequest req, |
| final HttpServletResponse rsp, final OpenIdUser user, |
| final String fullname, final String email) throws IOException { |
| final SignInDialog.Mode mode = signInMode(req); |
| Account account = null; |
| if (user != null) { |
| try { |
| final ReviewDb d = Common.getSchemaFactory().open(); |
| try { |
| switch (mode) { |
| case SIGN_IN: |
| account = openAccount(d, user, fullname, email); |
| break; |
| case LINK_IDENTIY: |
| account = linkAccount(req, d, user, email); |
| break; |
| } |
| } finally { |
| d.close(); |
| } |
| } catch (OrmException e) { |
| getServletContext().log("Account lookup failed", e); |
| account = null; |
| } |
| } |
| |
| rsp.reset(); |
| |
| String tok; |
| try { |
| final String idstr = String.valueOf(account.getId().get()); |
| tok = server.getAccountToken().newToken(idstr); |
| } catch (XsrfException e) { |
| getServletContext().log("Account cookie signature impossible", e); |
| account = null; |
| tok = ""; |
| } |
| |
| Cookie c = new Cookie(Gerrit.ACCOUNT_COOKIE, tok); |
| c.setPath(req.getContextPath() + "/"); |
| |
| if (account == null) { |
| if (mode == SignInDialog.Mode.SIGN_IN) { |
| c.setMaxAge(0); |
| rsp.addCookie(c); |
| } |
| callback(req, rsp, SignInResult.CANCEL); |
| } else { |
| c.setMaxAge(server.getSessionAge()); |
| rsp.addCookie(c); |
| |
| final UrlEncodedParameterMap me = |
| new UrlEncodedParameterMap(req.getRequestURL().toString()); |
| me.put(Constants.OPENID_MODE, GMODE_CHKCOOKIE); |
| me.put(Gerrit.ACCOUNT_COOKIE, tok); |
| save(me, req, OpenIdUtil.P_SIGNIN_CB); |
| save(me, req, OpenIdUtil.P_SIGNIN_MODE); |
| if ("on".equals(req.getParameter(OpenIdUtil.P_REMEMBERID))) { |
| final String ident = saveLastId(req, rsp, user.getClaimedId()); |
| me.put(OpenIdUtil.LASTID_COOKIE, ident); |
| save(me, req, OpenIdUtil.P_REMEMBERID); |
| } else { |
| c = new Cookie(OpenIdUtil.LASTID_COOKIE, ""); |
| c.setPath(req.getContextPath() + "/"); |
| c.setMaxAge(0); |
| rsp.addCookie(c); |
| } |
| rsp.sendRedirect(me.toString()); |
| } |
| } |
| |
| private String saveLastId(final HttpServletRequest req, |
| final HttpServletResponse rsp, String ident) { |
| final Cookie c = new Cookie(OpenIdUtil.LASTID_COOKIE, ident); |
| c.setPath(req.getContextPath() + "/"); |
| c.setMaxAge(LASTID_AGE); |
| rsp.addCookie(c); |
| return ident; |
| } |
| |
| private Account openAccount(final ReviewDb db, final OpenIdUser user, |
| final String fullname, final String email) throws OrmException { |
| Account account; |
| final AccountExternalIdAccess extAccess = db.accountExternalIds(); |
| AccountExternalId acctExt = lookup(extAccess, user.getClaimedId()); |
| |
| if (acctExt == null && email != null |
| && server.isAllowGoogleAccountUpgrade() && isGoogleAccount(user)) { |
| acctExt = lookupGoogleAccount(extAccess, email); |
| if (acctExt != null) { |
| // Legacy user from Gerrit 1? Attach the OpenID identity. |
| // |
| final AccountExternalId openidExt = |
| new AccountExternalId(new AccountExternalId.Key(acctExt |
| .getAccountId(), user.getClaimedId())); |
| extAccess.insert(Collections.singleton(openidExt)); |
| acctExt = openidExt; |
| } |
| } |
| |
| if (acctExt != null) { |
| // Existing user; double check the email is current. |
| // |
| if (email != null && !email.equals(acctExt.getEmailAddress())) { |
| acctExt.setEmailAddress(email); |
| } |
| acctExt.setLastUsedOn(); |
| extAccess.update(Collections.singleton(acctExt)); |
| account = Common.getAccountCache().get(acctExt.getAccountId(), db); |
| } else { |
| account = null; |
| } |
| |
| if (account == null) { |
| // New user; create an account entity for them. |
| // |
| final Transaction txn = db.beginTransaction(); |
| |
| account = new Account(new Account.Id(db.nextAccountId())); |
| account.setFullName(fullname); |
| account.setPreferredEmail(email); |
| |
| acctExt = |
| new AccountExternalId(new AccountExternalId.Key(account.getId(), user |
| .getClaimedId())); |
| acctExt.setLastUsedOn(); |
| acctExt.setEmailAddress(email); |
| |
| db.accounts().insert(Collections.singleton(account), txn); |
| extAccess.insert(Collections.singleton(acctExt), txn); |
| txn.commit(); |
| } |
| return account; |
| } |
| |
| private Account linkAccount(final HttpServletRequest req, final ReviewDb db, |
| final OpenIdUser user, final String email) throws OrmException { |
| final Cookie[] cookies = req.getCookies(); |
| if (cookies == null) { |
| return null; |
| } |
| Account.Id me = null; |
| for (final Cookie c : cookies) { |
| if (Gerrit.ACCOUNT_COOKIE.equals(c.getName())) { |
| try { |
| final ValidToken tok = |
| server.getAccountToken().checkToken(c.getValue(), null); |
| if (tok == null) { |
| return null; |
| } |
| me = Account.Id.parse(tok.getData()); |
| break; |
| } catch (XsrfException e) { |
| return null; |
| } catch (RuntimeException e) { |
| return null; |
| } |
| } |
| } |
| if (me == null) { |
| return null; |
| } |
| |
| final Account account = Common.getAccountCache().get(me, db); |
| if (account == null) { |
| return null; |
| } |
| |
| final AccountExternalId.Key idKey = |
| new AccountExternalId.Key(account.getId(), user.getClaimedId()); |
| AccountExternalId id = db.accountExternalIds().get(idKey); |
| if (id == null) { |
| id = new AccountExternalId(idKey); |
| id.setLastUsedOn(); |
| id.setEmailAddress(email); |
| db.accountExternalIds().insert(Collections.singleton(id)); |
| Common.getGroupCache().invalidate(account.getId()); |
| } else { |
| if (email != null && !email.equals(id.getEmailAddress())) { |
| id.setEmailAddress(email); |
| } |
| id.setLastUsedOn(); |
| db.accountExternalIds().update(Collections.singleton(id)); |
| } |
| return account; |
| } |
| |
| private static Mode signInMode(final HttpServletRequest req) { |
| final String p = req.getParameter(OpenIdUtil.P_SIGNIN_MODE); |
| if (p == null || p.length() == 0) { |
| return SignInDialog.Mode.SIGN_IN; |
| } |
| try { |
| return SignInDialog.Mode.valueOf(p); |
| } catch (RuntimeException e) { |
| return SignInDialog.Mode.SIGN_IN; |
| } |
| } |
| |
| private static AccountExternalId lookup( |
| final AccountExternalIdAccess extAccess, final String id) |
| throws OrmException { |
| final List<AccountExternalId> extRes = extAccess.byExternal(id).toList(); |
| switch (extRes.size()) { |
| case 0: |
| return null; |
| case 1: |
| return extRes.get(0); |
| default: |
| throw new OrmException("More than one account matches: " + id); |
| } |
| } |
| |
| private static boolean isGoogleAccount(final OpenIdUser user) { |
| return user.getClaimedId().startsWith(OpenIdUtil.URL_GOOGLE + "?"); |
| } |
| |
| private static AccountExternalId lookupGoogleAccount( |
| final AccountExternalIdAccess extAccess, final String email) |
| throws OrmException { |
| // We may have multiple records which match the email address, but |
| // all under the same account. This happens when the user does a |
| // login through different server hostnames, as Google issues |
| // unique OpenID tokens per server. |
| // |
| // Match to an existing account only if there is exactly one record |
| // for this email using the generic Google identity. |
| // |
| final List<AccountExternalId> m = new ArrayList<AccountExternalId>(); |
| for (final AccountExternalId e : extAccess.byEmailAddress(email)) { |
| if (e.getExternalId().equals("Google Account " + email)) { |
| m.add(e); |
| } |
| } |
| return m.size() == 1 ? m.get(0) : null; |
| } |
| |
| private void modeChkSetCookie(final HttpServletRequest req, |
| final HttpServletResponse rsp, final boolean isCheck) throws IOException { |
| final String exp = req.getParameter(Gerrit.ACCOUNT_COOKIE); |
| final ValidToken chk; |
| try { |
| chk = server.getAccountToken().checkToken(exp, null); |
| if (chk == null) { |
| callback(req, rsp, SignInResult.CANCEL); |
| return; |
| } |
| } catch (XsrfException e) { |
| getServletContext().log("Cannot validate cookie token", e); |
| callback(req, rsp, SignInResult.CANCEL); |
| return; |
| } |
| |
| final Account.Id id; |
| try { |
| id = new Account.Id(Integer.parseInt(chk.getData())); |
| } catch (NumberFormatException e) { |
| callback(req, rsp, SignInResult.CANCEL); |
| return; |
| } |
| |
| final String act = getCookie(req, Gerrit.ACCOUNT_COOKIE); |
| if (isCheck && !exp.equals(act)) { |
| // Cookie won't set without "user interaction" (thanks Safari). Lets |
| // send an HTML page to the browser and ask the user to click to let |
| // us set the cookie. |
| // |
| sendSetCookieHtml(req, rsp, exp); |
| return; |
| } |
| |
| Cookie c = new Cookie(Gerrit.ACCOUNT_COOKIE, exp); |
| c.setPath(req.getContextPath() + "/"); |
| c.setMaxAge(server.getSessionAge()); |
| rsp.addCookie(c); |
| |
| if ("on".equals(req.getParameter(OpenIdUtil.P_REMEMBERID))) { |
| saveLastId(req, rsp, req.getParameter(OpenIdUtil.LASTID_COOKIE)); |
| } else { |
| c = new Cookie(OpenIdUtil.LASTID_COOKIE, ""); |
| c.setPath(req.getContextPath() + "/"); |
| c.setMaxAge(0); |
| rsp.addCookie(c); |
| } |
| callback(req, rsp, new SignInResult(SignInResult.Status.SUCCESS)); |
| } |
| |
| private void sendSetCookieHtml(final HttpServletRequest req, |
| final HttpServletResponse rsp, final String exp) throws IOException { |
| final Document doc = HtmlDomUtil.clone(pleaseSetCookieDoc); |
| final Element set_form = HtmlDomUtil.find(doc, "set_form"); |
| set_form.setAttribute("action", req.getRequestURL().toString()); |
| HtmlDomUtil.addHidden(set_form, Constants.OPENID_MODE, GMODE_SETCOOKIE); |
| HtmlDomUtil.addHidden(set_form, Gerrit.ACCOUNT_COOKIE, exp); |
| save(set_form, req, OpenIdUtil.LASTID_COOKIE); |
| save(set_form, req, OpenIdUtil.P_REMEMBERID); |
| save(set_form, req, OpenIdUtil.P_SIGNIN_CB); |
| save(set_form, req, OpenIdUtil.P_SIGNIN_MODE); |
| sendHtml(req, rsp, HtmlDomUtil.toString(doc)); |
| } |
| |
| private static void save(Element f, HttpServletRequest r, String n) { |
| final String v = r.getParameter(n); |
| if (v != null) { |
| HtmlDomUtil.addHidden(f, n, v); |
| } |
| } |
| |
| private static String getCookie(final HttpServletRequest req, |
| final String name) { |
| final Cookie[] allCookies = req.getCookies(); |
| if (allCookies != null) { |
| for (final Cookie c : allCookies) { |
| if (name.equals(c.getName())) { |
| return c.getValue(); |
| } |
| } |
| } |
| return null; |
| } |
| |
| private void callback(final HttpServletRequest req, |
| final HttpServletResponse rsp, final SignInResult result) |
| throws IOException { |
| final String dcb = req.getParameter(OpenIdUtil.P_DISCOVERY_CB); |
| if (dcb != null) { |
| // We're in the middle of a discovery request; we need to use |
| // the discovery request callback and not the sign in callback. |
| // |
| sendJson(req, rsp, new DiscoveryResult(false), dcb); |
| return; |
| } |
| |
| // We're not in an OpenID transaction so try to clear out the |
| // OpenID management cookie. |
| // |
| relyingParty.getOpenIdUserManager().invalidate(req, rsp); |
| |
| final String cb = req.getParameter(OpenIdUtil.P_SIGNIN_CB); |
| if (cb != null && cb.startsWith("history:")) { |
| final StringBuffer rdr = req.getRequestURL(); |
| rdr.setLength(rdr.lastIndexOf("/")); |
| rdr.append("/Gerrit"); |
| rdr.append('#'); |
| rdr.append(cb.substring("history:".length())); |
| rsp.sendRedirect(rdr.toString()); |
| return; |
| } |
| sendJson(req, rsp, result, cb); |
| } |
| |
| private void sendJson(final HttpServletRequest req, |
| final HttpServletResponse rsp, final Object result, final String cb) |
| throws IOException { |
| final StringWriter body = new StringWriter(); |
| body.write("<html>"); |
| body.append("<body>"); |
| if (JsonServlet.SAFE_CALLBACK.matcher(cb).matches()) { |
| body.write("<script><!--\n"); |
| body.write(cb); |
| body.write("("); |
| JsonServlet.defaultGsonBuilder().create().toJson(result, body); |
| body.write(");\n"); |
| body.write("// -->\n"); |
| body.write("</script>"); |
| body.write("<p>Loading ...</p>"); |
| } else { |
| body.append("Unsafe JSON callback requested; refusing to execute it."); |
| } |
| body.append("</body>"); |
| body.write("</html>"); |
| sendHtml(req, rsp, body.toString()); |
| } |
| |
| private void sendHtml(final HttpServletRequest req, |
| final HttpServletResponse rsp, final String bodystr) throws IOException { |
| final byte[] raw = bodystr.getBytes(HtmlDomUtil.ENC); |
| final byte[] tosend; |
| if (RPCServletUtils.acceptsGzipEncoding(req)) { |
| rsp.setHeader("Content-Encoding", "gzip"); |
| final ByteArrayOutputStream compressed = new ByteArrayOutputStream(); |
| final GZIPOutputStream gz = new GZIPOutputStream(compressed); |
| gz.write(raw); |
| gz.finish(); |
| gz.flush(); |
| tosend = compressed.toByteArray(); |
| } else { |
| tosend = raw; |
| } |
| |
| rsp.setCharacterEncoding(HtmlDomUtil.ENC); |
| rsp.setContentType("text/html"); |
| rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT"); |
| rsp.setHeader("Pragma", "no-cache"); |
| rsp.setHeader("Cache-Control", "no-cache, must-revalidate"); |
| rsp.setContentLength(tosend.length); |
| final OutputStream out = rsp.getOutputStream(); |
| try { |
| out.write(tosend); |
| } finally { |
| out.close(); |
| } |
| } |
| |
| static String serverUrl(final HttpServletRequest req) { |
| // Assume this servlet is in the context with a simple name like "login" |
| // and we were accessed without any path info. Clipping the last part of |
| // the name from the URL should generate the web application's root path. |
| // |
| final String uri = req.getRequestURL().toString(); |
| final int s = uri.lastIndexOf('/'); |
| return s >= 0 ? uri.substring(0, s + 1) : uri; |
| } |
| } |