UPnP fixes part 7:

Don't set I2P user agent, spoof MiniUPnPc
Don't convert string-to-bytes-to-string
Don't fallback to alternate code after failure
Don't use HttpURLConnection if proxy enabled
Add location sanity checks
Force Connection: close
Don't attempt to set Host header, HttpURLConnection ignores it
Disable following redirects
This commit is contained in:
zzz
2020-05-27 12:49:15 +00:00
parent 3fac44874e
commit f3e821a65d
2 changed files with 82 additions and 41 deletions

View File

@ -157,11 +157,14 @@ public class Router implements RouterClock.ClockShiftListener {
System.setProperty("networkaddress.cache.ttl", DNS_CACHE_TIME);
System.setProperty("networkaddress.cache.negative.ttl", DNS_NEG_CACHE_TIME);
}
if (System.getProperty("I2P_DISABLE_HTTP_AGENT_OVERRIDE") == null) {
System.setProperty("http.agent", "I2P");
}
// ref: https://docs.oracle.com/javase/8/docs/api/java/net/doc-files/net-properties.html#Proxies
// This doesn't do what you think, and a bad idea anyway
//if (System.getProperty("I2P_DISABLE_HTTP_AGENT_OVERRIDE") == null) {
// System.setProperty("http.agent", "I2P");
//}
if (System.getProperty("I2P_DISABLE_HTTP_KEEPALIVE_OVERRIDE") == null) {
// (no need for keepalive)
// Note that doc link above is wrong, it IS capital A Alive
System.setProperty("http.keepAlive", "false");
}
// Save it for LogManager

View File

