forked from I2P_Developers/i2p.i2p
HTTP Proxy:
- Move error page methods to base - Preliminary code for digest auth
This commit is contained in:
@ -13,6 +13,7 @@ import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
@ -20,7 +21,6 @@ import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.PortMapper;
|
||||
|
||||
@ -58,6 +58,8 @@ import net.i2p.util.PortMapper;
|
||||
*/
|
||||
public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements Runnable {
|
||||
|
||||
private static final String AUTH_REALM = "I2P SSL Proxy";
|
||||
|
||||
private final static byte[] ERR_DESTINATION_UNKNOWN =
|
||||
("HTTP/1.1 503 Service Unavailable\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
@ -94,7 +96,7 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
"Content-Type: text/html; charset=UTF-8\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.5\r\n" + // try to get a UTF-8-encoded response back for the password
|
||||
"Proxy-Authenticate: Basic realm=\"I2P SSL Proxy\"\r\n" +
|
||||
"Proxy-Authenticate: Basic realm=\"" + AUTH_REALM + "\"\r\n" +
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: PROXY AUTHENTICATION REQUIRED</H1>"+
|
||||
"This proxy is configured to require authentication.<BR>")
|
||||
@ -165,6 +167,11 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
return super.close(forced);
|
||||
}
|
||||
|
||||
/** @since 0.9.4 */
|
||||
protected String getRealm() {
|
||||
return AUTH_REALM;
|
||||
}
|
||||
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
@ -237,10 +244,10 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
_log.debug(getPrefix(requestId) + "REST :" + restofline + ":");
|
||||
_log.debug(getPrefix(requestId) + "DEST :" + destination + ":");
|
||||
}
|
||||
} else if (line.toLowerCase(Locale.US).startsWith("proxy-authorization: basic ")) {
|
||||
} else if (line.toLowerCase(Locale.US).startsWith("proxy-authorization: ")) {
|
||||
// strip Proxy-Authenticate from the response in HTTPResponseOutputStream
|
||||
// save for auth check below
|
||||
authorization = line.substring(27); // "proxy-authorization: basic ".length()
|
||||
authorization = line.substring(21); // "proxy-authorization: ".length()
|
||||
line = null;
|
||||
} else if (line.length() > 0) {
|
||||
// Additional lines - shouldn't be too many. Firefox sends:
|
||||
@ -295,16 +302,11 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
|
||||
Destination clientDest = _context.namingService().lookup(destination);
|
||||
if (clientDest == null) {
|
||||
String str;
|
||||
byte[] header;
|
||||
if (usingWWWProxy)
|
||||
str = FileUtil.readTextFile((new File(_errorDir, "dnfp-header.ht")).getAbsolutePath(), 100, true);
|
||||
header = getErrorPage("dnfp-header.ht", ERR_DESTINATION_UNKNOWN);
|
||||
else
|
||||
str = FileUtil.readTextFile((new File(_errorDir, "dnfh-header.ht")).getAbsolutePath(), 100, true);
|
||||
if (str != null)
|
||||
header = str.getBytes();
|
||||
else
|
||||
header = ERR_DESTINATION_UNKNOWN;
|
||||
header = getErrorPage("dnfh-header.ht", ERR_DESTINATION_UNKNOWN);
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination);
|
||||
s.close();
|
||||
return;
|
||||
@ -341,12 +343,13 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
}
|
||||
|
||||
private static class OnTimeout implements Runnable {
|
||||
private Socket _socket;
|
||||
private OutputStream _out;
|
||||
private String _target;
|
||||
private boolean _usingProxy;
|
||||
private String _wwwProxy;
|
||||
private long _requestId;
|
||||
private final Socket _socket;
|
||||
private final OutputStream _out;
|
||||
private final String _target;
|
||||
private final boolean _usingProxy;
|
||||
private final String _wwwProxy;
|
||||
private final long _requestId;
|
||||
|
||||
public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) {
|
||||
_socket = s;
|
||||
_out = out;
|
||||
@ -355,6 +358,7 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
_wwwProxy = wwwProxy;
|
||||
_requestId = id;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Timeout occured requesting " + _target);
|
||||
@ -391,17 +395,12 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
|
||||
boolean usingWWWProxy, String wwwProxy, long requestId) {
|
||||
if (out == null)
|
||||
return;
|
||||
byte[] header;
|
||||
if (usingWWWProxy)
|
||||
header = getErrorPage(I2PAppContext.getGlobalContext(), "dnfp-header.ht", ERR_DESTINATION_UNKNOWN);
|
||||
else
|
||||
header = getErrorPage(I2PAppContext.getGlobalContext(), "dnf-header.ht", ERR_DESTINATION_UNKNOWN);
|
||||
try {
|
||||
String str;
|
||||
byte[] header;
|
||||
if (usingWWWProxy)
|
||||
str = FileUtil.readTextFile((new File(_errorDir, "dnfp-header.ht")).getAbsolutePath(), 100, true);
|
||||
else
|
||||
str = FileUtil.readTextFile((new File(_errorDir, "dnf-header.ht")).getAbsolutePath(), 100, true);
|
||||
if (str != null)
|
||||
header = str.getBytes();
|
||||
else
|
||||
header = ERR_DESTINATION_UNKNOWN;
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
|
@ -3,9 +3,7 @@
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@ -73,10 +71,14 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
* via address helper links
|
||||
*/
|
||||
private final ConcurrentHashMap<String, String> addressHelpers = new ConcurrentHashMap(8);
|
||||
|
||||
/**
|
||||
* Used to protect actions via http://proxy.i2p/
|
||||
*/
|
||||
private final String _proxyNonce;
|
||||
|
||||
private static final String AUTH_REALM = "I2P HTTP Proxy";
|
||||
|
||||
/**
|
||||
* These are backups if the xxx.ht error page is missing.
|
||||
*/
|
||||
@ -167,12 +169,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"\r\n" +
|
||||
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>" +
|
||||
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>").getBytes();
|
||||
|
||||
private final static byte[] ERR_AUTH =
|
||||
("HTTP/1.1 407 Proxy Authentication Required\r\n" +
|
||||
"Content-Type: text/html; charset=UTF-8\r\n" +
|
||||
"Cache-control: no-cache\r\n" +
|
||||
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.5\r\n" + // try to get a UTF-8-encoded response back for the password
|
||||
"Proxy-Authenticate: Basic realm=\"I2P HTTP Proxy\"\r\n" +
|
||||
"Proxy-Authenticate: Basic realm=\"" + AUTH_REALM + "\"\r\n" +
|
||||
"\r\n" +
|
||||
"<html><body><H1>I2P ERROR: PROXY AUTHENTICATION REQUIRED</H1>" +
|
||||
"This proxy is configured to require authentication.<BR>").getBytes();
|
||||
@ -300,6 +303,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** @since 0.9.4 */
|
||||
protected String getRealm() {
|
||||
return AUTH_REALM;
|
||||
}
|
||||
|
||||
private static final String HELPER_PARAM = "i2paddresshelper";
|
||||
public static final String LOCAL_SERVER = "proxy.i2p";
|
||||
private static final boolean DEFAULT_GZIP = true;
|
||||
@ -769,10 +778,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
// hop-by-hop header, and we definitely want to block Windows NTLM after a far-end 407.
|
||||
// Response to far-end shouldn't happen, as we
|
||||
// strip Proxy-Authenticate from the response in HTTPResponseOutputStream
|
||||
if(lowercaseLine.startsWith("proxy-authorization: basic ")) // save for auth check below
|
||||
{
|
||||
authorization = line.substring(27); // "proxy-authorization: basic ".length()
|
||||
}
|
||||
authorization = line.substring(21); // "proxy-authorization: ".length()
|
||||
line = null;
|
||||
continue;
|
||||
} else if(lowercaseLine.startsWith("icy")) {
|
||||
@ -858,7 +864,11 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
_log.warn(getPrefix(requestId) + "Auth required, sending 407");
|
||||
}
|
||||
}
|
||||
out.write(getErrorPage("auth", ERR_AUTH));
|
||||
if (isDigestAuthRequired()) {
|
||||
// weep
|
||||
} else {
|
||||
out.write(getErrorPage("auth", ERR_AUTH));
|
||||
}
|
||||
writeFooter(out);
|
||||
s.close();
|
||||
return;
|
||||
@ -1095,61 +1105,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
return Base32.encode(_dest.calculateHash().getData()) + ".b32.i2p";
|
||||
}
|
||||
|
||||
/**
|
||||
* foo => errordir/foo-header_xx.ht for lang xx, or errordir/foo-header.ht,
|
||||
* or the backup byte array on fail.
|
||||
*
|
||||
* .ht files must be UTF-8 encoded and use \r\n terminators so the
|
||||
* HTTP headers are conformant.
|
||||
* We can't use FileUtil.readFile() because it strips \r
|
||||
*
|
||||
* @return non-null
|
||||
*/
|
||||
private byte[] getErrorPage(String base, byte[] backup) {
|
||||
return getErrorPage(_context, base, backup);
|
||||
}
|
||||
|
||||
private static byte[] getErrorPage(I2PAppContext ctx, String base, byte[] backup) {
|
||||
File errorDir = new File(ctx.getBaseDir(), "docs");
|
||||
String lang = ctx.getProperty("routerconsole.lang", Locale.getDefault().getLanguage());
|
||||
if(lang != null && lang.length() > 0 && !lang.equals("en")) {
|
||||
File file = new File(errorDir, base + "-header_" + lang + ".ht");
|
||||
try {
|
||||
return readFile(file);
|
||||
} catch(IOException ioe) {
|
||||
// try the english version now
|
||||
}
|
||||
}
|
||||
File file = new File(errorDir, base + "-header.ht");
|
||||
try {
|
||||
return readFile(file);
|
||||
} catch(IOException ioe) {
|
||||
return backup;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] readFile(File file) throws IOException {
|
||||
FileInputStream fis = null;
|
||||
byte[] buf = new byte[512];
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
|
||||
try {
|
||||
int len = 0;
|
||||
fis = new FileInputStream(file);
|
||||
while((len = fis.read(buf)) > 0) {
|
||||
baos.write(buf, 0, len);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
} finally {
|
||||
try {
|
||||
if(fis != null) {
|
||||
fis.close();
|
||||
}
|
||||
} catch(IOException foo) {
|
||||
}
|
||||
}
|
||||
// we won't ever get here
|
||||
}
|
||||
|
||||
/**
|
||||
* Public only for LocalHTTPServer, not for general use
|
||||
*/
|
||||
@ -1163,12 +1118,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
|
||||
private static class OnTimeout implements Runnable {
|
||||
|
||||
private Socket _socket;
|
||||
private OutputStream _out;
|
||||
private String _target;
|
||||
private boolean _usingProxy;
|
||||
private String _wwwProxy;
|
||||
private long _requestId;
|
||||
private final Socket _socket;
|
||||
private final OutputStream _out;
|
||||
private final String _target;
|
||||
private final boolean _usingProxy;
|
||||
private final String _wwwProxy;
|
||||
private final long _requestId;
|
||||
|
||||
public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) {
|
||||
_socket = s;
|
||||
|
@ -3,6 +3,9 @@
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
@ -13,9 +16,11 @@ import java.util.Locale;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.InternalSocket;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.PasswordManager;
|
||||
|
||||
/**
|
||||
* Common things for HTTPClient and ConnectClient
|
||||
@ -25,6 +30,12 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implements Runnable {
|
||||
|
||||
private static final int PROXYNONCE_BYTES = 8;
|
||||
private static final int MD5_BYTES = 16;
|
||||
/** 24 */
|
||||
private static final int NONCE_BYTES = DataHelper.DATE_LENGTH + MD5_BYTES;
|
||||
private static final long MAX_NONCE_AGE = 30*24*60*60*1000L;
|
||||
|
||||
protected final List<String> _proxyList;
|
||||
|
||||
protected final static byte[] ERR_NO_OUTPROXY =
|
||||
@ -40,7 +51,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
protected static volatile long __clientId = 0;
|
||||
|
||||
protected static final File _errorDir = new File(I2PAppContext.getGlobalContext().getBaseDir(), "docs");
|
||||
private final byte[] _proxyNonce;
|
||||
|
||||
protected String getPrefix(long requestId) { return "Client[" + _clientId + "/" + requestId + "]: "; }
|
||||
|
||||
@ -63,6 +74,8 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
I2PTunnel tunnel) throws IllegalArgumentException {
|
||||
super(localPort, ownDest, l, notifyThis, handlerName, tunnel);
|
||||
_proxyList = new ArrayList(4);
|
||||
_proxyNonce = new byte[PROXYNONCE_BYTES];
|
||||
_context.random().nextBytes(_proxyNonce);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,6 +89,8 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
throws IllegalArgumentException {
|
||||
super(localPort, l, sktMgr, tunnel, notifyThis, clientId);
|
||||
_proxyList = new ArrayList(4);
|
||||
_proxyNonce = new byte[PROXYNONCE_BYTES];
|
||||
_context.random().nextBytes(_proxyNonce);
|
||||
}
|
||||
|
||||
/** all auth @since 0.8.2 */
|
||||
@ -91,22 +106,48 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
public static final String PROP_OUTPROXY_USER_PREFIX = PROP_OUTPROXY_USER + '.';
|
||||
public static final String PROP_OUTPROXY_PW_PREFIX = PROP_OUTPROXY_PW + '.';
|
||||
|
||||
protected abstract String getRealm();
|
||||
|
||||
/**
|
||||
* @param authorization may be null
|
||||
* @since 0.9.4
|
||||
*/
|
||||
protected boolean isDigestAuthRequired() {
|
||||
String authRequired = getTunnel().getClientOptions().getProperty(PROP_AUTH);
|
||||
if (authRequired == null)
|
||||
return true;
|
||||
return authRequired.toLowerCase(Locale.US).equals("digest");
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorization
|
||||
* Ref: RFC 2617
|
||||
* If the socket is an InternalSocket, no auth required.
|
||||
*
|
||||
* @param authorization may be null, the full auth line e.g. "Basic lskjlksjf"
|
||||
* @return success
|
||||
*/
|
||||
protected boolean authorize(Socket s, long requestId, String authorization) {
|
||||
// Authorization
|
||||
// Ref: RFC 2617
|
||||
// If the socket is an InternalSocket, no auth required.
|
||||
String authRequired = getTunnel().getClientOptions().getProperty(PROP_AUTH);
|
||||
if (Boolean.parseBoolean(authRequired) ||
|
||||
(authRequired != null && "basic".equals(authRequired.toLowerCase(Locale.US)))) {
|
||||
if (s instanceof InternalSocket) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Internal access, no auth required");
|
||||
return true;
|
||||
} else if (authorization != null) {
|
||||
if (authRequired == null)
|
||||
return true;
|
||||
authRequired = authRequired.toLowerCase(Locale.US);
|
||||
if (authRequired.equals("false"))
|
||||
return true;
|
||||
if (s instanceof InternalSocket) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Internal access, no auth required");
|
||||
return true;
|
||||
}
|
||||
if (authorization == null)
|
||||
return false;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Auth: " + authorization);
|
||||
String authLC = authorization.toLowerCase(Locale.US);
|
||||
if (authRequired.equals("true") || authRequired.equals("basic")) {
|
||||
if (!authLC.startsWith("basic "))
|
||||
return false;
|
||||
authorization = authorization.substring(6);
|
||||
|
||||
// hmm safeDecode(foo, true) to use standard alphabet is private in Base64
|
||||
byte[] decoded = Base64.decode(authorization.replace("/", "~").replace("+", "="));
|
||||
if (decoded != null) {
|
||||
@ -148,10 +189,136 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Bad auth B64: " + authorization);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} else if (authRequired.equals("digest")) {
|
||||
if (!authLC.startsWith("digest "))
|
||||
return false;
|
||||
authorization = authorization.substring(7);
|
||||
_log.error("Digest unimplemented");
|
||||
return true;
|
||||
} else {
|
||||
_log.error("Unknown proxy authorization type configured: " + authRequired);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Base 64 of 24 bytes: (now, md5 of (now, proxy nonce))
|
||||
* @since 0.9.4
|
||||
*/
|
||||
private String getNonce() {
|
||||
byte[] b = new byte[DataHelper.DATE_LENGTH + PROXYNONCE_BYTES];
|
||||
byte[] n = new byte[NONCE_BYTES];
|
||||
long now = _context.clock().now();
|
||||
DataHelper.toLong(b, 0, DataHelper.DATE_LENGTH, now);
|
||||
System.arraycopy(_proxyNonce, 0, b, DataHelper.DATE_LENGTH, PROXYNONCE_BYTES);
|
||||
System.arraycopy(b, 0, n, 0, DataHelper.DATE_LENGTH);
|
||||
byte[] md5 = PasswordManager.md5Sum(b);
|
||||
System.arraycopy(md5, 0, n, DataHelper.DATE_LENGTH, MD5_BYTES);
|
||||
return Base64.encode(n);
|
||||
}
|
||||
|
||||
enum AuthResult {AUTH_BAD, AUTH_STALE, AUTH_GOOD}
|
||||
|
||||
/**
|
||||
* Verify the Base 64 of 24 bytes: (now, md5 of (now, proxy nonce))
|
||||
* @since 0.9.4
|
||||
*/
|
||||
private AuthResult verifyNonce(String b64) {
|
||||
byte[] n = Base64.decode(b64);
|
||||
if (n == null || n.length != NONCE_BYTES)
|
||||
return AuthResult.AUTH_BAD;
|
||||
long now = _context.clock().now();
|
||||
long stamp = DataHelper.fromLong(n, 0, DataHelper.DATE_LENGTH);
|
||||
if (now - stamp > MAX_NONCE_AGE)
|
||||
return AuthResult.AUTH_STALE;
|
||||
byte[] b = new byte[DataHelper.DATE_LENGTH + PROXYNONCE_BYTES];
|
||||
System.arraycopy(n, 0, b, 0, DataHelper.DATE_LENGTH);
|
||||
System.arraycopy(_proxyNonce, 0, b, DataHelper.DATE_LENGTH, PROXYNONCE_BYTES);
|
||||
byte[] md5 = PasswordManager.md5Sum(b);
|
||||
if (!DataHelper.eq(md5, 0, n, DataHelper.DATE_LENGTH, MD5_BYTES))
|
||||
return AuthResult.AUTH_BAD;
|
||||
return AuthResult.AUTH_GOOD;
|
||||
}
|
||||
|
||||
protected String getDigestHeader(boolean isStale) {
|
||||
return
|
||||
"Proxy-Authenticate: Digest realm=\"" + getRealm() + "\"" +
|
||||
" nonce=\"" + getNonce() + "\"" +
|
||||
" algorithm=MD5" +
|
||||
" qop=\"auth\"" +
|
||||
(isStale ? " stale=true" : "") +
|
||||
"\r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* foo => errordir/foo-header_xx.ht for lang xx, or errordir/foo-header.ht,
|
||||
* or the backup byte array on fail.
|
||||
*
|
||||
* .ht files must be UTF-8 encoded and use \r\n terminators so the
|
||||
* HTTP headers are conformant.
|
||||
* We can't use FileUtil.readFile() because it strips \r
|
||||
*
|
||||
* @return non-null
|
||||
* @since 0.9.4 moved from I2PTunnelHTTPClient
|
||||
*/
|
||||
protected byte[] getErrorPage(String base, byte[] backup) {
|
||||
return getErrorPage(_context, base, backup);
|
||||
}
|
||||
|
||||
/**
|
||||
* foo => errordir/foo-header_xx.ht for lang xx, or errordir/foo-header.ht,
|
||||
* or the backup byte array on fail.
|
||||
*
|
||||
* .ht files must be UTF-8 encoded and use \r\n terminators so the
|
||||
* HTTP headers are conformant.
|
||||
* We can't use FileUtil.readFile() because it strips \r
|
||||
*
|
||||
* @return non-null
|
||||
* @since 0.9.4 moved from I2PTunnelHTTPClient
|
||||
*/
|
||||
protected static byte[] getErrorPage(I2PAppContext ctx, String base, byte[] backup) {
|
||||
File errorDir = new File(ctx.getBaseDir(), "docs");
|
||||
String lang = ctx.getProperty("routerconsole.lang", Locale.getDefault().getLanguage());
|
||||
if(lang != null && lang.length() > 0 && !lang.equals("en")) {
|
||||
File file = new File(errorDir, base + "-header_" + lang + ".ht");
|
||||
try {
|
||||
return readFile(file);
|
||||
} catch(IOException ioe) {
|
||||
// try the english version now
|
||||
}
|
||||
}
|
||||
File file = new File(errorDir, base + "-header.ht");
|
||||
try {
|
||||
return readFile(file);
|
||||
} catch(IOException ioe) {
|
||||
return backup;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.4 moved from I2PTunnelHTTPClient
|
||||
*/
|
||||
private static byte[] readFile(File file) throws IOException {
|
||||
FileInputStream fis = null;
|
||||
byte[] buf = new byte[2048];
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
|
||||
try {
|
||||
int len = 0;
|
||||
fis = new FileInputStream(file);
|
||||
while((len = fis.read(buf)) > 0) {
|
||||
baos.write(buf, 0, len);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
} finally {
|
||||
try {
|
||||
if(fis != null) {
|
||||
fis.close();
|
||||
}
|
||||
} catch(IOException foo) {
|
||||
}
|
||||
}
|
||||
// we won't ever get here
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user