/*
* Copyright (c) 1998 - 2012. University Corporation for Atmospheric Research/Unidata
* Portions of this software were developed by the Unidata Program at the
* University Corporation for Atmospheric Research.
*
* Access and use of this software shall impose the following obligations
* and understandings on the user. The user is granted the right, without
* any fee or cost, to use, copy, modify, alter, enhance and distribute
* this software, and any derivative works thereof, and its supporting
* documentation for any purpose whatsoever, provided that this entire
* notice appears in all copies of the software, derivative works and
* supporting documentation. Further, UCAR requests that the user credit
* UCAR/Unidata in any publications that result from the use of this
* software or in any product that includes this software. The names UCAR
* and/or Unidata, however, may not be used in any advertising or publicity
* to endorse or promote any products or commercial entity unless specific
* written permission is obtained from UCAR/Unidata. The user also
* understands that UCAR/Unidata is not obligated to provide the user with
* any support, consulting, training or assistance of any kind with regard
* to the use, operation and performance of this software nor to provide
* the user with any updates, revisions, new versions or "bug fixes."
*
* THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package ucar.nc2.util.net;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.junit.Assert;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import ucar.httpservices.*;
import ucar.unidata.util.test.UnitTestCommon;
import ucar.unidata.util.test.category.NeedsExternalResource;
import ucar.unidata.util.test.TestDir;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Test HttpFormBuilder
*/
@Category(NeedsExternalResource.class)
public class TestFormBuilder extends UnitTestCommon
{
//////////////////////////////////////////////////
// Constants
static final boolean DEBUG = true;
// Field values to use
static final String DESCRIPTIONENTRY = "TestFormBuilder";
static final String NAMEENTRY = "Mr. Jones";
static final String EMAILENTRY = "idv@ucar.edu";
static final String ORGENTRY = "UCAR";
static final String SUBJECTENTRY = "hello";
static final String SOFTWAREPACKAGEENTRY = "IDV";
static final String VERSIONENTRY = "1.0.1";
static final String HARDWAREENTRY = "x86";
static final String OSTEXT = System.getProperty("os.name");
static final String EXTRATEXT = "extra";
static final String BUNDLETEXT = "bundle";
static final String ATTACHTEXT = "arbitrary data\n";
static protected final String FAKEBOUNDARY = "XXXXXXXXXXXXXXXXXXXX";
static protected final String FAKEATTACH3 = "attach3XXXXXXXXXXXXXXXXXXXX.txt";
static final char QUOTE = '"';
static final char COLON = ':';
// This needs to be a real site in order to get
// the request info
static final String NULLURL = "http://" + TestDir.remoteTestServer;
//////////////////////////////////////////////////
// Instance Variables
protected String boundary = null;
File attach3file = null;
//////////////////////////////////////////////////
// Constructor(s)
public TestFormBuilder()
{
setTitle("HTTPFormBuilder test(s)");
setSystemProperties();
// Turn on Session debugging
HTTPSession.debugHeaders(false);
}
@Test
public void
testSimple()
throws Exception
{
HTTPSession.debugReset();
try {
HTTPFormBuilder builder = buildForm(false);
HttpEntity content = builder.build();
try (HTTPMethod postMethod = HTTPFactory.Post(NULLURL)) {
postMethod.setRequestContent(content);
// Execute, but ignore any problems
try {
postMethod.execute();
} catch (Exception e) {
// ignore
}
}
// Get the request that was used
HTTPUtil.InterceptRequest dbgreq = HTTPSession.debugRequestInterceptor();
Assert.assertTrue("Could not get debug request", dbgreq != null);
HttpEntity entity = dbgreq.getRequestEntity();
Assert.assertTrue("Could not get debug entity", entity != null);
// Extract the form info
Header ct = entity.getContentType();
String body = extract(entity, ct, false);
Assert.assertTrue("Malformed debug request", body != null);
if(DEBUG || prop_visual)
visual("TestFormBuilder.testsimple.RAW", body);
body = genericize(body, OSTEXT, null, null);
if(DEBUG)
visual("TestFormBuilder.testsimple.LOCALIZED", body);
String diffs = UnitTestCommon.compare("TestFormBuilder.testSimpl", simplebaseline, body);
if(diffs != null) {
System.err.println("TestFormBuilder.testsimple.diffs:\n" + diffs);
Assert.assertTrue("TestFormBuilder.testSimple: ***FAIL", false);
}
} catch (Exception e) {
Assert.assertTrue("***FAIL: " + e.getCause(), false);
if(DEBUG)
e.printStackTrace();
}
}
@Test
public void
testMultiPart()
throws Exception
{
// Try to create a tmp file
attach3file = HTTPUtil.fillTempFile("attach3.txt", ATTACHTEXT);
attach3file.deleteOnExit();
HTTPSession.debugReset();
try {
HTTPFormBuilder builder = buildForm(true);
HttpEntity content = builder.build();
try (HTTPMethod postMethod = HTTPFactory.Post(NULLURL)) {
postMethod.setRequestContent(content);
// Execute, but ignore any problems
try {
postMethod.execute();
} catch (Exception e) {
// ignore
}
}
// Get the request that was used
HTTPUtil.InterceptRequest dbgreq = HTTPSession.debugRequestInterceptor();
Assert.assertTrue("Could not get debug request", dbgreq != null);
HttpEntity entity = dbgreq.getRequestEntity();
Assert.assertTrue("Could not get debug entity", entity != null);
// Extract the form info
Header ct = entity.getContentType();
String body = extract(entity, ct, true);
Assert.assertTrue("Malformed debug request", body != null);
if(DEBUG || prop_visual)
visual("TestFormBuilder.testmultipart.RAW", body);
// Get the contenttype boundary
String boundary = getboundary(ct);
Assert.assertTrue("Missing boundary info", boundary != null);
String attach3 = getattach(body, "attach3");
Assert.assertTrue("Missing attach3 info", attach3 != null);
body = genericize(body, OSTEXT, boundary, attach3);
if(DEBUG)
visual("TestFormBuilder.testmultipart.LOCALIZED", body);
String diffs = UnitTestCommon.compare("TestFormBuilder.testMultiPart", multipartbaseline, body);
if(diffs != null) {
System.err.println("TestFormBuilder.testmultipart.diffs:\n" + diffs);
Assert.assertTrue("TestFormBuilder.testmultipart: ***FAIL", false);
}
} catch (Exception e) {
Assert.assertTrue("***FAIL: " + e.getCause(), false);
if(DEBUG)
e.printStackTrace();
}
}
protected HTTPFormBuilder buildForm(boolean multipart)
throws HTTPException
{
HTTPFormBuilder builder = new HTTPFormBuilder();
/*
StringBuffer javaInfo = new StringBuffer();
javaInfo.append("Java: home: " + System.getProperty("java.home"));
javaInfo.append(" version: " + System.getProperty("java.version"))
*/
builder.add("fullName", NAMEENTRY);
builder.add("emailAddress", EMAILENTRY);
builder.add("organization", ORGENTRY);
builder.add("subject", SUBJECTENTRY);
builder.add("description", DESCRIPTIONENTRY);
builder.add("softwarePackage", SOFTWAREPACKAGEENTRY);
builder.add("packageVersion", VERSIONENTRY);
builder.add("os", OSTEXT);
builder.add("hardware", HARDWAREENTRY);
if(multipart) {
// Use bytes
builder.add("attachmentOne", EXTRATEXT.getBytes(HTTPUtil.ASCII), "extra.html");
// Use Inputstream
byte[] bytes = BUNDLETEXT.getBytes(HTTPUtil.UTF8);
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
builder.add("attachmentTwo", bis, "bundle.xidv");
if(attach3file != null) {
// Use File
builder.add("attachmentThree", attach3file);
}
}
return builder;
}
protected String getboundary(Header contentype)
throws HTTPException
{
Assert.assertTrue("No content header", contentype != null);
String[] pieces = contentype.getValue().split("[ ]*[;][ ]*");
String boundary = null;
for(String s : pieces) {
if(s.toLowerCase().startsWith("boundary")) {
pieces = s.split("[=]");
Assert.assertTrue("Bad boundary", pieces.length == 2);
boundary = pieces[1];
break;
}
}
Assert.assertTrue("Missing boundary", boundary != null);
return boundary;
}
protected String getattach(String text, String attachfile)
throws HTTPException
{
String attach3 = null;
String[] lines = text.split("[\n]");
int pos = -1;
String boundary = null;
for(int i = 0; i < lines.length; i++) {
String line = lines[i];
line = line.replace("\r", "");
Map<String, String> map = parseheaderline(line);
if(map == null) continue;
String prefix = map.get("PREFIX");
if(prefix.equals("content-disposition")) {
if(map.get("name").equals("attachmentThree")) {
attach3 = map.get("filename");
Assert.assertTrue("Missing attach3 filename", attach3 != null);
}
}
}
return attach3;
}
protected String genericize(String body, String os, String boundary, String attach3name)
throws HTTPException
{
body = body.replace("\r", "");
// Generic os: Handle case with and without blank
body = body.replace(os, "<OSNAME>");
os = os.replace(' ', '+');
body = body.replace(os, "<OSNAME>");
if(boundary != null) {
// Convert to generic
body = body.replace(boundary, FAKEBOUNDARY);
}
if(attach3name != null) {
// Convert to generic
body = body.replace(attach3name, FAKEATTACH3);
}
return body;
}
protected Map<String, String>
parseheaderline(String line)
{
Map<String, String> map = new HashMap<>();
map.put("PREFIX", ""); // default
if(line == null || line.length() == 0)
return map;
int i = line.indexOf(":");
if(i < 0) {
map.put("PREFIX", line);
return map;
}
map.put("PREFIX", line.substring(0, i).trim().toLowerCase());
line = line.substring(i + 1).trim();
String[] pieces = line.split("[ \t]*[;][ \t]*");
if(pieces.length == 1) {
map.put(pieces[0], "");
return map;
}
for(String piece : pieces) {
String[] pair = piece.split("[=]");
String value = "";
String key = pair[0];
switch (pair.length) {
case 1:
break;
case 2:
default:
value = pair[1].trim();
if(value.charAt(0) == '"')
value = value.substring(1, value.length() - 1);
break;
}
map.put(key, value);
}
return map;
}
static final String patb = "--.*";
static final Pattern blockb = Pattern.compile(patb);
static final String patcd =
"Content-Disposition:\\s+form-data;\\s+name=[\"]([^\"]*)[\"]";
static final Pattern blockcd = Pattern.compile(patcd);
static final String patcdx = patcd
+ "\\s*[;]\\s+filename=[\"]([^\"]*)[\"]";
static final Pattern blockcdx = Pattern.compile(patcdx);
protected Map<String, String> parsemultipartbody(String body)
throws IOException
{
Map<String, String> map = new TreeMap<>();
body = body.replace("\r\n", "\n");
StringReader sr = new StringReader(body);
BufferedReader rdr = new BufferedReader(sr);
String line = rdr.readLine();
if(line == null)
throw new HTTPException("Empty body");
for(; ; ) { // invariant is that the next unconsumed line is in line
String name = null;
String filename = null;
StringBuilder value = new StringBuilder();
if(!line.startsWith("--"))
throw new HTTPException("Missing boundary marker : " + line);
line = rdr.readLine();
// This might have been the trailing boundary marker
if(line == null)
break;
if(line.toLowerCase().startsWith("content-disposition")) {
// Parse the content-disposition
Matcher mcd = blockcdx.matcher(line); // try extended
if(!mcd.lookingAt()) {
mcd = blockcd.matcher(line);
if(!mcd.lookingAt())
throw new HTTPException("Malformed Content-Disposition marker : " + line);
name = mcd.group(1);
} else {
name = mcd.group(1);
filename = mcd.group(2);
}
} else
throw new HTTPException("Missing Content-Disposition marker : " + line);
// Treat content-type line as optional; may or may not have charset
line = rdr.readLine();
if(line.toLowerCase().startsWith("content-type")) {
line = rdr.readLine();
}
// treat content-transfer-encoding line as optional
if(line.toLowerCase().startsWith("content-transfer-encoding")) {
line = rdr.readLine();
}
// Skip one blank line
line = rdr.readLine();
// Extract the content
value.setLength(0);
while(!line.startsWith("--")) {
value.append(line);
value.append("\n");
line = rdr.readLine();
}
map.put(name, value.toString());
}
return map;
}
static protected String join(String[] pieces, int offset, String sep)
{
StringBuilder buf = new StringBuilder();
boolean first = true;
for(int i = offset; i < pieces.length; i++) {
if(first) buf.append(sep);
first = false;
buf.append(pieces[i]);
}
return buf.toString();
}
static protected String mapjoin(Map<String, String> map, String sep1, String sep2)
{
StringBuilder buf = new StringBuilder();
boolean first = true;
for(Map.Entry<String, String> entry : map.entrySet()) {
if(!first) buf.append(sep1);
first = false;
buf.append(entry.getKey());
buf.append(sep2);
buf.append(entry.getValue());
}
return buf.toString();
}
static final String simplebaseline =
"softwarePackage=IDV&emailAddress=idv%40ucar.edu&os=<OSNAME>&subject=hello&organization=UCAR&fullName=Mr.+Jones&description=TestFormBuilder&packageVersion=1.0.1&hardware=x86";
static final String multipartbaseline =
"--XXXXXXXXXXXXXXXXXXXX\nContent-Disposition: form-data; name=\"softwarePackage\"\n\nIDV\n--XXXXXXXXXXXXXXXXXXXX\nContent-Disposition: form-data; name=\"emailAddress\"\n\nidv@ucar.edu\n--XXXXXXXXXXXXXXXXXXXX\nContent-Disposition: form-data; name=\"os\"\n\n<OSNAME>\n--XXXXXXXXXXXXXXXXXXXX\nContent-Disposition: form-data; name=\"subject\"\n\nhello\n--XXXXXXXXXXXXXXXXXXXX\nContent-Disposition: form-data; name=\"attachmentTwo\"; filename=\"bundle.xidv\"\nContent-Type: application/octet-stream\n\nbundle\n--XXXXXXXXXXXXXXXXXXXX\nContent-Disposition: form-data; name=\"organization\"\n\nUCAR\n--XXXXXXXXXXXXXXXXXXXX\nContent-Disposition: form-data; name=\"fullName\"\n\nMr. Jones\n--XXXXXXXXXXXXXXXXXXXX\nContent-Disposition: form-data; name=\"description\"\n\nTestFormBuilder\n--XXXXXXXXXXXXXXXXXXXX\nContent-Disposition: form-data; name=\"attachmentThree\"; filename=\"attach3XXXXXXXXXXXXXXXXXXXX.txt\"\nContent-Type: application/octet-stream\n\narbitrary data\n\n--XXXXXXXXXXXXXXXXXXXX\nContent-Disposition: form-data; name=\"packageVersion\"\n\n1.0.1\n--XXXXXXXXXXXXXXXXXXXX\nContent-Disposition: form-data; name=\"attachmentOne\"; filename=\"extra.html\"\nContent-Type: application/octet-stream\n\nextra\n--XXXXXXXXXXXXXXXXXXXX\nContent-Disposition: form-data; name=\"hardware\"\n\nx86\n--XXXXXXXXXXXXXXXXXXXX--\n";
protected String extract(HttpEntity entity, Header ct, boolean multipart)
{
try {
if(multipart) {
String[] pieces = ct.getValue().split("[ ]*[;][ ]*");
Assert.assertTrue("Wrong content header", pieces[0].equalsIgnoreCase("multipart/form-data"));
} else {
Assert.assertTrue("Wrong content header", ct.getValue().equalsIgnoreCase("application/x-www-form-urlencoded"));
}
ByteArrayOutputStream out = new ByteArrayOutputStream((int) entity.getContentLength());
entity.writeTo(out);
byte[] contents = out.toByteArray();
String result = new String(contents, HTTPUtil.UTF8);
return result;
} catch (IOException e) {
return null;
}
}
}