package net.sf.fmj.media.protocol.httpauth;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.media.MediaLocator;
import javax.media.Time;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.PullDataSource;
import javax.media.protocol.PullSourceStream;
import javax.media.protocol.SourceCloneable;
import com.lti.utils.StringUtils;
import net.sf.fmj.utility.LoggerSingleton;
/**
* HTTPAUTH (pseudo-) protocol handler DataSource.
* This is a pseudo-protocol that allows the username and password for an http datasource to be specified in the URL.
* Copied and modified from URLDataSource.
* The syntax for an httpauth URL is derived from an http URL as follows:
* http://myipcameraimage.com/cam
* with username "user" and password "pass" would become
* httpauth:user:pass@//myipcameraimage.com/cam
* This is simply a convenience data source. It allows things like FMJ studio to play IP camera streams that are password protected, without having to prompt the user.
*
* @author Ken Larson
*
*/
public class DataSource extends PullDataSource implements SourceCloneable
{
private static final Logger logger = LoggerSingleton.logger;
private URLConnection conn;
private boolean connected = false;
private String contentTypeStr;
private ContentDescriptor contentType;
protected URLSourceStream[] sources;
public DataSource()
{ super();
}
public DataSource(URL url)
{ setLocator(new MediaLocator(url));
}
public javax.media.protocol.DataSource createClone()
{
final DataSource d;
try
{
d = new DataSource(getLocator().getURL());
} catch (MalformedURLException e)
{
logger.log(Level.WARNING, "" + e, e);
return null; // according to the API, return null on failure.
}
if (connected)
{
try
{
d.connect();
}
catch (IOException e)
{
logger.log(Level.WARNING, "" + e, e);
return null; // according to the API, return null on failure.
}
}
return d;
}
@Override
public PullSourceStream[] getStreams()
{
if (!connected)
throw new Error("Unconnected source.");
return sources;
}
/** Strips trailing ; and anything after it. Is generally only used for multipart content. */
private String stripTrailer(String contentType)
{ final int index = contentType.indexOf(";");
if (index < 0)
return contentType;
final String result = contentType.substring(0, index);
return result;
}
@Override
public void connect() throws IOException
{
// we allow a re-connection even if we are connected, due to an oddity in the way Manager works. See comments there
// in createPlayer(MediaLocator sourceLocator).
//
// if (connected)
// return;
// example: httpauth:guest:guest@// + real url without http:
final String remainder = getLocator().getRemainder();
final int atIndex = remainder.indexOf('@');
if (atIndex < 0)
throw new IOException("Invalid httpauth url: expected: @");
final int colonIndex = remainder.indexOf(':');
if (colonIndex < 0 || colonIndex > atIndex)
throw new IOException("Invalid httpaut url: expected: :");
final String user = remainder.substring(0, colonIndex);
final String pass = remainder.substring(colonIndex + 1, atIndex);
final String realUrlStr = "http:" + getLocator().getRemainder().substring(atIndex + 1);
conn = new URL(realUrlStr).openConnection();
if (conn instanceof HttpURLConnection)
{ // TODO: this is probably why JMF has explicit HTTP and FTP data sources - so we can check things explicitly.
final HttpURLConnection huc = (HttpURLConnection) conn;
if (user != null && !user.equals(""))
{ huc.setRequestProperty("Authorization", "Basic " + StringUtils.byteArrayToBase64String((user + ":" + pass).getBytes()));
}
huc.connect();
final int code = huc.getResponseCode();
if (!(code >= 200 && code < 300))
{ huc.disconnect();
throw new IOException("HTTP response code: " + code);
}
// TODO: what is the right place to apply ContentDescriptor.mimeTypeToPackageName?
contentTypeStr = ContentDescriptor.mimeTypeToPackageName(stripTrailer(conn.getContentType()));
}
else
{
conn.connect();
// TODO: what is the right place to apply ContentDescriptor.mimeTypeToPackageName?
contentTypeStr = ContentDescriptor.mimeTypeToPackageName(conn.getContentType());
}
contentType = new ContentDescriptor(contentTypeStr);
sources = new URLSourceStream[1];
sources[0] = new URLSourceStream();
connected = true;
}
@Override
public String getContentType()
{ return contentTypeStr;
}
@Override
public void disconnect()
{ if (!connected)
return;
if (conn != null)
{
if (conn instanceof HttpURLConnection)
{
final HttpURLConnection huc = (HttpURLConnection) conn;
huc.disconnect();
}
// TODO: others
}
connected = false;
}
@Override
public void start() throws java.io.IOException
{
//throw new UnsupportedOperationException(); // TODO - what to do?
}
@Override
public void stop() throws java.io.IOException
{ //throw new UnsupportedOperationException(); // TODO - what to do?
}
@Override
public Time getDuration()
{ return Time.TIME_UNKNOWN; // TODO: any case where we know the duration?
}
@Override
public Object[] getControls()
{ return new Object[0];
}
@Override
public Object getControl(String controlName)
{ return null;
}
class URLSourceStream implements PullSourceStream
{
private boolean endOfStream = false;
public boolean endOfStream()
{
return endOfStream;
}
public ContentDescriptor getContentDescriptor()
{
return contentType;
}
public long getContentLength()
{
return conn.getContentLength(); // returns -1 if unknown, which is the same as LENGTH_UNKNOWN
}
public int read(byte[] buffer, int offset, int length) throws IOException
{
final int result = conn.getInputStream().read(buffer, offset, length); // TODO: does this handle the requirement of not returning 0 unless passed in 0?
if (result == -1) // end of stream
endOfStream = true;
return result;
}
public boolean willReadBlock()
{
try
{
return conn.getInputStream().available() <= 0;
} catch (IOException e)
{
return true;
}
}
public Object getControl(String controlType)
{
return null;
}
public Object[] getControls()
{
return new Object[0];
}
}
}