@ -27,6 +27,9 @@ import java.io.ByteArrayInputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import net.i2p.util.Addresses;
import net.i2p.router.transport.TransportUtil;
import org.cybergarage.http.HTTP;
import org.cybergarage.http.HTTPRequest;
import org.cybergarage.http.HTTPResponse;
@ -34,6 +37,15 @@ import org.cybergarage.util.Debug;
public abstract class Parser
{
// I2P
private static final String USER_AGENT = "Debian/buster/sid, UPnP/1.1, MiniUPnPc/2.1";
// HttpURLConnection proxy
// https://docs.oracle.com/javase/8/docs/api/java/net/doc-files/net-properties.html#Proxies
// socks and system proxies are checked in TransportManager
private static final String PROP_HURLC_PROXY1 = "http.proxyHost";
private static final boolean HURLC_PROXY_ENABLED = System.getProperty(PROP_HURLC_PROXY1) != null &&
System.getProperty(PROP_HURLC_PROXY1).length() > 0;
////////////////////////////////////////////////
// Constructor
////////////////////////////////////////////////
@ -54,7 +66,19 @@ public abstract class Parser
public Node parse(URL locationURL) throws ParserException
{
// I2P multiple sanity checks
if (!"http".equals(locationURL.getProtocol()))
throw new ParserException("Not HTTP");
String host = locationURL.getHost();
if (host == null ||
!Addresses.isIPv4Address(host) ||
host.startsWith("127."))
throw new ParserException("Bad host " + host);
byte[] ip = Addresses.getIP(host);
if (ip == null ||
TransportUtil.isPubliclyRoutable(ip, false))
throw new ParserException("Bad host " + host);
int port = locationURL.getPort();
// Thanks for Hao Hu
if (port == -1)
@ -65,47 +89,62 @@ public abstract class Parser
if (uri.length() <= 0)
uri = "/";
HttpURLConnection urlCon = null;
InputStream urlIn = null;
try {
urlCon = (HttpURLConnection)locationURL.openConnection();
// I2P mods to prevent hangs (see HTTPRequest for more info)
// this seems to work, getInputStream actually does the connect(),
// (as shown by a thread dump)
// so we can set these after openConnection()
// Alternative would be foo = new HttpURLConnection(locationURL); foo.set timeouts; foo.connect()
urlCon.setConnectTimeout(2*1000);
urlCon.setReadTimeout(1000);
urlCon.setRequestMethod("GET");
urlCon.setRequestProperty(HTTP.CONTENT_LENGTH,"0");
if (host != null)
urlCon.setRequestProperty(HTTP.HOST, host);
if (!HURLC_PROXY_ENABLED) {
HttpURLConnection urlCon = null;
InputStream urlIn = null;
try {
urlCon = (HttpURLConnection)locationURL.openConnection();
// I2P fix
int code = urlCon.getResponseCode();
if (code < 200 || code >= 300)
throw new ParserException("Bad response code " + code);
// I2P fix - Roku port 9080
// not valid json either; returns "status=ok"
if ("application/json".equals(urlCon.getContentType()))
throw new ParserException("JSON response");
// I2P mods to prevent hangs (see HTTPRequest for more info)
// this seems to work, getInputStream actually does the connect(),
// (as shown by a thread dump)
// so we can set these after openConnection()
// Alternative would be foo = new HttpURLConnection(locationURL); foo.set timeouts; foo.connect()
urlCon.setConnectTimeout(2*1000);
urlCon.setReadTimeout(1000);
urlCon.setRequestMethod("GET");
// See net.properties in Java install as of Java 12, however,
// this appears to be true well earlier than that.
// By default, the following request headers are not allowed to be set by user code
// in HttpRequests: "connection", "content-length", "expect", "host" and "upgrade".
// See sun.net.www.protocol.http.HttpUrlConnection source.
// In that code, Connection: close is allowed.
// Javadoc there says it throws IAE but it really is just ignored silently.
urlCon.setRequestProperty("User-Agent", USER_AGENT);
// Force even if System property http.keepAlive=true
urlCon.setRequestProperty("Connection", "close");
// I2P just in case
urlCon.setAllowUserInteraction(false);
urlCon.setUseCaches(false);
urlCon.setInstanceFollowRedirects(false);
urlIn = urlCon.getInputStream();
Node rootElem = parse(urlIn);
// I2P fix
int code = urlCon.getResponseCode();
if (code < 200 || code >= 300)
throw new ParserException("Bad response code " + code);
// I2P fix - Roku port 9080
// not valid json either; returns "status=ok"
if ("application/json".equals(urlCon.getContentType()))
throw new ParserException("JSON response");
// I2P just in case - typ. responses are under 1KB
if (urlCon.getContentLength() > 32768)
throw new ParserException("too big");
return rootElem;
} catch (ParserException pe) {
throw pe;
} catch (Exception e) {
// Why try twice???
//throw new ParserException(e);
Debug.warning("Failed fetch but retrying with HTTPRequest, URL: " + locationURL, e);
} finally {
if (urlIn != null) try { urlIn.close(); } catch (IOException ioe) {}
if (urlCon != null) urlCon.disconnect();
urlIn = urlCon.getInputStream();
Node rootElem = parse(urlIn);
return rootElem;
} catch (ParserException pe) {
throw pe;
} catch (Exception e) {
throw new ParserException(e);
} finally {
if (urlIn != null) try { urlIn.close(); } catch (IOException ioe) {}
if (urlCon != null) urlCon.disconnect();
}
}
// Fallback method, only if HURLC_PROXY_ENABLED
// This way of doing things does not follow redirects
HTTPRequest httpReq = new HTTPRequest();
httpReq.setMethod(HTTP.GET);
httpReq.setURI(uri);
@ -115,8 +154,7 @@ public abstract class Parser
"Unable to retrieve resource -> " + locationURL +
"\nRequest:\n" + httpReq +
"\nResponse:\n" + httpRes);
String content = new String(httpRes.getContent());
ByteArrayInputStream strBuf = new ByteArrayInputStream(content.getBytes());
ByteArrayInputStream strBuf = new ByteArrayInputStream(httpRes.getContent());
try {
return parse(strBuf);
} catch (ParserException pe) {