forked from I2P_Developers/i2p.i2p
refactor proxy.i2p server to its own class
This commit is contained in:
@ -36,8 +36,8 @@ import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.i2ptunnel.localServer.LocalHTTPServer;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.PortMapper;
|
||||
import net.i2p.util.Translate;
|
||||
@ -309,7 +309,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static final String LOCAL_SERVER = "proxy.i2p";
|
||||
public static final String LOCAL_SERVER = "proxy.i2p";
|
||||
private static final boolean DEFAULT_GZIP = true;
|
||||
/** all default to false */
|
||||
public static final String PROP_REFERER = "i2ptunnel.httpclient.sendReferer";
|
||||
@ -798,7 +798,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER)).booleanValue()) {
|
||||
out.write(ERR_HELPER_DISABLED);
|
||||
} else {
|
||||
serveLocalFile(out, method, targetRequest, _proxyNonce);
|
||||
LocalHTTPServer.serveLocalFile(out, method, targetRequest, _proxyNonce);
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
@ -1046,7 +1046,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
// we won't ever get here
|
||||
}
|
||||
|
||||
private static void writeFooter(OutputStream out) throws IOException {
|
||||
/**
|
||||
* Public only for LocalHTTPServer, not for general use
|
||||
*/
|
||||
public static void writeFooter(OutputStream out) throws IOException {
|
||||
// the css is hiding this div for now, but we'll keep it here anyway
|
||||
out.write("<div class=\"proxyfooter\"><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
@ -1196,20 +1199,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
return protocol.toLowerCase(Locale.US).equals("http://");
|
||||
}
|
||||
|
||||
private final static byte[] ERR_404 =
|
||||
("HTTP/1.1 404 Not Found\r\n"+
|
||||
"Content-Type: text/plain\r\n"+
|
||||
"\r\n"+
|
||||
"HTTP Proxy local file not found")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_ADD =
|
||||
("HTTP/1.1 409 Bad\r\n"+
|
||||
"Content-Type: text/plain\r\n"+
|
||||
"\r\n"+
|
||||
"Add to addressbook failed - bad parameters")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_HELPER_DISABLED =
|
||||
("HTTP/1.1 403 Disabled\r\n"+
|
||||
"Content-Type: text/plain\r\n"+
|
||||
@ -1217,186 +1206,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"Address helpers disabled")
|
||||
.getBytes();
|
||||
|
||||
/**
|
||||
* Very simple web server.
|
||||
*
|
||||
* Serve local files in the docs/ directory, for CSS and images in
|
||||
* error pages, using the reserved address proxy.i2p
|
||||
* (similar to p.p in privoxy).
|
||||
* This solves the problems with including links to the router console,
|
||||
* as assuming the router console is at 127.0.0.1 leads to broken
|
||||
* links if it isn't.
|
||||
*
|
||||
* Ignore all request headers (If-Modified-Since, etc.)
|
||||
*
|
||||
* There is basic protection here -
|
||||
* FileUtil.readFile() prevents traversal above the base directory -
|
||||
* but inproxy/gateway ops would be wise to block proxy.i2p to prevent
|
||||
* exposing the docs/ directory or perhaps other issues through
|
||||
* uncaught vulnerabilities.
|
||||
* Restrict to the /themes/ directory for now.
|
||||
*
|
||||
* @param targetRequest "proxy.i2p/themes/foo.png HTTP/1.1"
|
||||
*/
|
||||
private static void serveLocalFile(OutputStream out, String method, String targetRequest, String proxyNonce) {
|
||||
//System.err.println("targetRequest: \"" + targetRequest + "\"");
|
||||
// a home page message for the curious...
|
||||
if (targetRequest.startsWith(LOCAL_SERVER + "/ ")) {
|
||||
try {
|
||||
out.write(("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nCache-Control: max-age=86400\r\n\r\nI2P HTTP proxy OK").getBytes());
|
||||
out.flush();
|
||||
} catch (IOException ioe) {}
|
||||
return;
|
||||
}
|
||||
if ((method.equals("GET") || method.equals("HEAD")) &&
|
||||
targetRequest.startsWith(LOCAL_SERVER + "/themes/") &&
|
||||
!targetRequest.contains("..")) {
|
||||
int space = targetRequest.indexOf(' ');
|
||||
String filename = null;
|
||||
try {
|
||||
filename = targetRequest.substring(LOCAL_SERVER.length() + 8, space); // "/themes/".length
|
||||
} catch (IndexOutOfBoundsException ioobe) {
|
||||
return;
|
||||
}
|
||||
// theme hack
|
||||
if (filename.startsWith("console/default/"))
|
||||
filename = filename.replaceFirst("default", I2PAppContext.getGlobalContext().getProperty("routerconsole.theme", "light"));
|
||||
File themesDir = new File(_errorDir, "themes");
|
||||
File file = new File(themesDir, filename);
|
||||
if (file.exists() && !file.isDirectory()) {
|
||||
String type;
|
||||
if (filename.endsWith(".css"))
|
||||
type = "text/css";
|
||||
else if (filename.endsWith(".ico"))
|
||||
type = "image/x-icon";
|
||||
else if (filename.endsWith(".png"))
|
||||
type = "image/png";
|
||||
else if (filename.endsWith(".jpg"))
|
||||
type = "image/jpeg";
|
||||
else type = "text/html";
|
||||
try {
|
||||
out.write("HTTP/1.1 200 OK\r\nContent-Type: ".getBytes());
|
||||
out.write(type.getBytes());
|
||||
out.write("\r\nCache-Control: max-age=86400\r\n\r\n".getBytes());
|
||||
FileUtil.readFile(filename, themesDir.getAbsolutePath(), out);
|
||||
} catch (IOException ioe) {}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to addressbook (form submit)
|
||||
// Parameters are url, host, dest, nonce, and master | router | private.
|
||||
// Do the add and redirect.
|
||||
if (targetRequest.startsWith(LOCAL_SERVER + "/add?")) {
|
||||
int spc = targetRequest.indexOf(' ');
|
||||
String query = targetRequest.substring(LOCAL_SERVER.length() + 5, spc); // "/add?".length()
|
||||
Map<String, String> opts = new HashMap(8);
|
||||
StringTokenizer tok = new StringTokenizer(query, "=&;");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String k = tok.nextToken();
|
||||
if (!tok.hasMoreTokens())
|
||||
break;
|
||||
String v = tok.nextToken();
|
||||
opts.put(decode(k), decode(v));
|
||||
}
|
||||
|
||||
String url = opts.get("url");
|
||||
String host = opts.get("host");
|
||||
String b64Dest = opts.get("dest");
|
||||
String nonce = opts.get("nonce");
|
||||
String book = "privatehosts.txt";
|
||||
if (opts.get("master") != null)
|
||||
book = "userhosts.txt";
|
||||
else if (opts.get("router") != null)
|
||||
book = "hosts.txt";
|
||||
Destination dest = null;
|
||||
if (b64Dest != null) {
|
||||
try {
|
||||
dest = new Destination(b64Dest);
|
||||
} catch (DataFormatException dfe) {
|
||||
System.err.println("Bad dest to save?" + b64Dest);
|
||||
}
|
||||
}
|
||||
//System.err.println("url : \"" + url + "\"");
|
||||
//System.err.println("host : \"" + host + "\"");
|
||||
//System.err.println("b64dest : \"" + b64Dest + "\"");
|
||||
//System.err.println("book : \"" + book + "\"");
|
||||
//System.err.println("nonce : \"" + nonce + "\"");
|
||||
if (proxyNonce.equals(nonce) && url != null && host != null && dest != null) {
|
||||
try {
|
||||
NamingService ns = I2PAppContext.getGlobalContext().namingService();
|
||||
Properties nsOptions = new Properties();
|
||||
nsOptions.setProperty("list", book);
|
||||
nsOptions.setProperty("s", _("Added via address helper"));
|
||||
boolean success = ns.put(host, dest, nsOptions);
|
||||
writeRedirectPage(out, success, host, book, url);
|
||||
return;
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
try {
|
||||
out.write(ERR_ADD);
|
||||
out.flush();
|
||||
} catch (IOException ioe) {}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
out.write(ERR_404);
|
||||
out.flush();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
/** @since 0.8.7 */
|
||||
private static void writeRedirectPage(OutputStream out, boolean success, String host, String book, String url) throws IOException {
|
||||
out.write(("HTTP/1.1 200 OK\r\n"+
|
||||
"Content-Type: text/html; charset=UTF-8\r\n"+
|
||||
"\r\n"+
|
||||
"<html><head>"+
|
||||
"<title>" + _("Redirecting to {0}", host) + "</title>\n" +
|
||||
"<link rel=\"shortcut icon\" href=\"http://proxy.i2p/themes/console/images/favicon.ico\" >\n" +
|
||||
"<link href=\"http://proxy.i2p/themes/console/default/console.css\" rel=\"stylesheet\" type=\"text/css\" >\n" +
|
||||
"<meta http-equiv=\"Refresh\" content=\"1; url=" + url + "\">\n" +
|
||||
"</head><body>\n" +
|
||||
"<div class=logo>\n" +
|
||||
"<a href=\"http://127.0.0.1:7657/\" title=\"" + _("Router Console") + "\"><img src=\"http://proxy.i2p/themes/console/images/i2plogo.png\" alt=\"I2P Router Console\" border=\"0\"></a><hr>\n" +
|
||||
"<a href=\"http://127.0.0.1:7657/config\">" + _("Configuration") + "</a> <a href=\"http://127.0.0.1:7657/help.jsp\">" + _("Help") + "</a> <a href=\"http://127.0.0.1:7657/susidns/index\">" + _("Addressbook") + "</a>\n" +
|
||||
"</div>" +
|
||||
"<div class=warning id=warning>\n" +
|
||||
"<h3>" +
|
||||
(success ?
|
||||
_("Saved {0} to the {1} addressbook, redirecting now.", host, book) :
|
||||
_("Failed to save {0} to the {1} addressbook, redirecting now.", host, book)) +
|
||||
"</h3>\n<p><a href=\"" + url + "\">" +
|
||||
_("Click here if you are not redirected automatically.") +
|
||||
"</a></p></div>").getBytes("UTF-8"));
|
||||
writeFooter(out);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode %xx encoding
|
||||
* @since 0.8.7
|
||||
*/
|
||||
private static String decode(String s) {
|
||||
if (!s.contains("%"))
|
||||
return s;
|
||||
StringBuilder buf = new StringBuilder(s.length());
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c != '%') {
|
||||
buf.append(c);
|
||||
} else {
|
||||
try {
|
||||
buf.append((char) Integer.parseInt(s.substring(++i, (++i) + 1), 16));
|
||||
} catch (IndexOutOfBoundsException ioobe) {
|
||||
break;
|
||||
} catch (NumberFormatException nfe) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static final String BUNDLE_NAME = "net.i2p.i2ptunnel.web.messages";
|
||||
|
||||
/** lang in routerconsole.lang property, else current locale */
|
||||
|
@ -0,0 +1,248 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel.localServer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.naming.NamingService;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Translate;
|
||||
|
||||
/**
|
||||
* Very simple web server.
|
||||
*
|
||||
* Serve local files in the docs/ directory, for CSS and images in
|
||||
* error pages, using the reserved address proxy.i2p
|
||||
* (similar to p.p in privoxy).
|
||||
* This solves the problems with including links to the router console,
|
||||
* as assuming the router console is at 127.0.0.1 leads to broken
|
||||
* links if it isn't.
|
||||
*
|
||||
* @since 0.7.6, moved from I2PTunnelHTTPClient in 0.9
|
||||
*/
|
||||
public abstract class LocalHTTPServer {
|
||||
|
||||
private final static byte[] ERR_404 =
|
||||
("HTTP/1.1 404 Not Found\r\n"+
|
||||
"Content-Type: text/plain\r\n"+
|
||||
"\r\n"+
|
||||
"HTTP Proxy local file not found")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_ADD =
|
||||
("HTTP/1.1 409 Bad\r\n"+
|
||||
"Content-Type: text/plain\r\n"+
|
||||
"\r\n"+
|
||||
"Add to addressbook failed - bad parameters")
|
||||
.getBytes();
|
||||
|
||||
/**
|
||||
* Very simple web server.
|
||||
*
|
||||
* Serve local files in the docs/ directory, for CSS and images in
|
||||
* error pages, using the reserved address proxy.i2p
|
||||
* (similar to p.p in privoxy).
|
||||
* This solves the problems with including links to the router console,
|
||||
* as assuming the router console is at 127.0.0.1 leads to broken
|
||||
* links if it isn't.
|
||||
*
|
||||
* Ignore all request headers (If-Modified-Since, etc.)
|
||||
*
|
||||
* There is basic protection here -
|
||||
* FileUtil.readFile() prevents traversal above the base directory -
|
||||
* but inproxy/gateway ops would be wise to block proxy.i2p to prevent
|
||||
* exposing the docs/ directory or perhaps other issues through
|
||||
* uncaught vulnerabilities.
|
||||
* Restrict to the /themes/ directory for now.
|
||||
*
|
||||
* @param targetRequest "proxy.i2p/themes/foo.png HTTP/1.1"
|
||||
*/
|
||||
public static void serveLocalFile(OutputStream out, String method, String targetRequest, String proxyNonce) {
|
||||
//System.err.println("targetRequest: \"" + targetRequest + "\"");
|
||||
// a home page message for the curious...
|
||||
if (targetRequest.startsWith(I2PTunnelHTTPClient.LOCAL_SERVER + "/ ")) {
|
||||
try {
|
||||
out.write(("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nCache-Control: max-age=86400\r\n\r\nI2P HTTP proxy OK").getBytes());
|
||||
out.flush();
|
||||
} catch (IOException ioe) {}
|
||||
return;
|
||||
}
|
||||
if ((method.equals("GET") || method.equals("HEAD")) &&
|
||||
targetRequest.startsWith(I2PTunnelHTTPClient.LOCAL_SERVER + "/themes/") &&
|
||||
!targetRequest.contains("..")) {
|
||||
int space = targetRequest.indexOf(' ');
|
||||
String filename = null;
|
||||
try {
|
||||
filename = targetRequest.substring(I2PTunnelHTTPClient.LOCAL_SERVER.length() + 8, space); // "/themes/".length
|
||||
} catch (IndexOutOfBoundsException ioobe) {
|
||||
return;
|
||||
}
|
||||
// theme hack
|
||||
if (filename.startsWith("console/default/"))
|
||||
filename = filename.replaceFirst("default", I2PAppContext.getGlobalContext().getProperty("routerconsole.theme", "light"));
|
||||
File themesDir = new File(I2PAppContext.getGlobalContext().getBaseDir(), "docs/themes");
|
||||
File file = new File(themesDir, filename);
|
||||
if (file.exists() && !file.isDirectory()) {
|
||||
String type;
|
||||
if (filename.endsWith(".css"))
|
||||
type = "text/css";
|
||||
else if (filename.endsWith(".ico"))
|
||||
type = "image/x-icon";
|
||||
else if (filename.endsWith(".png"))
|
||||
type = "image/png";
|
||||
else if (filename.endsWith(".jpg"))
|
||||
type = "image/jpeg";
|
||||
else type = "text/html";
|
||||
try {
|
||||
out.write("HTTP/1.1 200 OK\r\nContent-Type: ".getBytes());
|
||||
out.write(type.getBytes());
|
||||
out.write("\r\nCache-Control: max-age=86400\r\n\r\n".getBytes());
|
||||
FileUtil.readFile(filename, themesDir.getAbsolutePath(), out);
|
||||
} catch (IOException ioe) {}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to addressbook (form submit)
|
||||
// Parameters are url, host, dest, nonce, and master | router | private.
|
||||
// Do the add and redirect.
|
||||
if (targetRequest.startsWith(I2PTunnelHTTPClient.LOCAL_SERVER + "/add?")) {
|
||||
int spc = targetRequest.indexOf(' ');
|
||||
String query = targetRequest.substring(I2PTunnelHTTPClient.LOCAL_SERVER.length() + 5, spc); // "/add?".length()
|
||||
Map<String, String> opts = new HashMap(8);
|
||||
StringTokenizer tok = new StringTokenizer(query, "=&;");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String k = tok.nextToken();
|
||||
if (!tok.hasMoreTokens())
|
||||
break;
|
||||
String v = tok.nextToken();
|
||||
opts.put(decode(k), decode(v));
|
||||
}
|
||||
|
||||
String url = opts.get("url");
|
||||
String host = opts.get("host");
|
||||
String b64Dest = opts.get("dest");
|
||||
String nonce = opts.get("nonce");
|
||||
String book = "privatehosts.txt";
|
||||
if (opts.get("master") != null)
|
||||
book = "userhosts.txt";
|
||||
else if (opts.get("router") != null)
|
||||
book = "hosts.txt";
|
||||
Destination dest = null;
|
||||
if (b64Dest != null) {
|
||||
try {
|
||||
dest = new Destination(b64Dest);
|
||||
} catch (DataFormatException dfe) {
|
||||
System.err.println("Bad dest to save?" + b64Dest);
|
||||
}
|
||||
}
|
||||
//System.err.println("url : \"" + url + "\"");
|
||||
//System.err.println("host : \"" + host + "\"");
|
||||
//System.err.println("b64dest : \"" + b64Dest + "\"");
|
||||
//System.err.println("book : \"" + book + "\"");
|
||||
//System.err.println("nonce : \"" + nonce + "\"");
|
||||
if (proxyNonce.equals(nonce) && url != null && host != null && dest != null) {
|
||||
try {
|
||||
NamingService ns = I2PAppContext.getGlobalContext().namingService();
|
||||
Properties nsOptions = new Properties();
|
||||
nsOptions.setProperty("list", book);
|
||||
nsOptions.setProperty("s", _("Added via address helper"));
|
||||
boolean success = ns.put(host, dest, nsOptions);
|
||||
writeRedirectPage(out, success, host, book, url);
|
||||
return;
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
try {
|
||||
out.write(ERR_ADD);
|
||||
out.flush();
|
||||
} catch (IOException ioe) {}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
out.write(ERR_404);
|
||||
out.flush();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
/** @since 0.8.7 */
|
||||
private static void writeRedirectPage(OutputStream out, boolean success, String host, String book, String url) throws IOException {
|
||||
out.write(("HTTP/1.1 200 OK\r\n"+
|
||||
"Content-Type: text/html; charset=UTF-8\r\n"+
|
||||
"\r\n"+
|
||||
"<html><head>"+
|
||||
"<title>" + _("Redirecting to {0}", host) + "</title>\n" +
|
||||
"<link rel=\"shortcut icon\" href=\"http://proxy.i2p/themes/console/images/favicon.ico\" >\n" +
|
||||
"<link href=\"http://proxy.i2p/themes/console/default/console.css\" rel=\"stylesheet\" type=\"text/css\" >\n" +
|
||||
"<meta http-equiv=\"Refresh\" content=\"1; url=" + url + "\">\n" +
|
||||
"</head><body>\n" +
|
||||
"<div class=logo>\n" +
|
||||
"<a href=\"http://127.0.0.1:7657/\" title=\"" + _("Router Console") + "\"><img src=\"http://proxy.i2p/themes/console/images/i2plogo.png\" alt=\"I2P Router Console\" border=\"0\"></a><hr>\n" +
|
||||
"<a href=\"http://127.0.0.1:7657/config\">" + _("Configuration") + "</a> <a href=\"http://127.0.0.1:7657/help.jsp\">" + _("Help") + "</a> <a href=\"http://127.0.0.1:7657/susidns/index\">" + _("Addressbook") + "</a>\n" +
|
||||
"</div>" +
|
||||
"<div class=warning id=warning>\n" +
|
||||
"<h3>" +
|
||||
(success ?
|
||||
_("Saved {0} to the {1} addressbook, redirecting now.", host, book) :
|
||||
_("Failed to save {0} to the {1} addressbook, redirecting now.", host, book)) +
|
||||
"</h3>\n<p><a href=\"" + url + "\">" +
|
||||
_("Click here if you are not redirected automatically.") +
|
||||
"</a></p></div>").getBytes("UTF-8"));
|
||||
I2PTunnelHTTPClient.writeFooter(out);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode %xx encoding
|
||||
* @since 0.8.7
|
||||
*/
|
||||
private static String decode(String s) {
|
||||
if (!s.contains("%"))
|
||||
return s;
|
||||
StringBuilder buf = new StringBuilder(s.length());
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c != '%') {
|
||||
buf.append(c);
|
||||
} else {
|
||||
try {
|
||||
buf.append((char) Integer.parseInt(s.substring(++i, (++i) + 1), 16));
|
||||
} catch (IndexOutOfBoundsException ioobe) {
|
||||
break;
|
||||
} catch (NumberFormatException nfe) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static final String BUNDLE_NAME = "net.i2p.i2ptunnel.web.messages";
|
||||
|
||||
/** lang in routerconsole.lang property, else current locale */
|
||||
protected static String _(String key) {
|
||||
return Translate.getString(key, I2PAppContext.getGlobalContext(), BUNDLE_NAME);
|
||||
}
|
||||
|
||||
/** {0} */
|
||||
protected static String _(String key, Object o) {
|
||||
return Translate.getString(key, o, I2PAppContext.getGlobalContext(), BUNDLE_NAME);
|
||||
}
|
||||
|
||||
/** {0} and {1} */
|
||||
protected static String _(String key, Object o, Object o2) {
|
||||
return Translate.getString(key, o, o2, I2PAppContext.getGlobalContext(), BUNDLE_NAME);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user