blob: 332bcdb00e7f94ff03746d3f15750eebd9d607a6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.modules.httpserver;
import java.awt.Dialog;
import java.util.Hashtable;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Properties;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.prefs.Preferences;
import javax.swing.event.EventListenerList;
import org.openide.DialogDescriptor;
import org.openide.util.NbBundle;
import org.openide.util.HelpCtx;
import org.openide.util.Utilities;
import org.openide.NotifyDescriptor;
import org.openide.DialogDisplayer;
import org.openide.nodes.BeanNode;
import org.openide.util.NbPreferences;
/** Options for http server
*
* @author Ales Novak, Petr Jiricka
*/
public final class HttpServerSettings {
private static HttpServerSettings INSTANCE = new HttpServerSettings();
private static BeanNode view = null;
private static final int MAX_START_RETRIES = 20;
private static int currentRetries = 0;
protected static final EventListenerList listenerList = new EventListenerList();
/** Has this been initialized ?
* Becomes true if a "running" getter or setter is called
*/
static boolean inited = false;
/** Contains threads which are or will be asking for access for the given IP address. */
private static Hashtable<InetAddress,Thread> whoAsking = new Hashtable<InetAddress,Thread>();
public static final int SERVER_STARTUP_TIMEOUT = 3000;
/** constant for local host */
public static final String LOCALHOST = "local"; // NOI18N
/** constant for any host */
public static final String ANYHOST = "any"; // NOI18N
public static HostProperty hostProperty = null;
public static final String PROP_PORT = "port"; // NOI18N
public static final String PROP_HOST_PROPERTY = "hostProperty"; // NOI18N
static final String PROP_WRAPPER_BASEURL = "wrapperBaseURL"; // NOI18N
public static final String PROP_RUNNING = "running"; // NOI18N
private static final String PROP_SHOW_GRANT_ACCESS = "showGrantAccess"; // NOI18N
/** port */
private static final int DEFAULT_PORT = 8082;
/** mapping of wrapper to URL */
private static String wrapperBaseURL = "/resource/"; // NOI18N
/** Reflects whether the server is actually running, not the running property */
static boolean running = false;
private static boolean startStopMessages = true;
private static Properties mappedServlets = new Properties();
/** http settings
* @deprecated use <CODE>SharedClassObject.findObject()</CODE>
*/
public static HttpServerSettings OPTIONS = null;
/** Lock for the httpserver operations */
private static Object httpLock;
private static Preferences getPreferences() {
return NbPreferences.forModule(HttpServerSettings.class);
}
/**
* Obtains lock for httpserver synchronization
*/
static final Object httpLock () {
if (httpLock == null) {
httpLock = new Object ();
}
return httpLock;
}
private HttpServerSettings() {
}
public static HttpServerSettings getDefault() {
return INSTANCE;
}
/** getter for running status */
public boolean isRunning() {
if (inited) {
return running;
}
else {
// this used to be true, but it seems more reasonable not to start the server by default
// Fixes bug 11347
setRunning(false);
return running;
}
}
/** Intended to be called by the thread which succeeded to start the server */
void runSuccess() {
synchronized (httpLock ()) {
currentRetries = 0;
running = true;
httpLock ().notifyAll();
}
}
/** Intended to be called by the thread which failed to start the server.
* It decides whether try to start server on next port or show appropriate
* error message.
*/
void runFailure(Throwable t) {
running = false;
if (t instanceof IncompatibleClassChangeError) {
// likely there is a wrong servlet API version on CLASSPATH
DialogDisplayer.getDefault ().notify(new NotifyDescriptor.Message(
NbBundle.getMessage (HttpServerSettings.class, "MSG_HTTP_SERVER_incompatbleClasses"),
NotifyDescriptor.Message.WARNING_MESSAGE));
}
else if (t instanceof java.net.BindException) {
// can't open socket - we can retry
currentRetries ++;
if (currentRetries <= MAX_START_RETRIES) {
setPort(getPort() + 1);
setRunning(true);
}
else {
currentRetries = 0;
DialogDisplayer.getDefault ().notify(new NotifyDescriptor.Message(
NbBundle.getMessage (HttpServerSettings.class, "MSG_HTTP_SERVER_START_FAIL"),
NotifyDescriptor.Message.WARNING_MESSAGE));
int p = getPort ();
if (p < 1024 && inited && Utilities.isUnix()) {
DialogDisplayer.getDefault ().notify(new NotifyDescriptor.Message(
NbBundle.getMessage (HttpServerSettings.class, "MSG_onlyRootOnUnix"),
NotifyDescriptor.WARNING_MESSAGE));
}
}
}
else {
// unknown problem
DialogDisplayer.getDefault ().notify(new NotifyDescriptor.Message(
NbBundle.getMessage (HttpServerSettings.class, "MSG_HTTP_SERVER_START_FAIL_unknown"),
NotifyDescriptor.Message.WARNING_MESSAGE));
}
}
/** Restarts the server if it is running - must be called in a synchronized block
* No need to restart if it is called during deserialization.
*/
private void restartIfNecessary(boolean printMessages) {
if (running) {
if (!printMessages)
setStartStopMessages(false);
HttpServerModule.stopHTTPServer();
HttpServerModule.initHTTPServer();
// messages will be enabled by the server thread
}
}
/** Returns a relative directory URL with a leading and a trailing slash */
private String getCanonicalRelativeURL(String url) {
String newURL;
if (url.length() == 0)
newURL = "/"; // NOI18N
else {
if (url.charAt(0) != '/')
newURL = "/" + url; // NOI18N
else
newURL = url;
if (newURL.charAt(newURL.length() - 1) != '/')
newURL = newURL + "/"; // NOI18N
}
return newURL;
}
/** setter for running status */
public void setRunning(boolean running) {
inited = true;
if (this.running == running)
return;
synchronized (httpLock ()) {
if (running) {
// running status is set by another thread
HttpServerModule.initHTTPServer();
}
else {
this.running = false;
HttpServerModule.stopHTTPServer();
}
}
}
// NOT publicly available
/** getter for classpath base */
String getWrapperBaseURL() {
return wrapperBaseURL;
}
/** setter for classpath base */
void setWrapperBaseURL(String wrapperBaseURL) {
// canonical form starts and ends with a /
String oldURL;
String newURL = getCanonicalRelativeURL(wrapperBaseURL);
// check if any change is taking place
if (this.wrapperBaseURL.equals(newURL))
return;
// implement the change
synchronized (httpLock ()) {
oldURL = this.wrapperBaseURL;
this.wrapperBaseURL = newURL;
restartIfNecessary(false);
}
}
/** setter for port */
public void setPort(int p) {
if (p <= 0 || p >65535) {
NotifyDescriptor.Message msg = new NotifyDescriptor.Message(
NbBundle.getMessage(HttpServerSettings.class, "ERR_PortNumberOutOfRange", Integer.valueOf(p)), NotifyDescriptor.ERROR_MESSAGE);
DialogDisplayer.getDefault().notify(msg);
return;
}
synchronized (httpLock ()) {
getPreferences().putInt(PROP_PORT,p);
restartIfNecessary(true);
}
}
/** getter for port */
public int getPort() {
return getPreferences().getInt(PROP_PORT, DEFAULT_PORT);
}
public void setStartStopMessages(boolean ssm) {
startStopMessages = ssm;
}
public boolean isStartStopMessages() {
return startStopMessages;
}
public HelpCtx getHelpCtx () {
return new HelpCtx (HttpServerSettings.class);
}
/** Returns string for localhost */
private String getLocalHost() {
try {
return InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException e) {
return "localhost"; // NOI18N
}
}
public void addGrantAccessListener(GrantAccessListener l) {
listenerList.add(GrantAccessListener.class, l);
}
public void removeGrantAccessListener(GrantAccessListener l) {
listenerList.remove(GrantAccessListener.class, l);
}
/** Returns true if oneof the listeners allowed access */
protected boolean fireGrantAccessEvent(InetAddress clientAddress, String resource) {
Object[] listeners = listenerList.getListenerList();
GrantAccessEvent grantAccessEvent = null;
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==GrantAccessListener.class) {
if (grantAccessEvent == null)
grantAccessEvent = new GrantAccessEvent(this, clientAddress, resource);
((GrantAccessListener)listeners[i+1]).grantAccess(grantAccessEvent);
}
}
return (grantAccessEvent == null) ? false : grantAccessEvent.isGranted();
}
/** Requests access for address addr. If necessary asks the user. Returns true it the access
* has been granted. */
boolean allowAccess(InetAddress addr, String requestPath) {
if (accessAllowedNow(addr, requestPath))
return true;
Thread askThread = null;
synchronized (whoAsking) {
// one more test in the synchronized block
if (accessAllowedNow(addr, requestPath))
return true;
askThread = (Thread)whoAsking.get(addr);
if (askThread == null) {
askThread = Thread.currentThread();
whoAsking.put(addr, askThread);
}
}
// now ask the user
synchronized (HttpServerSettings.class) {
if (askThread != Thread.currentThread()) {
return accessAllowedNow(addr, requestPath);
}
try {
if (!isShowGrantAccessDialog ())
return false;
String msg = NbBundle.getMessage (HttpServerSettings.class, "MSG_AddAddress", addr.getHostAddress ());
final GrantAccessPanel panel = new GrantAccessPanel (msg);
DialogDescriptor descriptor = new DialogDescriptor (
panel,
NbBundle.getMessage (HttpServerSettings.class, "CTL_GrantAccessTitle"),
true,
NotifyDescriptor.YES_NO_OPTION,
NotifyDescriptor.NO_OPTION,
null
);
descriptor.setMessageType (NotifyDescriptor.QUESTION_MESSAGE);
// descriptor.setOptionsAlign (DialogDescriptor.BOTTOM_ALIGN);
final Dialog d = DialogDisplayer.getDefault ().createDialog (descriptor);
d.setSize (580, 180);
d.setVisible(true);
setShowGrantAccessDialog (panel.getShowDialog ());
if (NotifyDescriptor.YES_OPTION.equals(descriptor.getValue ())) {
appendAddressToGranted(addr.getHostAddress());
return true;
}
else
return false;
}
finally {
whoAsking.remove(addr);
}
} // end synchronized
}
/** Checks whether access to the server is now allowed. */
private boolean accessAllowedNow(InetAddress addr, String resource) {
if (hostProperty.getHost().equals(HttpServerSettings.ANYHOST))
return true;
Set hs = getGrantedAddressesSet();
if (hs.contains(addr.getHostAddress()))
return true;
if (fireGrantAccessEvent(addr, resource))
return true;
return false;
}
/** Appends the address to the list of addresses which have been granted access. */
private void appendAddressToGranted(String addr) {
synchronized (httpLock ()) {
String granted = hostProperty.getGrantedAddresses().trim();
if ((granted.length() > 0) &&
(granted.charAt(granted.length() - 1) != ';') &&
(granted.charAt(granted.length() - 1) != ','))
granted += ',';
granted += addr;
hostProperty.setGrantedAddresses(granted);
}
}
/** Returns a list of addresses which have been granted access to the web server,
* including the localhost. Addresses are represented as strings. */
Set<String> getGrantedAddressesSet() {
HashSet<String> addr = new HashSet<String>();
try {
addr.add(InetAddress.getByName("localhost").getHostAddress()); // NOI18N
addr.add(InetAddress.getLocalHost().getHostAddress());
}
catch (UnknownHostException e) {}
StringTokenizer st = new StringTokenizer(hostProperty.getGrantedAddresses(), ",;"); // NOI18N
while (st.hasMoreTokens()) {
String ipa = st.nextToken();
ipa = ipa.trim();
try {
addr.add(InetAddress.getByName(ipa).getHostAddress());
}
catch (UnknownHostException e) {}
}
return addr;
}
Properties getMappedServlets() {
return mappedServlets;
}
/** Converts string into string that is usable in URL.
* This mangling changes some characters
*/
static String mangle (String name) {
StringBuffer sb = new StringBuffer ();
for (int i = 0; i < name.length (); i++) {
if (Character.isLetterOrDigit (name.charAt (i)) ||
name.charAt (i) == '.') {
sb.append (name.charAt (i));
}
else {
String code = Integer.toHexString ((int)name.charAt (i)).toUpperCase ();
if (code.length ()<2)
code = (code.length () == 0)? "00": "0"+code; // NOI18N
sb.append ("%"). // NOI18N
append ((code.length () == 2)? code: code.substring (code.length ()-2));
}
}
return sb.toString ();
}
/** Unconverts string from URL into old string.
* This mangling decodes '%xy'
*/
static String demangle (String name) {
StringBuffer sb = new StringBuffer ();
try {
for (int i = 0; i < name.length (); i++) {
if (name.charAt (i) != '%') {
sb.append (name.charAt (i));
}
else {
sb.append ((char)Integer.parseInt (name.substring (i+1, i+3), 16));
i += 2;
}
}
}
catch (NumberFormatException ex) {
ex.printStackTrace ();
return ""; // NOI18N
}
return sb.toString ();
}
/** Getter for property hostProperty.
* @return Value of property hostProperty.
*/
public HttpServerSettings.HostProperty getHostProperty () {
if (hostProperty == null) {
hostProperty = new HostProperty(getPreferences().get("grantedAddresses",""),
getPreferences().get("host",LOCALHOST));
}
return hostProperty;
}
/** Setter for property hostProperty.
* @param hostProperty New value of property hostProperty.
*/
public void setHostProperty (HttpServerSettings.HostProperty hostProperty) {
if (ANYHOST.equals(hostProperty.getHost ()) || LOCALHOST.equals(hostProperty.getHost ())) {
this.hostProperty.setHost(hostProperty.getHost());
this.hostProperty.setGrantedAddresses(hostProperty.getGrantedAddresses());
getPreferences().put("host", hostProperty.getHost());//NOI18N
getPreferences().put("grantedAddresses", hostProperty.getGrantedAddresses());//NOI18N
}
}
public boolean isShowGrantAccessDialog () {
return getPreferences().getBoolean(PROP_SHOW_GRANT_ACCESS, true);
}
public void setShowGrantAccessDialog (boolean show) {
getPreferences().putBoolean(PROP_SHOW_GRANT_ACCESS,show);
}
/** Property value that describes set of host with granted access
*/
public static class HostProperty implements java.io.Serializable {
private String grantedAddresses;
private String host;
private static final long serialVersionUID = 1927848926692414249L;
HostProperty (String grantedAddresses, String host) {
this.grantedAddresses = grantedAddresses;
this.host = host;
}
/** Getter for property host.
* @return Value of property host.
*/
public String getHost () {
return host;
}
/** Setter for property host.
* @param host New value of property host.
*/
public void setHost (String host) {
this.host = host;
}
/** Getter for property grantedAddresses.
* @return Value of property grantedAddresses.
*/
public String getGrantedAddresses () {
return grantedAddresses;
}
/** Setter for property grantedAddresses.
* @param grantedAddresses New value of property grantedAddresses.
*/
public void setGrantedAddresses (String grantedAddresses) {
this.grantedAddresses = grantedAddresses;
}
}
}