2

I'm building a secure android application that runs with IOCipher and SQLCipher. My app is storing PDF, DOC, DOCX, XLS, XLSX files that are intended to be openned by a third party application. Currently I can open all these type of files but DOCX. When I open a docx file that is stored in IOCipher using this method:

    File file = new File(path);
    Uri contentUri = Uri.parse(VFSContentProvider.FILES_URI + file.getName());

    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_VIEW);
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.setData(contentUri);

Microsoft Word prompt me with that error message :

Can't open file "myFile.docx.docx".

This happen For all my docx file, it seems to append the mime type twice at the end of the file when it's returned by the content provider...

Here is my IOCipher Content Provider:

public class VFSContentProvider extends ContentProvider {
    public static final String TAG = "VFSContentProvider";
    public static final Uri FILES_URI = Uri
            .parse("content://com.plante.android.cobalt.VFSContentProvider/");
    private MimeTypeMap mimeTypeMap;

    @Override
    public boolean onCreate() {
        mimeTypeMap = MimeTypeMap.getSingleton();
        return true;
    }

    @Override
    public String getType(Uri uri) {
        String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString());
        return mimeTypeMap.getMimeTypeFromExtension(fileExtension);
    }

    @Override
    public ParcelFileDescriptor openFile(Uri uri, String mode)
            throws FileNotFoundException {
        ParcelFileDescriptor[] pipe = null;
        InputStream in = null;

        try {
            pipe = ParcelFileDescriptor.createPipe();
            String path = uri.getPath();
            Log.i(TAG, "streaming " + path);
            // BufferedInputStream could help, AutoCloseOutputStream conflicts
            in = new FileInputStream(new File(path));
            new PipeFeederThread(in, new AutoCloseOutputStream(pipe[1])).start();
        } catch (IOException e) {
            Log.e(TAG, "Error opening pipe", e);
            throw new FileNotFoundException("Could not open pipe for: "
                    + uri.toString());
        }

        return (pipe[0]);
    }

    @Override
    public Cursor query(Uri url, String[] projection, String selection,
                        String[] selectionArgs, String sort) {
        // throw new RuntimeException("Operation not supported");
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues initialValues) {
        throw new RuntimeException("Operation not supported");
    }

    @Override
    public int update(Uri uri, ContentValues values, String where,
                      String[] whereArgs) {
        throw new RuntimeException("Operation not supported");
    }

    @Override
    public int delete(Uri uri, String where, String[] whereArgs) {
        throw new RuntimeException("Operation not supported");
    }

    static class PipeFeederThread extends Thread {
        InputStream in;
        OutputStream out;

        PipeFeederThread(InputStream in, OutputStream out) {
            this.in = in;
            this.out = out;
        }

        @Override
        public void run() {
            byte[] buf = new byte[8192];
            int len;

            try {
                while ((len = in.read(buf)) > 0) {
                    out.write(buf, 0, len);
                }

                in.close();
                out.flush();
                out.close();
            } catch (IOException e) {
                Log.e(TAG, "File transfer failed:", e);
            }
        }
    }

Here is the log file:

03-16 13:38:20.714 800-1325/? I/ActivityManager: START u0 {act=android.intent.action.VIEW dat=content://com.plante.android.cobalt.VFSContentProvider/0c8dbc0e1671e127ed9fcb2786b218d379f12888Finance_Corporate_BCP_Checklist_2014_MH20150115.docx flg=0x13000003 cmp=com.microsoft.office.word/com.microsoft.office.apphost.LaunchActivity} from uid 10182 on display 0
  • 1
    "it seems to append the mime type twice at the end of the file when it's returned by the content provider" -- are you sure that `path` does not have the double extension? That's what is being used to build your `Uri`, after all. Also, note that `MimeTypeMap` does not know anything about `docx` or other Microsoft MIME types, based on [the source code](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/webkit/MimeTypeMap.java). I recommend that you handle those yourself, to return accurate MIME types. – CommonsWare Mar 16 '16 at 14:59
  • 1
    @CommonsWare "are you sure that path does not have the double extension?" Yes I printed it before generating the URI and it is there just once. "MimeTypeMap does not know anything about docx", I also printed what was returned by the mipmaptype and it was the right thing... Which is : **application/vnd.openxmlformats-officedocument.wordprocessingml.document** ... At first I handled it myself by specifying the type in intent.setDataAndType(contentUri, myType); but still, same error just with the docx file –  Mar 16 '16 at 15:05
  • My mistake on `MimeTypeMap` -- I wasn't paying close enough attention. You might try writing yourself a client app that can get the DOCX through your provider and stream it out to a file that you can examine to see if it is a binary match for the original. You might also consider implementing `query()` to support the `OpenableColumns`, which is technically required of providers like this. Is LogCat showing any stack traces from whatever app you're trying to have open the file? – CommonsWare Mar 16 '16 at 15:10
  • @CommonsWare I added the log... –  Mar 16 '16 at 15:28
  • OK, that didn't help. So, I'm back to: test to see that your provider is correctly serving the document, and implement `query()` for the `OpenableColumns`, and see what that turns up. Some clients cannot use streams from a pipe, because they need the streams to be seekable, and streams from pipes aren't. So, there's no guarantee that we can get this working at all. I would be a bit surprised if Microsoft can handle XLSX files but not DOCX files, but I've been surprised by many things in my life. :-) – CommonsWare Mar 16 '16 at 15:37
  • 1
    @CommonsWare I printed the URI that was given as parameter to the query() function and it looks fine : content://com.my.first.app.VFSContentProvider/myfile.docx –  Mar 16 '16 at 16:13
  • 1
    @CommonsWare I added a breakpoint in the openFile method of my ContentProvider and it never goes inside for ANY type of file... SO i'm kinda lost because IOCipher recommended to overide that method... I don't know where to look anymore –  Mar 16 '16 at 16:55
  • I thought that you said that other files were working? If they are, I'd blame the debugger. I've had problems with missed breakpoints before. Are your `Log` statements in `openFile()` being invoked? – CommonsWare Mar 16 '16 at 17:02
  • @CommonsWare No, even the log aren't printed... And yes other type of file are working, god knows how... –  Mar 16 '16 at 17:19
  • 1
    Do you mean that the `Log` statements are showing up for other file types, but not DOCX? Or do you mean that the `Log` statements are not showing up for anything? If you are using Android Studio, I recommend switching the Android Monitor from "Show only selected application" to "No filters", then using some string in the search field to narrow down the logging to your app, as "Show only selected application" has been unreliable for me. – CommonsWare Mar 16 '16 at 17:29
  • 1
    @CommonsWare Some improvement here... You are right, there is some log actually... I'll add them in the post –  Mar 16 '16 at 17:40
  • @CommonsWare I think the issue might be related to the way I download the docx file according to this post : [http://stackoverflow.com/questions/2477564/why-are-docx-files-being-corrupted-when-downloading-from-an-asp-net-page] Do you mind if we go to chat since the discussion is getting long? –  Mar 16 '16 at 17:43
  • "I think the issue might be related to the way I download the docx" -- copy the file to external storage, grab it, and see if it's valid and matches the original. If it doesn't, start making copies earlier in the process (e.g., download straight to external storage) until you find where the corruption is coming in. "Do you mind if we go to chat" -- I offer chats for [my subscribers](https://commonsware.com/warescription), so I do not do Stack Overflow chats, sorry. – CommonsWare Mar 16 '16 at 17:48
  • @CommonsWare So I just found out it was probably related to the PHP API that provided me the Docx file that causes the issue... Sorry for the waste of time and thank you –  Mar 16 '16 at 19:19

0 Answers0