First the problem.
The user can upload file from the web with ajax. If the file is relatively big, the uploading takes a while. If the user's connection is lost or something happens during the uploading process, the file is going to be damaged or empty.
How should I secure the upload process so the file remains the same if it fails for some reason?
I'm using the following libraries on the Arduino ESP32:
ESPAsyncWebServer
LITTLEFS
I have a basic file upload handler on my esp32 which looks like this:
server.on("/uploading", HTTP_POST, [](AsyncWebServerRequest * request) {
}, handleFileUpload);
void handleFileUpload(AsyncWebServerRequest * request, String filename,size_t index, uint8_t *data, size_t len, bool final) {
if (!index) {
if (!filename.startsWith("/"))
filename = "/" + filename;
if (LITTLEFS.exists(filename)) {
LITTLEFS.remove(filename);
}
uploadFile = LITTLEFS.open(filename, "w");
}
for (size_t i = 0; i < len; i++) {
uploadFile.write(data[i]);
}
if (final) {
uploadFile.close();
if(filename == "/myHomeProgram.json"){initProgram = true;}
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "File Uploaded;"+filename);
response->addHeader("Access-Control-Allow-Origin","*");
request->send(response);
}
}
This is working pretty well, the files are uploaded correctly 99% of the cases, but if it fails I lost the file data, or if some other part of the program wants to open the same file it fails too.
Should I write to a temporary file and after if it succeeded write the content to the intended file somehow?
Here is an example from client ( JS ) side:
// Example call:
saveFile(JSON.stringify(places),"/myHomeProgram.json","application/json");
function saveFile(data, filename, type) {
var file = new Blob([data], {type: type});
form = new FormData();
form.append("blob", file, filename);
$.ajax({
url: '/uploading',
type: 'POST',
data: form,
processData: false,
contentType: false
}).done(function(resp){
var response = resp.split(";");
$(".saveIconGraph").removeClass("fas fa-spinner fa-spin");
$(".saveIconGraph").addClass("far fa-save");
if(response[1] == "/myHomeProgram.json"){
toast("success","saveOk","progInfo",3500);
showSaved();
setTimeout(() => {
$("#saveMe").fadeOut( "slow", function() {
showSave();
});
}, 1000);
initPlaces();
}
}).fail(function(resp){
var response = resp.split(";");
$(".saveIconGraph").removeClass("fas fa-spinner fa-spin");
$(".saveIconGraph").addClass("far fa-save");
if(response[1] == "/myHomeProgram.json"){
toast("error","saveNotOk","progInfo",3500);
showSaveError();
$("#saveMeBtn").addClass("shakeEffect");
setTimeout(() => {
$("#saveMeBtn").removeClass("shakeEffect");
showSave();
}, 4500);
}
});
}
I could save the file in a temporary char variable before write, and on the final I could match the size of the file and the temporary variable size and if it is not the same, roll back to the previous. Is this manageable?
Something like this:
String uploadTemp = "";
inline boolean saveFileToTemp(String fileName){
uploadTemp = "";
File f = LITTLEFS.open(fileName, "r");
if (!f) {
f.close();
return false;
}else{
for (int i = 0; i < f.size(); i++){
uploadTemp += (char)f.read();
}
}
f.close();
return true;
}
inline boolean revertBackFile(String fileName){
File g = LITTLEFS.open(fileName, "w");
if (!g) {
g.close();
return false;
}else{
g.print(uploadTemp);
}
g.close();
return true;
}
inline boolean matchFileSizes(String fileName,boolean isFileExists){
boolean isCorrect = false;
if(isFileExists){
File writedFile = LITTLEFS.open(fileName, "w");
if( writedFile.size() == uploadTemp.length()){
isCorrect = true;
}else{
isCorrect = false;
}
writedFile.close();
return isCorrect;
}else{
return true;
}
}
void handleFileUpload(AsyncWebServerRequest * request, String filename,size_t index, uint8_t *data, size_t len, bool final) {
String webResponse;
boolean error = false,isFileExists = false;
if (!index) {
if (!filename.startsWith("/"))
filename = "/" + filename;
if (LITTLEFS.exists(filename)) {
isFileExists = true;
// Save the file to a temporary String if it success we continue.
if( saveFileToTemp(filename) ){
LITTLEFS.remove(filename);
}else{
// If the file save was fail we abort everything.
webResponse = "File NOT Uploaded " + filename;
final = true;
error = true;
}
}
if( !error ){
uploadFile = LITTLEFS.open(filename, "w");
}
}
if( !error ){
// Copy content to the actual file
for (size_t i = 0; i < len; i++) {
uploadFile.write(data[i]);
}
}
if (final) {
uploadFile.close();
if( !error ){
if( matchFileSizes(filename,isFileExists) ){
if(filename == "/myHomeProgram.json"){initProgram = true;}
webResponse = "File Uploaded " + filename;
}else{
error = true;
webResponse = "File length mismatch";
}
}
if( error ){
revertBackFile(filename);
}
Serial.println(webResponse);
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", webResponse);
response->addHeader("Access-Control-Allow-Origin","*");
request->send(response);
}
}
It seems to me that the problem solved.
I have managed to replace the String buffer with a char one in external memory. It seems stable but requires more testing. I'll post the solution but if anyone has a better approach feel free to comment here.
Thanks.
char * uploadTemp;
inline boolean saveFileToTemp(String fileName){
File f = LITTLEFS.open(fileName, "r");
if (!f) {
f.close();
return false;
}else{
size_t fileSize = f.size();
uploadTemp = (char*)ps_malloc(fileSize + 1);
for (int i = 0; i < fileSize; i++){
uploadTemp[i] = (char)f.read();
}
uploadTemp[fileSize] = '\0';
}
f.close();
return true;
}
inline boolean revertBackFile(String fileName){
File g = LITTLEFS.open(fileName, "w");
if (!g) {
g.close();
return false;
}else{
g.print(uploadTemp);
}
g.close();
return true;
}
inline boolean matchFileSizes(String fileName,boolean isFileExists){
boolean isCorrect = false;
if(isFileExists){
File writedFile = LITTLEFS.open(fileName, "w");
if( writedFile.size() == sizeof(uploadTemp)){
isCorrect = true;
}else{
isCorrect = false;
}
writedFile.close();
return isCorrect;
}else{
return true;
}
}
void handleFileUpload(AsyncWebServerRequest * request, String filename,size_t index, uint8_t *data, size_t len, bool final) {
boolean isFileExists = false,error = false;
String webResponse = "";
int httpStatus = 200;
// Start of the file upload
if (!index) {
// Make sure that there is a / char at the start of the string
if (!filename.startsWith("/")){ filename = "/" + filename; }
// Check if the file exists
if (LITTLEFS.exists(filename)) {
isFileExists = true;
// Get the file contents for safety reasons
// If it succeded we can create a new file in the palce
if( saveFileToTemp(filename) ){
uploadFile = LITTLEFS.open(filename, "w");
}else{
// If we can not save it abort the upload process.
webResponse = "File NOT Uploaded " + filename;
final = true;error = true;
}
}
}
// If we have no error at this point, we can start to copy the content to the file.
if( !error ){
for (size_t i = 0; i < len; i++) {
uploadFile.write(data[i]);
}
}
// If no more data we can start responding back to the client
if (final) {
uploadFile.close();
// Check if we got any error before.
if( !error && matchFileSizes(filename,isFileExists) ){
// Copyed file is the same, upload success.
if(filename == "/myHomeProgram.json"){initProgram = true;}
webResponse = "File Uploaded " + filename;
}else{
webResponse = "File length mismatch";
revertBackFile(filename);
httpStatus = 500;
}
free(uploadTemp);
AsyncWebServerResponse *response = request->beginResponse(httpStatus, "text/plain", webResponse);
response->addHeader("Access-Control-Allow-Origin","*");
request->send(response);
}
}
EDIT:
Yeah, so it was completely wrong.
I have to do the following things:
Save the file we want to upload if it exist into a temporary char array.
Get the uploaded file into a temporary file on upload.
If everything was a success, copy the contents of the temporary file to the intended file.
If something fails, revert back the saved file to the original and report an error.
Something like this ( still in test ):
char * prevFileTemp;
inline boolean saveFileToTemp(String fileName){
File f = LITTLEFS.open(fileName, "r");
if (!f) {
f.close();
return false;
}else{
size_t fileSize = f.size();
prevFileTemp = (char*)ps_malloc(fileSize + 1);
for (int i = 0; i < fileSize; i++){
prevFileTemp[i] = (char)f.read();
}
}
f.close();
return true;
}
inline boolean revertBackFile(String fileName){
if (LITTLEFS.exists(fileName)) {
Serial.println("Reverting back the file");
File g = LITTLEFS.open(fileName, "w");
if (!g) {
g.close();
return false;
}else{
g.print(prevFileTemp);
}
g.close();
}
return true;
}
static const inline boolean copyContent(String fileName){
File arrivedFile = LITTLEFS.open(uploadTemp, "r");
File newFile = LITTLEFS.open(fileName, "w");
// Check if we can open the files as intended.
if( !arrivedFile || !newFile){
revertBackFile(fileName);
return false;
}
// Copy one file content to another.
for (size_t i = 0; i < arrivedFile.size(); i++) { newFile.write( (char)arrivedFile.read() ); }
// Check the sizes, if no match, abort mission.
if( newFile.size() != arrivedFile.size()){ return false; }
arrivedFile.close();newFile.close();
return true;
}
boolean isFileExists = false,uploadError = false,newFileArrived = false;
String webResponse = "",newArrivalFileName = "";
int httpStatus = 200;
inline void resetVariables(){
isFileExists = false;
uploadError = false;
webResponse = "";
httpStatus = 200;
}
void handleFileUpload(AsyncWebServerRequest * request, String filename,size_t index, uint8_t *data, size_t len, bool final) {
// Start file upload process
if (!index) {
// Reset all the variables
resetVariables();
// Make sure that there is a '/' char at the start of the string
if (!filename.startsWith("/")){ filename = "/" + filename; }
// Open the temporary file for content copy if it is exist
if (LITTLEFS.exists(filename)) {
if( saveFileToTemp(filename) ){
uploadFile = LITTLEFS.open(uploadTemp, "w");
}else{
// If we can not save it abort the upload process.
webResponse = "File NOT Uploaded " + filename;
final = true;uploadError = true;
}
}
}
// If we have no error at this point, we can start to copy the content to the temporary file.
if( !uploadError ){
for (size_t i = 0; i < len; i++) {
uploadFile.write(data[i]);
}
}
// If no more data we can start responding back to the client
if (final) {
if (!filename.startsWith("/")){ filename = "/" + filename; }
uploadFile.close();
if( !uploadError && copyContent(filename) ){
webResponse = "File Uploaded " + filename;
}else{
webResponse = "File length mismatch";
revertBackFile(filename);
httpStatus = 500;
}
free(prevFileTemp);
AsyncWebServerResponse *response = request->beginResponse(httpStatus, "text/plain", webResponse);
response->addHeader("Access-Control-Allow-Origin","*");
request->send(response);
}
}
Related
I'm trying to read the mime type in GWT client side in order to validate a file before upload it. To do this I use JSNI to read the file header using HTML5 filereader API. However my problem is that GWT does not wait for the result of the reading and continue the code execution. The side effect is that my boolean is not set yet and my condition goes wrong. Is there any mechanism like promise implemented in GWT?
Any help on this would be much appreciated!
UploadImageButtonWidget.java
private boolean isMimeTypeValid = false;
private String mimeType = null;
public native boolean isValid(Element element)/*-{
var widget = this;
var files = element.files;
var reader = new FileReader();
var CountdownLatch = function (limit){
this.limit = limit;
this.count = 0;
this.waitBlock = function (){};
};
CountdownLatch.prototype.countDown = function (){
this.count = this.count + 1;
if(this.limit <= this.count){
return this.waitBlock();
}
};
CountdownLatch.prototype.await = function(callback){
this.waitBlock = callback;
};
var barrier = new CountdownLatch(1);
reader.readAsArrayBuffer(files[0]);
reader.onloadend = function(e) {
var arr = (new Uint8Array(e.target.result)).subarray(0, 4);
var header = "";
for (var i = 0; i < arr.length; i++) {
header += arr[i].toString(16);
}
widget.#com.portal.client.widgets.base.UploadImageButtonWidget::setMimeType(Ljava/lang/String;)(header);
barrier.countDown();
}
return barrier.await(function(){
return widget.#com.portal.client.widgets.base.UploadImageButtonWidget::isMimeTypeValid();
});
}-*/
public void setMimeType(String headerString) {
boolean mimeValid = true;
if (headerString.equalsIgnoreCase(PNG_HEADER)) {
mimeType = PNG_MIMETYPE;
} else if (headerString.equalsIgnoreCase(GIF_HEADER)) {
mimeType = GIF_MIMETYPE;
} else if (headerString.equalsIgnoreCase(JPG_HEADER1) || headerString.equalsIgnoreCase(JPG_HEADER2) || headerString.equalsIgnoreCase(JPG_HEADER3)) {
mimeType = JPG_MIMETYPE;
} else {
mimeValid = false;
setValidationError(i18n.uploadErrorNotImageBasedOnMimeType());
fileChooser.getElement().setPropertyJSO("files", null);
setErrorStatus();
}
setMimeTypeValid(mimeValid);
}
public boolean isMimeTypeValid() {
GWT.log("mimeType" + mimeType);
GWT.log("isMimetypeValid" + String.valueOf(isMimeTypeValid));
return mimeType != null;
}
in the activity:
public void validateAndUpload() {
UploadImageButtonWidget uploadImageButtonWidget = view.getUpload();
if (uploadImageButtonWidget.isValid()) {
GWT.log("mime ok: will be uploaded");
uploadImage();
} else {
GWT.log("mime not ok: will not be uploaded");
}
}
I need to delete files from the cache of APT and with the functions of files that Vala provides not let me.
Someone who can give me a hand?
The code is the following:
//Compile it using: valac --pkg gtk+-3.0 --pkg glib-2.0 --pkg gio-2.0 del-apt-cache.vala
using Gtk;
using GLib;
private int64[] get_info_and_clean (File file, string space = "", Cancellable? cancellable = null) throws Error
{
int64 files = 0;
int64 size = 0;
int64[] data = new int64[2];
Array<string> paths = new Array<string> ();
FileInfo info = null;
FileEnumerator enumerator;
try {//This Try/Catch is to ignore the permissions of '/var/cache/apt/archives/partial'
enumerator = file.enumerate_children (
"standard::*",
FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
cancellable);
} catch (IOError e) {
stderr.printf ("WARNING: Unable to get size of dir '%s': %s\n", file.get_path (), e.message);
data[0] = 0;
data[1] = 0;
return data;
}
while (cancellable.is_cancelled () == false && ((info = enumerator.next_file (cancellable)) != null)) {
if (info.get_file_type () == FileType.DIRECTORY) {
File subdir = file.resolve_relative_path (info.get_name ());
get_info_and_clean (subdir, space + " ", cancellable);
} else {
files += 1;//Sum Files
size += info.get_size ();//Accumulates Size
paths.append_val (file.get_uri () + "/" + info.get_name ());
}
}
if (cancellable.is_cancelled ()) {
throw new IOError.CANCELLED ("Operation was cancelled");
}
data[0] = files;
data[1] = size;
File apt_file;
for (int i = 0; i < paths.length; i++) {
apt_file = File.new_for_uri (paths.index (i));
stdout.printf ("FILE: %s", paths.index (i));
try {
apt_file.delete ();
stdout.printf (" [DELETED]\n");
} catch (Error e) {
stdout.printf (" [ERROR: %s]\n\n", e.message);
}
}
stdout.printf ("APT CACHE FILES: %s\n", files.to_string());
stdout.printf ("APT CACHE SIZE: %s\n", size.to_string());
return data;
}
public static int main (string[] args) {
Gtk.init (ref args);
File APT_CACHE_PATH = File.new_for_path ("/var/cache/apt/archives");
try {
get_info_and_clean (APT_CACHE_PATH, "", new Cancellable ());
} catch (Error e) {
stdout.printf ("ERROR: %s\n", e.message);
}
Gtk.main ();
return 0;
}
When I run the program, I get the following error:
FILE: file:///var/cache/apt/archives/libdbus-1-3_1.10.6-1ubuntu3_amd64.deb [ERROR: Failed to delete file: Permission denied]
There's nothing Vala can do if the operating system is denying you permission. You need to run your Vala program as root either using sudo or setting the “setuid” bit on the application and changing the owner to root.
I cannot display the Json message, Im trying prevente that user upload files with same name this is my controller code:
//POST: /Quote/Create Save the Uploaded file
public ActionResult SaveUploadedFile(int? chunk, string name)
{
bool exists;
var fileUpload = Request.Files[0];
var uploadPath = "C:\\Files";
chunk = chunk ?? 0;
if (System.IO.File.Exists(Path.Combine(uploadPath, name)))
{
exists = true;
}
else {
exists = false;
}
if (!exists)
{
using (var fs = new FileStream(Path.Combine(uploadPath, name), chunk == 0 ? FileMode.Create : FileMode.Append))
{
var buffer = new byte[fileUpload.InputStream.Length];
fileUpload.InputStream.Read(buffer, 0, buffer.Length);
fs.Write(buffer, 0, buffer.Length);
}
return Json(new { success = true }, JsonRequestBehavior.AllowGet);
}
else {
return Json(new { success = false, Message = "The file" + name +"already exists" }, JsonRequestBehavior.AllowGet);
}
}
This is my view code, if files success is false, then display the Json message:
UploadComplete: function (up, files) {
if (!files.success) {
alert(files.Message);
console.log(up);
} else {
var j = 0;
if (count > 0) {
j = count;
} else {
j = #i + '';
}
$.each(files, function (i, file) {
var extension = file.name.split(".");
$('.files').append('<input type=\"hidden\" name=\"Files[' + j + '].Name\" value=\"' + file.name + '\" />');
$('.files').append('<input type=\"hidden\" name=\"Files[' + j + '].Date\" value=\"' + "#DateTime.Now" + '\" />');
j++;
});
}
}
Thanks in advance !!
It seems that you need to be returning a string result rather than an ActionResult, since all you really want is if it passed or not. Also shortened your code a little to reflect the changes.
If you did want to return an object (meaning you wanted more than one property), I would create a model (class and then object), then return JsonResult rather than ActionResult.
Good documentation on how to return JsonResult object
C#
public string SaveUploadedFile(int? chunk, string name)
{
bool exists = false;
var fileUpload = Request.Files[0];
var uploadPath = "C:\\Files";
chunk = chunk ?? 0;
exists = System.IO.File.Exists(Path.Combine(uploadPath, name));
if (!exists)
{
using (var fs = new FileStream(Path.Combine(uploadPath, name), chunk == 0 ? FileMode.Create : FileMode.Append))
{
var buffer = new byte[fileUpload.InputStream.Length];
fileUpload.InputStream.Read(buffer, 0, buffer.Length);
fs.Write(buffer, 0, buffer.Length);
}
}
return message = !exists ? string.Empty
: "The file" + name + " already exists";
}
Javascript
if (files.message != '') { // meaning "exists" is true
console.log(up);
} else {
......
......
}
I have been using Spring's multipart uploader controllers to upload and store entries from zipped files, but I am finding that the occaisional PNG file being corrupted, where instead of begginning with something like "PNG..." in its byte[], it starts with "fþ»ÀÃgÞÉ" or similar. This seems to happen to the same files on each run. I tried all of this using java.util.ZipEntry and then I tried Apache Compress and found that Apache compress corrupted different files to the Java 7 utility, but always the same files on subsequent runs.
The code (firstly java.util.zip.ZipEntry):
protected void processZipFile(String path, MultipartFile file, String signature) throws IOException {
DateFormat df = new SimpleDateFormat("yyyyMMddhhmmss");
File tempFile = new File(System.getProperty("user.dir") + "/" + file.getName() + df.format(new Date()));
file.transferTo(tempFile);
ZipFile zipFile = null;
try {
zipFile = new ZipFile(tempFile);
LOG.debug("Processing archive with name={}, size={}.", file.getName(), file.getSize());
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while ( entries.hasMoreElements() )
{
ZipEntry entry = entries.nextElement();
LOG.debug("Processing file={} is directory?={}.", entry.getName(), entry.isDirectory());
// we don't bother processing directories, and we don't process any resource fork info
// from Mac OS X (which does not seem to be transparent to ZipFile).
if (!(entry.isDirectory() || entry.getName().contains("__MACOSX") || entry.getName().contains(".DS_Store"))) {
// if the entry is a file, extract it
Content contentToSave = null;
if(entry.getName().contains("gif") || entry.getName().contains("png") || entry.getName().contains("jpeg")) {
byte[] bytes = readInputStream( zipFile.getInputStream( entry ), entry.getSize() );
LOG.debug("{} is of inflated-length={} from compressed-length={}",
entry.getName(), bytes.length, entry.getCompressedSize());
if(entry.getName().contains("gif")) {
contentToSave = Content.makeImage(path + entry.getName(), Content.GIF, signature, bytes);
} else if (entry.getName().contains("png")) {
contentToSave = Content.makeImage(path + entry.getName(), Content.PNG, signature, bytes);
} else if (entry.getName().contains("jpeg")) {
contentToSave = Content.makeImage(path + entry.getName(), Content.JPEG, signature, bytes);
}
} else {
InputStream is = zipFile.getInputStream(entry);
if (entry.getName().contains("json")) {
contentToSave = Content.makeFile(path + entry.getName(), Content.JSON, signature, convertStreamToString(is));
} else if (entry.getName().contains("js")) {
contentToSave = Content.makeFile(path + entry.getName(), Content.JS, signature, convertStreamToString(is));
} else if (entry.getName().contains("css")) {
contentToSave = Content.makeFile(path + entry.getName(), Content.CSS, signature, convertStreamToString(is));
} else if (entry.getName().contains("xml")) {
contentToSave = Content.makeFile(path + entry.getName(), Content.XML, signature, convertStreamToString(is));
} else if (entry.getName().contains("html")) {
contentToSave = Content.makeFile(path + entry.getName(), Content.HTML, signature, convertStreamToString(is));
}
}
contentService.putOrReplace(contentToSave);
LOG.info("Persisted file: {} from uploaded version.", contentToSave.getName());
}
}
} catch (ZipException e) {
// If I can't create a ZipFile, then this is not a zip file at all and it cannot be processed
// by this method. Its pretty dumb that there's no way to determine whether the contents are zipped through
// the ZipFile API, but that's just one of its many problems.
e.printStackTrace();
LOG.error("{} is not a zipped file, or it is empty", file.getName());
} finally {
zipFile = null;
}
tempFile.delete();
}
And now the same thing for org.apache.commons.compress.archivers.zip.ZipFile:
protected void processZipFile(String path, MultipartFile file, String signature) throws IOException {
DateFormat df = new SimpleDateFormat("yyyyMMddhhmmss");
File tempFile = new File(System.getProperty("user.dir") + "/" + file.getName() + df.format(new Date()));
file.transferTo(tempFile);
ZipFile zipFile = null;
try {
zipFile = new ZipFile(tempFile);
LOG.debug("Processing archive with name={}, size={}.", file.getName(), file.getSize());
final Enumeration<? extends ZipArchiveEntry> entries = zipFile.getEntries();
while ( entries.hasMoreElements() ) {
ZipArchiveEntry entry = entries.nextElement();
LOG.debug("Processing file={} is directory?={}.", entry.getName(), entry.isDirectory());
// we don't bother processing directories, and we don't process any resource fork info
// from Mac OS X (which does not seem to be transparent to ZipFile).
if (!(entry.isDirectory() || entry.getName().contains("__MACOSX") || entry.getName().contains(".DS_Store"))) {
// if the entry is a file, extract it
Content contentToSave = null;
if(entry.getName().contains("gif") || entry.getName().contains("png") || entry.getName().contains("jpeg")) {
byte[] bytes = readInputStream( zipFile.getInputStream( entry ), entry.getSize() );
LOG.debug("{} is of inflated-length={} from compressed-length={}",
entry.getName(), bytes.length, entry.getCompressedSize());
if(entry.getName().contains("gif")) {
contentToSave = Content.makeImage(path + entry.getName(), Content.GIF, signature, bytes);
} else if (entry.getName().contains("png")) {
contentToSave = Content.makeImage(path + entry.getName(), Content.PNG, signature, bytes);
} else if (entry.getName().contains("jpeg")) {
contentToSave = Content.makeImage(path + entry.getName(), Content.JPEG, signature, bytes);
}
} else {
InputStream is = zipFile.getInputStream(entry);
if (entry.getName().contains("json")) {
contentToSave = Content.makeFile(path + entry.getName(), Content.JSON, signature, convertStreamToString(is));
} else if (entry.getName().contains("js")) {
contentToSave = Content.makeFile(path + entry.getName(), Content.JS, signature, convertStreamToString(is));
} else if (entry.getName().contains("css")) {
contentToSave = Content.makeFile(path + entry.getName(), Content.CSS, signature, convertStreamToString(is));
} else if (entry.getName().contains("xml")) {
contentToSave = Content.makeFile(path + entry.getName(), Content.XML, signature, convertStreamToString(is));
} else if (entry.getName().contains("html")) {
contentToSave = Content.makeFile(path + entry.getName(), Content.HTML, signature, convertStreamToString(is));
}
}
contentService.putOrReplace(contentToSave);
LOG.info("Persisted file: {} from uploaded version.", contentToSave.getName());
}
}
} catch (ZipException e) {
e.printStackTrace();
LOG.error("{} is not a zipped file, or it is empty", file.getName());
} catch (IOException e) {
e.printStackTrace();
LOG.error("{} is not a file, or it is empty", file.getName());
} finally {
zipFile = null;
}
tempFile.delete();
}
The two called methods are:
private static byte[] readInputStream( final InputStream is, final long length ) throws IOException {
final byte[] buf = new byte[ (int) length ];
int read = 0;
int cntRead;
while ( ( cntRead = is.read( buf, 0, buf.length ) ) >=0 )
{
read += cntRead;
}
return buf;
}
and:
public String convertStreamToString(InputStream is) throws IOException {
StringBuilder sb = new StringBuilder(2048);
char[] read = new char[128];
try (InputStreamReader ir = new InputStreamReader(is, StandardCharsets.UTF_8)) {
for (int i; -1 != (i = ir.read(read)); sb.append(read, 0, i));
}
// need to remove the ? at teh beginning of some files. This comes from the UTF8 BOM
// that is added to some files saved as UTF8
String out = sb.toString();
String utf8Bom = new String(new char[]{'\ufeff'});
if(out.contains(utf8Bom)) {
out = out.replace(utf8Bom,"");
}
return out;
}
The second one is, of course, not likely part of the problem.
I have googled around and it looks like issues similar to this have been found, but its always been some outside issue. Does anyone know why this might be the case?
I have re-edited some images and found that if I change the image to black and white, or change the hue of the whole image, the problem goes away, but if I add a border or change a single colour the problem remains. It looks like a particular arrangement of bytes in some files tickles a bug in whatever underlying API that both Java's own and Apache's compressed file readers use, but that's just speculation.
EDIT: additional usage shows that the corruption happens in gifs over 10K in size, so perhaps this has something to do with the bug? I have tried arbitrarily doubling the size of the buffer in the call to ReadInputStream(), but it did nothing except overflow the blob size in MySQL in particularly large images (49K became 98K, which was too big).
com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'encoded_content' at row 1
My finding is that this issue arise when the 'packed size' is larger that the actual size, this can happen with png files for example which are already 'zipped' them selves.
I'm doing http(PUT) upload using WinInet library, on WinXP its working fine but on Win7 its not working and the strange thing is 'InterWriteFile' function of WinInet files is returning true. Any help will be appreciated.
Thanks in Advance.
//this is the first function that is called when upload is requested.
void Upload()
{
//check if handle is already open or not
if(!m_hNetOpen)
{
//open interconnect: Initializes an application's use of the WinINet functions.
m_hNetOpen = InternetOpen("XYZ",
INTERNET_OPEN_TYPE_DIRECT,
NULL,
NULL,
INTERNET_INVALID_PORT_NUMBER);
//return if handle is NULL
if(m_hNetOpen == NULL)
{
return FALSE;
}
}
//we'll get user specified URL, could be some xyz.com too.
if(!m_hNetConnect)
{
//opens HTTP or FTP connection for a site requested...pchServerName requested
m_hNetConnect = InternetConnect(m_hNetOpen,
strServerName,
INTERNET_DEFAULT_HTTP_PORT,
NULL,
NULL,
INTERNET_SERVICE_HTTP,
INTERNET_FLAG_PASSIVE,
dwContext);
//return if handle is NULL
if(m_hNetConnect == NULL)
{
return FALSE;
}
}
dwContext = 0;
//now, create HTTP request
m_hHttpConnect = HttpOpenRequest(m_hNetConnect,
"PUT",
strObjectName,
NULL,
NULL,
NULL,
INTERNET_FLAG_CACHE_IF_NET_FAIL,
dwContext);
//return if handle is NULL
if(m_hHttpConnect == NULL)
{
return FALSE;
}
//call UseHttpSendReqEx
BOOL bRet = UseHttpSendReqEx(m_hHttpConnect, strUploadFileName);
//if not suuceeded log the message
if(!bRet)
{
//Logmessage
}
if(m_hHttpConnect)
{
//close the handle
InternetCloseHandle(m_hHttpConnect);
}
return bRet;
}
//this function is will send HTTP request and writes data to internetfile
BOOL UseHttpSendReqEx(HINTERNET hConnect, char *upFile)
{
INTERNET_BUFFERS BufferIn = {0};
DWORD dwBytesRead = 0;
DWORD dwBytesWritten = 0;
//buffer
BYTE strBuffer[20480] = {0};
BOOL bRead;
BOOL bRet = TRUE;
//get the size of structure
BufferIn.dwStructSize = sizeof(INTERNET_BUFFERS);
//create file
m_hFile = CreateFile(upFile, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
//check for invalid handle
if (m_hFile == INVALID_HANDLE_VALUE)
{
return FALSE;
}
//get the file size
BufferIn.dwBufferTotal = GetFileSize(m_hFile, NULL);
//send request to HTTP server
if(!HttpSendRequestEx(hConnect, &BufferIn, NULL, HSR_INITIATE, 0))
{
//close the file handle
CloseHandle(m_hFile);
m_hFile = NULL;
return FALSE;
}
DWORD sum = 0;
do
{
//read the file that is to be uploaded
if (!(bRead = ReadFile(m_hFile, strBuffer, sizeof(strBuffer),
&dwBytesRead, NULL)))
{
bRet = FALSE;
break;
}
//write the internet file
bRet=InternetWriteFile(hConnect, strBuffer, dwBytesRead,
&dwBytesWritten);
if(!bRet)
{
DWORD d = GetLastError();
bRet = FALSE;
break;
}
memset(strBuffer, '\0', sizeof(strBuffer));
sum += dwBytesWritten;
}
while(dwBytesRead == sizeof(strBuffer)) ;
//This sleep was introduced as if we immediately after the above loop
//if we try to close the handle then file does not get created on the server
Sleep(100);
BOOL b = CloseHandle(m_hFile);
m_hFile = NULL;
return bRet;
}