/*
* P2P-Radio - Peer to peer streaming system
* Project homepage: http://p2p-radio.sourceforge.net/
* Copyright (C) 2003-2004 Michael Kaufmann <hallo@michael-kaufmann.ch>
*
* ---------------------------------------------------------------------------
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* ---------------------------------------------------------------------------
*/
package p2pradio.webinterface;
import p2pradio.*;
import p2pradio.io.*;
import p2pradio.logging.Logger;
import p2pradio.players.*;
import p2pradio.packets.MetadataPacket;
import p2pradio.tools.StringReplacer;
import java.io.*;
import java.net.*;
import java.util.*;
/**
* A simple web server that provides a page with status information.
*
* @author Michael Kaufmann
*/
public class WebServer extends Thread
{
private Peer peer;
private Player player;
private ServerSocket tcpSocket;
// Ist der WebServer bereit, um Verbindungen anzunehmen?
private boolean isReady = false;
public static final int REFRESH_PERIOD = 30;
/**
* Creates a Web Server.
*
* @param peer The peer whose status information will be displayed
* @param player The player with additional status information (currently only {@link HttpPlayer} is supported)
*/
public WebServer(Peer peer, Player player)
{
super(Messages.getString("WebServer.THREAD_NAME")); //$NON-NLS-1$
setDaemon(true);
this.peer = peer;
this.player = player;
}
public void run()
{
try
{
tcpSocket = new ServerSocket(peer.getSocketAddress().getPort() + Peer.WEBINTERFACE_PORT_OFFSET);
}
catch (Exception e)
{
Logger.warning("WebServer", "COULD_NOT_CREATE_TCP_SERVERSOCKET", e); //$NON-NLS-1$ //$NON-NLS-2$
return;
}
Logger.finer("WebServer", "WebServer.THREAD_RUNNING_PORT_NUMBER", new Integer(tcpSocket.getLocalPort())); //$NON-NLS-1$ //$NON-NLS-2$
isReady = true;
while(true)
{
Socket newSocket = null;
HttpInputStream inputStream = null;
OutputStream outputStream = null;
try
{
isReady = true;
newSocket = tcpSocket.accept();
}
catch (IOException e)
{
Logger.fine("WebServer", "IO_ERROR", e); //$NON-NLS-1$ //$NON-NLS-2$
continue;
}
try
{
inputStream = new HttpInputStream(newSocket.getInputStream());
outputStream = new BufferedOutputStream(newSocket.getOutputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
NewLineWriter writer = new NewLineWriter(new OutputStreamWriter(outputStream), NewLineWriter.CRLF);
// Die Anfrage lesen
// Beispiel: "GET / HTTP/1.0"
String line = reader.readLine();
if (line == null)
{
Logger.fine("WebServer", "WebServer.STREAM_FROM_BROWSER_ENDED"); //$NON-NLS-1$ //$NON-NLS-2$
continue;
}
line = line.trim();
int firstSpacePos = line.indexOf(" "); //$NON-NLS-1$
if (firstSpacePos == -1)
{
// Ung�ltige Anfrage
Logger.fine("WebServer", "INVALID_REQUEST"); //$NON-NLS-1$ //$NON-NLS-2$
continue;
}
String method = line.substring(0, firstSpacePos).toLowerCase();
if (!(method.equals("get") || method.equals("head"))) //$NON-NLS-1$ //$NON-NLS-2$
{
// Ung�ltige Anfrage
Logger.fine("WebServer", "INVALID_REQUEST"); //$NON-NLS-1$ //$NON-NLS-2$
continue;
}
int secondSpacePos = line.indexOf(" ", firstSpacePos+1); //$NON-NLS-1$
if (secondSpacePos == -1)
{
// Kein zweites Leerzeichen
Logger.fine("WebServer", "INVALID_REQUEST"); //$NON-NLS-1$ //$NON-NLS-2$
continue;
}
String requestURL = line.substring(firstSpacePos+1, secondSpacePos).toLowerCase();
if (requestURL.length() == 0)
{
// Ung�ltige URL
Logger.fine("WebServer", "INVALID_REQUEST"); //$NON-NLS-1$ //$NON-NLS-2$
continue;
}
String protocol = line.substring(secondSpacePos+1, line.length()).toLowerCase();
protocol = protocol.trim();
// Version entfernen (statt "HTTP/1.0" nur "HTTP")
int slashPos = protocol.indexOf("/"); //$NON-NLS-1$
if (slashPos != -1)
{
protocol = protocol.substring(0, slashPos);
}
if (!protocol.equals("http")) //$NON-NLS-1$
{
// Unbekanntes Protokoll
Logger.fine("WebServer", "UNKNOWN_PROTOCOL", protocol); //$NON-NLS-1$ //$NON-NLS-2$
return;
}
// Alle Headerzeilen �berlesen
boolean error = false;
while (true)
{
line = reader.readLine();
if ((line == null) || (line.length() == 0))
{
break;
}
int colonPos = line.indexOf(":"); //$NON-NLS-1$
if (colonPos == -1)
{
// Kein Doppelpunkt kommt vor - es kann keine
// Headerzeile mehr sein
error = true;
break;
}
/*
String key = line.substring(0, colonPos);
String value = line.substring(colonPos+1, line.length());
key = key.trim();
value = value.trim();
String keyLowerCase = key.toLowerCase();
if (keyLowerCase.equals("user-agent"))
{
debugMessage("Zugriff von: " + value);
}
*/
}
if (error)
{
// N�chste Anfrage
continue;
}
// Anfrage bearbeiten
if (method.equals("get")) //$NON-NLS-1$
{
// URL spielt keine Rolle
printInformation(writer);
}
else
if (method.equals("head")) //$NON-NLS-1$
{
writer.writeln("HTTP/1.0 200 OK"); //$NON-NLS-1$
writer.writeln("Content-type: text/html"); //$NON-NLS-1$
writer.writeln();
writer.flush();
continue;
}
}
catch (IOException e)
{
Logger.fine("WebServer", "IO_ERROR", e); //$NON-NLS-1$ //$NON-NLS-2$
}
catch (Exception e)
{
Logger.severe("WebServer", "INTERNAL_ERROR", e); //$NON-NLS-1$ //$NON-NLS-2$
}
finally
{
if (inputStream != null)
{
try
{
inputStream.close();
}
catch (IOException e)
{
}
}
if (outputStream != null)
{
try
{
outputStream.close();
}
catch (IOException e)
{
}
}
if (newSocket != null)
{
try
{
newSocket.close();
}
catch (IOException e)
{
}
}
}
}
}
protected void printInformation(NewLineWriter writer) throws IOException
{
writer.writeln("HTTP/1.0 200 OK"); //$NON-NLS-1$
writer.writeln("Content-type: text/html"); //$NON-NLS-1$
writer.writeln();
writer.setNewLineMark(NewLineWriter.LF);
writer.writeln("<HTML>"); //$NON-NLS-1$
writer.writeln("<HEAD>"); //$NON-NLS-1$
writer.writeln("<TITLE>" + textToHTML(Radio.Name) + "</TITLE>"); //$NON-NLS-1$ //$NON-NLS-2$
writer.writeln("<STYLE TYPE=\"text/css\">"); //$NON-NLS-1$
writer.writeln("<!--"); //$NON-NLS-1$
writer.writeln("body, h1, h2, th, td, p, ul, li { font-family: Arial, Helvetica, sans-serif }"); //$NON-NLS-1$
writer.writeln("th { text-align: right }"); //$NON-NLS-1$
writer.writeln("table.freeloader th { text-align: left }"); //$NON-NLS-1$
writer.writeln("-->"); //$NON-NLS-1$
writer.writeln("</STYLE>"); //$NON-NLS-1$
writer.writeln("<META HTTP-EQUIV=\"refresh\" CONTENT=\"" + REFRESH_PERIOD + "; URL=http://" + getWebHost(peer) + "/\">"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
writer.writeln("<META HTTP-EQUIV=\"Content-type\" CONTENT=\"text/html; charset=ISO-8859-1\">"); //$NON-NLS-1$
writer.writeln("</HEAD>"); //$NON-NLS-1$
writer.writeln("<BODY>"); //$NON-NLS-1$
writer.writeln("<H1>" + textToHTML(Messages.getString("WebServer.TITLE", new Object[]{Radio.Name, peer.getSocketAddress()})) + "</H1>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
MetadataPacket metadataPacket = peer.getBuffer().getNewestMetadataPacket();
if (metadataPacket != null)
{
Metadata metadata = metadataPacket.getMetadata();
writer.writeln("<H2>" + textToHTML(Messages.getString("WebServer.STREAM")) + "</H2>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
writer.writeln("<TABLE BORDER>"); //$NON-NLS-1$
writer.writeln("<TR><TH>" + textToHTML(Messages.getString("WebServer.STATION_NAME")) + "</TH><TD>" + makeLinkOfName(metadata.getStationName(), metadata.getStationURL()) + "</TD></TR>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
// Hack
if (metadata.getContentType().toLowerCase().equals("audio/mpeg") || metadata.getContentType().toLowerCase().equals("application/ogg")) //$NON-NLS-1$ //$NON-NLS-2$
{
writer.writeln("<TR><TH>" + textToHTML(Messages.getString("WebServer.CURRENT_SONG")) + "</TH><TD>" + makeLinkOfName(metadata.getSongTitle(), metadata.getSongURL()) + "</TD></TR>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
else if (metadata.getContentType().toLowerCase().equals("video/nsv")) //$NON-NLS-1$ //$NON-NLS-2$
{
writer.writeln("<TR><TH>" + textToHTML(Messages.getString("WebServer.CURRENT_VIDEO")) + "</TH><TD>" + makeLinkOfName(metadata.getSongTitle(), metadata.getSongURL()) + "</TD></TR>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
else
{
writer.writeln("<TR><TH>" + textToHTML(Messages.getString("WebServer.CURRENT_CONTENT")) + "</TH><TD>" + makeLinkOfName(metadata.getSongTitle(), metadata.getSongURL()) + "</TD></TR>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
writer.writeln("<TR><TH>" + textToHTML(Messages.getString("WebServer.STATION_GENRE")) + "</TH><TD>" + textToHTML(metadata.getGenre()) + "</TD></TR>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
writer.writeln("<TR><TH>" + textToHTML(Messages.getString("WebServer.STATION_DESCRIPTION")) + "</TH><TD>" + textToHTML(metadata.getDescription()) + "</TD></TR>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
writer.writeln("<TR><TH>" + textToHTML(Messages.getString("WebServer.BIT_RATE")) + "</TH><TD>" + textToHTML(Messages.getString("WebServer.BIT_RATE_VALUE", new Integer(metadata.getAverageByterate() / 125))) + "</TD></TR>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
writer.writeln("</TABLE>"); //$NON-NLS-1$
/*
if (player instanceof ShoutcastPlayer)
{
writer.writeln("<H2>Zusätzliche Shoutcast-Metadaten:</H2>");
writer.writeln("<TABLE BORDER>");
Enumeration enumeration = metadata.propertyNames();
while (enumeration.hasMoreElements())
{
String key = (String)enumeration.nextElement();
boolean found = false;
for (int i=0; i < Metadata.knownMetadataKeys.length; i++)
{
if (Metadata.knownMetadataKeys[i].equals(key))
{
found = true;
break;
}
}
if (found)
{
continue;
}
String value = (String)metadata.getProperty(key);
writer.writeln("<TR><TH>" + textToHTML(key) + ":</TH><TD>" + textToHTML(value) + "</TD></TR>");
}
writer.writeln("</TABLE>");
}
*/
}
if (player instanceof HttpPlayer)
{
HttpPlayer shoutcastPlayer = (HttpPlayer)player;
writer.writeln("<P><A HREF=\"" + shoutcastPlayer.getPlaylistURL() + "\">" + textToHTML(Messages.getString("WebServer.PLAY_THIS_STATION")) + "</A></P>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
writer.writeln("<P>" + textToHTML(Messages.getString("WebServer.STREAM_ADDRESS")) + " <TT>" + shoutcastPlayer.getStreamURL() + "</TT></P>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
synchronized(peer)
{
writer.writeln("<H2>" + textToHTML(Messages.getString("WebServer.P2P_NETWORK")) + "</H2>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
writer.writeln("<TABLE BORDER>"); //$NON-NLS-1$
if (peer.isServer())
{
writer.writeln("<TR><TH>" + textToHTML(Messages.getString("WebServer.BROADCASTER")) + "</TH><TD><EM>" + textToHTML(Messages.getString("WebServer.THAT_IS_ME")) + "</EM></TD></TR>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
}
else
{
if (peer.getServer() != null)
{
writer.writeln("<TR><TH>" + textToHTML(Messages.getString("WebServer.BROADCASTER")) + "</TH><TD>" + makeLink(peer.getServer()) + "</TD></TR>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
else
{
writer.writeln("<TR><TH>" + textToHTML(Messages.getString("WebServer.BROADCASTER")) + "</TH><TD><EM>" + textToHTML(Messages.getString("WebServer.MISSING")) + "</EM></TD></TR>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
}
if (peer.getSupplier() != null)
{
writer.writeln("<TR><TH>" + textToHTML(Messages.getString("WebServer.SUPPLIER")) + "</TH><TD>" + makeLink(peer.getSupplier()) + "</TD></TR>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
else
{
writer.writeln("<TR><TH>" + textToHTML(Messages.getString("WebServer.SUPPLIER")) + "</TH><TD><EM>" + textToHTML(Messages.getString("WebServer.MISSING")) + "</EM></TD></TR>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
}
}
writer.write("<TR><TH>" + textToHTML(Messages.getString("WebServer.CHILDREN")) + "</TH><TD>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
Vector children = peer.getChildren();
if (children.size() != 0)
{
for (int i=0; i < children.size(); i++)
{
if (i != 0)
{
writer.write("<BR>"); //$NON-NLS-1$
}
writer.write(makeLink((RemotePeer)children.get(i)));
}
}
else
{
writer.write("<EM>" + textToHTML(Messages.getString("WebServer.NONE")) + "</EM>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
writer.writeln("</TD></TR>"); //$NON-NLS-1$
writer.write("<TR><TH>" + textToHTML(Messages.getString("WebServer.BANNED_PEERS")) + "</TH><TD>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
Vector bannedPeers = peer.getBannedPeers();
if (bannedPeers.size() != 0)
{
for (int i=0; i < bannedPeers.size(); i++)
{
if (i != 0)
{
writer.write("<BR>"); //$NON-NLS-1$
}
writer.write(makeLink((RemotePeer)bannedPeers.get(i)));
}
}
else
{
writer.write("<EM>" + textToHTML(Messages.getString("WebServer.NONE")) + "</EM>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
writer.writeln("</TD></TR>"); //$NON-NLS-1$
writer.writeln("</TABLE>"); //$NON-NLS-1$
writer.writeln("<H2>" + textToHTML(Messages.getString("WebServer.FREELOADER_COMPLAINTS")) + "</H2>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
boolean freeloaderFound = false;
for (int i=0; i < children.size(); i++)
{
RemotePeerData childData = peer.getChildData((RemotePeer)children.get(i));
Vector freeloaderComplainants = childData.getFreeloaderComplainants();
if (freeloaderComplainants.size() != 0)
{
if (!freeloaderFound)
{
freeloaderFound = true;
writer.writeln("<TABLE CLASS=\"freeloader\" BORDER>"); //$NON-NLS-1$
writer.writeln("<TR><TH>" + textToHTML(Messages.getString("WebServer.POSSIBLE_FREELOADER")) + "</TH><TH>" + textToHTML(Messages.getString("WebServer.REPORTED_BY")) + "</TH></TR>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
}
writer.write("<TR><TD>" + makeLink((RemotePeer)children.get(i)) + "</TD><TD>"); //$NON-NLS-1$ //$NON-NLS-2$
for (int j=0; j < freeloaderComplainants.size(); j++)
{
if (j != 0)
{
writer.write("<BR>"); //$NON-NLS-1$
}
writer.write(makeLink((RemotePeer)freeloaderComplainants.get(j)));
}
writer.writeln("</TD></TR>"); //$NON-NLS-1$
}
}
if (freeloaderFound)
{
writer.writeln("</TABLE>"); //$NON-NLS-1$
}
else
{
writer.writeln("<EM>" + textToHTML(Messages.getString("WebServer.NONE")) + "</EM>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
if (player instanceof HttpPlayer)
{
HttpPlayer httpPlayer = (HttpPlayer)player;
// Adresse des eigenen Sockets zusammensetzen
// Nur f�r interne Verwendung
int ownPort = tcpSocket.getLocalPort();
String ownURL ="http://localhost:" + ownPort; //$NON-NLS-1$
writer.writeln("<P><A HREF=\"" + ownURL + "\">" + textToHTML(Messages.getString("WebServer.REFRESH")) + "</A></P>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
// writer.writeln("<P>Sie können diese Informationen auch in Ihrem Web-Browser anschauen: <STRONG>" + ownURL + "/</STRONG><BR>Dort ist es auch möglich, den Media-Player automatisch zu starten und die Informationen der anderen Peers einzusehen.</P>");
writer.writeln(Messages.getString("WebServer.HINTS_HTML", new Object[]{httpPlayer.getStreamURL(), (ownURL + "/"), textToHTML(Messages.getString("WebServer.PLAY_THIS_STATION"))})); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/*
writer.writeln("<HR>");
writer.writeln("<ADDRESS>" + Radio.Name + " " + Radio.Version + "</ADDRESS>");
*/
writer.writeln("</BODY>"); //$NON-NLS-1$
writer.writeln("</HTML>"); //$NON-NLS-1$
writer.flush();
return;
}
private static String makeLink(RemotePeer peer)
{
return "<A HREF=\"http://" + getWebHost(peer) + "/\">" + getRealHost(peer) + "</A>"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
private static String getRealHost(RemotePeer peer)
{
return peer.getSocketAddress().getAddress().getCanonicalHostName() + ":" + peer.getSocketAddress().getPort(); //$NON-NLS-1$
}
private static String getWebHost(RemotePeer peer)
{
return peer.getSocketAddress().getAddress().getHostAddress() + ":" + (peer.getSocketAddress().getPort() + Peer.WEBINTERFACE_PORT_OFFSET); //$NON-NLS-1$
}
private static String makeLinkOfName(String name, String url)
{
if (url.length() == 0)
{
return textToHTML(name);
}
else
{
return "<A HREF=\"" + textToHTML(url) + "\">" + textToHTML(name) + "</A>"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
private static String textToHTML(String text)
{
text = StringReplacer.replaceAll(text, "&", "&"); // muss zuerst gemacht werden! //$NON-NLS-1$ //$NON-NLS-2$
text = StringReplacer.replaceAll(text, "<", "<"); //$NON-NLS-1$ //$NON-NLS-2$
text = StringReplacer.replaceAll(text, ">", ">"); //$NON-NLS-1$ //$NON-NLS-2$
text = StringReplacer.replaceAll(text, "\"", """); //$NON-NLS-1$ //$NON-NLS-2$
text = StringReplacer.replaceAll(text, "�", "Ä"); //$NON-NLS-1$ //$NON-NLS-2$
text = StringReplacer.replaceAll(text, "�", "ä"); //$NON-NLS-1$ //$NON-NLS-2$
text = StringReplacer.replaceAll(text, "�", "Ö"); //$NON-NLS-1$ //$NON-NLS-2$
text = StringReplacer.replaceAll(text, "�", "ö"); //$NON-NLS-1$ //$NON-NLS-2$
text = StringReplacer.replaceAll(text, "�", "Ü"); //$NON-NLS-1$ //$NON-NLS-2$
text = StringReplacer.replaceAll(text, "�", "ü"); //$NON-NLS-1$ //$NON-NLS-2$
return text;
}
/**
* Returns whether this WebServer is ready to accept connections.
*
*/
public boolean isReady()
{
return isReady;
}
}