Audio not working in Android WebView using shouldInterceptRequest() - android

My Android app is showing an html5 e-book in a WebView.
I have a zipped file containing an e-book with all its resources: text, images and audio (mp3 files).
In order to unzip the book I use shouldInterceptRequest(), which intercepts the file:///... requests, and returns the data via a WebResourceResponse object. The code works fine for text and images.
When I get to audio resources, I get runtime errors, and the audio file is not played.
Note: I do see the unzipped file is returned with the correct size (about 10MB).
Error messages I get:
cr_MediaResourceGetter File does not exist
cr_MediaResourceGetter Unable to configure metadata extractor
My HTML code for the audio :
<div xmlns="http://www.w3.org/1999/xhtml">
<p style="text-align:center;margin:0px;">
<audio controls="controls" src="../Audio/01-AudioTrack-01.mp3">Your browser does not support the audio tag.</audio>
<br />
</p>
</div>
My Android Code:
setWebViewClient(new WebViewClient()
{
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, final String url)
{
String urlWithoutAnchor = URLUtil.stripAnchor(url);
String fileName = urlWithoutAnchor;
try {
byte [] resource = tbxPool.getResource(fileName);
/* SIMPLE VERSION without calling setResponseHeaders():
return new WebResourceResponse(mimeType, "UTF-8", new ByteArrayInputStream(resource));
*/
WebResourceResponse returnedMediaResource = new WebResourceResponse(mimeType, "UTF-8", new ByteArrayInputStream(resource));
if (mimeType.toLowerCase().startsWith("audio")) {
Map<String, String> responseHeaders = new HashMap<String, String>();
responseHeaders.put("Content-Type", mimeType);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//2CLEAN
returnedMediaResource.setResponseHeaders(responseHeaders);
Logger.v(TAG, "Response Headers added to audio resource");
}
else {
//TODO: Handle else for API<21. Toast?
}
}
return returnedMediaResource;
} catch (IOException e) {
Logger.e(TAG, "failed to load resource "+fileName,e);
return null;
}
}
}
Environment
Android 6.0.1 (Nexus 5)
Android System WebView version 47
Requirement Clarification
The audio is to play in browser like an html5 document should, without laucnhing external player.
Question:
What am I doing wrong?! Many Thanks in advance!

