/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This file works on the old-style "bookmarks.html" file. It includes
* functions to import and export existing bookmarks to this file format.
*
* Format
* ------
*
* Primary heading := h1
* Old version used this to set attributes on the bookmarks RDF root, such
* as the last modified date. We only use H1 to check for the attribute
* PLACES_ROOT, which tells us that this hierarchy root is the places root.
* For backwards compatibility, if we don't find this, we assume that the
* hierarchy is rooted at the bookmarks menu.
* Heading := any heading other than h1
* Old version used this to set attributes on the current container. We only
* care about the content of the heading container, which contains the title
* of the bookmark container.
* Bookmark := a
* HREF is the destination of the bookmark
* FEEDURL is the URI of the RSS feed. This is deprecated and no more
* supported, but some old files may still contain it.
* LAST_CHARSET is stored as an annotation so that the next time we go to
* that page we remember the user's preference.
* ICON will be stored in the favicon service
* ICON_URI is new for places bookmarks.html, it refers to the original
* URI of the favicon so we don't have to make up favicon URLs.
* Text of the container is the name of the bookmark
* Ignored: LAST_VISIT, ID (writing out non-RDF IDs can confuse Firefox 2)
* Bookmark comment := dd
* This affects the previosly added bookmark
* Separator := hr
* Insert a separator into the current container
* The folder hierarchy is defined by /
, or
* to see what the text content of that node should be.
*/
this.previousText = "";
/**
* true when we hit a /
).
*
* Overall design
* --------------
*
* We need to emulate a recursive parser. A "Bookmark import frame" is created
* corresponding to each folder we encounter. These are arranged in a stack,
* and contain all the state we need to keep track of.
*
* A frame is created when we find a heading, which defines a new container.
* The frame also keeps track of the nesting of
s, (in well-formed
* bookmarks files, these will have a 1-1 correspondence with frames, but we
* try to be a little more flexible here). When the nesting count decreases
* to 0, then we know a frame is complete and to pop back to the previous
* frame.
*
* Note that a lot of things happen when tags are CLOSED because we need to
* get the text from the content of the tag. For example, link and heading tags
* both require the content (= title) before actually creating it.
*/
import { NetUtil } from "resource://gre/modules/NetUtil.sys.mjs";
import { FileUtils } from "resource://gre/modules/FileUtils.sys.mjs";
import { PlacesUtils } from "resource://gre/modules/PlacesUtils.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
PlacesBackups: "resource://gre/modules/PlacesBackups.sys.mjs",
});
const Container_Normal = 0;
const Container_Toolbar = 1;
const Container_Menu = 2;
const Container_Unfiled = 3;
const Container_Places = 4;
const MICROSEC_PER_SEC = 1000000;
const EXPORT_INDENT = " "; // four spaces
function base64EncodeString(aString) {
let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
Ci.nsIStringInputStream
);
stream.setData(aString, aString.length);
let encoder = Cc["@mozilla.org/scriptablebase64encoder;1"].createInstance(
Ci.nsIScriptableBase64Encoder
);
return encoder.encodeToString(stream, aString.length);
}
/**
* Provides HTML escaping for use in HTML attributes and body of the bookmarks
* file, compatible with the old bookmarks system.
*/
function escapeHtmlEntities(aText) {
return (aText || "")
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
/**
* Provides URL escaping for use in HTML attributes of the bookmarks file,
* compatible with the old bookmarks system.
*/
function escapeUrl(aText) {
return (aText || "").replace(/"/g, "%22");
}
function notifyObservers(aTopic, aInitialImport) {
Services.obs.notifyObservers(
null,
aTopic,
aInitialImport ? "html-initial" : "html"
);
}
export var BookmarkHTMLUtils = Object.freeze({
/**
* Loads the current bookmarks hierarchy from a "bookmarks.html" file.
*
* @param aSpec
* String containing the "file:" URI for the existing "bookmarks.html"
* file to be loaded.
* @param [options.replace]
* Whether we should erase existing bookmarks before loading.
* Defaults to `false`.
* @param [options.source]
* The bookmark change source, used to determine the sync status for
* imported bookmarks. Defaults to `RESTORE` if `replace = true`, or
* `IMPORT` otherwise.
*
* @returns {Promise
s have been nested. Each frame/container should start
* with a heading, and is then followed by a
,
, or
s won't
* be nested so this will be 0 or 1.
*/
this.containerNesting = 0;
/**
* when we find a heading tag, it actually affects the title of the NEXT
* container in the list. This stores that heading tag and whether it was
* special. 'consumeHeading' resets this._
*/
this.lastContainerType = Container_Normal;
/**
* this contains the text from the last begin tag until now. It is reset
* at every begin tag. We can check it when we see a
"); if (aItem.children) { await this._writeContainerContents(aItem, aIndent); } if (aItem == this._root) { this._writeLine(aIndent + "
"); } }, async _writeContainerContents(aItem, aIndent) { let localIndent = aIndent + EXPORT_INDENT; for (let child of aItem.children) { if (child.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) { await this._writeContainer(child, localIndent); } else if (child.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) { this._writeSeparator(child, localIndent); } else { await this._writeItem(child, localIndent); } } }, _writeSeparator(aItem, aIndent) { this._write(aIndent + "