def __on_signal(self, connection, sender, path, interface, signal, params): """ Add video to download manager @param connection as Gio.DBusConnection @param sender as str @param path as str @param interface as str @param signal as str @param parameters as GLib.Variant """ if signal == "VideoInPage": uri = params[0] title = params[1] page_id = params[2] El().download_manager.add_video(uri, title, page_id) elif signal == "UnsecureFormFocused": self._window.toolbar.title.show_input_warning(self) elif signal == "InputMouseDown": if self.__last_click_event: model = FormMenu(params[0], self.get_page_id()) popover = Gtk.Popover.new_from_model(self, model) popover.set_modal(False) self._window.register(popover) rect = Gdk.Rectangle() rect.x = self.__last_click_event["x"] rect.y = self.__last_click_event["y"] - 10 rect.width = rect.height = 1 popover.set_pointing_to(rect) helper = PasswordsHelper() helper.get(self.get_uri(), self.__on_password, popover, model)
def __on_save_credentials(self, source, result, form_uri, user_form_name, pass_form_name): """ Get password and push credential to sync @param source as GObject.Object @param result as Gio.AsyncResult @param form_uri as str @param user_form_name as str @param pass_form_name as str """ helper = PasswordsHelper() helper.get(form_uri, user_form_name, pass_form_name, self.__on_get_password)
def SetAuthForms(self, username, page_id): """ Get password forms for page id @param username as str @param page id as int """ try: page = self.__extension.get_page(page_id) if page is None: return helper = PasswordsHelper() helper.get(page.get_uri(), self.__forms.set_input_forms, page, username) except Exception as e: print("ProxyExtension::SetAuthForms():", e)
def __on_signal(self, signal, params): """ Handle proxy signals @param signal as str @params as [] """ if signal == "UnsecureFormFocused": self._window.toolbar.title.show_input_warning(self) elif signal == "AskSaveCredentials": (uuid, user_form_name, user_form_value, pass_form_name, uri, form_uri) = params[0] self._window.close_popovers() self._window.toolbar.title.show_password(uuid, user_form_name, user_form_value, pass_form_name, uri, form_uri, self.get_page_id()) elif signal == "ShowCredentials": (userform, form_uri) = params model = FormMenu(self.get_page_id(), self._window) helper = PasswordsHelper() helper.get(form_uri, userform, None, self.__on_password, model)
class FormsExtension: """ Handle forms prefill """ def __init__(self, extension, settings): """ Connect wanted signal @param extension as WebKit2WebExtension @param settings as Settings """ self.__helper = PasswordsHelper() self.__settings = settings self.__input_logins = [] self.__input_passwords = [] extension.connect("page-created", self.__on_page_created) def set_credentials(self, webpage): """ Set credentials on page @param webpage as WebKit2WebExtension.WebPage """ forms = self.update_inputs_list(webpage) for form in forms: self.__helper.get(form.get_action(), self.set_input_forms, webpage) def get_input_forms(self, webpage): """ Return forms for webpage @param webpage as WebKit2WebExtension.WebPage @return [WebKit2WebExtension.DOMHTMLInputElement] """ forms = [] dom_document = webpage.get_dom_document() inputs = dom_document.get_elements_by_tag_name("input") i = 0 while i < inputs.get_length(): if inputs.item(i).get_input_type() in ["text", "email", "search"]: forms.append(inputs.item(i)) i += 1 return forms def get_textarea_forms(self, webpage): """ Return forms for webpage @param webpage as WebKit2WebExtension.WebPage @return [WebKit2WebExtension.DOMHTMLInputElement] """ forms = [] dom_document = webpage.get_dom_document() textareas = dom_document.get_elements_by_tag_name("textarea") i = 0 while i < textareas.get_length(): forms.append(textareas.item(i)) i += 1 return forms def get_password_inputs(self, webpage): """ Return password inputs @param webpage as WebKit2WebExtension.WebPage @return [WebKit2WebExtension.DOMHTMLInputElement] """ return self.__input_passwords def is_password_input(self, name, webpage): """ Return True if a password input @param name as str @param webpage as WebKit2WebExtension.WebPage @return WebKit2WebExtension.DOMHTMLInputElement/None """ for input_password in self.__input_passwords: input_name = input_password.get_attribute("name") input_id = input_password.get_attribute("id") # We search for wanted name if name and (name == input_name or name == input_id): return True return False def set_input_forms(self, attributes, password, uri, index, count, webpage, login=None): """ Set login/password input @param attributes as {} @param password as str @param uri as str @param index as int @param count as int @param webpage as WebKit2WebExtension.WebPage @param login as str/None """ # We only set first available password if (index != 0 or count > 1) and login is None: return parsed = urlparse(uri) # Allow unsecure completion if wanted by user if parsed.scheme != "https" and login is None: return submit_uri = "%s://%s" % (parsed.scheme, parsed.netloc) # Do not set anything if no attributes or # If we have already text in input if attributes is None or\ (login is not None and attributes["login"] != login) or\ attributes["formSubmitURL"] != submit_uri: return try: wanted_input_login = None name = attributes["userform"] for input_login in self.__input_logins: input_name = input_login.get_attribute("name") input_id = input_login.get_attribute("id") # We search for wanted name if name and (name == input_name or name == input_id): wanted_input_login = input_login break if wanted_input_login is None: return wanted_input_password = None name = attributes["passform"] for input_password in self.__input_passwords: input_name = input_password.get_attribute("name") input_id = input_password.get_attribute("id") if not name or name == input_name or name == input_id: wanted_input_password = input_password break if wanted_input_password is None: return wanted_input_login.set_value(attributes["login"]) wanted_input_password.set_value(password) except Exception as e: print("FormsExtension::set_input_forms()", e) def is_login_form(self, form): """ Return True if form is a login form @param form as WebKit2WebExtension.DOMHTMLInputElement @return bool """ input_name = form.get_attribute("name") input_id = form.get_attribute("id") # We search for common name for search in LOGINS: if (input_name is not None and input_name.lower().find(search) != -1) or\ (input_id is not None and input_id.lower().find(search) != -1): return True return False def update_inputs_list(self, webpage): """ Update login and password inputs @param webpage as WebKit2WebExtension.WebPage @return forms with a password input """ self.__input_logins = [] self.__input_passwords = [] forms = [] collection = webpage.get_dom_document().get_forms() i = 0 while i < collection.get_length(): form = collection.item(i) if form.get_method() == "post": elements_collection = form.get_elements() h = 0 while h < elements_collection.get_length(): element = elements_collection.item(h) if not isinstance(element, WebKit2WebExtension.DOMHTMLInputElement): h += 1 continue if element.get_input_type() == "password": self.__input_passwords.append(element) if form not in forms: forms.append(form) elif element.get_input_type() in ["text", "email", "search"]: self.__input_logins.append(element) h += 1 i += 1 return forms ####################### # PRIVATE # ####################### def __on_page_created(self, extension, webpage): """ Connect to send request @param extension as WebKit2WebExtension @param webpage as WebKit2WebExtension.WebPage """ webpage.connect("document-loaded", self.__on_document_loaded) def __on_document_loaded(self, webpage): """ Restore forms @param webpage as WebKit2WebExtension.WebPage """ if not self.__settings.get_value("remember-passwords"): return self.set_credentials(webpage)
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 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 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 FormsExtension: """ Handle forms prefill """ def __init__(self, extension, settings): """ Connect wanted signal @param extension as WebKit2WebExtension @param settings as Settings """ self.__helper = PasswordsHelper() self.__settings = settings extension.connect("page-created", self.__on_page_created) def get_forms(self, webpage): """ Return forms for webpage @param webpage as WebKit2WebExtension.WebPage @return (WebKit2WebExtension.DOMElement, WebKit2WebExtension.DOMElement) """ inputs = webpage.get_dom_document().get_elements_by_tag_name('input') i = 0 username_input = None password_input = None while i < inputs.get_length(): name = inputs.item(i).get_attribute('name') if name is None: i += 1 continue if password_input is None and\ inputs.item(i).get_input_type() == "password": password_input = inputs.item(i) i += 1 continue if username_input is None and\ inputs.item(i).get_input_type() != "hidden": for search in LOGINS: if name.lower().find(search) != -1: username_input = inputs.item(i) break i += 1 return (username_input, password_input) ####################### # PRIVATE # ####################### def __on_page_created(self, extension, webpage): """ Connect to send request @param extension as WebKit2WebExtension @param webpage as WebKit2WebExtension.WebPage """ webpage.connect("document-loaded", self.__on_document_loaded) def __on_document_loaded(self, webpage): """ Restore forms @param webpage as WebKit2WebExtension.WebPage """ if not self.__settings.get_value("remember-passwords"): return (username_input, password_input) = self.get_forms(webpage) if username_input is None or password_input is None: return self.__helper.get(webpage.get_uri(), self.__set_input, username_input, password_input) def __set_input(self, attributes, password, uri, username_input, password_input): """ Set username/password input @param attributes as {} @param password as str @param uri as str @param username_input as WebKit2WebExtension.DOMElement @param password_input as WebKit2WebExtension.DOMElement """ # Do not set anything if no attributes or # If we have already text in input and we are not a perfect completion if attributes is None or ( password_input.get_value() and uri != attributes["formSubmitURL"]): return try: username_input.set_value(attributes["login"]) password_input.set_value(password) except Exception as e: print("FormsExtension::__set_input()", 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 = None self.__session = None self.__helper = PasswordsHelper() self.__helper.get_sync(self.__set_credentials) def login(self, attributes, password): """ Login to service @param attributes as {} @param password as str @raise exceptions """ if attributes is None: return from base64 import b64encode if self.__mozilla_sync is None: self.__mozilla_sync = MozillaSync() 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 = 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, self.on_password_stored, True) 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 self.syncing or\ not Gio.NetworkMonitor.get_default().get_network_available(): return self.__stop = False thread = Thread(target=self.__sync, args=(first_sync,)) thread.daemon = True thread.start() return loop 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, username, uri): """ Remove password from passwords collection @param username as str @param uri as str """ if Gio.NetworkMonitor.get_default().get_network_available(): self.__helper.get(uri, self.__remove_from_passwords_thread, username) def delete_secret(self): """ Delete sync secret """ self.__username = "" self.__password = "" self.__session = None self.__stop = True self.__helper.clear_sync() def stop(self, force=False): """ Stop update, if force, kill session too @param force as bool """ self.__stop = True 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 """ if El().sync_worker is not None: self.__helper.get_sync(self.__set_credentials) # Wait for credentials if sync: GLib.timeout_add(10, El().sync_worker.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 not self.__stop @property def status(self): """ True if sync is working @return bool """ try: if self.__mozilla_sync is None: self.__mozilla_sync = MozillaSync() self.__get_session_bulk_keys() 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.__mozilla_sync is None: self.__mozilla_sync = MozillaSync() 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 __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(EOLIE_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, username): """ Remove password from passwords collection @param attributes as {} @param password as str @param uri as str @param username as str """ if not self.__username or not self.__password or\ attributes is None or username != attributes["login"]: 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(username, uri) except Exception as e: print("SyncWorker::__remove_from_passwords():", e) def __remove_from_passwords_thread(self, attributes, password, uri, index, count, username): """ Remove password from passwords collection @param attributes as {} @param password as str @param uri as str @param index as int @param count as int @param username as str """ thread = Thread(target=self.__remove_from_passwords, args=(attributes, password, uri, username)) 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 or not self.__token: self.__stop = True return try: self.__mtimes = load(open(EOLIE_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 # ######################## 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 if self.__stop: return ###################### # 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 if self.__stop: return ######################## # 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 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(EOLIE_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) children_array = [] for record in records: if self.__stop: raise StopIteration("Cancelled") 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) # Keep folder only for firefox compatiblity if "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"], [], 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"], 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) El().bookmarks.set_mtime(bookmark_id, record["modified"], False) # Deleted bookmark elif "deleted" in bookmark.keys(): El().bookmarks.remove(bookmark_id) # 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: if self.__stop: raise StopIteration("Cancelled") if record["modified"] < self.__mtimes["passwords"]: continue sleep(0.01) debug("pulling %s" % record) password = record["payload"] if "formSubmitURL" in password.keys(): self.__helper.clear(password["username"], password["formSubmitURL"]) self.__helper.store(password["username"], password["password"], password["formSubmitURL"], password["id"], password["usernameField"], password["passwordField"], None) elif "deleted" in password.keys(): # We assume True self.__helper.clear(password["username"], password["formSubmitURL"]) 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") 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: print("SyncWorker::__set_credentials()", e)
class FormsExtension(GObject.Object): """ Handle forms prefill """ __gsignals__ = { 'submit-form': (GObject.SignalFlags.RUN_FIRST, None, (GLib.Variant, )) } def __init__(self, extension, settings): """ Connect wanted signal @param extension as WebKit2WebExtension @param settings as Settings """ GObject.Object.__init__(self) self.__helper = PasswordsHelper() self.__extension = extension self.__settings = settings self.__elements_uri = None self.__pending_credentials = None self.__page_id = None extension.connect("page-created", self.__on_page_created) def set_credentials(self, form, webpage): """ Set credentials on page @param form as {} @param webpage as WebKit2WebExtension.WebPage """ if self.__settings.get_value("remember-passwords"): form_input_username = form["username"].get_name() form_input_password = form["password"].get_name() if form_input_username is not None and\ form_input_password is not None: self.__helper.get(form["element"].get_action(), form_input_username, form_input_password, self.set_input_forms, webpage, form) def set_input_forms(self, attributes, password, uri, index, count, webpage, form, username=None): """ Set login/password input @param attributes as {} @param password as str @param uri as str @param index as int @param count as int @param webpage as WebKit2WebExtension.WebPage @param form as {} @param username as str """ if attributes is None: return # We only set first available password if (index != 0 or count > 1) and username is None: return parsed = urlparse(form["element"].get_action()) # Allow unsecure completion if wanted by user if parsed.scheme != "https" and username is None: return # We want a user, check if it wanted password if username is not None and username != attributes["login"]: return try: form["username"].set_value(attributes["login"]) form["password"].set_value(password) except Exception as e: print("FormsExtension::set_input_forms()", e) def get_elements(self, elements): """ Get forms as dict and textareas for elements @param elements as [WebKit2WebExtension.DOMElement] @return elements as ([{}], [WebKit2WebExtension.DOMHTMLTextAreaElement]) """ forms = [] textareas = [] for element in elements: if isinstance(element, WebKit2WebExtension.DOMHTMLTextAreaElement): textareas.append(element) elif isinstance(element, WebKit2WebExtension.DOMHTMLFormElement): form = {"element": element} elements_collection = element.get_elements() h = 0 while h < elements_collection.get_length(): element = elements_collection.item(h) if isinstance(element, WebKit2WebExtension.DOMHTMLInputElement): if element.get_input_type() == "password" and\ element.get_name() is not None: form["password"] = element elif element.get_input_type() in ["text", "email", "search"] and\ element.get_name() is not None: form["username"] = element elif isinstance( element, WebKit2WebExtension.DOMHTMLTextAreaElement): textareas.append(element) h += 1 keys = form.keys() if "username" in keys and "password" in keys: forms.append(form) return (forms, textareas) def on_form_submit(self, element, event): """ Ask user for saving credentials @param element as WebKit2WebExtension.DOMElement @param event as WebKit2WebExtension.DOMUIEvent """ page = self.__extension.get_page(self.__page_id) if page is None: return (forms, textareas) = self.get_elements([element]) if not forms or not forms[0]["password"].get_value(): return try: form = forms[0] uri = page.get_uri() form_uri = form["element"].get_action() parsed_form_uri = urlparse(form_uri) form_uri = "%s://%s" % (parsed_form_uri.scheme, parsed_form_uri.netloc) user_form_name = form["username"].get_name() user_form_value = form["username"].get_value() pass_form_name = form["password"].get_name() pass_form_value = form["password"].get_value() self.__helper.get(form_uri, user_form_name, pass_form_name, self.__on_get_password, user_form_name, user_form_value, pass_form_name, pass_form_value, uri, self.__page_id) except Exception as e: print("FormsExtension::on_form_submit():", e) @property def pending_credentials(self): """ Get credentials @return (str, str, str, str, str, str)/None/Type.NONE """ return self.__pending_credentials ####################### # PRIVATE # ####################### def __on_get_password(self, attributes, password, form_uri, index, count, user_form_name, user_form_value, pass_form_name, pass_form_value, uri, page_id): """ Ask for credentials through DBus @param attributes as {} @param password as str @param form_uri as str @param index as int @param count as int @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 page_id as int """ try: # If credentials not found (!=Type.NONE) and something new # is submitted if self.__pending_credentials != Type.NONE and ( attributes is None or attributes["login"] != user_form_value or password != pass_form_value): if attributes is None or\ attributes["login"] != user_form_value: uuid = "" else: uuid = attributes["uuid"] self.__pending_credentials = (uuid, user_form_name, user_form_value, pass_form_name, pass_form_value, uri, form_uri) else: # Found, no more lookup self.__pending_credentials = Type.NONE # Last found credentials if count < 1 or index == count - 1: # Reset pending if self.__pending_credentials == Type.NONE: self.__pending_credentials = None # Ask for user input elif self.__pending_credentials not in [None, Type.NONE]: (uuid, user_form_name, user_form_value, pass_form_name, pass_form_value, uri, form_uri) = self.__pending_credentials args = (uuid, user_form_name, user_form_value, pass_form_name, uri, form_uri) variant = GLib.Variant.new_tuple( GLib.Variant("(ssssss)", args)) self.emit("submit-form", variant) except Exception as e: print("FormsExtension::__on_get_password()", e) def __on_page_created(self, extension, webpage): """ Connect to send request @param extension as WebKit2WebExtension @param webpage as WebKit2WebExtension.WebPage """ self.__page_id = webpage.get_id()
class FormsExtension(GObject.Object): """ Handle forms prefill """ __gsignals__ = { 'submit-form': (GObject.SignalFlags.RUN_FIRST, None, (GLib.Variant, )) } def __init__(self, extension): """ Connect wanted signal @param extension as WebKit2WebExtension """ GObject.Object.__init__(self) self.__helper = PasswordsHelper() self.__extension = extension self.__elements_uri = None self.__pending_credentials = None self.__page_id = None extension.connect("page-created", self.__on_page_created) def set_credentials(self, form, webpage): """ Set credentials on page @param form as { "element":WebKit2WebExtension.DOMHTMLFormElement, "username": WebKit2WebExtension.DOMHTMLInputElement, "password": WebKit2WebExtension.DOMHTMLInputElement} @param webpage as WebKit2WebExtension.WebPage """ if App().settings.get_value("remember-passwords"): form_input_username = form["username"].get_name() form_input_password = form["password"].get_name() if form_input_username is not None and\ form_input_password is not None: self.__helper.get(self.get_form_uri(form["element"]), form_input_username, form_input_password, self.set_input_forms, webpage, form) def set_input_forms(self, attributes, password, uri, index, count, webpage, form, username=None): """ Set login/password input @param attributes as {} @param password as str @param uri as str @param index as int @param count as int @param webpage as WebKit2WebExtension.WebPage @param form as { "element":WebKit2WebExtension.DOMHTMLFormElement, "username": WebKit2WebExtension.DOMHTMLInputElement, "password": WebKit2WebExtension.DOMHTMLInputElement} @param username as str """ if attributes is None: return # We only set first available password if (index != 0 or count > 1) and username is None: return parsed = urlparse(self.get_form_uri(form["element"])) # Allow unsecure completion if wanted by user if parsed.scheme != "https" and username is None: return # We want a user, check if it wanted password if username is not None and username != attributes["login"]: return try: form["username"].set_value(attributes["login"]) form["password"].set_value(password) except Exception as e: Logger.error("FormsExtension::set_input_forms(): %s", e) def get_elements(self, elements): """ Get forms as dict and textareas for elements @param elements as [WebKit2WebExtension.DOMElement] @return elements as ([{"element":WebKit2WebExtension.DOMHTMLFormElement, "username": WebKit2WebExtension.DOMHTMLInputElement, "password": WebKit2WebExtension.DOMHTMLInputElement}], [WebKit2WebExtension.DOMHTMLTextAreaElement]) """ forms = [] textareas = [] for element in elements: if isinstance(element, WebKit2WebExtension.DOMHTMLTextAreaElement): textareas.append(element) elif isinstance(element, WebKit2WebExtension.DOMHTMLFormElement): form = {"element": element} elements_collection = element.get_elements() h = 0 while h < elements_collection.get_length(): element = elements_collection.item(h) # Ignore hidden elements if element.get_client_top() == 0: h += 1 continue if isinstance(element, WebKit2WebExtension.DOMHTMLInputElement): if element.get_input_type() == "password" and\ element.get_name() is not None: form["password"] = element elif element.get_input_type() in ["text", "email", "search"] and\ element.get_name() is not None: form["username"] = element elif isinstance( element, WebKit2WebExtension.DOMHTMLTextAreaElement): textareas.append(element) h += 1 keys = form.keys() if "username" in keys and "password" in keys: forms.append(form) return (forms, textareas) def get_hostname_uri(self, page): """ Get form uri for page @param page as WebKit2WebExtension.WebPage @return str @raise Exception """ page = self.__extension.get_page(self.__page_id) if page is None: raise Exception("Can't find page!") uri = page.get_uri() parsed = urlparse(uri) return "%s://%s" % (parsed.scheme, parsed.netloc) def get_form_uri(self, form): """ Get form uri for form @param form as WebKit2WebExtension.DOMHTMLFormElement @return str """ form_uri = form.get_action() if form_uri is None: page = self.__extension.get_page(self.__page_id) return self.get_hostname_uri(page) else: parsed_form_uri = urlparse(form_uri) return "%s://%s" % (parsed_form_uri.scheme, parsed_form_uri.netloc) @property def pending_credentials(self): """ Get credentials @return (str, str, str, str, str, str)/None/Type.NONE """ return self.__pending_credentials ####################### # PRIVATE # ####################### def __on_get_password(self, attributes, password, form_uri, index, count, user_form_name, user_form_value, pass_form_name, pass_form_value, hostname_uri, page_id): """ Ask for credentials through DBus @param attributes as {} @param password as str @param form_uri as str @param index as int @param count as int @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 hostname_uri as str @param page_id as int """ try: # If credentials not found (!=Type.NONE) and something new # is submitted if self.__pending_credentials != Type.NONE and ( attributes is None or attributes["login"] != user_form_value or password != pass_form_value): if attributes is None or\ attributes["login"] != user_form_value: uuid = "" else: uuid = attributes["uuid"] self.__pending_credentials = (uuid, user_form_name, user_form_value, pass_form_name, pass_form_value, hostname_uri, form_uri) else: # Found, no more lookup self.__pending_credentials = Type.NONE # Last found credentials if count < 1 or index == count - 1: # Reset pending if self.__pending_credentials == Type.NONE: self.__pending_credentials = None # Ask for user input elif self.__pending_credentials not in [None, Type.NONE]: (uuid, user_form_name, user_form_value, pass_form_name, pass_form_value, hostname_uri, form_uri) = self.__pending_credentials args = (uuid, user_form_name, user_form_value, pass_form_name, hostname_uri, form_uri) variant = GLib.Variant.new_tuple( GLib.Variant("(ssssss)", args)) self.emit("submit-form", variant) except Exception as e: Logger.error("FormsExtension::__on_get_password(): %s", e) def __on_will_submit_form(self, webpage, form, step, source, target, names, values): """ @param webpage as WebKit2WebExtension.WebPage @param form as WebKit2WebExtension.DOMHTMLFormElement @param step as WebKit2WebExtension.FormSubmissionStep @param source as WebKit2WebExtension.Frame @param target as WebKit2WebExtension.Frame @param names as [str] @param values as [str] """ if step != WebKit2WebExtension.FormSubmissionStep.SEND_DOM_EVENT or\ not names or not values: return try: # Check elements document = webpage.get_dom_document() username_idx = None password_idx = None idx = 0 for name in names: if username_idx is not None and password_idx is not None: break elements = document.get_elements_by_name(name) for elements_idx in range(0, elements.get_length()): element = elements.item(elements_idx) if isinstance(element, WebKit2WebExtension.DOMHTMLInputElement): if element.get_input_type() == "password" and\ element.get_name() is not None: password_idx = idx break elif element.get_input_type() in ["text", "email", "search"] and\ element.get_name() is not None: username_idx = idx break idx += 1 if username_idx is not None and password_idx is not None: hostname_uri = self.get_hostname_uri(webpage) form_uri = self.get_form_uri(form) user_form_name = names[username_idx] user_form_value = values[username_idx] pass_form_name = names[password_idx] pass_form_value = values[password_idx] self.__helper.get(form_uri, user_form_name, pass_form_name, self.__on_get_password, user_form_name, user_form_value, pass_form_name, pass_form_value, hostname_uri, self.__page_id) except Exception as e: print("FormsExtension::__on_will_submit_form():", e) def __on_page_created(self, extension, webpage): """ Connect to send request @param extension as WebKit2WebExtension @param webpage as WebKit2WebExtension.WebPage """ self.__page_id = webpage.get_id() webpage.connect("will-submit-form", self.__on_will_submit_form)
class FormsExtension: """ Handle forms prefill """ def __init__(self, extension, settings): """ Connect wanted signal @param extension as WebKit2WebExtension @param settings as Settings """ self.__helper = PasswordsHelper() self.__settings = settings extension.connect("page-created", self.__on_page_created) def has_password(self, webpage): """ True if webpage has a password input """ dom_document = webpage.get_dom_document() inputs = dom_document.get_elements_by_tag_name("input") i = 0 while i < inputs.get_length(): if inputs.item(i).get_input_type() == "password": return True i += 1 return False def get_input_forms(self, webpage): """ Return forms for webpage @param webpage as WebKit2WebExtension.WebPage @return [WebKit2WebExtension.DOMHTMLInputElement] """ forms = [] dom_document = webpage.get_dom_document() inputs = dom_document.get_elements_by_tag_name("input") i = 0 while i < inputs.get_length(): if inputs.item(i).get_input_type() in ["text", "search"]: forms.append(inputs.item(i)) i += 1 return forms def get_textarea_forms(self, webpage): """ Return forms for webpage @param webpage as WebKit2WebExtension.WebPage @return [WebKit2WebExtension.DOMHTMLInputElement] """ forms = [] dom_document = webpage.get_dom_document() textareas = dom_document.get_elements_by_tag_name("textarea") i = 0 while i < textareas.get_length(): forms.append(textareas.item(i)) i += 1 return forms def get_password_forms(self, name, webpage): """ Return forms for webpage @param name as str @param webpage as WebKit2WebExtension.WebPage @return [WebKit2WebExtension.DOMHTMLInputElement] """ forms = [] dom_document = webpage.get_dom_document() inputs = dom_document.get_elements_by_tag_name("input") i = 0 while i < inputs.get_length(): if inputs.item(i).get_input_type() == "password" and\ (not name or name == inputs.item(i).get_attribute("name")): forms.append(inputs.item(i)) i += 1 return forms def get_login_forms(self, name, webpage): """ Return auth forms for webpage @param name as str @param webpage as WebKit2WebExtension.WebPage @return [WebKit2WebExtension.DOMHTMLInputElement] """ dom_document = webpage.get_dom_document() inputs = dom_document.get_elements_by_tag_name("input") i = 0 forms = [] while i < inputs.get_length(): if inputs.item(i).get_input_type() == "text": input_name = inputs.item(i).get_attribute("name") input_id = inputs.item(i).get_attribute("id") # We search for wanted name if name and (name == input_name or name == input_id): forms.append(inputs.item(i)) else: if self.is_login_form(inputs.item(i)): forms.append(inputs.item(i)) i += 1 return forms def set_input_forms(self, attributes, password, uri, index, count, webpage, username=None): """ Set username/password input @param attributes as {} @param password as str @param uri as str @param index as int @param count as int @param webpage as WebKit2WebExtension.WebPage @param username as str/None """ # We only set first available password if index != 0 and username is None: return parsed = urlparse(uri) # Allow unsecure completion if wanted by user if parsed.scheme != "https" and username is None: return submit_uri = "%s://%s" % (parsed.scheme, parsed.netloc) # Do not set anything if no attributes or # If we have already text in input and we are not a perfect completion if attributes is None or\ (username is not None and attributes["login"] != username) or\ attributes["formSubmitURL"] != submit_uri: return try: usernames = self.get_login_forms(attributes["userform"], webpage) passwords = self.get_password_forms(attributes["passform"], webpage) # Passwords form may have changed, take first if not passwords: passwords = self.get_password_forms("", webpage) if usernames and passwords: usernames[0].set_value(attributes["login"]) passwords[0].set_value(password) except Exception as e: print("FormsExtension::set_input_forms()", e) def is_login_form(self, form): """ Return True if form is a login form @param form as WebKit2WebExtension.DOMHTMLInputElement @return bool """ input_name = form.get_attribute("name") input_id = form.get_attribute("id") # We search for common name for search in LOGINS: if (input_name is not None and input_name.lower().find(search) != -1) or\ (input_id is not None and input_id.lower().find(search) != -1): return True return False ####################### # PRIVATE # ####################### def __on_page_created(self, extension, webpage): """ Connect to send request @param extension as WebKit2WebExtension @param webpage as WebKit2WebExtension.WebPage """ webpage.connect("document-loaded", self.__on_document_loaded) def __on_document_loaded(self, webpage): """ Restore forms @param webpage as WebKit2WebExtension.WebPage """ if not self.__settings.get_value("remember-passwords"): return if self.has_password(webpage): self.__helper.get(webpage.get_uri(), self.set_input_forms, webpage)