The workaround I found to this problem is not elegant, but it's the only one that worked for me: Write the audio file to sd card :(.
Stage 1): When shouldInterceptRequest() is called with a chapter url.
The chapter is intercepted first (before the other chapter resources (images, audio, fonts, ..) are intercepted.
When the chapter is intercepted we search the html for the <audio> tag. If found, we replace the relative path (e.g. SRC="../Audio/abc.mp3")
with an absolute path (e.g. SRC="/storage/tmp/abc.mp3")
Stage 2): When shouldInterceptRequest() is called with an audio url.
Your attention. Like all workarounds this is a bit tricky (but works!):
After Stage 1) the audio url will be an absolute url (the absolute url is what is now written in the modified html).
We now have to do 2 things:
a) read the audio file from the zipped epub.
To do this we need to "fool" the code, and read the audio file from its original zipped relative url, e.g. "../Audio/abc.mp3" in our example
(although shouldInterceptRequest has been called with "/storage/tmp/abc.mp3").
b) After reading the zipped audio file, write it to the storage (sdcard)
Stage 3) When shouldInterceptRequest() is called with a chapter url,
We delete the temp audio files
Note: If you follow the code, you will see this is Step 0) in shouldInterceptRequest(), executed before stage 1), but I found it clearer explained as above.
if (isChapterFile(mimeType)) {
deleteTempFiles(); // this line is stage 3)
changeAudioPathsInHtml(tzis); // this line is stage 1)
This is the code:
setWebViewClient(new WebViewClient()
{
private String tmpPath = TbxApplication.getAppPath(null) + "/tmp/";
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, final String url)
{
Logger.d(TAG, "in shouldInterceptRequest for " + url);
String urlWithoutAnchor = URLUtil.stripAnchor(url);
String mimeType = StringUtils.getFileMimeType(urlWithoutAnchor);
String urlWithoutBase; //the url stripped from leading 'epubBaseUrl' (base url for example:"file:///storage/.../123456.tbx")
if (isAudioFile(mimeType)) { //write AUDIO file to phone storage. See AUDIO WORKAROUND DOCUMENTATION
String storagePath = StringUtils.truncateFileScheme(url); //WebView calls shoudlInterceptRequest() with "file://..."
try {
String oEBPSAudioPath = storagePathToOEBPSAudioPath(storagePath); //e.g. change"/storage/tmp" to "OEBPS/Audio/abc.mp3"
byte[] audioBytes = tbxPool.getMedia(oEBPSAudioPath);
FileUtils.writeByteArrayToFile(audioBytes, storagePath); //TODO: To be strict, write in separate thread
Logger.d(TAG, String.format("%s written to %s", oEBPSAudioPath, storagePath));
return null;//webView will read resource from file
//Note: return new WebResourceResponse("audio/mpeg", "UTF-8", new ByteArrayInputStream(audioBytes));
//did NOT work,so we had to change html for audio to point to local storage & write to disk
//see AUDIO WORKAROUND DOCUMENTATION in this file
} catch (Exception e) {
Logger.e(TAG,e.getMessage());
return null;
}
}
.....
else {
if (isChapterFile(mimeType)) { //This is a CHAPTER
deleteTempFiles(); //Loading a new chapter. Delete previous chapter audio files. See AUDIO WORKAROUND DOCUMENTATION in this file
InputStream htmlWithChangedAudioPaths = changeAudioPathsInHtml(tzis); //see AUDIO WORKAROUND DOCUMENTATION in this file
WebResourceResponse webResourceResponse = new WebResourceResponse(mimeType, "UTF-8", htmlWithChangedAudioPaths);
return webResourceResponse;
}
//Changes relative paths of audio files, to absolute paths on storage
//see AUDIO WORKAROUND DOCUMENTATION in this file
private InputStream changeAudioPathsInHtml(InputStream inputStream) {
String inputString = StringUtils.inputStreamToString(inputStream);
String outputString = inputString.replaceAll("\"../Audio/", "\"" + tmpPath);// e.g. SRC="../Audio/abc.mp3" ==>SRC="/sdcard/tmp/abc.mp3" //where '*' stands for multiple whitespaces would be more elegant
return StringUtils.stringToInputStream(outputString);
}
/** Example:
* storagePath="/storage/tmp/abc.mp3
* Returns: "OEBPS/Audio/abc.mp3"*/
private String storagePathToOEBPSAudioPath(String storagePath){
String fileName = StringUtils.getFileName(storagePath);
String tbxOEBPSAudioPath = "OEBPS/Audio/" + fileName;
return tbxOEBPSAudioPath;
}
public static void writeByteArrayToFile(byte[] byteArray, String outPath) {
try {
File file = new File(outPath);
FileOutputStream fos = new FileOutputStream(file);
fos.write(byteArray);
fos.close();
} catch (IOException e) {
Logger.e(TAG, String.format("Could not write %s", outPath));
}
}

Related

How to load SVF to Autodeks Forge viewer Offline in Android?

I am trying to load SVF file on Autodesk forge viewer locally in Xamarin.Android. I copied the content to my project Assets/html folder. My code to load the content looks like this.
In MyWebViewClient.cs
public WebResourceResponse ShouldInterceptRequest(WebView webView, IWebResourceRequest request)
{
try
{
Android.Net.Uri url = request.Url;
//Uri uri = url;
String path = url.Path;
if (path.StartsWith("/android_asset/"))
{
try
{
AssetManager assetManager = this.context.Assets;
String relPath = path.Replace("/android_asset/", "").Replace("gz", "gz.mp3");
//InputStream stream = assetManager.Open(relPath);
return new WebResourceResponse(null, null, assetManager.Open(relPath));
}
catch (IOException ex)
{
String str = ex.Message;
}
}
}
catch (Exception ex) { }
return null;
}
Then in my Activity.cs
SetContentView(Resource.Layout.webview);
var wbMain = FindViewById<WebView>(Resource.Id.webView1);
wbMain.Settings.DomStorageEnabled = true;
wbMain.Settings.JavaScriptEnabled = true;
wbMain.Settings.AllowFileAccessFromFileURLs = true;
wbMain.Settings.AllowUniversalAccessFromFileURLs = true;
var customWebViewClient = new MyWebViewClient(BaseContext);
customWebViewClient.OnPageLoaded += MyWebViewClient_OnPageLoaded;
wbMain.SetWebViewClient(customWebViewClient);
wbMain.LoadUrl("file:///android_asset/html/index.html");
This only loads the side views not the main viewer.
Whats the reason for this and how can I resolve this?
Please note that the sample is a bit outdated and there have been some changes in our legal terms since then. Currently, the legal T&C state that all viewer assets (JS, CSS, icons, images, etc.) must be coming from the Autodesk domain.
If you need to be able to run your viewer-based app in "temporarily offline" scenarios (for example, on a construction site), I'd suggest that you look at the following blog post: https://forge.autodesk.com/blog/disconnected-workflows. This approach (using Service Workers and Cache API) is consistent with the legal requirements.

Android | Fresco / FrescoLib + Azure

I'm hosting my application in azure (Web service + file storage service).
I wish to use Fresco (SimpleDraweeView) in my application, the problem is that i can not give a direct url to the user, as the storage in azure is private.
The only thing i can do is to get the image as byte array (using my web service) and foward this byte array back to the android client.
How can one use simpledraweeview with a byte array instead of a direct link?
I have tried to set an endpoint in my webservice where the user is giving me the image id and the endpoint returns back the byte array, i have tried to use this endpoint as the url for the simpledraweeview.setImageUrl method but with no luck.
According to your description, per my experience, it sounds like you need to create a web app as proxy service to access and response the image hosted on Azure File Storgae back to andoird.
Assumption that you are a Java Web developer, I think the simple way is that you can create a Java web app in Azure App Service, then create a Java servlet for the Java web app and refer to the article How to use File Storage from Java to download the image to pipe the stream to http servlet response, please see the sample code below.
private static final String storageConnectionString =
"DefaultEndpointsProtocol=http;" +
"AccountName=your_storage_account_name;" +
"AccountKey=your_storage_account_key";
private CloudFileClient fileClient;
/**
* #see HttpServlet#HttpServlet()
*/
public PipeStream4FileStorage() {
super();
try {
CloudStorageAccount storageAccount = CloudStorageAccount.parse(storageConnectionString);
fileClient = storageAccount.createCloudFileClient();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* #see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String dirName = request.getParameter("dir");
String fileName = request.getParameter("file");
OutputStream outStream = response.getOutputStream();
try {
// Get a reference to the file share
CloudFileShare share = fileClient.getShareReference("sampleshare");
//Get a reference to the root directory for the share.
CloudFileDirectory rootDir = share.getRootDirectoryReference();
//Get a reference to the directory that contains the file
CloudFileDirectory sampleDir = rootDir.getDirectoryReference(dirName);
//Get a reference to the file you want to download
CloudFile file = sampleDir.getFileReference(fileName);
//Write the stream of the file to the httpServletResponse.
file.download(outStream);
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (StorageException e) {
e.printStackTrace();
}
}
Note, please see the javadoc for the key function download(OutputStream outStream).
Then, you just need to set the image url for SimpleDraweeView like below.
Uri uri = Uri.parse("https://<your-webapp-name>.azurewebsites.net/<servlet-url-mapping-name>?dir=<dir-name>&file=<file-name>");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);

Application Error on Android Cordova/Phonegap Application

I have a cordova (2.7.0) android app that is crashing with an Application Error when it tries to load an iframe where the source has a protocol relative (network-path reference) src.
For instance, if the iframe is:
<iframe src="//instagram.com/p/beGdCuhQYl/embed/?wmode=opaque&wmode=opaque" width="800" height="928" style="border:0;" frameborder="0"></iframe>
Then the app tries to load the source from
file://instagram.com/p/beGdCuhQYl/embed/?wmode=opaque&wmode=opaque
Since the html page that loads this iframe is loaded from the file system, it makes sense that it is doing this. However, is there a way to stop the app from crashing? The same cordova app on iOS just doesn't load anything, and has a blank iframe. I would be nice if the android app behaved the same way.
It would be even nicer if there was a way to tell the cordova app to load these types of urls from http:// and not file:// but I think that is asking too much.
Ok, so I ended up doing this in two parts. First part, try to fix as many protocol relative urls as possible in javascript, and the second part was to provide some java code to ignore any that I missed.
First part (uses jQuery)
/**
* Takes text, looks for elements with src attributes that are
* protocol relative (//) and converts them to http (http://)
* #param {String} text the text that you want to fix urls in
* #returns {String} the updated text with corrected urls
*/
fixProtocolRelativeUrlsInText: function(text) {
var $html, $elements;
try {
$html = $('<div>' + text + '</div>');
$elements = $html.find('[src^="//"]');
if ($elements.length) {
$elements.each(function() {
var $this = $(this);
$this.attr('src', 'http:' + $this.attr('src'));
});
return $html.html();
} else {
return text;
}
} catch(ex) {
return text;
}
},
Second part:
/**
* Override the default makeWebViewClient and provide a custom handler for protocol
* relative urls.
*/
#Override
public CordovaWebViewClient makeWebViewClient(CordovaWebView webView) {
//
// We already try to fix protocol relative urls in the javascript. But this is a safety net in case anything
// gets through. So, in order to not crash the app, lets handle these types ourself and just swallow them up
// for now. The url won't load but at least it won't crash the app either. By the time the protocol relative
// url gets in here, it has the file: appended to it already. If it was a true file:// path to something on the
// device, then it will have file:///some/path, and if it was a protocol relative url that was converted to a
// file:// then it will have file://some.domain, so we look for urls that don't have the three /'s
//
final Pattern pattern = Pattern.compile("^file://[^/].*$");
CordovaWebViewClient webViewClient;
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
webViewClient = new CordovaWebViewClient(this, webView) {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Matcher matcher = pattern.matcher(url);
if (matcher.matches()) {
Log.i(LOG_TAG, "swallowing url '" + url + "'");
return true;
} else {
return super.shouldOverrideUrlLoading(view, url);
}
}
};
} else {
webViewClient = new IceCreamCordovaWebViewClient(this, webView) {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Matcher matcher = pattern.matcher(url);
if (matcher.matches()) {
Log.i(LOG_TAG, "swallowing url '" + url + "'");
return true;
} else {
return super.shouldOverrideUrlLoading(view, url);
}
}
};
}
return webViewClient;
}
Cordova doesn't support protocol relative src, it expects you to specify either file, or http.

PhoneGap application: Upload a file from javascript code to JSP web page on Tomcat [duplicate]

How can I upload files to server using JSP/Servlet?
I tried this:
<form action="upload" method="post">
<input type="text" name="description" />
<input type="file" name="file" />
<input type="submit" />
</form>
However, I only get the file name, not the file content. When I add enctype="multipart/form-data" to the <form>, then request.getParameter() returns null.
During research I stumbled upon Apache Common FileUpload. I tried this:
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List items = upload.parseRequest(request); // This line is where it died.
Unfortunately, the servlet threw an exception without a clear message and cause. Here is the stacktrace:
SEVERE: Servlet.service() for servlet UploadServlet threw exception
javax.servlet.ServletException: Servlet execution threw an exception
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:313)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:852)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
at java.lang.Thread.run(Thread.java:637)
Introduction
To browse and select a file for upload you need a HTML <input type="file"> field in the form. As stated in the HTML specification you have to use the POST method and the enctype attribute of the form has to be set to "multipart/form-data".
<form action="upload" method="post" enctype="multipart/form-data">
<input type="text" name="description" />
<input type="file" name="file" />
<input type="submit" />
</form>
After submitting such a form, the binary multipart form data is available in the request body in a different format than when the enctype isn't set.
Before Servlet 3.0 (Dec 2009), the Servlet API didn't natively support multipart/form-data. It supports only the default form enctype of application/x-www-form-urlencoded. The request.getParameter() and consorts would all return null when using multipart form data. This is where the well known Apache Commons FileUpload came into the picture.
Don't manually parse it!
You can in theory parse the request body yourself based on ServletRequest#getInputStream(). However, this is a precise and tedious work which requires precise knowledge of RFC2388. You shouldn't try to do this on your own or copypaste some homegrown library-less code found elsewhere on the Internet. Many online sources have failed hard in this, such as roseindia.net. See also uploading of pdf file. You should rather use a real library which is used (and implicitly tested!) by millions of users for years. Such a library has proven its robustness.
When you're already on Servlet 3.0 or newer, use native API
If you're using at least Servlet 3.0 (Tomcat 7, Jetty 9, JBoss AS 6, GlassFish 3, etc, they exist already since 2010), then you can just use standard API provided HttpServletRequest#getPart() to collect the individual multipart form data items (most Servlet 3.0 implementations actually use Apache Commons FileUpload under the covers for this!). Also, normal form fields are available by getParameter() the usual way.
First annotate your servlet with #MultipartConfig in order to let it recognize and support multipart/form-data requests and thus get getPart() to work:
#WebServlet("/upload")
#MultipartConfig
public class UploadServlet extends HttpServlet {
// ...
}
Then, implement its doPost() as follows:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String description = request.getParameter("description"); // Retrieves <input type="text" name="description">
Part filePart = request.getPart("file"); // Retrieves <input type="file" name="file">
String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); // MSIE fix.
InputStream fileContent = filePart.getInputStream();
// ... (do your job here)
}
Note the Path#getFileName(). This is a MSIE fix as to obtaining the file name. This browser incorrectly sends the full file path along the name instead of only the file name.
In case you want to upload multiple files via either multiple="true",
<input type="file" name="files" multiple="true" />
or the old-fashioned way with multiple inputs,
<input type="file" name="files" />
<input type="file" name="files" />
<input type="file" name="files" />
...
then you can collect them as below (unfortunately there is no such method as request.getParts("files")):
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// ...
List<Part> fileParts = request.getParts().stream().filter(part -> "files".equals(part.getName()) && part.getSize() > 0).collect(Collectors.toList()); // Retrieves <input type="file" name="files" multiple="true">
for (Part filePart : fileParts) {
String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); // MSIE fix.
InputStream fileContent = filePart.getInputStream();
// ... (do your job here)
}
}
When you're not on Servlet 3.1 yet, manually get submitted file name
Note that Part#getSubmittedFileName() was introduced in Servlet 3.1 (Tomcat 8, Jetty 9, WildFly 8, GlassFish 4, etc, they exist since 2013 already). If you're not on Servlet 3.1 yet (really?), then you need an additional utility method to obtain the submitted file name.
private static String getSubmittedFileName(Part part) {
for (String cd : part.getHeader("content-disposition").split(";")) {
if (cd.trim().startsWith("filename")) {
String fileName = cd.substring(cd.indexOf('=') + 1).trim().replace("\"", "");
return fileName.substring(fileName.lastIndexOf('/') + 1).substring(fileName.lastIndexOf('\\') + 1); // MSIE fix.
}
}
return null;
}
String fileName = getSubmittedFileName(filePart);
Note the MSIE fix as to obtaining the file name. This browser incorrectly sends the full file path along the name instead of only the file name.
When you're not on Servlet 3.0 yet, use Apache Commons FileUpload
If you're not on Servlet 3.0 yet (isn't it about time to upgrade? it's released over a decade ago!), the common practice is to make use of Apache Commons FileUpload to parse the multpart form data requests. It has an excellent User Guide and FAQ (carefully go through both). There's also the O'Reilly ("cos") MultipartRequest, but it has some (minor) bugs and isn't actively maintained anymore for years. I wouldn't recommend using it. Apache Commons FileUpload is still actively maintained and currently very mature.
In order to use Apache Commons FileUpload, you need to have at least the following files in your webapp's /WEB-INF/lib:
commons-fileupload.jar
commons-io.jar
Your initial attempt failed most likely because you forgot the commons IO.
Here's a kickoff example how the doPost() of your UploadServlet may look like when using Apache Commons FileUpload:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
List<FileItem> items = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);
for (FileItem item : items) {
if (item.isFormField()) {
// Process regular form field (input type="text|radio|checkbox|etc", select, etc).
String fieldName = item.getFieldName();
String fieldValue = item.getString();
// ... (do your job here)
} else {
// Process form file field (input type="file").
String fieldName = item.getFieldName();
String fileName = FilenameUtils.getName(item.getName());
InputStream fileContent = item.getInputStream();
// ... (do your job here)
}
}
} catch (FileUploadException e) {
throw new ServletException("Cannot parse multipart request.", e);
}
// ...
}
It's very important that you don't call getParameter(), getParameterMap(), getParameterValues(), getInputStream(), getReader(), etc on the same request beforehand. Otherwise the servlet container will read and parse the request body and thus Apache Commons FileUpload will get an empty request body. See also a.o. ServletFileUpload#parseRequest(request) returns an empty list.
Note the FilenameUtils#getName(). This is a MSIE fix as to obtaining the file name. This browser incorrectly sends the full file path along the name instead of only the file name.
Alternatively you can also wrap this all in a Filter which parses it all automagically and put the stuff back in the parametermap of the request so that you can continue using request.getParameter() the usual way and retrieve the uploaded file by request.getAttribute(). You can find an example in this blog article.
Workaround for GlassFish3 bug of getParameter() still returning null
Note that Glassfish versions older than 3.1.2 had a bug wherein the getParameter() still returns null. If you are targeting such a container and can't upgrade it, then you need to extract the value from getPart() with help of this utility method:
private static String getValue(Part part) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(part.getInputStream(), "UTF-8"));
StringBuilder value = new StringBuilder();
char[] buffer = new char[1024];
for (int length = 0; (length = reader.read(buffer)) > 0;) {
value.append(buffer, 0, length);
}
return value.toString();
}
String description = getValue(request.getPart("description")); // Retrieves <input type="text" name="description">
Saving uploaded file (don't use getRealPath() nor part.write()!)
Head to the following answers for detail on properly saving the obtained InputStream (the fileContent variable as shown in the above code snippets) to disk or database:
Recommended way to save uploaded files in a servlet application
How to upload an image and save it in database?
How to convert Part to Blob, so I can store it in MySQL?
Serving uploaded file
Head to the following answers for detail on properly serving the saved file from disk or database back to the client:
Load images from outside of webapps / webcontext / deploy folder using <h:graphicImage> or <img> tag
How to retrieve and display images from a database in a JSP page?
Simplest way to serve static data from outside the application server in a Java web application
Abstract template for static resource servlet supporting HTTP caching
Ajaxifying the form
Head to the following answers how to upload using Ajax (and jQuery). Do note that the servlet code to collect the form data does not need to be changed for this! Only the way how you respond may be changed, but this is rather trivial (i.e. instead of forwarding to JSP, just print some JSON or XML or even plain text depending on whatever the script responsible for the Ajax call is expecting).
How can I upload files to a server using JSP/Servlet and Ajax?
Send a file as multipart through XMLHttpRequest
HTML5 drag and drop file upload to Java Servlet
Hope this all helps :)
If you happen to use Spring MVC, this is how to (I'm leaving this here in case someone find it useful):
Use a form with enctype attribute set to "multipart/form-data" (the same as BalusC's answer):
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="Upload"/>
</form>
In your controller, map the request parameter file to MultipartFile type as follows:
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public void handleUpload(#RequestParam("file") MultipartFile file) throws IOException {
if (!file.isEmpty()) {
byte[] bytes = file.getBytes(); // alternatively, file.getInputStream();
// application logic
}
}
You can get the filename and size using MultipartFile's getOriginalFilename() and getSize().
I've tested this with Spring version 4.1.1.RELEASE.
Without components or external libraries in Tomcat 6 or Tomcat 7
Enabling upload in the web.xml file:
Manually Installing PHP, Tomcat and Httpd Lounge.
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<multipart-config>
<max-file-size>3145728</max-file-size>
<max-request-size>5242880</max-request-size>
</multipart-config>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
As you can see:
<multipart-config>
<max-file-size>3145728</max-file-size>
<max-request-size>5242880</max-request-size>
</multipart-config>
Uploading files using JSP. files:
In the HTML file
<form method="post" enctype="multipart/form-data" name="Form" >
<input type="file" name="fFoto" id="fFoto" value="" /></td>
<input type="file" name="fResumen" id="fResumen" value=""/>
In the JSP File or Servlet
InputStream isFoto = request.getPart("fFoto").getInputStream();
InputStream isResu = request.getPart("fResumen").getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte buf[] = new byte[8192];
int qt = 0;
while ((qt = isResu.read(buf)) != -1) {
baos.write(buf, 0, qt);
}
String sResumen = baos.toString();
Edit your code to servlet requirements, like max-file-size, max-request-size and other options that you can to set...
You need the common-io.1.4.jar file to be included in your lib directory, or if you're working in any editor, like NetBeans, then you need to go to project properties and just add the JAR file and you will be done.
To get the common.io.jar file just google it or just go to the Apache Tomcat website where you get the option for a free download of this file. But remember one thing: download the binary ZIP file if you're a Windows user.
I am using a common Servlet for every HTML form whether it has attachments or not.
This Servlet returns a TreeMap where the keys are JSP name parameters and values are user inputs and saves all attachments in a fixed directory and later you rename the directory of your choice. Here Connections is our custom interface having a connection object.
public class ServletCommonfunctions extends HttpServlet implements
Connections {
private static final long serialVersionUID = 1L;
public ServletCommonfunctions() {}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException,
IOException {}
public SortedMap<String, String> savefilesindirectory(
HttpServletRequest request, HttpServletResponse response)
throws IOException {
// Map<String, String> key_values = Collections.synchronizedMap(new
// TreeMap<String, String>());
SortedMap<String, String> key_values = new TreeMap<String, String>();
String dist = null, fact = null;
PrintWriter out = response.getWriter();
File file;
String filePath = "E:\\FSPATH1\\2KL06CS048\\";
System.out.println("Directory Created ????????????"
+ new File(filePath).mkdir());
int maxFileSize = 5000 * 1024;
int maxMemSize = 5000 * 1024;
// Verify the content type
String contentType = request.getContentType();
if ((contentType.indexOf("multipart/form-data") >= 0)) {
DiskFileItemFactory factory = new DiskFileItemFactory();
// Maximum size that will be stored in memory
factory.setSizeThreshold(maxMemSize);
// Location to save data that is larger than maxMemSize.
factory.setRepository(new File(filePath));
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
// maximum file size to be uploaded.
upload.setSizeMax(maxFileSize);
try {
// Parse the request to get file items.
#SuppressWarnings("unchecked")
List<FileItem> fileItems = upload.parseRequest(request);
// Process the uploaded file items
Iterator<FileItem> i = fileItems.iterator();
while (i.hasNext()) {
FileItem fi = (FileItem) i.next();
if (!fi.isFormField()) {
// Get the uploaded file parameters
String fileName = fi.getName();
// Write the file
if (fileName.lastIndexOf("\\") >= 0) {
file = new File(filePath
+ fileName.substring(fileName
.lastIndexOf("\\")));
} else {
file = new File(filePath
+ fileName.substring(fileName
.lastIndexOf("\\") + 1));
}
fi.write(file);
} else {
key_values.put(fi.getFieldName(), fi.getString());
}
}
} catch (Exception ex) {
System.out.println(ex);
}
}
return key_values;
}
}
For Spring MVC
I managed to have a simpler version that worked for taking form input, both data and images.
<form action="/handleform" method="post" enctype="multipart/form-data">
<input type="text" name="name" />
<input type="text" name="age" />
<input type="file" name="file" />
<input type="submit" />
</form>
Controller to handle
#Controller
public class FormController {
#RequestMapping(value="/handleform",method= RequestMethod.POST)
ModelAndView register(#RequestParam String name, #RequestParam int age, #RequestParam MultipartFile file)
throws ServletException, IOException {
System.out.println(name);
System.out.println(age);
if(!file.isEmpty()){
byte[] bytes = file.getBytes();
String filename = file.getOriginalFilename();
BufferedOutputStream stream =new BufferedOutputStream(new FileOutputStream(new File("D:/" + filename)));
stream.write(bytes);
stream.flush();
stream.close();
}
return new ModelAndView("index");
}
}
Another source of this problem occurs if you are using Geronimo with its embedded Tomcat. In this case, after many iterations of testing Commons IO and commons-fileupload, the problem arises from a parent classloader handling the commons-xxx JAR files. This has to be prevented. The crash always occurred at:
fileItems = uploader.parseRequest(request);
Note that the List type of fileItems has changed with the current version of commons-fileupload to be specifically List<FileItem> as opposed to prior versions where it was generic List.
I added the source code for commons-fileupload and Commons IO into my Eclipse project to trace the actual error and finally got some insight. First, the exception thrown is of type Throwable not the stated FileIOException nor even Exception (these will not be trapped). Second, the error message is obfuscatory in that it stated class not found because axis2 could not find commons-io. Axis2 is not used in my project at all, but it exists as a folder in the Geronimo repository subdirectory as part of standard installation.
Finally, I found one place that posed a working solution which successfully solved my problem. You must hide the JAR files from the parent loader in the deployment plan. This was put into the geronimo-web.xml file with my full file shown below.
Pasted from http://osdir.com/ml/user-geronimo-apache/2011-03/msg00026.html:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<web:web-app xmlns:app="http://geronimo.apache.org/xml/ns/j2ee/application-2.0" xmlns:client="http://geronimo.apache.org/xml/ns/j2ee/application-client-2.0" xmlns:conn="http://geronimo.apache.org/xml/ns/j2ee/connector-1.2" xmlns:dep="http://geronimo.apache.org/xml/ns/deployment-1.2" xmlns:ejb="http://openejb.apache.org/xml/ns/openejb-jar-2.2" xmlns:log="http://geronimo.apache.org/xml/ns/loginconfig-2.0" xmlns:name="http://geronimo.apache.org/xml/ns/naming-1.2" xmlns:pers="http://java.sun.com/xml/ns/persistence" xmlns:pkgen="http://openejb.apache.org/xml/ns/pkgen-2.1" xmlns:sec="http://geronimo.apache.org/xml/ns/security-2.0" xmlns:web="http://geronimo.apache.org/xml/ns/j2ee/web-2.0.1">
<dep:environment>
<dep:moduleId>
<dep:groupId>DataStar</dep:groupId>
<dep:artifactId>DataStar</dep:artifactId>
<dep:version>1.0</dep:version>
<dep:type>car</dep:type>
</dep:moduleId>
<!-- Don't load commons-io or fileupload from parent classloaders -->
<dep:hidden-classes>
<dep:filter>org.apache.commons.io</dep:filter>
<dep:filter>org.apache.commons.fileupload</dep:filter>
</dep:hidden-classes>
<dep:inverse-classloading/>
</dep:environment>
<web:context-root>/DataStar</web:context-root>
</web:web-app>
Here's an example using apache commons-fileupload:
// apache commons-fileupload to handle file upload
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(new File(DataSources.TORRENTS_DIR()));
ServletFileUpload fileUpload = new ServletFileUpload(factory);
List<FileItem> items = fileUpload.parseRequest(req.raw());
FileItem item = items.stream()
.filter(e ->
"the_upload_name".equals(e.getFieldName()))
.findFirst().get();
String fileName = item.getName();
item.write(new File(dir, fileName));
log.info(fileName);
You first have to set the enctype attribute of the form to "multipart/form-data"
This is shown below.
<form action="Controller" method="post" enctype="multipart/form-data">
<label class="file-upload"> Click here to upload an Image </label>
<input type="file" name="file" id="file" required>
</form>
And then, in the Servlet "Controller" add the Annotation for a Multi-part to indicate multipart data is processed in the servlet.
After doing this, retrieve the part sent through the form and then retrieve the file name (with path)of the submitted file. Use this to create a new file in the desired path and write the parts of the file to the newly created file to recreate the file.
As shown below:
#MultipartConfig
public class Controller extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
insertImage(request, response);
}
private void addProduct(HttpServletRequest request, HttpServletResponse response) {
Part filePart = request.getPart("file");
String imageName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString();
String imageSavePath = "specify image path to save image"; //path to save image
FileOutputStream outputStream = null;
InputStream fileContent = null;
try {
outputStream = new FileOutputStream(new File(imageSavePath + File.separator + imageName));
// Creating a new file with file path and the file name
fileContent = filePart.getInputStream();
// Getting the input stream
int readBytes = 0;
byte[] readArray = new byte[1024];
// Initializing a byte array with size 1024
while ((readBytes = fileContent.read(readArray)) != -1) {
outputStream.write(readArray, 0, readBytes);
} // This loop will write the contents of the byte array unitl the end to the output stream
} catch (Exception ex) {
System.out.println("Error Writing File: " + ex);
} finally {
if (outputStream != null) {
outputStream.close();
// Closing the output stream
}
if (fileContent != null) {
fileContent.close();
// Closing the input stream
}
}
}
}
The simplest way I could come up with for files and input controls, without a billion libraries:
<%
if (request.getContentType() == null)
return;
// For input type=text controls
String v_Text =
(new BufferedReader(new InputStreamReader(request.getPart("Text1").getInputStream()))).readLine();
// For input type=file controls
InputStream inStr = request.getPart("File1").getInputStream();
char charArray[] = new char[inStr.available()];
new InputStreamReader(inStr).read(charArray);
String contents = new String(charArray);
%>
Use:
DiskFileUpload upload = new DiskFileUpload();
From this object you have to get the file items and fields, and then you can store into the server like the following:
String loc = "./webapps/prjct name/server folder/" + contentid + extension;
File uploadFile = new File(loc);
item.write(uploadFile);
You can upload a file using JSP /servlet.
<form action="UploadFileServlet" method="post">
<input type="text" name="description" />
<input type="file" name="file" />
<input type="submit" />
</form>
On the other hand, on the server side, use the following code.
package com.abc..servlet;
import java.io.File;
---------
--------
/**
* Servlet implementation class UploadFileServlet
*/
public class UploadFileServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public UploadFileServlet() {
super();
// TODO Auto-generated constructor stub
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.sendRedirect("../jsp/ErrorPage.jsp");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
PrintWriter out = response.getWriter();
HttpSession httpSession = request.getSession();
String filePathUpload = (String) httpSession.getAttribute("path") != null ? httpSession.getAttribute("path").toString() : "" ;
String path1 = filePathUpload;
String filename = null;
File path = null;
FileItem item = null;
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (isMultipart) {
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
String FieldName = "";
try {
List items = upload.parseRequest(request);
Iterator iterator = items.iterator();
while (iterator.hasNext()) {
item = (FileItem) iterator.next();
if (fieldname.equals("description")) {
description = item.getString();
}
}
if (!item.isFormField()) {
filename = item.getName();
path = new File(path1 + File.separator);
if (!path.exists()) {
boolean status = path.mkdirs();
}
/* Start of code fro privilege */
File uploadedFile = new File(path + Filename); // for copy file
item.write(uploadedFile);
}
} else {
f1 = item.getName();
}
} // END OF WHILE
response.sendRedirect("welcome.jsp");
} catch (FileUploadException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
HTML page
<html>
<head>
<title>File Uploading Form</title>
</head>
<body>
<h3>File Upload:</h3>
Select a file to upload: <br />
<form action="UploadServlet" method="post"
enctype="multipart/form-data">
<input type="file" name="file" size="50" />
<br />
<input type="submit" value="Upload File" />
</form>
</body>
</html>
Servlet file
// Import required java libraries
import java.io.*;
import java.util.*;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.output.*;
public class UploadServlet extends HttpServlet {
private boolean isMultipart;
private String filePath;
private int maxFileSize = 50 * 1024;
private int maxMemSize = 4 * 1024;
private File file;
public void init() {
// Get the file location where it would be stored.
filePath =
getServletContext().getInitParameter("file-upload");
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, java.io.IOException {
// Check that we have a file upload request
isMultipart = ServletFileUpload.isMultipartContent(request);
response.setContentType("text/html");
java.io.PrintWriter out = response.getWriter();
if (!isMultipart) {
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet upload</title>");
out.println("</head>");
out.println("<body>");
out.println("<p>No file uploaded</p>");
out.println("</body>");
out.println("</html>");
return;
}
DiskFileItemFactory factory = new DiskFileItemFactory();
// Maximum size that will be stored in memory
factory.setSizeThreshold(maxMemSize);
// Location to save data that is larger than maxMemSize.
factory.setRepository(new File("c:\\temp"));
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
// maximum file size to be uploaded.
upload.setSizeMax(maxFileSize);
try {
// Parse the request to get file items.
List fileItems = upload.parseRequest(request);
// Process the uploaded file items
Iterator i = fileItems.iterator();
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet upload</title>");
out.println("</head>");
out.println("<body>");
while (i.hasNext())
{
FileItem fi = (FileItem)i.next();
if (!fi.isFormField())
{
// Get the uploaded file parameters
String fieldName = fi.getFieldName();
String fileName = fi.getName();
String contentType = fi.getContentType();
boolean isInMemory = fi.isInMemory();
long sizeInBytes = fi.getSize();
// Write the file
if (fileName.lastIndexOf("\\") >= 0) {
file = new File(filePath +
fileName.substring(fileName.lastIndexOf("\\")));
}
else {
file = new File(filePath +
fileName.substring(fileName.lastIndexOf("\\") + 1));
}
fi.write(file);
out.println("Uploaded Filename: " + fileName + "<br>");
}
}
out.println("</body>");
out.println("</html>");
}
catch(Exception ex) {
System.out.println(ex);
}
}
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, java.io.IOException {
throw new ServletException("GET method used with " +
getClass().getName() + ": POST method required.");
}
}
File web.xml
Compile the above servlet UploadServlet and create the required entry in the web.xml file as follows.
<servlet>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/UploadServlet</url-pattern>
</servlet-mapping>
Sending multiple files for file, we have to use enctype="multipart/form-data".
And to send multiple files, use multiple="multiple" in the input tag:
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="fileattachments" multiple="multiple"/>
<input type="submit" />
</form>

android webview encrypted content

I have an application that uses a webview in order to display content and the Javascript calls are the controller of my application.
In order to provide a level of security I obfuscated the code. This is not enough as I would like to encrypt the html and js files and then decrypt them at runtime. I packed the apk file with these resources encrypted with RC4 algorithm. When loading the files, I am decrypting the javascript files, load them and then decrypt the html file and load it. However this doesn't work as the webcontent displays a message in the form of: the web page at data:text/html might be temporarily down or it may have moved permanently, etc, etc.
I overloaded onLoadResource in order to see what content is loaded and I can see it loads the Javascript content, but the content loaded is html escaped also.
My questions are:
1. How to secure the html and javascript files (located in assets folder) in order to not be accessible?
2. In case my approach is correct, has anyone any idea on what I am doing wrong?
Thanks!
Below is the code that decrypts and loads the resources:
protected void loadWebContent() {
checkEncryptionEnabled();
loadJSFiles();
logger.info("Loaded js ... going for html");
loadAssetFile("www/index.html", "text/html");
}
private void loadJSFiles() {
String[] jsFilesArray = { "app.js", "iscroll.js", "iui.js", "json.js" };
for (String js : jsFilesArray) {
loadAssetFile("www/js/" + js, "application/javascript");
}
}
private void loadAssetFile(String filePath, String mimeType) {
AssetManager assetMgr = getAssets();
InputStream is = null;
try {
is = assetMgr.open(filePath);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] temp = new byte[512];
int bytesRead = -1;
while ((bytesRead = is.read(temp)) > 0) {
baos.write(temp, 0, bytesRead);
}
byte[] encrypted = baos.toByteArray();
String content = null;
/**
* true
* */
if (Config.ENCRYPTION_ENABLED) {
byte[] decrypted = new RC4Encrypter("rc4_key").rc4(encrypted);
content = new String(decrypted, "utf-8");
} else {
content = new String(encrypted, "utf-8");
}
/**
* The webview to use
* */
if("application/javascript".equals(mimeType)) {
webContent.loadUrl("javascript:" + content);
} else {
webContent.loadData(content, mimeType, "utf-8");
}
} catch (IOException ex) {
logger.error(null, ex);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
found the answer for the second question question instead of: webContent.loadData(content, mimeType, "utf-8"); I used: webContent.loadDataWithBaseURL("file:///android_asset/www/", content, mimeType, "utf-8", null); Content is shown with no problems ... However, the first question kind of stands and not; but considering there was no answer for more than a year, I'll consider encrypting data is OK.
Data encryption is OK as long as you can also keep the decryption key confidential, which is not the case in the above code. The hardcoded decryption key can be easily spotted after decompiling the DEX files embedded inside the APK.
If you want to hide the application logic inside the HTML and JavaScript files and if that application logic doesn't require offline capabilities then you could outsource the code of that application logic on a server.
From here you have two choices:
Load the application code dynamically from the server whenever
you need it (and run the application code on the client).
Implement the application logic on the server side, e.g., as a
web service (and run the application code on the server, the client
knows only how to call the web service)
The short answer to your first question is that there is no methodology or technology to perfectly protect your application. I recommend to you to take a look at How to avoid reverse engineering of an APK file? for an overview of possible protection methods.

Categories

Resources