Util: EepPost

Adapted from old jrandom EepPost, removed in 2012 as unused
Possible base for I2PSocketEepPost, or may get moved,
because there's not much use for a CLI EepPost.
This commit is contained in:
zzz
2025-05-12 18:21:44 -04:00
parent 6af930ce84
commit 5ece1f47aa
2 changed files with 403 additions and 6 deletions

View File

@ -57,7 +57,8 @@ public class EepGet {
protected final String _url;
/** the URL we actually fetch from (may differ from the _url in case of redirect) */
protected String _actualURL;
private final String _postData;
private String _postData;
private byte[] _postBinaryData;
private boolean _allowCaching;
protected final List<StatusListener> _listeners;
protected List<String> _extraHeaders;
@ -421,7 +422,7 @@ public class EepGet {
" [-t timeout] (default 60 sec)\n" +
" [-e etag]\n" +
" [-h headerName=headerValue]\n" +
" [-u username] [-x password] url\n" +
" [-u proxyUsername] [-x proxyPassword] url\n" +
" (use -c or -p :0 for no proxy)");
}
@ -1439,6 +1440,8 @@ public class EepGet {
timeout.setSocket(_proxy);
_proxyOut.write(DataHelper.getUTF8(req));
if (_postBinaryData != null)
_proxyOut.write(_postBinaryData);
_proxyOut.flush();
if (_log.shouldLog(Log.DEBUG))
@ -1448,7 +1451,8 @@ public class EepGet {
protected String getRequest() throws IOException {
StringBuilder buf = new StringBuilder(2048);
boolean post = false;
if ( (_postData != null) && (_postData.length() > 0) )
if ((_postData != null && _postData.length() > 0) ||
(_postBinaryData != null && _postBinaryData.length > 0))
post = true;
URI url;
try {
@ -1525,8 +1529,14 @@ public class EepGet {
buf.append(_lastModified);
buf.append("\r\n");
}
if (post)
buf.append("Content-length: ").append(_postData.length()).append("\r\n");
if (post) {
buf.append("Content-length: ");
if (_postData != null)
buf.append(_postData.length());
else
buf.append(_postBinaryData.length);
buf.append("\r\n");
}
// This will be replaced if we are going through I2PTunnelHTTPClient
buf.append("Accept-Encoding: ");
// as of 0.9.23, the proxy passes the Accept-Encoding header through
@ -1546,8 +1556,9 @@ public class EepGet {
buf.append("\r\n");
}
buf.append("Connection: close\r\n\r\n");
if (post)
if (_postData != null)
buf.append(_postData);
// _postBinaryData will be appended by caller
if (_log.shouldLog(Log.DEBUG))
_log.debug("Request:\n" + buf.toString().trim());
return buf.toString();
@ -1668,6 +1679,34 @@ public class EepGet {
}
}
/**
* Set post data.
* Must be called before fetch().
*
* @throws IllegalStateException if already set
* @since 0.9.67
*/
protected void setPostData(String contentType, String data) {
if (_postData != null || _postBinaryData != null)
throw new IllegalStateException();
addHeader("Content-Type", contentType);
_postData = data;
}
/**
* Set post data.
* Must be called before fetch().
*
* @throws IllegalStateException if already set
* @since 0.9.67
*/
protected void setPostData(String contentType, byte[] data) {
if (_postData != null || _postBinaryData != null)
throw new IllegalStateException();
addHeader("Content-Type", contentType);
_postBinaryData = data;
}
/**
* Parse the args in an authentication header.
*

View File

@ -0,0 +1,358 @@
package net.i2p.util;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import gnu.getopt.Getopt;
import net.i2p.I2PAppContext;
import net.i2p.data.Base32;
import net.i2p.data.DataHelper;
/**
* Extends EepGet for POST.
* Adapted from old jrandom EepPost, removed 2012 as unused.
*
* @since 0.9.67
*/
public class EepPost extends EepGet {
private static final String CRLF = "\r\n";
private static final byte[] CRLFB = DataHelper.getASCII(CRLF);
public EepPost(I2PAppContext ctx, String proxyHost, int proxyPort, int numRetries, String outputFile, String url) {
// we're using this constructor:
// public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, long minSize, long maxSize, String outputFile, OutputStream outputStream, String url, boolean allowCaching, String etag, String postData) {
super(ctx, true, proxyHost, proxyPort, numRetries, -1, -1, outputFile, null, url, true, null, null);
}
/**
* Submit an HTTP POST to the given URL (using the proxy if specified),
* uploading the given fields. If the field's value is a File object, then
* that file is uploaded, and if the field's value is a String object, the
* value is posted for that particular field. Multiple values for one
* field name is not currently supported.
*
* Warning: Files are loaded in-memory. Do not use for large files.
*
* @param field values must be String or File.
*/
public boolean post(Map<String, Object> fields, long headerTimeout, long totalTimeout, long inactivityTimeout) {
if (fields.isEmpty())
throw new IllegalArgumentException();
boolean multipart = false;
for (Object o : fields.values()) {
if (o instanceof File) {
multipart = true;
break;
}
}
if (multipart) {
String sep = multipart ? getSeparator() : null;
ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
try {
sendFields(out, sep, fields);
} catch (IOException ioe) {
return false;
}
String type = "multipart/form-data, boundary=" + sep;
return post(type, out.toByteArray(), headerTimeout, totalTimeout, inactivityTimeout);
} else {
StringBuilder out = new StringBuilder(2048);
sendFields(out, fields);
String type = "application/x-www-form-urlencoded";
return post(type, out.toString(), headerTimeout, totalTimeout, inactivityTimeout);
}
}
public boolean post(String contentType, String data, long headerTimeout, long totalTimeout, long inactivityTimeout) {
if (data.length() == 0)
throw new IllegalArgumentException();
setPostData(contentType, data);
return super.fetch(headerTimeout, totalTimeout, inactivityTimeout);
}
public boolean post(String contentType, byte[] data, long headerTimeout, long totalTimeout, long inactivityTimeout) {
if (data.length == 0)
throw new IllegalArgumentException();
setPostData(contentType, data);
return super.fetch(headerTimeout, totalTimeout, inactivityTimeout);
}
/**
* @throws UnsupportedOperationException always
*/
public boolean fetch() {
throw new UnsupportedOperationException("use post()");
}
/**
* @throws UnsupportedOperationException always
*/
public boolean fetch(long fetchHeaderTimeout) {
throw new UnsupportedOperationException("use post()");
}
/**
* @throws UnsupportedOperationException always
*/
public boolean fetch(long fetchHeaderTimeout, long totalTimeout, long inactivityTimeout) {
throw new UnsupportedOperationException("use post()");
}
/**
* Adapted from old jrandom EepPost
*/
private static void sendFields(StringBuilder out, Map<String, Object> fields) {
boolean first = true;
for (Map.Entry<String, Object> e : fields.entrySet()) {
String field = e.getKey();
Object val = e.getValue();
if (!first)
out.append('&');
sendField(out, field, val.toString());
first = false;
}
out.append(CRLF);
}
/**
* Multipart
* Adapted from old jrandom EepPost
* @param separator non-null
*/
private static void sendFields(OutputStream out, String separator, Map<String, Object> fields) throws IOException {
boolean first = true;
for (Map.Entry<String, Object> e : fields.entrySet()) {
String field = e.getKey();
Object val = e.getValue();
if (val instanceof File) {
sendFile(out, separator, field, (File)val);
} else {
if (separator == null && !first)
out.write('&');
sendField(out, separator, field, val.toString());
first = false;
}
}
out.write(DataHelper.getUTF8("--" + separator + "--" + CRLF));
}
/**
* Adapted from old jrandom EepPost
*/
private static void sendField(StringBuilder out, String field, String val) {
// TODO % encoding
out.append(field.replace(" ", "+")).append('=').append(val.replace(" ", "+"));
}
/**
* Multipart
* Adapted from old jrandom EepPost
* @param separator non-null
*/
private static void sendField(OutputStream out, String separator, String field, String val) throws IOException {
out.write(DataHelper.getUTF8("--" + separator + CRLF));
out.write(DataHelper.getUTF8("Content-Disposition: form-data; name=\"" + field + "\"" + CRLF + CRLF));
out.write(DataHelper.getUTF8(val));
out.write(CRLFB);
}
/**
* Multipart
* Adapted from old jrandom EepPost
* @param separator non-null
*/
private static void sendFile(OutputStream out, String separator, String field, File file) throws IOException {
out.write(DataHelper.getUTF8("--" + separator + CRLF));
out.write(DataHelper.getUTF8("Content-Disposition: form-data; name=\"" + field + "\"; filename=\"" + file.getName() + "\"" + CRLF));
out.write(DataHelper.getUTF8("Content-Type: application/octet-stream" + CRLF + CRLF));
FileInputStream in = new FileInputStream(file);
try {
DataHelper.copy(in, out);
} finally {
in.close();
}
out.write(CRLFB);
}
/**
* Adapted from old jrandom EepPost
*/
private String getSeparator() {
byte separator[] = new byte[32];
_context.random().nextBytes(separator);
return Base32.encode(separator);
}
/**
* EepPost [-p 127.0.0.1:4444] [-n #retries] url
*/
public static void main(String args[]) {
String proxyHost = "127.0.0.1";
int proxyPort = 4444;
int numRetries = 0;
int headerTimeout = CONNECT_TIMEOUT;
int totalTimeout = -1;
int inactivityTimeout = INACTIVITY_TIMEOUT;
int markSize = 1024;
int lineLen = 40;
String saveAs = null;
String username = null;
String password = null;
boolean error = false;
Map<String, Object> fields = new HashMap<String, Object>(8);
Getopt g = new Getopt("eeppost", args, "p:cn:t:v:w:o:u:x:l:m:s:f:");
try {
int c;
while ((c = g.getopt()) != -1) {
switch (c) {
case 'p':
String s = g.getOptarg();
int colon = s.indexOf(':');
if (colon >= 0) {
// Todo IPv6 [a:b:c]:4444
proxyHost = s.substring(0, colon);
String port = s.substring(colon + 1);
proxyPort = Integer.parseInt(port);
} else {
proxyHost = s;
// proxyPort remains default
}
break;
case 'c':
// no proxy, same as -p :0
proxyHost = "";
proxyPort = 0;
break;
case 'f': {
String[] t = DataHelper.split(g.getOptarg(), "=", 2);
if (t.length == 2 && t[0].length() > 0)
fields.put(t[0], new File(t[1]));
else
error = true;
break;
}
case 'l':
lineLen = Integer.parseInt(g.getOptarg());
break;
case 'm':
markSize = Integer.parseInt(g.getOptarg());
break;
case 'n':
numRetries = Integer.parseInt(g.getOptarg());
break;
case 'o':
saveAs = g.getOptarg();
break;
case 's': {
String[] t = DataHelper.split(g.getOptarg(), "=", 2);
if (t.length == 2 && t[0].length() > 0)
fields.put(t[0], t[1]);
else
error = true;
break;
}
case 't':
inactivityTimeout = 1000 * Integer.parseInt(g.getOptarg());
break;
case 'u':
username = g.getOptarg();
break;
case 'v':
headerTimeout = 1000 * Integer.parseInt(g.getOptarg());
break;
case 'w':
totalTimeout = 1000 * Integer.parseInt(g.getOptarg());
break;
case 'x':
password = g.getOptarg();
break;
case '?':
case ':':
default:
error = true;
break;
} // switch
} // while
} catch (RuntimeException e) {
e.printStackTrace();
error = true;
}
if (error || args.length - g.getOptind() != 1 || fields.isEmpty()) {
if (fields.isEmpty())
System.err.println("At least one -s or -f parameter required");
usage();
System.exit(1);
}
String url = args[g.getOptind()];
if (saveAs == null)
saveAs = suggestName(url);
EepPost post = new EepPost(I2PAppContext.getGlobalContext(), proxyHost, proxyPort, numRetries, saveAs, url);
if (username != null) {
if (password == null) {
try {
BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
do {
System.err.print("Proxy password: ");
password = r.readLine();
if (password == null)
throw new IOException();
password = password.trim();
} while (password.length() <= 0);
} catch (IOException ioe) {
System.exit(1);
}
}
post.addAuthorization(username, password);
}
post.addStatusListener(post.new CLIStatusListener(markSize, lineLen));
if (!post.post(fields, headerTimeout, totalTimeout, inactivityTimeout)) {
System.err.println("Failed " + url);
System.exit(1);
}
}
private static void usage() {
System.err.println("eeppost [-p 127.0.0.1[:4444]] [-c] [-o outputFile]\n" +
" [-s key=value]*\n" +
" [-f key=file]*\n" +
" [-m markSize] (default 1024)\n" +
" [-l lineLen] (default 40)\n" +
" [-n #retries] (default 0)\n" +
" [-t headerTimeout] (default 45 sec)\n" +
" [-u inactivityTimeout] (default 60 sec)\n" +
" [-w totalTimeout] (default unlimited)\n" +
" [-u username] [-x password] url\n" +
" (use -c or -p :0 for no proxy)");
}
}