2

I am making a Share extension for all possible types. Now I am stuck with sharing from Safari. I'm using "NSExtensionJavaScriptPreprocessingFile" to get title, favicon and html of common pages. But this option changes type of attachment from "com.adobe.pdf" to "com.apple.property-list" without pdf content.

Ivan Ičin
  • 9,672
  • 5
  • 36
  • 57
Nik.206
  • 21
  • 4

2 Answers2

1

After taking some more insight I think I can safely say that this is a bug in Safari.

Safari sends only NSData that has 135 bytes. It can be converted to the property list that appears as NSDictionary, but it has only 4 keys that don't appear to hold any useful information ("version", "archiver", "objects" - it is nil, "top").

Developers can report it to Apple using the Feedback Assistant app.

Ivan Ičin
  • 9,672
  • 5
  • 36
  • 57
0

Dropbox sets it to a JS file with the following code:

var WebSaverExtension = function() {};

function db_checkDefined(object) {
    return (typeof object !== "undefined") && (object !== null);
}

var db_baseURL = "";
var db_documentHTML = "";
var db_title = "";
var db_height = -1;
var db_width = -1;
var db_URL = "";

// This is used for unit tests. This must point to the document object that this script alters.
var unitTest_DBWebSaverSecurityTests_updatedDocument = "";

if (db_checkDefined(window) && db_checkDefined(window.location)) {
    db_URL = window.location.href;
}

if (db_checkDefined(document)) {
    // get width and height of element
    // documentClone doesn't have body defined so we get the body straight from the document
    if (db_checkDefined(document.body)) {
        db_height = document.body.scrollHeight > 0 ? document.body.scrollHeight : window.innerHeight;
        db_width = document.body.scrollWidth;
    }

    // get title
    if (db_checkDefined(document.title)) {
        db_title = document.title;
    }

    // get base url
    var baseArray = document.getElementsByTagName('base');
    if  (baseArray.length != 0) {
        db_baseURL = baseArray[0].href;
    } else {
        var href = db_URL;
        var index = href.lastIndexOf("/");
        if (index == -1) {
            db_baseURL = href;
        } else {
            db_baseURL = href.substring(0, index + 1);
        }
    }

    // documentElement should always exist for a html file
    if (db_checkDefined(document.documentElement)) {
        var documentClone = document.cloneNode(true);

        // for unit tests to test this document object
        unitTest_DBWebSaverSecurityTests_updatedDocument = documentClone;

        // set the image sizes so they don't change size when we reload them
        // getElementsByTagName returns nodes in the order in which they would be encountered in a
        // preorder traversal of the Document tree.
        var images = document.documentElement.getElementsByTagName('img');

        // don't bother. This will be a timeout
        if (images.length <= 10000) {
            var imagesCloned = documentClone.documentElement.getElementsByTagName('img');
            for (var i = images.length-1; i >= 0; i--) {
                imagesCloned[i].style.height = images[i].scrollHeight + "px";
                imagesCloned[i].style.width = images[i].scrollWidth + "px";
            }
        }

        var tags = [ "header", "div", "span", "footer", "iframe" ];
        for (var iTag = 0; iTag < tags.length; iTag++) {
            var tag = tags[iTag];
            var elements = document.documentElement.getElementsByTagName(tag);

            // don't bother. This will be a timeout
            if (elements.length > 10000) {
                continue;
            }

            var elementsCloned = documentClone.documentElement.getElementsByTagName(tag);

            for (var i = elements.length-1; i >= 0; i--) {
                var element = elements[i];
                var elementClone = elementsCloned[i];

                if (db_checkDefined(element) && db_checkDefined(elementClone) && db_checkDefined(element.style)) {
                    var height = window.getComputedStyle(element).getPropertyValue('height');

                    if (db_checkDefined(height)) {
                        var minHeight = window.getComputedStyle(element).getPropertyValue('min-height');
                        if (db_checkDefined(minHeight)) {
                            elementClone.style.minHeight = height;
                        }

                        elementClone.style.height = height;

                        var maxHeight = window.getComputedStyle(element).getPropertyValue('max-height');
                        if (db_checkDefined(maxHeight)) {
                            elementClone.style.maxHeight = height;
                        }
                    }

                    var width = window.getComputedStyle(element).getPropertyValue('width');
                    if (db_checkDefined(width)) {
                        var minWidth = window.getComputedStyle(element).getPropertyValue('min-width');
                        if (db_checkDefined(minWidth)) {
                            elementClone.style.minWidth = width;
                        }
                    }
                }
            }
        }

        // remove script tags
        var scripts = documentClone.documentElement.getElementsByTagName('script');
        for (var i = scripts.length-1; i >= 0; i--) {
            scripts[i].parentNode.removeChild(scripts[i]);
        }

        // disable scripts in iframes (can cause Javascript selected ads to not be shown)
        // we can't access the html in the iframe so we have to live with it being reloaded but we can prevent
        // Javascript from being executed again
        var iframes = documentClone.documentElement.getElementsByTagName('iframe');
        for (var i = iframes.length-1; i >= 0; i--) {
            var iframe = iframes[i];
            iframe.sandbox = true;
        }

        // Add csp policy to prevent script loading

        // safari should auto create head if the html is missing it
        // this is here for safety
        var head = documentClone.head;
        if (!db_checkDefined(head)) {
            // make a head
            head = documentClone.createElement('head');

            // if firstchild is null, insertBefore adds to end of children list
            documentClone.documentElement.insertBefore(head, documentClone.documentElement.firstChild);
        }

        // http://www.w3.org/TR/CSP2/#delivery-html-meta-element
        // Note: Having multiple policies is ok
        var metaElement = documentClone.createElement('meta');
        metaElement.httpEquiv = 'Content-Security-Policy';
        metaElement.content = "script-src 'none'";

        // if firstchild is null, insertBefore adds to end of children list
        head.insertBefore(metaElement, head.firstChild);

        // get the changed html
        if (db_checkDefined(documentClone.documentElement.outerHTML)) {
            db_documentHTML = documentClone.documentElement.outerHTML;
        }
    }
}

WebSaverExtension.prototype = {
    run: function(arguments) {
        arguments.completionFunction({"baseURL": db_baseURL,
                                     "URL": db_URL,
                                     "html": db_documentHTML,
                                     "title": db_title,
                                     "height": db_height,
                                     "width": db_width});
    },
    finalize: function(arguments) {

    }
}

var ExtensionPreprocessingJS = new WebSaverExtension;

It nicely creates a PDF file out of the web page in Safari, but just creates a .url file when used in Chrome.

Rivera
  • 10,792
  • 3
  • 58
  • 102
  • Thanks, but think that either me or you missed out something. As far as I understand your description and code this tries to create a pdf doc from the html url. However the question was not about that. It is that if the url is for the PDF document then Safari sends nothing. As far as I can tell it is a bug and as such it can be resolved only by Apple. – Ivan Ičin Apr 04 '23 at 17:22