blob: e3734a687db9f2f7d54f8dc1f3297770c354430f [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*******************************************************************************/
package org.apache.ofbiz.base.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.imaging.ImageReadException;
import org.apache.ofbiz.entity.Delegator;
/**
* HttpRequestFileUpload - Receive a file upload through an HttpServletRequest
*
*/
public class HttpRequestFileUpload {
private static final String MODULE = HttpRequestFileUpload.class.getName();
private static final int BUFFER_SIZE = 4096;
private static final int WAIT_INTERVAL = 200; // in milliseconds
private static final int MAX_WAITS = 20;
private int waitCount = 0;
private String savePath;
private String filepath;
private String filename;
private String contentType;
private String overrideFilename = null;
private Map<String, String> fields;
/**
* Gets override filename.
* @return the override filename
*/
public String getOverrideFilename() {
return overrideFilename;
}
/**
* Sets override filename.
* @param ofName the of name
*/
public void setOverrideFilename(String ofName) {
overrideFilename = ofName;
}
/**
* Gets filename.
* @return the filename
*/
public String getFilename() {
return filename;
}
/**
* Gets filepath.
* @return the filepath
*/
public String getFilepath() {
return filepath;
}
/**
* Sets save path.
* @param savePath the save path
*/
public void setSavePath(String savePath) {
this.savePath = savePath;
}
/**
* Gets content type.
* @return the content type
*/
public String getContentType() {
return contentType;
}
/**
* Gets field value.
* @param fieldName the field name
* @return the field value
*/
public String getFieldValue(String fieldName) {
if (fields == null || fieldName == null) {
return null;
}
return fields.get(fieldName);
}
private void setFilename(String s) {
if (s == null) {
return;
}
int pos = s.indexOf("filename=\"");
if (pos != -1) {
filepath = s.substring(pos + 10, s.length() - 1);
// Windows browsers include the full path on the client
// But Linux/Unix and Mac browsers only send the filename
// test if this is from a Windows browser
pos = filepath.lastIndexOf("\\");
if (pos != -1) {
filename = filepath.substring(pos + 1);
} else {
filename = filepath;
}
}
}
private void setContentType(String s) {
if (s == null) {
return;
}
int pos = s.indexOf(": ");
if (pos != -1) {
contentType = s.substring(pos + 2, s.length());
}
}
/**
* Do upload.
* @param request the request
* @throws IOException the io exception
*/
public boolean doUpload(HttpServletRequest request, String fileType) throws IOException {
Delegator delegator = (Delegator) request.getAttribute("delegator");
ServletInputStream in = request.getInputStream();
String reqLengthString = request.getHeader("content-length");
Debug.logInfo("expect " + reqLengthString + " bytes.", MODULE);
int requestLength = 0;
try {
requestLength = Integer.parseInt(reqLengthString);
} catch (NumberFormatException e) {
Debug.logError(e, e.getMessage(), MODULE);
return false;
}
byte[] line = new byte[BUFFER_SIZE];
int i = -1;
i = waitingReadLine(in, line, 0, BUFFER_SIZE, requestLength);
requestLength -= i;
if (i < 3) {
Debug.logError("Possibly a waitingReadLine error", MODULE);
return false;
}
int boundaryLength = i - 2;
String boundary = new String(line, 0, boundaryLength, StandardCharsets.UTF_8); // -2 discards the newline character
Debug.logInfo("boundary=[" + boundary + "] length is " + boundaryLength, MODULE);
fields = new HashMap<>();
while (requestLength > 0/* i != -1*/) {
String newLine = "";
if (i > -1) {
newLine = new String(line, 0, i, StandardCharsets.UTF_8);
}
if (newLine.startsWith("Content-Disposition: form-data; name=\"")) {
if (newLine.indexOf("filename=\"") != -1) {
setFilename(new String(line, 0, i - 2, StandardCharsets.UTF_8));
if (filename == null) {
return false;
}
// this is the file content
i = waitingReadLine(in, line, 0, BUFFER_SIZE, requestLength);
requestLength -= i;
setContentType(new String(line, 0, i - 2, StandardCharsets.UTF_8));
// blank line
i = waitingReadLine(in, line, 0, BUFFER_SIZE, requestLength);
requestLength -= i;
String filenameToUse = filename;
if (overrideFilename != null) {
filenameToUse = overrideFilename;
}
// first line of actual file
i = waitingReadLine(in, line, 0, BUFFER_SIZE, requestLength);
requestLength -= i;
byte[] lastTwoBytes = new byte[2];
if (i > 1) {
lastTwoBytes[0] = line[i - 2];
lastTwoBytes[1] = line[i - 1];
}
Debug.logInfo("about to create a file:" + (savePath == null ? "" : savePath) + filenameToUse, MODULE);
// before creating the file make sure directory exists
if (savePath == null) {
throw new IllegalArgumentException("savePath is null");
}
File savePathFile = new File(savePath);
if (!savePathFile.exists()) {
if (!savePathFile.mkdirs()) {
Debug.logError("Directory could not be created", filenameToUse);
}
}
String fileTocheck = savePath + filenameToUse;
try (FileOutputStream fos = new FileOutputStream(fileTocheck);) {
boolean bail = (new String(line, 0, i, StandardCharsets.UTF_8).startsWith(boundary));
boolean oneByteLine = (i == 1); // handle one-byte lines
while ((requestLength > 0/* i != -1 */) && !bail) {
// write the current buffer, except the last 2 bytes;
if (i > 1) {
fos.write(line, 0, i - 2);
}
oneByteLine = (i == 1); // we need to track on-byte lines differently
i = waitingReadLine(in, line, 0, BUFFER_SIZE, requestLength);
requestLength -= i;
// the problem is the last line of the file content
// contains the new line character.
// if the line just read was the last line, we're done.
// if not, we must write the last 2 bytes of the previous buffer
// just assume that a one-byte line isn't the last line
if (requestLength < 1) {
bail = true;
} else if (oneByteLine) {
fos.write(lastTwoBytes, 0, 1); // we only saved one byte
} else {
fos.write(lastTwoBytes, 0, 2);
}
if (i > 1) {
// save the last 2 bytes of the buffer
lastTwoBytes[0] = line[i - 2];
lastTwoBytes[1] = line[i - 1];
} else {
lastTwoBytes[0] = line[0]; // only save one byte
}
}
fos.flush();
if (!org.apache.ofbiz.security.SecuredUpload.isValidFile(fileTocheck, fileType, delegator)) {
return false;
}
} catch (ImageReadException e) {
Debug.logError(e, MODULE);
return false;
}
} else {
// this is a field
// get the field name
int pos = newLine.indexOf("name=\"");
String fieldName = newLine.substring(pos + 6, newLine.length() - 3);
// blank line
i = waitingReadLine(in, line, 0, BUFFER_SIZE, requestLength);
requestLength -= i;
i = waitingReadLine(in, line, 0, BUFFER_SIZE, requestLength);
requestLength -= i;
newLine = new String(line, 0, i, StandardCharsets.UTF_8);
StringBuilder fieldValue = new StringBuilder(BUFFER_SIZE);
while (requestLength > 0/* i != -1*/ && !newLine.startsWith(boundary)) {
// The last line of the field
// contains the new line character.
// So, we need to check if the current line is
// the last line.
i = waitingReadLine(in, line, 0, BUFFER_SIZE, requestLength);
requestLength -= i;
if ((i == boundaryLength + 2 || i == boundaryLength + 4) // + 4 is eof
&& (new String(line, 0, i).startsWith(boundary))) {
fieldValue.append(newLine.substring(0, newLine.length() - 2));
} else {
fieldValue.append(newLine);
}
newLine = new String(line, 0, i, StandardCharsets.UTF_8);
}
fields.put(fieldName, fieldValue.toString());
}
}
i = waitingReadLine(in, line, 0, BUFFER_SIZE, requestLength);
if (i > -1) {
requestLength -= i;
}
} // end while
return true;
}
// reads a line, waiting if there is nothing available and reqLen > 0
private int waitingReadLine(ServletInputStream in, byte[] buf, int off, int len, int reqLen) throws IOException {
int i = -1;
while (((i = in.readLine(buf, off, len)) == -1) && (reqLen > 0)) {
Debug.logInfo("waiting", MODULE);
if (waitCount > MAX_WAITS) {
Debug.logInfo("waited " + waitCount + " times, bailing out while still expecting "
+ reqLen + " bytes.", MODULE);
throw new IOException("waited " + waitCount + " times, bailing out while still expecting "
+ reqLen + " bytes.");
}
waitCount++;
long endMS = new Date().getTime() + WAIT_INTERVAL;
while (endMS > (new Date().getTime())) {
try {
wait(WAIT_INTERVAL);
} catch (InterruptedException e) {
Debug.logInfo(".", MODULE);
}
}
Debug.logInfo((new Date().getTime() - endMS) + " ms", MODULE);
}
return i;
}
}