class CredentialsPopover(Gtk.Popover): """ Tell user to save form credentials """ def __init__(self, username, userform, password, passform, uri, window): """ Init popover @param username as str @param password as str @param netloc as str @param window as Window """ Gtk.Popover.__init__(self) self.set_modal(False) window.register(self) self.__helper = PasswordsHelper() self.__username = username self.__userform = userform self.__password = password self.__passform = passform self.__uri = uri self.__uuid = None builder = Gtk.Builder() builder.add_from_resource('/org/gnome/Eolie/PopoverCredentials.ui') builder.connect_signals(self) self.__label = builder.get_object('label') parsed = urlparse(uri) builder.get_object('uri').set_text(parsed.netloc) builder.get_object('username').set_text(username) builder.get_object('password').set_text(password) self.add(builder.get_object('widget')) ####################### # PROTECTED # ####################### def _on_save_clicked(self, button): """ Save username and password @param button as Gtk.Button """ try: parsed = urlparse(self.__uri) uri = "%s://%s" % (parsed.scheme, parsed.netloc) self.__helper.clear(self.__username, uri) if self.__uuid is None: self.__uuid = str(uuid3(NAMESPACE_DNS, parsed.netloc)) self.__helper.store(self.__username, self.__password, uri, self.__uuid, self.__userform, self.__passform, None) if El().sync_worker is not None: El().sync_worker.push_password(self.__username, self.__userform, self.__password, self.__passform, uri, self.__uuid) self.destroy() except Exception as e: print("CredentialsPopover::_on_save_clicked()", e) def popup(self): """ Overwrite popup """ self.__helper.get(self.__uri, self.__on_get_password) ####################### # PRIVATE # ####################### def __on_get_password(self, attributes, password, uri, index, count): """ Set username/password input @param attributes as {} @param password as str @param uri as str @param index as int @param count as int """ try: # No saved password if attributes is None: Gtk.Popover.popup(self) # Password saved and unchanged elif attributes["login"] == self.__username and\ self.__password == password and\ attributes["userform"] == self.__userform and\ attributes["passform"] == self.__passform: self.emit("closed") self.set_relative_to(None) # Prevent popover to be displayed # Password changed elif attributes["login"] == self.__username: Gtk.Popover.popup(self) self.__uuid = attributes["uuid"] self.__label.set_text( _("Do you want to modify this password?")) # Last password, it's a new login/password elif index == count - 1: Gtk.Popover.popup(self) except Exception as e: print("CredentialsPopover::__on_get_password()", e)
class SyncWorker: """ Manage sync with mozilla server, will start syncing on init """ def check_modules(): """ True if deps are installed @return bool """ from importlib import util fxa = util.find_spec("fxa") crypto = util.find_spec("Crypto") return fxa is not None and crypto is not None def __init__(self): """ Init worker """ self.__syncing = False self.__sync_cancellable = Gio.Cancellable() self.__username = "" self.__password = "" self.__token = "" self.__uid = "" self.__keyB = b"" self.__mtimes = {"bookmarks": 0.1, "history": 0.1} # We do not create this here because it's slow down Eolie startup # See __mozilla_sync property self.__mz = None self.__state_lock = True self.__session = None self.__helper = PasswordsHelper() self.set_credentials() def login(self, attributes, password): """ Login to service @param attributes as {} @param password as str @raise exceptions """ self.__username = "" self.__password = "" self.__uid = "" self.__token = "" self.__keyB = b"" if attributes is None or not attributes["login"] or not password: print("SyncWorker::login():", attributes) return from base64 import b64encode session = None self.__username = attributes["login"] self.__password = password # Connect to mozilla sync try: session = self.__mozilla_sync.login(self.__username, password) bid_assertion, key = self.__mozilla_sync.\ get_browserid_assertion(session) self.__token = session.token self.__uid = session.uid self.__keyB = session.keys[1] keyB_encoded = b64encode(self.__keyB).decode("utf-8") self.__helper.clear_sync(self.__helper.store_sync, self.__username, password, self.__uid, self.__token, keyB_encoded, self.on_password_stored, True) except Exception as e: self.__helper.clear_sync(self.__helper.store_sync, attributes["login"], password, "", "", "") raise e def new_session(self): """ Start a new session """ # Just reset session, will be set by get_session_bulk_keys() self.__session = None def set_credentials(self): """ Set credentials using Secret """ self.__helper.get_sync(self.__set_credentials) def sync(self, loop=False, first_sync=False): """ Start syncing, you need to check sync_status property @param loop as bool -> for GLib.timeout_add() @param first_sync as bool """ if Gio.NetworkMonitor.get_default().get_network_available() and\ self.__username and self.__password and not self.syncing: task_helper = TaskHelper() task_helper.run(self.__sync, first_sync) return loop def push_history(self, history_ids): """ Push history ids @param history_ids as [int] """ if Gio.NetworkMonitor.get_default().get_network_available() and\ self.__username and self.__password: task_helper = TaskHelper() task_helper.run(self.__push_history, history_ids) def push_password(self, user_form_name, user_form_value, pass_form_name, pass_form_value, uri, form_uri, uuid): """ Push password @param user_form_name as str @param user_form_value as str @param pass_form_name as str @param pass_form_value as str @param uri as str @param form_uri as str @param uuid as str """ if Gio.NetworkMonitor.get_default().get_network_available() and\ self.__username and self.__password: task_helper = TaskHelper() task_helper.run(self.__push_password, user_form_name, user_form_value, pass_form_name, pass_form_value, uri, form_uri, uuid) def remove_from_history(self, guid): """ Remove history guid from remote history @param guid as str """ if Gio.NetworkMonitor.get_default().get_network_available() and\ self.__username and self.__password: task_helper = TaskHelper() task_helper.run(self.__remove_from_history, guid) def remove_from_bookmarks(self, guid): """ Remove bookmark guid from remote bookmarks @param guid as str """ if Gio.NetworkMonitor.get_default().get_network_available() and\ self.__username and self.__password: task_helper = TaskHelper() task_helper.run(self.__remove_from_bookmarks, guid) def remove_from_passwords(self, uuid): """ Remove password from passwords collection @param uuid as str """ if Gio.NetworkMonitor.get_default().get_network_available() and\ self.__username and self.__password: task_helper = TaskHelper() task_helper.run(self.__remove_from_passwords, uuid) def delete_secret(self): """ Delete sync secret """ self.__username = "" self.__password = "" self.__session = None self.__sync_cancellable.reset() self.__helper.clear_sync(None) def stop(self, force=False): """ Stop update, if force, kill session too @param force as bool """ self.__sync_cancellable.cancel() if force: self.__session = None def on_password_stored(self, secret, result, sync): """ Update credentials @param secret as Secret @param result as Gio.AsyncResult @param sync as bool """ self.set_credentials() if sync: # Wait for credentials (server side) GLib.timeout_add(10, self.sync, True) @property def mtimes(self): """ Sync engine modification times @return {} """ return self.__mtimes @property def syncing(self): """ True if sync is running @return bool """ return self.__syncing @property def status(self): """ True if sync is working @return bool """ try: if self.__username: self.__get_session_bulk_keys() self.__mozilla_sync.client.info_collections() return True except Exception as e: print("SyncWorker::status(): %s" % e) @property def username(self): """ Get username @return str """ return self.__username ####################### # PRIVATE # ####################### @property def __mozilla_sync(self): """ Get mozilla sync, create if None """ if self.__mz is None: self.__mz = MozillaSync() return self.__mz def __get_session_bulk_keys(self): """ Get session decrypt keys @return keys as (b"", b"") """ if self.__session is None: from fxa.core import Session as FxASession from fxa.crypto import quick_stretch_password self.__session = FxASession(self.__mozilla_sync.fxa_client, self.__username, quick_stretch_password( self.__username, self.__password), self.__uid, self.__token) self.__session.keys = [b"", self.__keyB] self.__session.check_session_status() bid_assertion, key = self.__mozilla_sync.get_browserid_assertion( self.__session) bulk_keys = self.__mozilla_sync.connect(bid_assertion, key) return bulk_keys def __update_state(self): """ Update state file """ try: # If syncing, state will be written by self.__sync() if not self.syncing: f = open(EOLIE_DATA_PATH + "/mozilla_sync.bin", "wb") # Lock file flock(f, LOCK_EX | LOCK_NB) self.__mtimes = self.__mozilla_sync.client.info_collections() dump(self.__mtimes, f) # Unlock file flock(f, LOCK_UN) except Exception as e: debug("SyncWorker::__update_state(): %s" % e) def __push_history(self, history_ids): """ Push history ids if atime is available, else, ask to remove @param history ids as [int] """ try: bulk_keys = self.__get_session_bulk_keys() for history_id in history_ids: self.__check_worker() sleep(0.01) record = {} atimes = El().history.get_atimes(history_id) guid = El().history.get_guid(history_id) if atimes: record["histUri"] = El().history.get_uri(history_id) record["id"] = guid record["title"] = El().history.get_title(history_id) record["visits"] = [] for atime in atimes: record["visits"].append({"date": atime*1000000, "type": 1}) debug("pushing %s" % record) self.__mozilla_sync.add(record, "history", bulk_keys) else: record["id"] = guid record["type"] = "item" record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "history", bulk_keys) self.__update_state() except Exception as e: debug("SyncWorker::__push_history(): %s" % e) def __push_password(self, user_form_name, user_form_value, pass_form_name, pass_form_value, uri, form_uri, uuid): """ Push password @param user_form_name as str @param user_form_value as str @param pass_form_name as str @param pass_form_value as str @param uri as str @param uuid as str """ try: self.__check_worker() bulk_keys = self.__get_session_bulk_keys() record = {} record["id"] = "{%s}" % uuid record["hostname"] = uri record["formSubmitURL"] = form_uri record["httpRealm"] = None record["username"] = user_form_value record["password"] = pass_form_value record["usernameField"] = user_form_name record["passwordField"] = pass_form_name mtime = int(time()*1000) record["timeCreated"] = mtime record["timePasswordChanged"] = mtime debug("pushing %s" % record) self.__mozilla_sync.add(record, "passwords", bulk_keys) self.__update_state() except Exception as e: print("SyncWorker::__push_password():", e) def __remove_from_history(self, guid): """ Remove from history @param guid as str """ try: self.__check_worker() bulk_keys = self.__get_session_bulk_keys() record = {} record["id"] = guid record["type"] = "item" record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "history", bulk_keys) self.__update_state() except Exception as e: debug("SyncWorker::__remove_from_history(): %s" % e) def __remove_from_bookmarks(self, guid): """ Remove from history @param guid as str """ try: self.__check_worker() bulk_keys = self.__get_session_bulk_keys() record = {} record["id"] = guid record["type"] = "bookmark" record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "bookmark", bulk_keys) self.__update_state() except Exception as e: debug("SyncWorker::__remove_from_bookmarks(): %s" % e) def __remove_from_passwords(self, uuid): """ Remove password from passwords collection @param uuid as str """ try: self.__check_worker() bulk_keys = self.__get_session_bulk_keys() record = {} record["id"] = uuid record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "passwords", bulk_keys) self.__update_state() except Exception as e: debug("SyncWorker::__remove_from_passwords(): %s" % e) def __sync(self, first_sync): """ Sync Eolie objects (bookmarks, history, ...) with Mozilla Sync @param first_sync as bool """ debug("Start syncing") self.__syncing = True self.__sync_cancellable.reset() try: self.__mtimes = load(open(EOLIE_DATA_PATH + "/mozilla_sync.bin", "rb")) except: self.__mtimes = {"bookmarks": 0.1, "history": 0.1, "passwords": 0.1} try: self.__check_worker() bulk_keys = self.__get_session_bulk_keys() new_mtimes = self.__mozilla_sync.client.info_collections() self.__check_worker() ######################## # Passwords Management # ######################## try: debug("local passwords: %s, remote passwords: %s" % ( self.__mtimes["passwords"], new_mtimes["passwords"])) # Only pull if something new available if self.__mtimes["passwords"] != new_mtimes["passwords"]: self.__pull_passwords(bulk_keys) except: pass # No passwords in sync self.__check_worker() ###################### # History Management # ###################### try: debug("local history: %s, remote history: %s" % ( self.__mtimes["history"], new_mtimes["history"])) # Only pull if something new available if self.__mtimes["history"] != new_mtimes["history"]: self.__pull_history(bulk_keys) except: pass # No history in sync self.__check_worker() ######################## # Bookmarks Management # ######################## try: debug("local bookmarks: %s, remote bookmarks: %s" % ( self.__mtimes["bookmarks"], new_mtimes["bookmarks"])) # Push new bookmarks self.__push_bookmarks(bulk_keys) except: pass # No bookmarks in sync self.__check_worker() # Only pull if something new available if self.__mtimes["bookmarks"] != new_mtimes["bookmarks"]: self.__pull_bookmarks(bulk_keys, first_sync) # Update last sync mtime self.__syncing = False self.__update_state() debug("Stop syncing") except Exception as e: debug("SyncWorker::__sync(): %s" % e) if str(e) == "The authentication token could not be found": self.set_credentials() self.__syncing = False def __push_bookmarks(self, bulk_keys): """ Push to bookmarks @param bulk keys as KeyBundle @param start time as float @raise StopIteration """ debug("push bookmarks") parents = [] for bookmark_id in El().bookmarks.get_ids_for_mtime( self.__mtimes["bookmarks"]): self.__check_worker() sleep(0.01) parent_guid = El().bookmarks.get_parent_guid(bookmark_id) # No parent, move it to unfiled if parent_guid is None: parent_guid = "unfiled" parent_id = El().bookmarks.get_id_by_guid(parent_guid) if parent_id not in parents: parents.append(parent_id) record = {} record["bmkUri"] = El().bookmarks.get_uri(bookmark_id) record["id"] = El().bookmarks.get_guid(bookmark_id) record["title"] = El().bookmarks.get_title(bookmark_id) record["tags"] = El().bookmarks.get_tags(bookmark_id) record["parentid"] = parent_guid record["parentName"] = El().bookmarks.get_parent_name(bookmark_id) record["type"] = "bookmark" debug("pushing %s" % record) self.__mozilla_sync.add(record, "bookmarks", bulk_keys) # Del old bookmarks for bookmark_id in El().bookmarks.get_deleted_ids(): self.__check_worker() sleep(0.01) parent_guid = El().bookmarks.get_parent_guid(bookmark_id) parent_id = El().bookmarks.get_id_by_guid(parent_guid) if parent_id not in parents: parents.append(parent_id) record = {} record["id"] = El().bookmarks.get_guid(bookmark_id) record["type"] = "bookmark" record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "bookmarks", bulk_keys) El().bookmarks.remove(bookmark_id) # Push parents in this order, parents near root are handled later # Otherwise, order will be broken by new children updates while parents: parent_id = parents.pop(0) parent_guid = El().bookmarks.get_guid(parent_id) parent_name = El().bookmarks.get_title(parent_id) children = El().bookmarks.get_children(parent_guid) # So search if children in parents found = False for child_guid in children: child_id = El().bookmarks.get_id_by_guid(child_guid) if child_id in parents: found = True break # Handle children first if found: parents.append(parent_id) debug("later: %s" % parent_name) continue record = {} record["id"] = parent_guid record["type"] = "folder" # A parent with parent as unfiled needs to be moved to places # Firefox internal grand_parent_guid = El().bookmarks.get_parent_guid(parent_id) if grand_parent_guid == "unfiled": grand_parent_guid = "places" record["parentid"] = grand_parent_guid record["parentName"] = El().bookmarks.get_parent_name(parent_id) record["title"] = parent_name record["children"] = children debug("pushing parent %s" % record) self.__mozilla_sync.add(record, "bookmarks", bulk_keys) El().bookmarks.clean_tags() def __pull_bookmarks(self, bulk_keys, first_sync): """ Pull from bookmarks @param bulk_keys as KeyBundle @param first_sync as bool @raise StopIteration """ debug("pull bookmarks") SqlCursor.add(El().bookmarks) records = self.__mozilla_sync.get_records("bookmarks", bulk_keys) children_array = [] for record in records: self.__check_worker() if record["modified"] < self.__mtimes["bookmarks"]: continue sleep(0.01) bookmark = record["payload"] bookmark_id = El().bookmarks.get_id_by_guid(bookmark["id"]) # Nothing to apply, continue if El().bookmarks.get_mtime(bookmark_id) >= record["modified"]: continue debug("pulling %s" % record) # Deleted bookmark if "deleted" in bookmark.keys(): El().bookmarks.remove(bookmark_id) # Keep folder only for firefox compatiblity elif "type" in bookmark.keys() and bookmark["type"] == "folder"\ and bookmark["id"] is not None\ and bookmark["title"]: if bookmark_id is None: bookmark_id = El().bookmarks.add(bookmark["title"], bookmark["id"], bookmark["id"], [], 0, False) # Will calculate position later if "children" in bookmark.keys(): children_array.append(bookmark["children"]) # We have a bookmark, add it elif "type" in bookmark.keys() and bookmark["type"] == "bookmark"\ and bookmark["id"] is not None\ and bookmark["title"]: # Add a new bookmark if bookmark_id is None: # Use parent name if no bookmarks tags if "tags" not in bookmark.keys() or\ not bookmark["tags"]: if "parentName" in bookmark.keys() and\ bookmark["parentName"]: bookmark["tags"] = [bookmark["parentName"]] else: bookmark["tags"] = [] bookmark_id = El().bookmarks.add(bookmark["title"], bookmark["bmkUri"], bookmark["id"], bookmark["tags"], 0, False) # Update bookmark else: El().bookmarks.set_title(bookmark_id, bookmark["title"], False) El().bookmarks.set_uri(bookmark_id, bookmark["bmkUri"], False) # Update tags current_tags = El().bookmarks.get_tags(bookmark_id) for tag in El().bookmarks.get_tags(bookmark_id): if "tags" in bookmark.keys() and\ tag not in bookmark["tags"]: tag_id = El().bookmarks.get_tag_id(tag) current_tags.remove(tag) El().bookmarks.del_tag_from(tag_id, bookmark_id, False) if "tags" in bookmark.keys(): for tag in bookmark["tags"]: # Tag already associated if tag in current_tags: continue tag_id = El().bookmarks.get_tag_id(tag) if tag_id is None: tag_id = El().bookmarks.add_tag(tag, False) El().bookmarks.add_tag_to(tag_id, bookmark_id, False) # Update parent name if available if bookmark_id is not None and "parentName" in bookmark.keys(): El().bookmarks.set_parent(bookmark_id, bookmark["parentid"], bookmark["parentName"], False) El().bookmarks.set_mtime(bookmark_id, record["modified"], False) # Update bookmark position for children in children_array: position = 0 for child in children: bid = El().bookmarks.get_id_by_guid(child) El().bookmarks.set_position(bid, position, False) position += 1 El().bookmarks.clean_tags() # Will commit SqlCursor.remove(El().bookmarks) def __pull_passwords(self, bulk_keys): """ Pull from passwords @param bulk_keys as KeyBundle @raise StopIteration """ debug("pull passwords") records = self.__mozilla_sync.get_records("passwords", bulk_keys) for record in records: self.__check_worker() if record["modified"] < self.__mtimes["passwords"]: continue sleep(0.01) debug("pulling %s" % record) password = record["payload"] password_id = password["id"].strip("{}") if "formSubmitURL" in password.keys(): self.__helper.clear(password_id, self.__helper.store, password["usernameField"], password["username"], password["passwordField"], password["password"], password["hostname"], password["formSubmitURL"], password_id, None) elif "deleted" in password.keys(): # We assume True self.__helper.clear(password_id) def __pull_history(self, bulk_keys): """ Pull from history @param bulk_keys as KeyBundle @raise StopIteration """ debug("pull history") records = self.__mozilla_sync.get_records("history", bulk_keys) for record in records: self.__check_worker() if record["modified"] < self.__mtimes["history"]: continue sleep(0.01) El().history.thread_lock.acquire() history = record["payload"] keys = history.keys() history_id = El().history.get_id_by_guid(history["id"]) # Check we have a valid history item if "histUri" in keys and\ "title" in keys and\ history["title"] and\ El().history.get_mtime(history_id) < record["modified"]: # Try to get visit date atimes = [] try: for visit in history["visits"]: atimes.append(round(int(visit["date"]) / 1000000, 2)) except: El().history.thread_lock.release() continue debug("pulling %s" % record) title = history["title"].rstrip().lstrip() history_id = El().history.add(title, history["histUri"], record["modified"], history["id"], atimes, True) elif "deleted" in keys: history_id = El().history.get_id_by_guid(history_id) El().history.remove(history_id) El().history.thread_lock.release() def __set_credentials(self, attributes, password, uri, index, count): """ Set credentials @param attributes as {} @param password as str @param uri as None @param index as int @param count as int """ if attributes is None: return from base64 import b64decode try: self.__username = attributes["login"] self.__password = password self.__token = attributes["token"] self.__uid = attributes["uid"] self.__keyB = b64decode(attributes["keyB"]) # Force login if no token if not self.__token: self.login(attributes, password) except Exception as e: debug("SyncWorker::__set_credentials(): %s" % e) def __check_worker(self): """ Raise an exception if worker should not be syncing: error&cancel """ if self.__sync_cancellable.is_cancelled(): raise StopIteration("SyncWorker: cancelled") elif not self.__username: raise StopIteration("SyncWorker: missing username") elif not self.__password: raise StopIteration("SyncWorker: missing password") elif not self.__token: raise StopIteration("SyncWorker: missing token")
class ProxyExtensionServer(Server): ''' <!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN' 'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'> <node> <interface name="org.gnome.Eolie.Proxy"> <method name="FormsFilled"> <arg type="b" name="results" direction="out" /> </method> <method name="SetPreviousElementValue"> </method> <method name="SetNextElementValue"> </method> <method name="SaveCredentials"> <arg type="s" name="uuid" direction="in" /> <arg type="s" name="user_form_name" direction="in" /> <arg type="s" name="pass_form_name" direction="in" /> <arg type="s" name="uri" direction="in" /> <arg type="s" name="form_uri" direction="in" /> </method> <method name="SetAuthForms"> <arg type="s" name="username" direction="in" /> <arg type="s" name="username" direction="in" /> </method> <method name="GetScripts"> <arg type="as" name="results" direction="out" /> </method> <method name="GetImages"> <arg type="as" name="results" direction="out" /> </method> <method name="GetVideos"> <arg type="aas" name="results" direction="out" /> </method> <method name="GetImageLinks"> <arg type="as" name="results" direction="out" /> </method> <method name="GetSelection"> <arg type="s" name="selection" direction="out" /> </method> <signal name='UnsecureFormFocused'> <arg type="as" name="forms" direction="out" /> </signal> <signal name='ShowCredentials'> <arg type="as" name="forms" direction="out" /> <arg type="as" name="form_uri" direction="out" /> </signal> <signal name='AskSaveCredentials'> <arg type="s" name="uuid" direction="out" /> <arg type="s" name="user_form_value" direction="out" /> <arg type="s" name="uri" direction="out" /> <arg type="s" name="form_uri" direction="out" /> </signal> </interface> </node> ''' def __init__(self, extension, page, form_extension, jsblock_extension): """ Init server @param extension as WebKit2WebExtension.WebExtension @param page as WebKit2WebExtension.WebPage @param form_extension as FormsExtension @param jsblock_extension as JSBlockExtension """ self.__extension = extension self.__page = page self.__form_extension = form_extension self.__jsblock_extension = jsblock_extension self.__focused = None self.__on_input_timeout_id = None self.__elements_history = {} self.__send_requests = [] self.__helper = PasswordsHelper() self.__proxy_bus = PROXY_BUS % self.__page.get_id() addr = Gio.dbus_address_get_for_bus_sync(Gio.BusType.SESSION, None) self.__bus = None Gio.DBusConnection.new_for_address( addr, Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT | Gio.DBusConnectionFlags.MESSAGE_BUS_CONNECTION, None, None, self.__on_bus_new_for_address) form_extension.connect("submit-form", self.__on_submit_form) page.connect("send-request", self.__on_send_request) page.connect("context-menu", self.__on_context_menu) page.connect("notify::uri", self.__on_notify_uri) page.connect("form-controls-associated", self.__on_form_control_associated) def FormsFilled(self): """ True if form contains data @return bool """ dom = self.__page.get_dom_document() collection = dom.get_elements_by_tag_name("textarea") for i in range(0, collection.get_length()): if collection.item(i).is_edited(): return True return False def SaveCredentials(self, uuid, user_form_name, pass_form_name, uri, form_uri): """ Save credentials to org.freedesktop.Secrets @param uuid as str @param user_form_name as str @param pass_form_name as str @param uri as str @param form_uri as str """ if self.__form_extension.pending_credentials in [None, Type.NONE]: return try: (_uuid, _user_form_name, user_form_value, _pass_form_name, pass_form_value, _uri, _form_uri) = self.__form_extension.pending_credentials if user_form_name != _user_form_name or\ pass_form_name != _pass_form_name or\ uri != _uri or\ form_uri != _form_uri: return parsed = urlparse(uri) uri = "%s://%s" % (parsed.scheme, parsed.netloc) if not uuid: uuid = str(uuid4()) self.__helper.store(user_form_name, user_form_value, pass_form_name, pass_form_value, uri, form_uri, uuid, None) else: self.__helper.clear(uuid, self.__helper.store, user_form_name, user_form_value, pass_form_name, pass_form_value, uri, form_uri, uuid, None) except Exception as e: print("ProxyExtension::SaveCredentials():", e) def SetAuthForms(self, userform, username): """ Get password forms @param userform as str """ try: collection = self.__page.get_dom_document().get_forms() elements = [] for i in range(0, collection.get_length()): elements.append(collection.item(i)) (forms, textareas) = self.__form_extension.get_elements(elements) for form in forms: if form["username"].get_name() == userform: self.__helper.get(form["element"].get_action(), userform, form["password"].get_name(), self.__form_extension.set_input_forms, self.__page, form, username) return except Exception as e: print("ProxyExtension::SetAuthForms():", e) def GetScripts(self): """ Get images @return [str] """ try: return self.__jsblock_extension.scripts except Exception as e: print("ProxyExtension::GetScripts():", e) return [] def GetImages(self): """ Get images @return [str] """ try: page = self.__extension.get_page(self.__page.get_id()) if page is None: return [] dom_list = page.get_dom_document().get_elements_by_tag_name("img") uris = [] for i in range(0, dom_list.get_length()): uri = dom_list.item(i).get_src() if uri not in uris: uris.append(uri) return uris except Exception as e: print("ProxyExtension::GetImages():", e) return [] def GetVideos(self): """ Get videos @return [str] """ page = self.__extension.get_page(self.__page.get_id()) if page is None: return [] videos = [] extensions = ["avi", "flv", "mp4", "mpg", "mpeg", "webm"] for uri in self.__send_requests: parsed = urlparse(uri) title = None # Search for video in page if uri.split(".")[-1] in extensions: title = uri elif parsed.netloc.endswith("googlevideo.com") and\ parsed.path == "/videoplayback": title = page.get_dom_document().get_title() if title is None: title = uri # For youtube, we only want one video: return [(title, uri)] if title is not None and (title, uri) not in videos: videos.append((title, uri)) return videos def GetImageLinks(self): """ Get image links @return [str] """ try: page = self.__extension.get_page(self.__page.get_id()) if page is None: return [] dom_list = page.get_dom_document().get_elements_by_tag_name("a") uris = [] for i in range(0, dom_list.get_length()): uri = dom_list.item(i).get_href() if uri is None or uri in uris: continue ext = uri.split(".")[-1] if ext in ["gif", "jpg", "png", "jpeg"]: uris.append(uri) return uris except Exception as e: print("ProxyExtension::GetImagesLinks():", e) return [] def GetSelection(self): """ Get selected text @param page id as int @return str """ webpage = self.__extension.get_page(self.__page.get_id()) document = webpage.get_dom_document() if document is None: return "" window = document.get_default_view() if window is None: return "" selection = window.get_selection() if selection is None: return "" try: dom_range = selection.get_range_at(0) except: dom_range = None value = "" if dom_range is not None: value = dom_range.to_string() if value is None: value = "" return value def SetPreviousElementValue(self): """ Set focused form to previous value """ if self.__focused is None: return try: value = self.__focused.get_value().rstrip(" ") if self.__focused in self.__elements_history.keys(): current = self.__elements_history[self.__focused] if current.prev is not None: self.__elements_history[self.__focused] = current.prev self.__focused.set_value(current.prev.value) elif value: next = LinkedList(value, None, None) current = LinkedList("", next, None) next.set_prev(current) self.__elements_history[self.__focused] = current self.__focused.set_value("") except Exception as e: print("ProxyExtension::SetPreviousForm():", e) def SetNextElementValue(self): """ Set focused form to next value """ if self.__focused is None: return try: if self.__focused in self.__elements_history.keys(): current = self.__elements_history[self.__focused] if current.next is not None: self.__elements_history[self.__focused] = current.next self.__focused.set_value(current.next.value) except Exception as e: print("ProxyExtension::SetNextForm():", e) ####################### # PRIVATE # ####################### def __add_event_listeners(self, forms, textareas, webpage): """ Add event listeners on inputs and textareas @param forms as {} @param textareas as [WebKit2WebExtension.DOMHTMLTextAreaElement] @param webpage as WebKit2WebExtension.WebPage """ self.__focused = None self.__elements_history = {} parsed = urlparse(webpage.get_uri()) # Manage forms events for form in forms: form["username"].add_event_listener("input", self.__on_input, False) form["username"].add_event_listener("focus", self.__on_focus, False) form["username"].add_event_listener("blur", self.__on_blur, False) form["username"].add_event_listener("mousedown", self.__on_mouse_down, False) # Check for unsecure content if parsed.scheme == "http": form["password"].add_event_listener("focus", self.__on_focus, False) # Manage textareas events for textarea in textareas: textarea.add_event_listener("input", self.__on_input, False) textarea.add_event_listener("focus", self.__on_focus, False) def __on_bus_new_for_address(self, source, result): """ Init bus @param source as GObject.Object @param result as Gio.AsyncResult """ self.__bus = source.new_for_address_finish(result) Gio.bus_own_name_on_connection(self.__bus, self.__proxy_bus, Gio.BusNameOwnerFlags.NONE, None, None) Server.__init__(self, self.__bus, PROXY_PATH) def __on_focus(self, element, event): """ Keep last focused form @param element as WebKit2WebExtension.DOMElement @param event as WebKit2WebExtension.DOMUIEvent """ if isinstance(element, WebKit2WebExtension.DOMHTMLInputElement): if element.get_input_type() == "password" and\ self.__bus is not None: self.__bus.emit_signal( None, PROXY_PATH, self.__proxy_bus, "UnsecureFormFocused", None) self.__focused = element def __on_blur(self, element, event): """ Reset focus @param element as WebKit2WebExtension.DOMElement @param event as WebKit2WebExtension.DOMUIEvent """ if self.__focused == element: self.__focused = None def __on_mouse_down(self, element, event): """ Emit Input mouse down signal @param element as WebKit2WebExtension.DOMHTMLInputElement @param event as WebKit2WebExtension.DOMUIEvent """ if element.get_name() is None: return if self.__focused != element and\ self.__bus is not None: form_uri = element.get_form().get_action() parsed_form_uri = urlparse(form_uri) form_uri = "%s://%s" % (parsed_form_uri.scheme, parsed_form_uri.netloc) args = GLib.Variant("(ss)", (element.get_name(), form_uri)) self.__bus.emit_signal( None, PROXY_PATH, self.__proxy_bus, "ShowCredentials", args) def __on_input(self, element, event): """ Run a timeout before saving buffer to history @param element as WebKit2WebExtension.DOMElement @param event as WebKit2WebExtension.DOMUIEvent """ if self.__on_input_timeout_id is not None: GLib.source_remove(self.__on_input_timeout_id) self.__on_input_timeout_id = GLib.timeout_add(500, self.__on_input_timeout, element) def __on_input_timeout(self, element): """ Save buffer to history @param element as WebKit2WebExtension.DOMElement """ self.__on_input_timeout_id = None new_value = element.get_value() item = LinkedList("", None, None) if element in self.__elements_history.keys(): item = self.__elements_history[element] next = LinkedList(new_value.rstrip(" "), None, item) if item is not None: item.set_next(next) self.__elements_history[element] = next def __on_form_control_associated(self, webpage, elements): """ Add elements to forms @param webpage as WebKit2WebExtension.WebPage @param elements as [WebKit2WebExtension.DOMElement] """ (forms, textareas) = self.__form_extension.get_elements(elements) self.__add_event_listeners(forms, textareas, webpage) for form in forms: form["element"].add_event_listener( "submit", self.__form_extension.on_form_submit, False) self.__form_extension.set_credentials(form, webpage) def __on_context_menu(self, webpage, context_menu, hit): """ Add selection to context menu user data @param webpage as WebKit2WebExtension.WebPage @param context_menu as WebKit2WebExtension.ContextMenu @param hit as WebKit2.HitTestResult """ value = self.GetSelection() context_menu.set_user_data(GLib.Variant("s", value)) def __on_submit_form(self, forms, variant): """ Ask for credentials save @param forms as FormsExtension @param variant as GLib.Variant """ if self.__bus is not None: self.__bus.emit_signal( None, PROXY_PATH, self.__proxy_bus, "AskSaveCredentials", variant) def __on_notify_uri(self, webpage, param): """ Reset send requests @param webpage as WebKit2WebExtension.WebPage @param uri as GObject.ParamSpec """ self.__send_requests = [] def __on_send_request(self, webpage, request, redirect): """ Keep send requests @param webpage as WebKit2WebExtension.WebPage @param request as WebKit2.URIRequest @param redirect as WebKit2WebExtension.URIResponse """ self.__send_requests.append(request.get_uri())
class CredentialsPopover(Gtk.Popover): """ Tell user to save form credentials """ def __init__(self, username, userform, password, passform, uri): """ Init popover @param username as str @param password as str @param netloc as str """ Gtk.Popover.__init__(self) self.__helper = PasswordsHelper() self.__username = username self.__userform = userform self.__password = password self.__passform = passform self.__uri = uri self.__uuid = None builder = Gtk.Builder() builder.add_from_resource('/org/gnome/Eolie/PopoverCredentials.ui') builder.connect_signals(self) self.__label = builder.get_object('label') parsed = urlparse(uri) builder.get_object('uri').set_text(parsed.netloc) builder.get_object('username').set_text(username) builder.get_object('password').set_text(password) self.add(builder.get_object('widget')) ####################### # PROTECTED # ####################### def _on_save_clicked(self, button): """ Save username and password @param button as Gtk.Button """ try: parsed = urlparse(self.__uri) uri = "%s://%s" % (parsed.scheme, parsed.netloc) if self.__uuid is None: self.__uuid = str(uuid3(NAMESPACE_DNS, parsed.netloc)) else: self.__helper.clear(self.__uuid) self.__helper.store(self.__username, self.__password, uri, self.__uuid, None) if El().sync_worker is not None: El().sync_worker.push_password(self.__username, self.__userform, self.__password, self.__passform, uri, self.__uuid) self.destroy() except Exception as e: print("CredentialsPopover::_on_save_clicked()", e) def show(self): """ Overwrite show """ self.__helper.get(self.__uri, self.__on_get_password) ####################### # PRIVATE # ####################### def __on_get_password(self, attributes, password, uri): """ Set username/password input @param attributes as {} @param password as str @param uri as str """ try: if attributes is None: Gtk.Popover.show(self) elif attributes["login"] == self.__username and\ self.__password == password: self.emit("closed") else: Gtk.Popover.show(self) self.__uuid = attributes["uuid"] self.__label.set_text( _("Do you want to modify this password?")) except Exception as e: print("CredentialsPopover::__on_get_password()", e)
class SyncWorker(GObject.GObject): """ Manage sync with mozilla server, will start syncing on init """ __gsignals__ = {"sync-finished": (GObject.SignalFlags.RUN_FIRST, None, ())} def __init__(self): """ Init worker """ GObject.GObject.__init__(self) self.__stop = True self.__username = "" self.__password = "" self.__mtimes = {"bookmarks": 0.1, "history": 0.1} self.__mozilla_sync = MozillaSync() self.__session = None self.__helper = PasswordsHelper() def login(self, attributes, password, uri): """ Login to service @param attributes as {} @param password as str @param uri as None @raise exceptions """ if attributes is None: return keyB = "" session = None # Connect to mozilla sync session = self.__mozilla_sync.login(attributes["login"], password) bid_assertion, key = self.__mozilla_sync.get_browserid_assertion( session) keyB = base64.b64encode(session.keys[1]).decode("utf-8") # Store credentials if session is None: uid = "" token = "" else: uid = session.uid token = session.token self.__helper.store_sync(attributes["login"], password, uid, token, keyB, lambda x, y: self.sync(True)) def sync(self, first_sync=False): """ Start syncing, you need to check sync_status property @param first_sync as bool """ if self.syncing or\ not Gio.NetworkMonitor.get_default().get_network_available(): return self.__username = "" self.__password = "" self.__stop = False # We force session reset to user last stored token if first_sync: self.__session = None self.__helper.get_sync(self.__start_sync, first_sync) def push_history(self, history_ids): """ Push history ids @param history_ids as [int] """ if Gio.NetworkMonitor.get_default().get_network_available(): thread = Thread(target=self.__push_history, args=(history_ids, )) thread.daemon = True thread.start() def push_password(self, username, userform, password, passform, uri, uuid): """ Push password @param username as str @param userform as str @param password as str @param passform as str @param uri as str @param uuid as str """ if Gio.NetworkMonitor.get_default().get_network_available(): thread = Thread(target=self.__push_password, args=(username, userform, password, passform, uri, uuid)) thread.daemon = True thread.start() def remove_from_history(self, guid): """ Remove history id from remote history A first call to sync() is needed to populate secrets @param guid as str """ if Gio.NetworkMonitor.get_default().get_network_available(): thread = Thread(target=self.__remove_from_history, args=(guid, )) thread.daemon = True thread.start() def remove_from_passwords(self, uri): """ Remove password from passwords collection @param uri as str """ if Gio.NetworkMonitor.get_default().get_network_available(): self.__helper.get(uri, self.__remove_from_passwords_thread) def delete_secret(self): """ Delete sync secret """ self.__username = "" self.__password = "" self.__session = None self.__stop = True self.__helper.clear_sync() def stop(self): """ Stop update """ self.__stop = True @property def mtimes(self): """ Sync engine modification times @return {} """ return self.__mtimes @property def syncing(self): """ True if sync is running @return bool """ return not self.__stop @property def status(self): """ True if sync is working @return bool """ try: self.__mozilla_sync.client.info_collections() return True except: return False @property def username(self): """ Get username @return str """ return self.__username ####################### # PRIVATE # ####################### def __get_session_bulk_keys(self): """ Get session decrypt keys @return keys as (b"", b"") """ if self.__session is None: self.__session = FxASession( self.__mozilla_sync.fxa_client, self.__username, quick_stretch_password(self.__username, self.__password), self.__uid, self.__token) self.__session.keys = [b"", self.__keyB] self.__session.check_session_status() bid_assertion, key = self.__mozilla_sync.get_browserid_assertion( self.__session) bulk_keys = self.__mozilla_sync.connect(bid_assertion, key) return bulk_keys def __push_history(self, history_ids): """ Push history ids if atime is available, else, ask to remove @param history ids as [int] """ if not self.__username or not self.__password: return try: bulk_keys = self.__get_session_bulk_keys() for history_id in history_ids: sleep(0.01) record = {} atimes = El().history.get_atimes(history_id) guid = El().history.get_guid(history_id) if atimes: record["histUri"] = El().history.get_uri(history_id) record["id"] = guid record["title"] = El().history.get_title(history_id) record["visits"] = [] for atime in atimes: record["visits"].append({ "date": atime * 1000000, "type": 1 }) debug("pushing %s" % record) self.__mozilla_sync.add(record, "history", bulk_keys) else: record["id"] = guid record["type"] = "item" record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "history", bulk_keys) self.__mtimes = self.__mozilla_sync.client.info_collections() dump(self.__mtimes, open(LOCAL_PATH + "/mozilla_sync.bin", "wb")) except Exception as e: print("SyncWorker::__push_history():", e) def __push_password(self, username, userform, password, passform, uri, uuid): """ Push password @param username as str @param userform as str @param password as str @param passform as str @param uri as str @param uuid as str """ if not self.__username or not self.__password: return try: bulk_keys = self.__get_session_bulk_keys() record = {} record["id"] = "{%s}" % uuid record["hostname"] = uri record["formSubmitURL"] = uri record["httpRealm"] = None record["username"] = username record["password"] = password record["usernameField"] = userform record["passwordField"] = passform mtime = int(time() * 1000) record["timeCreated"] = mtime record["timePasswordChanged"] = mtime debug("pushing %s" % record) self.__mozilla_sync.add(record, "passwords", bulk_keys) except Exception as e: print("SyncWorker::__push_password():", e) def __remove_from_history(self, guid): """ Remove from history @param guid as str """ if not self.__username or not self.__password: return try: bulk_keys = self.__get_session_bulk_keys() record = {} record["id"] = guid record["type"] = "item" record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "history", bulk_keys) except Exception as e: print("SyncWorker::__remove_from_history():", e) def __remove_from_passwords(self, attributes, password, uri): """ Remove password from passwords collection @param attributes as {} @param password as str @param uri as str """ if not self.__username or not self.__password: return try: bulk_keys = self.__get_session_bulk_keys() record = {} record["id"] = attributes["uuid"] record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "passwords", bulk_keys) self.__helper.clear(uri) except Exception as e: print("SyncWorker::__remove_from_passwords():", e) def __remove_from_passwords_thread(self, attributes, password, uri): """ Remove password from passwords collection @param attributes as {} @param password as str @param uri as str """ thread = Thread(target=self.__remove_from_passwords, args=(attributes, password, uri)) thread.daemon = True thread.start() def __sync(self, first_sync): """ Sync Eolie objects (bookmarks, history, ...) with Mozilla Sync @param first_sync as bool """ debug("Start syncing") if not self.__username or not self.__password: self.__stop = True return try: self.__mtimes = load(open(LOCAL_PATH + "/mozilla_sync.bin", "rb")) except: self.__mtimes = { "bookmarks": 0.1, "history": 0.1, "passwords": 0.1 } try: bulk_keys = self.__get_session_bulk_keys() new_mtimes = self.__mozilla_sync.client.info_collections() if self.__stop: return ###################### # Passwords Management # ###################### debug("local passwords: %s, remote passwords: %s" % (self.__mtimes["passwords"], new_mtimes["passwords"])) # Only pull if something new available if self.__mtimes["passwords"] != new_mtimes["passwords"]: self.__pull_passwords(bulk_keys) if self.__stop: return ###################### # History Management # ###################### debug("local history: %s, remote history: %s" % (self.__mtimes["history"], new_mtimes["history"])) # Only pull if something new available if self.__mtimes["history"] != new_mtimes["history"]: self.__pull_history(bulk_keys) if self.__stop: return ######################## # Bookmarks Management # ######################## debug("local bookmarks: %s, remote bookmarks: %s" % (self.__mtimes["bookmarks"], new_mtimes["bookmarks"])) # Push new bookmarks self.__push_bookmarks(bulk_keys) if self.__stop: return # Only pull if something new available if self.__mtimes["bookmarks"] != new_mtimes["bookmarks"]: self.__pull_bookmarks(bulk_keys, first_sync) # Update last sync mtime self.__mtimes = self.__mozilla_sync.client.info_collections() dump(self.__mtimes, open(LOCAL_PATH + "/mozilla_sync.bin", "wb")) debug("Stop syncing") GLib.idle_add(self.emit, "sync-finished") except Exception as e: print("SyncWorker::__sync():", e) if str(e) == "The authentication token could not be found": self.__helper.get_sync(self.login) self.__stop = True def __push_bookmarks(self, bulk_keys): """ Push to bookmarks @param bulk keys as KeyBundle @param start time as float @raise StopIteration """ debug("push bookmarks") parents = [] for bookmark_id in El().bookmarks.get_ids_for_mtime( self.__mtimes["bookmarks"]): if self.__stop: raise StopIteration("Cancelled") sleep(0.01) parent_guid = El().bookmarks.get_parent_guid(bookmark_id) # No parent, move it to unfiled if parent_guid is None: parent_guid = "unfiled" parent_id = El().bookmarks.get_id_by_guid(parent_guid) if parent_id not in parents: parents.append(parent_id) record = {} record["bmkUri"] = El().bookmarks.get_uri(bookmark_id) record["id"] = El().bookmarks.get_guid(bookmark_id) record["title"] = El().bookmarks.get_title(bookmark_id) record["tags"] = El().bookmarks.get_tags(bookmark_id) record["parentid"] = parent_guid record["type"] = "bookmark" debug("pushing %s" % record) self.__mozilla_sync.add(record, "bookmarks", bulk_keys) # Del old bookmarks for bookmark_id in El().bookmarks.get_deleted_ids(): if self.__stop: raise StopIteration("Cancelled") sleep(0.01) parent_guid = El().bookmarks.get_parent_guid(bookmark_id) parent_id = El().bookmarks.get_id_by_guid(parent_guid) if parent_id not in parents: parents.append(parent_id) record = {} record["id"] = El().bookmarks.get_guid(bookmark_id) record["type"] = "item" record["deleted"] = True debug("deleting %s" % record) self.__mozilla_sync.add(record, "bookmarks", bulk_keys) El().bookmarks.remove(bookmark_id) # Push parents in this order, parents near root are handled later # Otherwise, order will be broken by new children updates while parents: parent_id = parents.pop(0) parent_guid = El().bookmarks.get_guid(parent_id) parent_name = El().bookmarks.get_title(parent_id) children = El().bookmarks.get_children(parent_guid) # So search if children in parents found = False for child_guid in children: child_id = El().bookmarks.get_id_by_guid(child_guid) if child_id in parents: found = True break # Handle children first if found: parents.append(parent_id) debug("later: %s" % parent_name) continue record = {} record["id"] = parent_guid record["type"] = "folder" # A parent with parent as unfiled needs to be moved to places # Firefox internal grand_parent_guid = El().bookmarks.get_parent_guid(parent_id) if grand_parent_guid == "unfiled": grand_parent_guid = "places" record["parentid"] = grand_parent_guid record["parentName"] = El().bookmarks.get_parent_name(parent_id) record["title"] = parent_name record["children"] = children debug("pushing parent %s" % record) self.__mozilla_sync.add(record, "bookmarks", bulk_keys) El().bookmarks.clean_tags() def __pull_bookmarks(self, bulk_keys, first_sync): """ Pull from bookmarks @param bulk_keys as KeyBundle @param first_sync as bool @raise StopIteration """ debug("pull bookmarks") SqlCursor.add(El().bookmarks) records = self.__mozilla_sync.get_records("bookmarks", bulk_keys) # We get all guids here and remove them while sync # At the end, we have deleted records # On fist sync, keep all if first_sync: to_delete = [] else: to_delete = El().bookmarks.get_guids() for record in records: if self.__stop: raise StopIteration("Cancelled") sleep(0.01) bookmark = record["payload"] if "type" not in bookmark.keys() or\ bookmark["type"] not in ["folder", "bookmark"]: continue bookmark_id = El().bookmarks.get_id_by_guid(bookmark["id"]) # This bookmark exists, remove from to delete if bookmark["id"] in to_delete: to_delete.remove(bookmark["id"]) # Nothing to apply, continue if El().bookmarks.get_mtime(bookmark_id) >= record["modified"]: continue debug("pulling %s" % record) if bookmark_id is None: if "bmkUri" in bookmark.keys(): # Use parent name if no bookmarks tags if "tags" not in bookmark.keys() or\ not bookmark["tags"]: if "parentName" in bookmark.keys() and\ bookmark["parentName"]: bookmark["tags"] = [bookmark["parentName"]] else: bookmark["tags"] = [] bookmark_id = El().bookmarks.add(bookmark["title"], bookmark["bmkUri"], bookmark["id"], bookmark["tags"], False) else: bookmark["tags"] = [] bookmark_id = El().bookmarks.add(bookmark["title"], bookmark["id"], bookmark["id"], bookmark["tags"], False) else: El().bookmarks.set_title(bookmark_id, bookmark["title"], False) if "bmkUri" in bookmark.keys(): El().bookmarks.set_uri(bookmark_id, bookmark["bmkUri"], False) elif "children" in bookmark.keys(): position = 0 for child in bookmark["children"]: bid = El().bookmarks.get_id_by_guid(child) El().bookmarks.set_position(bid, position, False) position += 1 # Remove previous tags current_tags = El().bookmarks.get_tags(bookmark_id) for tag in El().bookmarks.get_tags(bookmark_id): if "tags" in bookmark.keys() and\ tag not in bookmark["tags"]: tag_id = El().bookmarks.get_tag_id(tag) current_tags.remove(tag) El().bookmarks.del_tag_from(tag_id, bookmark_id, False) if "tags" in bookmark.keys(): for tag in bookmark["tags"]: # Tag already associated if tag in current_tags: continue tag_id = El().bookmarks.get_tag_id(tag) if tag_id is None: tag_id = El().bookmarks.add_tag(tag, False) El().bookmarks.add_tag_to(tag_id, bookmark_id, False) El().bookmarks.set_mtime(bookmark_id, record["modified"], False) if "parentName" in bookmark.keys(): El().bookmarks.set_parent(bookmark_id, bookmark["parentid"], bookmark["parentName"], False) for guid in to_delete: if self.__stop: raise StopIteration("Cancelled") debug("deleting: %s" % guid) bookmark_id = El().bookmarks.get_id_by_guid(guid) if bookmark_id is not None: El().bookmarks.remove(bookmark_id, False) El().bookmarks.clean_tags() # Will commit SqlCursor.remove(El().bookmarks) def __pull_passwords(self, bulk_keys): """ Pull from passwords @param bulk_keys as KeyBundle @raise StopIteration """ debug("pull passwords") records = self.__mozilla_sync.get_records("passwords", bulk_keys) for record in records: if self.__stop: raise StopIteration("Cancelled") sleep(0.01) debug("pulling %s" % record) password = record["payload"] if "formSubmitURL" in password.keys(): self.__helper.clear(password["formSubmitURL"]) self.__helper.store(password["username"], password["password"], password["formSubmitURL"], password["id"], None) elif "deleted" in password.keys(): # We assume True self.__helper.clear(password["id"]) def __pull_history(self, bulk_keys): """ Pull from history @param bulk_keys as KeyBundle @raise StopIteration """ debug("pull history") records = self.__mozilla_sync.get_records("history", bulk_keys) for record in records: if self.__stop: raise StopIteration("Cancelled") sleep(0.01) El().history.thread_lock.acquire() history = record["payload"] keys = history.keys() # Ignore pages without a title if "title" not in keys or not history["title"]: El().history.thread_lock.release() continue # Ignore pages without an uri (deleted) if "histUri" not in keys: El().history.thread_lock.release() continue history_id = El().history.get_id_by_guid(history["id"]) # Nothing to apply, continue if El().history.get_mtime(history_id) >= record["modified"]: El().history.thread_lock.release() continue # Try to get visit date atimes = [] try: for visit in history["visits"]: atimes.append(round(int(visit["date"]) / 1000000, 2)) except: El().history.thread_lock.release() continue debug("pulling %s" % record) title = history["title"].rstrip().lstrip() history_id = El().history.add(title, history["histUri"], record["modified"], history["id"], atimes, True) El().history.thread_lock.release() def __start_sync(self, attributes, password, uri, first_sync): """ Set params and start sync @param attributes as {} @param password as str @param uri as None @param first_sync as bool """ if attributes is None: return try: self.__username = attributes["login"] self.__password = password self.__token = attributes["token"] self.__uid = attributes["uid"] self.__keyB = base64.b64decode(attributes["keyB"]) if Gio.NetworkMonitor.get_default().get_network_available(): thread = Thread(target=self.__sync, args=(first_sync, )) thread.daemon = True thread.start() except Exception as e: print("SyncWorker::__start_sync()", e)