mirror of
https://github.com/fergalmoran/chrometophone.git
synced 2025-12-22 09:41:51 +00:00
A couple of updates to make it easier to use OAuth1 instead of cookies from the extension.
- Use JSON because it's cleaner and url-encoded with oauth1 is mostly broken. - Refactor common code - Update the version of the appengine SDK - change to version 9 ( will not be live ) to avoid breaking existign devices. In theory everything should be backward compatible.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="c2dm"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="con" path="com.google.appengine.eclipse.core.GAE_CONTAINER/appengine-java-sdk-1.3.8"/>
|
||||
<classpathentry kind="con" path="com.google.appengine.eclipse.core.GAE_CONTAINER"/>
|
||||
<classpathentry kind="con" path="com.google.appengine.eclipse.core.GAE_CONTAINER/App Engine"/>
|
||||
<classpathentry kind="output" path="war/WEB-INF/classes"/>
|
||||
</classpath>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#Sun Oct 24 20:19:26 BST 2010
|
||||
#Mon Apr 25 17:13:56 PDT 2011
|
||||
eclipse.preferences.version=1
|
||||
filesCopiedToWebInfLib=appengine-api-1.0-sdk-1.3.8.jar|appengine-api-labs-1.3.8.jar|appengine-jsr107cache-1.3.8.jar|jsr107cache-1.1.jar|datanucleus-appengine-1.0.7.final.jar|datanucleus-core-1.1.5.jar|datanucleus-jpa-1.1.5.jar|geronimo-jpa_3.0_spec-1.1.1.jar|geronimo-jta_1.1_spec-1.1.1.jar|jdo2-api-2.3-eb.jar
|
||||
filesCopiedToWebInfLib=appengine-jsr107cache-1.4.3.jar|jdo2-api-2.3-eb.jar|geronimo-jpa_3.0_spec-1.1.1.jar|geronimo-jta_1.1_spec-1.1.1.jar|datanucleus-jpa-1.1.5.jar|datanucleus-appengine-1.0.8.final.jar|datanucleus-core-1.1.5.jar|appengine-api-labs-1.4.3.jar|appengine-api-1.0-sdk-1.4.3.jar|jsr107cache-1.1.jar
|
||||
ormEnhancementInclusions=src/**|c2dm/**
|
||||
|
||||
@@ -35,8 +35,18 @@ public final class C2DMConfig {
|
||||
@Persistent
|
||||
private String authToken;
|
||||
|
||||
@Persistent
|
||||
private String oauth2RefreshToken;
|
||||
|
||||
@Persistent
|
||||
private String clientId;
|
||||
|
||||
@Persistent
|
||||
private String clientSecret;
|
||||
|
||||
|
||||
public static final String DATAMESSAGING_SEND_ENDPOINT =
|
||||
"https://android.clients.google.com/c2dm/send";
|
||||
"https://android.apis.google.com/c2dm/send";
|
||||
|
||||
@Persistent
|
||||
private String c2dmUrl;
|
||||
@@ -64,4 +74,28 @@ public final class C2DMConfig {
|
||||
return c2dmUrl;
|
||||
}
|
||||
}
|
||||
|
||||
public void setOauth2RefreshToken(String oauth2RefreshToken) {
|
||||
this.oauth2RefreshToken = oauth2RefreshToken;
|
||||
}
|
||||
|
||||
public String getOauth2RefreshToken() {
|
||||
return oauth2RefreshToken;
|
||||
}
|
||||
|
||||
public void setOAuth2ClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getOAuth2ClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setOAuth2ClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public String getOAuth2ClientSecret() {
|
||||
return clientSecret;
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ import com.google.appengine.api.datastore.KeyFactory;
|
||||
* Stores config information related to data messaging.
|
||||
*
|
||||
*/
|
||||
class C2DMConfigLoader {
|
||||
public class C2DMConfigLoader {
|
||||
private final PersistenceManagerFactory PMF;
|
||||
private static final Logger log = Logger.getLogger(C2DMConfigLoader.class.getName());
|
||||
|
||||
@@ -99,7 +99,7 @@ class C2DMConfigLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private C2DMConfig getDataMessagingConfig(PersistenceManager pm) {
|
||||
public static C2DMConfig getDataMessagingConfig(PersistenceManager pm) {
|
||||
Key key = KeyFactory.createKey(C2DMConfig.class.getSimpleName(), 1);
|
||||
C2DMConfig dmConfig = null;
|
||||
try {
|
||||
@@ -110,7 +110,7 @@ class C2DMConfigLoader {
|
||||
dmConfig.setKey(key);
|
||||
// Must be in classpath, before sending. Do not checkin !
|
||||
try {
|
||||
InputStream is = this.getClass().getClassLoader().getResourceAsStream("/dataMessagingToken.txt");
|
||||
InputStream is = C2DMConfigLoader.class.getClassLoader().getResourceAsStream("/dataMessagingToken.txt");
|
||||
if (is != null) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
String token = reader.readLine();
|
||||
|
||||
@@ -27,6 +27,13 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import com.google.appengine.api.users.UserService;
|
||||
import com.google.appengine.api.users.UserServiceFactory;
|
||||
|
||||
/**
|
||||
* Handles login/logout requests by redirecting to the cookie-based login page.
|
||||
* Has logic to handle redirect limitations, the redirect URL can't be a chrome
|
||||
* URL.
|
||||
*
|
||||
* Not needed if OAuth1 is used.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class AuthServlet extends HttpServlet {
|
||||
private static final Logger log =
|
||||
@@ -69,8 +76,9 @@ public class AuthServlet extends HttpServlet {
|
||||
log.warning("Invalid redirect " + extRet);
|
||||
}
|
||||
} else {
|
||||
String followOnURL = req.getRequestURI() + "?completed=true&extret=" +
|
||||
URLEncoder.encode(extRet, "UTF-8");
|
||||
// Called directly from extension, redirect
|
||||
String followOnURL = req.getRequestURI() + "?completed=true" +
|
||||
"&extret=" + URLEncoder.encode(extRet, "UTF-8");
|
||||
UserService userService = UserServiceFactory.getUserService();
|
||||
resp.sendRedirect(signIn ? userService.createLoginURL(followOnURL) :
|
||||
userService.createLogoutURL(followOnURL));
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
*/
|
||||
package com.google.android.chrometophone.server;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public class DebugServlet extends HttpServlet {
|
||||
|
||||
@Override
|
||||
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
// Nothing, we're just looking for logs to find response times and delivery
|
||||
// confirmation.
|
||||
// TODO: use memcache to dynamically get statistics.
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.google.android.chrometophone.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
@@ -32,157 +33,156 @@ import com.google.android.c2dm.server.C2DMessaging;
|
||||
import com.google.appengine.api.channel.ChannelServiceFactory;
|
||||
import com.google.appengine.api.datastore.Key;
|
||||
import com.google.appengine.api.datastore.KeyFactory;
|
||||
import com.google.appengine.api.oauth.OAuthService;
|
||||
import com.google.appengine.api.oauth.OAuthServiceFactory;
|
||||
import com.google.appengine.api.users.User;
|
||||
import com.google.appengine.api.users.UserService;
|
||||
import com.google.appengine.api.users.UserServiceFactory;
|
||||
import com.google.appengine.repackaged.org.json.JSONArray;
|
||||
import com.google.appengine.repackaged.org.json.JSONException;
|
||||
import com.google.appengine.repackaged.org.json.JSONObject;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class RegisterServlet extends HttpServlet {
|
||||
private static final Logger log =
|
||||
Logger.getLogger(RegisterServlet.class.getName());
|
||||
private static final String OK_STATUS = "OK";
|
||||
private static final String LOGIN_REQUIRED_STATUS = "LOGIN_REQUIRED";
|
||||
private static final String ERROR_STATUS = "ERROR";
|
||||
|
||||
private static int MAX_DEVICES = 5;
|
||||
private static int MAX_DEVICES = 10;
|
||||
|
||||
/**
|
||||
* Get the user using the UserService.
|
||||
*
|
||||
* If not logged in, return an error message.
|
||||
*
|
||||
* @return user, or null if not logged in.
|
||||
* @throws IOException
|
||||
* For debug - and possibly show the info, allow device selection.
|
||||
*/
|
||||
static User checkUser(HttpServletRequest req, HttpServletResponse resp,
|
||||
boolean errorIfNotLoggedIn) throws IOException {
|
||||
// Is it OAuth ?
|
||||
User user = null;
|
||||
OAuthService oauthService = OAuthServiceFactory.getOAuthService();
|
||||
try {
|
||||
user = oauthService.getCurrentUser();
|
||||
if (user != null) {
|
||||
log.info("Found OAuth user " + user);
|
||||
return user;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
user = null;
|
||||
@Override
|
||||
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
|
||||
RequestInfo reqInfo = RequestInfo.processRequest(req, resp, getServletContext());
|
||||
if (reqInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
UserService userService = UserServiceFactory.getUserService();
|
||||
user = userService.getCurrentUser();
|
||||
if (user == null && errorIfNotLoggedIn) {
|
||||
// TODO: redirect to OAuth/user service login, or send the URL
|
||||
// TODO: 401 instead of 400
|
||||
resp.setStatus(400);
|
||||
resp.getWriter().println(LOGIN_REQUIRED_STATUS);
|
||||
resp.setContentType("application/json");
|
||||
JSONObject regs = new JSONObject();
|
||||
try {
|
||||
regs.put("user", reqInfo.userName);
|
||||
|
||||
JSONArray devices = new JSONArray();
|
||||
for (DeviceInfo di: reqInfo.devices) {
|
||||
JSONObject dijson = new JSONObject();
|
||||
dijson.put("key", di.getKey().toString());
|
||||
dijson.put("name", di.getName());
|
||||
dijson.put("type", di.getType());
|
||||
dijson.put("regid", di.getDeviceRegistrationID());
|
||||
dijson.put("ts", di.getRegistrationTimestamp());
|
||||
devices.put(dijson);
|
||||
}
|
||||
regs.put("devices", devices);
|
||||
|
||||
PrintWriter out = resp.getWriter();
|
||||
regs.write(out);
|
||||
} catch (JSONException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
return user;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
resp.setContentType("text/plain");
|
||||
|
||||
// Basic XSRF protection
|
||||
if (req.getHeader("X-Same-Domain") == null) {
|
||||
log.warning("Blocked XSRF");
|
||||
resp.setStatus(400);
|
||||
resp.getWriter().println(ERROR_STATUS + " (Missing X-Same-Domain header)");
|
||||
RequestInfo reqInfo = RequestInfo.processRequest(req, resp,
|
||||
getServletContext());
|
||||
if (reqInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String deviceRegistrationId = req.getParameter("devregid");
|
||||
if (deviceRegistrationId == null || "".equals(deviceRegistrationId.trim())) {
|
||||
if (reqInfo.deviceRegistrationID == null) {
|
||||
resp.setStatus(400);
|
||||
resp.getWriter().println(ERROR_STATUS + "(Must specify devregid)");
|
||||
log.severe("Missing registration id ");
|
||||
return;
|
||||
}
|
||||
|
||||
String deviceName = req.getParameter("deviceName");
|
||||
String deviceName = reqInfo.getParameter("deviceName");
|
||||
if (deviceName == null) {
|
||||
deviceName = "Phone";
|
||||
}
|
||||
// TODO: generate the device name by adding a number suffix for multiple
|
||||
// devices of same type. Change android app to send model/type.
|
||||
|
||||
String deviceType = req.getParameter("deviceType");
|
||||
String deviceType = reqInfo.getParameter("deviceType");
|
||||
if (deviceType == null) {
|
||||
deviceType = "ac2dm";
|
||||
}
|
||||
|
||||
// Because the deviceRegistrationId isn't static, we use a static
|
||||
// identifier for the device. (Can be null in older clients)
|
||||
String deviceId = req.getParameter("deviceId");
|
||||
String deviceId = reqInfo.getParameter("deviceId");
|
||||
|
||||
User user = checkUser(req, resp, true);
|
||||
if (user != null) {
|
||||
// Context-shared PMF.
|
||||
PersistenceManager pm =
|
||||
C2DMessaging.getPMF(getServletContext()).getPersistenceManager();
|
||||
// Context-shared PMF.
|
||||
PersistenceManager pm =
|
||||
C2DMessaging.getPMF(getServletContext()).getPersistenceManager();
|
||||
|
||||
try {
|
||||
List<DeviceInfo> registrations = DeviceInfo.getDeviceInfoForUser(pm,
|
||||
user.getEmail());
|
||||
try {
|
||||
List<DeviceInfo> registrations = reqInfo.devices;
|
||||
|
||||
if (registrations.size() > MAX_DEVICES) {
|
||||
// we could return an error - but user can't handle it yet.
|
||||
// we can't let it grow out of bounds.
|
||||
// TODO: we should also define a 'ping' message and expire/remove
|
||||
// unused registrations
|
||||
DeviceInfo oldest = registrations.get(0);
|
||||
if (oldest.getRegistrationTimestamp() == null) {
|
||||
pm.deletePersistent(oldest);
|
||||
} else {
|
||||
long oldestTime = oldest.getRegistrationTimestamp().getTime();
|
||||
for (int i = 1; i < registrations.size(); i++) {
|
||||
if (registrations.get(i).getRegistrationTimestamp().getTime() <
|
||||
oldestTime) {
|
||||
oldest = registrations.get(i);
|
||||
oldestTime = oldest.getRegistrationTimestamp().getTime();
|
||||
}
|
||||
if (registrations.size() > MAX_DEVICES) {
|
||||
// we could return an error - but user can't handle it yet.
|
||||
// we can't let it grow out of bounds.
|
||||
// TODO: we should also define a 'ping' message and expire/remove
|
||||
// unused registrations
|
||||
DeviceInfo oldest = registrations.get(0);
|
||||
if (oldest.getRegistrationTimestamp() == null) {
|
||||
pm.deletePersistent(oldest);
|
||||
} else {
|
||||
long oldestTime = oldest.getRegistrationTimestamp().getTime();
|
||||
for (int i = 1; i < registrations.size(); i++) {
|
||||
if (registrations.get(i).getRegistrationTimestamp().getTime() <
|
||||
oldestTime) {
|
||||
oldest = registrations.get(i);
|
||||
oldestTime = oldest.getRegistrationTimestamp().getTime();
|
||||
}
|
||||
pm.deletePersistent(oldest);
|
||||
}
|
||||
pm.deletePersistent(oldest);
|
||||
}
|
||||
|
||||
// Get device if it already exists, else create
|
||||
String suffix =
|
||||
(deviceId != null ? "#" + Long.toHexString(Math.abs(deviceId.hashCode())) : "");
|
||||
Key key = KeyFactory.createKey(DeviceInfo.class.getSimpleName(),
|
||||
user.getEmail() + suffix);
|
||||
|
||||
DeviceInfo device = null;
|
||||
try {
|
||||
device = pm.getObjectById(DeviceInfo.class, key);
|
||||
} catch (JDOObjectNotFoundException e) { }
|
||||
if (device == null) {
|
||||
device = new DeviceInfo(key, deviceRegistrationId);
|
||||
device.setType(deviceType);
|
||||
} else {
|
||||
// update registration id
|
||||
device.setDeviceRegistrationID(deviceRegistrationId);
|
||||
device.setRegistrationTimestamp(new Date());
|
||||
}
|
||||
|
||||
device.setName(deviceName); // update display name
|
||||
pm.makePersistent(device);
|
||||
|
||||
if (device.getType().equals(DeviceInfo.TYPE_CHROME)) {
|
||||
String channelId =
|
||||
ChannelServiceFactory.getChannelService().createChannel(deviceRegistrationId);
|
||||
resp.getWriter().println(OK_STATUS + " " + channelId);
|
||||
} else {
|
||||
resp.getWriter().println(OK_STATUS);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
resp.setStatus(500);
|
||||
resp.getWriter().println(ERROR_STATUS + " (Error registering device)");
|
||||
log.log(Level.WARNING, "Error registering device.", e);
|
||||
} finally {
|
||||
pm.close();
|
||||
}
|
||||
|
||||
// Get device if it already exists, else create
|
||||
String suffix =
|
||||
(deviceId != null ? "#" + Long.toHexString(Math.abs(deviceId.hashCode())) : "");
|
||||
Key key = KeyFactory.createKey(DeviceInfo.class.getSimpleName(),
|
||||
reqInfo.userName + suffix);
|
||||
|
||||
DeviceInfo device = null;
|
||||
try {
|
||||
device = pm.getObjectById(DeviceInfo.class, key);
|
||||
} catch (JDOObjectNotFoundException e) { }
|
||||
if (device == null) {
|
||||
device = new DeviceInfo(key, reqInfo.deviceRegistrationID);
|
||||
device.setType(deviceType);
|
||||
} else {
|
||||
// update registration id
|
||||
device.setDeviceRegistrationID(reqInfo.deviceRegistrationID);
|
||||
device.setRegistrationTimestamp(new Date());
|
||||
}
|
||||
|
||||
device.setName(deviceName); // update display name
|
||||
// TODO: only need to write if something changed, for chrome nothing
|
||||
// changes, we just create a new channel
|
||||
pm.makePersistent(device);
|
||||
log.log(Level.INFO, "Registered device " + reqInfo.userName + " " +
|
||||
deviceType);
|
||||
|
||||
if (device.getType().equals(DeviceInfo.TYPE_CHROME)) {
|
||||
String channelId =
|
||||
ChannelServiceFactory.getChannelService().createChannel(reqInfo.deviceRegistrationID);
|
||||
resp.getWriter().println(OK_STATUS + " " + channelId);
|
||||
} else {
|
||||
resp.getWriter().println(OK_STATUS);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
resp.setStatus(500);
|
||||
resp.getWriter().println(ERROR_STATUS + " (Error registering device)");
|
||||
log.log(Level.WARNING, "Error registering device.", e);
|
||||
} finally {
|
||||
pm.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
*/
|
||||
package com.google.android.chrometophone.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.jdo.JDOObjectNotFoundException;
|
||||
import javax.jdo.PersistenceManager;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.google.android.c2dm.server.C2DMessaging;
|
||||
import com.google.appengine.api.datastore.Key;
|
||||
import com.google.appengine.api.oauth.OAuthService;
|
||||
import com.google.appengine.api.oauth.OAuthServiceFactory;
|
||||
import com.google.appengine.api.users.User;
|
||||
import com.google.appengine.api.users.UserService;
|
||||
import com.google.appengine.api.users.UserServiceFactory;
|
||||
import com.google.appengine.repackaged.org.json.JSONException;
|
||||
import com.google.appengine.repackaged.org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Common code and helpers to handle a request and manipulate device info.
|
||||
*
|
||||
*/
|
||||
public class RequestInfo {
|
||||
private static final Logger log =
|
||||
Logger.getLogger(RequestInfo.class.getName());
|
||||
private static final String ERROR_STATUS = "ERROR";
|
||||
private static final String LOGIN_REQUIRED_STATUS = "LOGIN_REQUIRED";
|
||||
|
||||
public List<DeviceInfo> devices = new ArrayList<DeviceInfo>();
|
||||
|
||||
public String userName;
|
||||
|
||||
private ServletContext ctx;
|
||||
public String deviceRegistrationID;
|
||||
|
||||
// Request parameters - transitioning to JSON, but need to support existing
|
||||
// code.
|
||||
Map<String, String[]> parameterMap;
|
||||
JSONObject jsonParams;
|
||||
|
||||
|
||||
public boolean isAuth() {
|
||||
return userName != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user, check headers and pull the registration data.
|
||||
*
|
||||
* @return null if authentication fails.
|
||||
* @throws IOException
|
||||
*/
|
||||
public static RequestInfo processRequest(HttpServletRequest req,
|
||||
HttpServletResponse resp, ServletContext ctx) throws IOException {
|
||||
|
||||
// Basic XSRF protection
|
||||
if (req.getHeader("X-Same-Domain") == null) {
|
||||
resp.setStatus(400);
|
||||
resp.getWriter().println(ERROR_STATUS + " (Missing X-Same-Domain header)");
|
||||
log.warning("Missing X-Same-Domain");
|
||||
return null;
|
||||
}
|
||||
|
||||
User user = null;
|
||||
RequestInfo ri = new RequestInfo();
|
||||
ri.ctx= ctx;
|
||||
OAuthService oauthService = OAuthServiceFactory.getOAuthService();
|
||||
try {
|
||||
user = oauthService.getCurrentUser();
|
||||
if (user != null) {
|
||||
ri.userName = user.getEmail();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
log.log(Level.SEVERE, "Oauth error ", t);
|
||||
user = null;
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
// Try ClientLogin
|
||||
UserService userService = UserServiceFactory.getUserService();
|
||||
user = userService.getCurrentUser();
|
||||
if (user != null) {
|
||||
ri.userName = user.getEmail();
|
||||
}
|
||||
}
|
||||
|
||||
if ("application/json".equals(req.getContentType())) {
|
||||
Reader reader = req.getReader();
|
||||
// where is readFully ?
|
||||
char[] tmp = new char[2048];
|
||||
StringBuffer body = new StringBuffer();
|
||||
while (true) {
|
||||
int cnt = reader.read(tmp);
|
||||
if (cnt <= 0) {
|
||||
break;
|
||||
}
|
||||
body.append(tmp, 0, cnt);
|
||||
}
|
||||
try {
|
||||
ri.jsonParams = new JSONObject(body.toString());
|
||||
} catch (JSONException e) {
|
||||
resp.setStatus(500);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
ri.parameterMap = req.getParameterMap();
|
||||
}
|
||||
|
||||
ri.deviceRegistrationID = ri.getParameter("devregid");
|
||||
if (ri.deviceRegistrationID != null) {
|
||||
ri.deviceRegistrationID = ri.deviceRegistrationID.trim();
|
||||
if ("".equals(ri.deviceRegistrationID)) {
|
||||
ri.deviceRegistrationID = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (ri.userName == null) {
|
||||
resp.setStatus(400); // what android app expects. Should fix this
|
||||
resp.getWriter().println(LOGIN_REQUIRED_STATUS);
|
||||
log.warning("Missing user");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ctx != null) {
|
||||
ri.initDevices(ctx);
|
||||
}
|
||||
|
||||
|
||||
return ri;
|
||||
}
|
||||
|
||||
public String getParameter(String name) {
|
||||
if (jsonParams != null) {
|
||||
return jsonParams.optString(name, null);
|
||||
} else {
|
||||
String res[] = parameterMap.get(name);
|
||||
if (res == null || res.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return res[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate using the req, fetch devices.
|
||||
*/
|
||||
private RequestInfo() {
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return userName + " " + devices.size() + " " + jsonParams;
|
||||
}
|
||||
|
||||
public RequestInfo(String userN, ServletContext ctx) {
|
||||
this.userName = userN;
|
||||
this.ctx= ctx;
|
||||
if (ctx != null) {
|
||||
initDevices(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
private void initDevices(ServletContext ctx) {
|
||||
// Context-shared PMF.
|
||||
PersistenceManager pm =
|
||||
C2DMessaging.getPMF(ctx).getPersistenceManager();
|
||||
|
||||
try {
|
||||
devices = DeviceInfo.getDeviceInfoForUser(pm,
|
||||
userName);
|
||||
// cleanup for multi-device
|
||||
if (devices.size() > 1) {
|
||||
// Make sure there is no 'bare' registration
|
||||
// Keys are sorted - check the first
|
||||
DeviceInfo first = devices.get(0);
|
||||
Key oldKey = first.getKey();
|
||||
if (oldKey.toString().indexOf("#") < 0) {
|
||||
log.warning("Removing old-style key " + oldKey.toString());
|
||||
// multiple devices, first is old-style.
|
||||
devices.remove(0);
|
||||
pm.deletePersistent(first);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "Error loading registrations ", e);
|
||||
} finally {
|
||||
pm.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// We need to iterate again - can be avoided with a query.
|
||||
// delete will fail if the pm is different than the one used to
|
||||
// load the object - we must close the object when we're done
|
||||
public void deleteRegistration(String regId) {
|
||||
if (ctx == null) {
|
||||
return;
|
||||
}
|
||||
PersistenceManager pm =
|
||||
C2DMessaging.getPMF(ctx).getPersistenceManager();
|
||||
try {
|
||||
List<DeviceInfo> registrations = DeviceInfo.getDeviceInfoForUser(pm, userName);
|
||||
for (int i = 0; i < registrations.size(); i++) {
|
||||
DeviceInfo deviceInfo = registrations.get(i);
|
||||
if (deviceInfo.getDeviceRegistrationID().equals(regId)) {
|
||||
pm.deletePersistent(deviceInfo);
|
||||
// Keep looping in case of duplicates
|
||||
}
|
||||
}
|
||||
} catch (JDOObjectNotFoundException e) {
|
||||
log.warning("User unknown");
|
||||
} catch (Exception e) {
|
||||
log.warning("Error unregistering device: " + e.getMessage());
|
||||
} finally {
|
||||
pm.close();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,8 @@
|
||||
package com.google.android.chrometophone.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.jdo.PersistenceManager;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -28,15 +26,12 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import com.google.android.c2dm.server.C2DMessaging;
|
||||
import com.google.appengine.api.channel.ChannelMessage;
|
||||
import com.google.appengine.api.channel.ChannelServiceFactory;
|
||||
import com.google.appengine.api.datastore.Key;
|
||||
import com.google.appengine.api.users.User;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class SendServlet extends HttpServlet {
|
||||
static final Logger log =
|
||||
Logger.getLogger(SendServlet.class.getName());
|
||||
private static final String OK_STATUS = "OK";
|
||||
private static final String LOGIN_REQUIRED_STATUS = "LOGIN_REQUIRED";
|
||||
private static final String DEVICE_NOT_REGISTERED_STATUS = "DEVICE_NOT_REGISTERED";
|
||||
private static final String ERROR_STATUS = "ERROR";
|
||||
|
||||
@@ -46,41 +41,43 @@ public class SendServlet extends HttpServlet {
|
||||
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
resp.setContentType("text/plain");
|
||||
|
||||
// Basic XSRF protection (TODO: remove X-Extension in a future release for consistency)
|
||||
if (req.getHeader("X-Same-Domain") == null && req.getHeader("X-Extension") == null) {
|
||||
resp.setStatus(400);
|
||||
resp.getWriter().println(ERROR_STATUS + " (Missing header)");
|
||||
log.warning("Missing header");
|
||||
RequestInfo reqInfo = RequestInfo.processRequest(req, resp,
|
||||
getServletContext());
|
||||
if (reqInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String sel = req.getParameter("sel");
|
||||
String sel = reqInfo.getParameter("sel");
|
||||
if (sel == null) sel = ""; // optional
|
||||
|
||||
String title = req.getParameter("title");
|
||||
String title = reqInfo.getParameter("title");
|
||||
if (title == null) title = ""; // optional
|
||||
|
||||
String url = req.getParameter("url");
|
||||
String url = reqInfo.getParameter("url");
|
||||
if (url == null) {
|
||||
resp.setStatus(400);
|
||||
resp.getWriter().println(ERROR_STATUS + " (Must specify url parameter)");
|
||||
return;
|
||||
}
|
||||
|
||||
String[] deviceNames = req.getParameter("deviceName") != null ? req.getParameter("deviceName").split(",") : null;
|
||||
String deviceType = req.getParameter("deviceType");
|
||||
String deviceName = reqInfo.getParameter("deviceName");
|
||||
String[] deviceNames = deviceName != null ?
|
||||
deviceName.split(",") : null;
|
||||
|
||||
User user = RegisterServlet.checkUser(req, resp, false);
|
||||
if (user != null) {
|
||||
doSendToDevice(url, title, sel, user.getEmail(),
|
||||
deviceNames, deviceType, resp);
|
||||
} else {
|
||||
resp.getWriter().println(LOGIN_REQUIRED_STATUS);
|
||||
String deviceType = reqInfo.getParameter("deviceType");
|
||||
|
||||
String id = doSendToDevice(url, title, sel, reqInfo,
|
||||
deviceNames, deviceType);
|
||||
|
||||
if (id.startsWith(ERROR_STATUS)) {
|
||||
resp.setStatus(500);
|
||||
}
|
||||
resp.getWriter().println(id);
|
||||
}
|
||||
|
||||
protected boolean doSendToDevice(String url, String title, String sel, String userAccount,
|
||||
String deviceNames[], String deviceType, HttpServletResponse resp) throws IOException {
|
||||
protected String doSendToDevice(String url, String title,
|
||||
String sel, RequestInfo reqInfo,
|
||||
String deviceNames[], String deviceType) throws IOException {
|
||||
|
||||
// ok = we sent to at least one device.
|
||||
boolean ok = false;
|
||||
@@ -91,93 +88,75 @@ public class SendServlet extends HttpServlet {
|
||||
|
||||
String collapseKey = "" + url.hashCode();
|
||||
|
||||
PersistenceManager pm =
|
||||
C2DMessaging.getPMF(getServletContext()).getPersistenceManager();
|
||||
boolean reqDebug = "1".equals(reqInfo.getParameter("debug"));
|
||||
|
||||
// delete will fail if the pm is different than the one used to
|
||||
// load the object - we must close the object when we're done
|
||||
int ac2dmCnt = 0;
|
||||
|
||||
List<DeviceInfo> registrations = null;
|
||||
try {
|
||||
registrations = DeviceInfo.getDeviceInfoForUser(pm, userAccount);
|
||||
|
||||
// Deal with upgrades and multi-device:
|
||||
// If user has one device with an old version and few new ones -
|
||||
// the old registration will be deleted.
|
||||
if (registrations.size() > 1) {
|
||||
// Make sure there is no 'bare' registration
|
||||
// Keys are sorted - check the first
|
||||
DeviceInfo first = registrations.get(0);
|
||||
Key oldKey = first.getKey();
|
||||
if (oldKey.toString().indexOf("#") < 0) {
|
||||
log.warning("Removing old-style key " + oldKey.toString());
|
||||
// multiple devices, first is old-style.
|
||||
registrations.remove(0); // don't send to it
|
||||
pm.deletePersistent(first);
|
||||
for (DeviceInfo deviceInfo : reqInfo.devices) {
|
||||
if ("ac2dm".equals(deviceInfo.getType())) {
|
||||
ac2dmCnt++;
|
||||
}
|
||||
if (deviceNames != null) {
|
||||
boolean found = false;
|
||||
for (int i = 0; i < deviceNames.length; i++) {
|
||||
if (deviceNames[i].equals(deviceInfo.getName())) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) continue; // user-specified device name
|
||||
}
|
||||
|
||||
int numSendAttempts = 0;
|
||||
for (DeviceInfo deviceInfo : registrations) {
|
||||
if (deviceNames != null) {
|
||||
boolean found = false;
|
||||
for (int i = 0; i < deviceNames.length; i++) {
|
||||
if (deviceNames[i].equals(deviceInfo.getName())) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) continue; // user-specified device name
|
||||
}
|
||||
|
||||
if (deviceType != null && !deviceType.equals(deviceInfo.getType())) {
|
||||
continue; // user-specified device type
|
||||
}
|
||||
|
||||
try {
|
||||
if (deviceInfo.getType().equals(DeviceInfo.TYPE_CHROME)) {
|
||||
res = doSendViaBrowserChannel(url, deviceInfo);
|
||||
} else {
|
||||
res = doSendViaC2dm(url, title, sel, push, collapseKey, deviceInfo);
|
||||
}
|
||||
numSendAttempts++;
|
||||
|
||||
if (res) {
|
||||
log.info("Link sent to phone! collapse_key:" + collapseKey);
|
||||
ok = true;
|
||||
} else {
|
||||
log.warning("Error: Unable to send link to phone.");
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
if ("NotRegistered".equals(ex.getMessage()) ||
|
||||
"InvalidRegistration".equals(ex.getMessage())) {
|
||||
// Prune device, it no longer works
|
||||
pm.deletePersistent(deviceInfo);
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
if (deviceType != null && !deviceType.equals(deviceInfo.getType())) {
|
||||
continue; // user-specified device type
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
resp.getWriter().println(OK_STATUS);
|
||||
return true;
|
||||
} else if (numSendAttempts == 0) {
|
||||
log.warning("Device not registered " + userAccount);
|
||||
resp.getWriter().println(DEVICE_NOT_REGISTERED_STATUS);
|
||||
return false;
|
||||
try {
|
||||
if (deviceInfo.getType().equals(DeviceInfo.TYPE_CHROME)) {
|
||||
res = doSendViaBrowserChannel(url, deviceInfo);
|
||||
} else {
|
||||
res = doSendViaC2dm(url, title, sel, push, collapseKey,
|
||||
deviceInfo, reqDebug);
|
||||
}
|
||||
|
||||
if (res) {
|
||||
log.info("Link sent to phone! collapse_key:" + collapseKey);
|
||||
ok = true;
|
||||
} else {
|
||||
log.warning("Error: Unable to send link to device: " +
|
||||
deviceInfo.getDeviceRegistrationID());
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
if ("NotRegistered".equals(ex.getMessage()) ||
|
||||
"InvalidRegistration".equals(ex.getMessage())) {
|
||||
// Prune device, it no longer works
|
||||
reqInfo.deleteRegistration(deviceInfo.getDeviceRegistrationID());
|
||||
reqInfo.devices.remove(deviceInfo);
|
||||
ac2dmCnt--;
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
// TODO: return a count of devices we sent to, maybe names as well
|
||||
return OK_STATUS;
|
||||
} else {
|
||||
// Show the 'no devices' if only the browser is registered.
|
||||
// We should also clarify that 'error status' mean no matching
|
||||
// device found ( when the extension allow specifying the destination )
|
||||
if (ac2dmCnt == 0 && "ac2dm".equals(deviceType)) {
|
||||
log.warning("No device registered for " + reqInfo.userName);
|
||||
return DEVICE_NOT_REGISTERED_STATUS;
|
||||
} else {
|
||||
resp.setStatus(500);
|
||||
resp.getWriter().println(ERROR_STATUS + " (Unable to send link)");
|
||||
return false;
|
||||
return ERROR_STATUS + " (Unable to send link)";
|
||||
}
|
||||
} finally {
|
||||
pm.close();
|
||||
}
|
||||
}
|
||||
|
||||
boolean doSendViaC2dm(String url, String title, String sel, C2DMessaging push,
|
||||
String collapseKey, DeviceInfo deviceInfo) throws IOException {
|
||||
private boolean doSendViaC2dm(String url, String title, String sel, C2DMessaging push,
|
||||
String collapseKey, DeviceInfo deviceInfo, boolean reqDebug) throws IOException {
|
||||
|
||||
// Trim title, sel if needed.
|
||||
if (url.length() + title.length() + sel.length() > 1000) {
|
||||
@@ -201,11 +180,11 @@ public class SendServlet extends HttpServlet {
|
||||
"url", url,
|
||||
"title", title,
|
||||
"sel", sel,
|
||||
"debug", deviceInfo.getDebug() ? "1" : null);
|
||||
"debug", deviceInfo.getDebug() || reqDebug ? "1" : null);
|
||||
return res;
|
||||
}
|
||||
|
||||
boolean doSendViaBrowserChannel(String url, DeviceInfo deviceInfo) {
|
||||
private boolean doSendViaBrowserChannel(String url, DeviceInfo deviceInfo) {
|
||||
String channelToken = deviceInfo.getDeviceRegistrationID();
|
||||
ChannelServiceFactory.getChannelService().sendMessage(
|
||||
new ChannelMessage(channelToken, url));
|
||||
|
||||
@@ -17,24 +17,13 @@
|
||||
package com.google.android.chrometophone.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.jdo.JDOObjectNotFoundException;
|
||||
import javax.jdo.PersistenceManager;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.google.android.c2dm.server.C2DMessaging;
|
||||
import com.google.appengine.api.users.User;
|
||||
import com.google.appengine.api.users.UserService;
|
||||
import com.google.appengine.api.users.UserServiceFactory;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class UnregisterServlet extends HttpServlet {
|
||||
private static final Logger log =
|
||||
Logger.getLogger(RegisterServlet.class.getName());
|
||||
private static final String OK_STATUS = "OK";
|
||||
private static final String ERROR_STATUS = "ERROR";
|
||||
|
||||
@@ -42,50 +31,19 @@ public class UnregisterServlet extends HttpServlet {
|
||||
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
resp.setContentType("text/plain");
|
||||
|
||||
// Basic XSRF protection
|
||||
if (req.getHeader("X-Same-Domain") == null) {
|
||||
resp.setStatus(400);
|
||||
resp.getWriter().println(ERROR_STATUS + " (Missing X-Same-Domain header)");
|
||||
RequestInfo reqInfo = RequestInfo.processRequest(req, resp,
|
||||
getServletContext());
|
||||
if (reqInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String deviceRegistrationID = req.getParameter("devregid");
|
||||
if (deviceRegistrationID == null) {
|
||||
if (reqInfo.deviceRegistrationID == null) {
|
||||
resp.setStatus(400);
|
||||
resp.getWriter().println(ERROR_STATUS + " (Must specify devregid)");
|
||||
return;
|
||||
}
|
||||
|
||||
// Authorize & store device info
|
||||
UserService userService = UserServiceFactory.getUserService();
|
||||
User user = userService.getCurrentUser();
|
||||
if (user != null) {
|
||||
PersistenceManager pm =
|
||||
C2DMessaging.getPMF(getServletContext()).getPersistenceManager();
|
||||
try {
|
||||
List<DeviceInfo> registrations = DeviceInfo.getDeviceInfoForUser(pm, user.getEmail());
|
||||
for (int i = 0; i < registrations.size(); i++) {
|
||||
DeviceInfo deviceInfo = registrations.get(i);
|
||||
if (deviceInfo.getDeviceRegistrationID().equals(deviceRegistrationID)) {
|
||||
pm.deletePersistent(deviceInfo);
|
||||
// Keep looping in case of duplicates
|
||||
}
|
||||
}
|
||||
|
||||
resp.getWriter().println(OK_STATUS);
|
||||
} catch (JDOObjectNotFoundException e) {
|
||||
resp.setStatus(400);
|
||||
resp.getWriter().println(ERROR_STATUS + " (User unknown)");
|
||||
log.warning("User unknown");
|
||||
} catch (Exception e) {
|
||||
resp.setStatus(500);
|
||||
resp.getWriter().println(ERROR_STATUS + " (Error unregistering device)");
|
||||
log.warning("Error unregistering device: " + e.getMessage());
|
||||
} finally {
|
||||
pm.close();
|
||||
}
|
||||
} else {
|
||||
resp.getWriter().println(ERROR_STATUS + " (Not authorized)");
|
||||
}
|
||||
reqInfo.deleteRegistration(reqInfo.deviceRegistrationID);
|
||||
resp.getWriter().println(OK_STATUS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.google.appengine.api.users.User;
|
||||
import com.google.appengine.api.xmpp.JID;
|
||||
import com.google.appengine.api.xmpp.Message;
|
||||
import com.google.appengine.api.xmpp.MessageBuilder;
|
||||
import com.google.appengine.api.xmpp.XMPPService;
|
||||
import com.google.appengine.api.xmpp.XMPPServiceFactory;
|
||||
|
||||
@@ -58,6 +58,10 @@ public class XMPPSendServlet extends SendServlet {
|
||||
if (resIdx > 0) {
|
||||
jid = jid.substring(0, resIdx);
|
||||
}
|
||||
if (body.equals("register")) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, String> params = new HashMap<String, String>();
|
||||
|
||||
@@ -87,8 +91,17 @@ public class XMPPSendServlet extends SendServlet {
|
||||
|
||||
|
||||
log.info("Sending " + jid);
|
||||
doSendToDevice(url, title, sel, jid,
|
||||
RequestInfo reqInfo = new RequestInfo(jid, getServletContext());
|
||||
|
||||
String id = doSendToDevice(url, title, sel, reqInfo,
|
||||
deviceName == null ? null : new String[] {deviceName},
|
||||
deviceType, resp);
|
||||
deviceType);
|
||||
// Confirm
|
||||
Message respmsg =
|
||||
new MessageBuilder()
|
||||
.withBody(id)
|
||||
.withRecipientJids(fromJid)
|
||||
.withFromJid(message.getRecipientJids()[0]).build();
|
||||
xmpp.sendMessage(respmsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
-->
|
||||
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
|
||||
<application>chrometophone</application>
|
||||
<version>8</version>
|
||||
<version>9</version>
|
||||
<system-properties>
|
||||
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
|
||||
</system-properties>
|
||||
|
||||
@@ -24,11 +24,18 @@
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>UnregisterServlet</servlet-name>
|
||||
<servlet-class>
|
||||
com.google.android.chrometophone.server.UnregisterServlet
|
||||
</servlet-class>
|
||||
</servlet>
|
||||
<servlet-name>UnregisterServlet</servlet-name>
|
||||
<servlet-class>
|
||||
com.google.android.chrometophone.server.UnregisterServlet
|
||||
</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>DebugServlet</servlet-name>
|
||||
<servlet-class>
|
||||
com.google.android.chrometophone.server.DebugServlet
|
||||
</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>SendServlet</servlet-name>
|
||||
@@ -36,34 +43,39 @@
|
||||
</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>XMPPSendServlet</servlet-name>
|
||||
<servlet-class>com.google.android.chrometophone.server.XMPPSendServlet
|
||||
</servlet-class>
|
||||
</servlet>
|
||||
<servlet>
|
||||
<servlet-name>XMPPSendServlet</servlet-name>
|
||||
<servlet-class>com.google.android.chrometophone.server.XMPPSendServlet
|
||||
</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>AuthServlet</servlet-name>
|
||||
<servlet-class>com.google.android.chrometophone.server.AuthServlet
|
||||
</servlet-class>
|
||||
</servlet>
|
||||
<servlet>
|
||||
<servlet-name>AuthServlet</servlet-name>
|
||||
<servlet-class>com.google.android.chrometophone.server.AuthServlet
|
||||
</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>dataMessagingServlet</servlet-name>
|
||||
<servlet-class>
|
||||
com.google.android.c2dm.server.C2DMRetryServlet
|
||||
<servlet>
|
||||
<servlet-name>dataMessagingServlet</servlet-name>
|
||||
<servlet-class>
|
||||
com.google.android.c2dm.server.C2DMRetryServlet
|
||||
</servlet-class>
|
||||
</servlet>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>RegisterServlet</servlet-name>
|
||||
<url-pattern>/register</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>UnregisterServlet</servlet-name>
|
||||
<url-pattern>/unregister</url-pattern>
|
||||
</servlet-mapping>
|
||||
<servlet-mapping>
|
||||
<servlet-name>UnregisterServlet</servlet-name>
|
||||
<url-pattern>/unregister</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>DebugServlet</servlet-name>
|
||||
<url-pattern>/debug</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>XMPPSendServlet</servlet-name>
|
||||
|
||||
Reference in New Issue
Block a user