2019-01-31 14:07:06 +00:00
/* exported main */
2020-01-31 08:23:48 +00:00
imports . gi . versions . Gtk = '3.0' ;
2014-02-17 16:19:18 +00:00
const Format = imports . format ;
const Gettext = imports . gettext ;
2021-08-07 01:38:15 +00:00
const { Gio , GLib , GObject , Gtk , Pango , WebKit2 : WebKit } = imports . gi ;
2014-02-17 16:19:18 +00:00
const _ = Gettext . gettext ;
const Config = imports . misc . config ;
2018-09-06 00:55:20 +00:00
const { loadInterfaceXML } = imports . misc . fileUtils ;
2014-02-17 16:19:18 +00:00
const PortalHelperResult = {
CANCELLED : 0 ,
COMPLETED : 1 ,
2019-08-20 21:43:54 +00:00
RECHECK : 2 ,
2014-02-17 16:19:18 +00:00
} ;
2017-02-10 20:08:01 +00:00
const PortalHelperSecurityLevel = {
NOT _YET _DETERMINED : 0 ,
SECURE : 1 ,
2019-08-20 21:43:54 +00:00
INSECURE : 2 ,
2017-02-10 20:08:01 +00:00
} ;
2021-08-07 01:38:15 +00:00
const HTTP _URI _FLAGS =
GLib . UriFlags . HAS _PASSWORD |
GLib . UriFlags . ENCODED _PATH |
GLib . UriFlags . ENCODED _QUERY |
GLib . UriFlags . ENCODED _FRAGMENT |
GLib . UriFlags . SCHEME _NORMALIZE |
GLib . UriFlags . PARSE _RELAXED ;
2016-10-24 14:58:24 +00:00
const CONNECTIVITY _CHECK _HOST = 'nmcheck.gnome.org' ;
2022-02-07 14:14:06 +00:00
const CONNECTIVITY _CHECK _URI = ` http:// ${ CONNECTIVITY _CHECK _HOST } ` ;
2014-02-17 16:19:18 +00:00
const CONNECTIVITY _RECHECK _RATELIMIT _TIMEOUT = 30 * GLib . USEC _PER _SEC ;
2018-09-06 00:55:20 +00:00
const HelperDBusInterface = loadInterfaceXML ( 'org.gnome.Shell.PortalHelper' ) ;
2014-02-17 16:19:18 +00:00
2017-10-31 01:23:39 +00:00
var PortalHeaderBar = GObject . registerClass (
class PortalHeaderBar extends Gtk . HeaderBar {
2017-10-31 00:03:21 +00:00
_init ( ) {
2017-10-31 01:23:39 +00:00
super . _init ( { show _close _button : true } ) ;
2017-02-10 20:08:01 +00:00
// See ephy-title-box.c in epiphany for the layout
2020-03-29 21:51:13 +00:00
const vbox = new Gtk . Box ( {
orientation : Gtk . Orientation . VERTICAL ,
spacing : 0 ,
} ) ;
2017-02-10 20:08:01 +00:00
this . set _custom _title ( vbox ) ;
/* TRANSLATORS: this is the title of the wifi captive portal login window */
2020-03-29 21:51:13 +00:00
const titleLabel = new Gtk . Label ( {
label : _ ( 'Hotspot Login' ) ,
wrap : false ,
single _line _mode : true ,
ellipsize : Pango . EllipsizeMode . END ,
} ) ;
2017-02-10 20:08:01 +00:00
titleLabel . get _style _context ( ) . add _class ( 'title' ) ;
vbox . add ( titleLabel ) ;
2020-03-29 21:51:13 +00:00
const hbox = new Gtk . Box ( {
orientation : Gtk . Orientation . HORIZONTAL ,
spacing : 4 ,
halign : Gtk . Align . CENTER ,
valign : Gtk . Align . BASELINE ,
} ) ;
2017-02-10 20:08:01 +00:00
hbox . get _style _context ( ) . add _class ( 'subtitle' ) ;
vbox . add ( hbox ) ;
2020-03-29 21:51:13 +00:00
this . _lockImage = new Gtk . Image ( {
icon _size : Gtk . IconSize . MENU ,
valign : Gtk . Align . BASELINE ,
} ) ;
2017-02-10 20:08:01 +00:00
hbox . add ( this . _lockImage ) ;
2020-03-29 21:51:13 +00:00
this . subtitleLabel = new Gtk . Label ( {
wrap : false ,
single _line _mode : true ,
ellipsize : Pango . EllipsizeMode . END ,
valign : Gtk . Align . BASELINE ,
selectable : true ,
} ) ;
2017-02-10 20:08:01 +00:00
this . subtitleLabel . get _style _context ( ) . add _class ( 'subtitle' ) ;
hbox . add ( this . subtitleLabel ) ;
vbox . show _all ( ) ;
2017-10-31 01:23:39 +00:00
}
2017-02-10 20:08:01 +00:00
2017-10-31 00:03:21 +00:00
setSubtitle ( label ) {
2017-02-10 20:08:01 +00:00
this . subtitleLabel . set _text ( label ) ;
2017-10-31 01:23:39 +00:00
}
2017-02-10 20:08:01 +00:00
2017-10-31 00:03:21 +00:00
setSecurityIcon ( securityLevel ) {
2017-02-10 20:08:01 +00:00
switch ( securityLevel ) {
case PortalHelperSecurityLevel . NOT _YET _DETERMINED :
this . _lockImage . hide ( ) ;
break ;
case PortalHelperSecurityLevel . SECURE :
this . _lockImage . show ( ) ;
this . _lockImage . set _from _icon _name ( "channel-secure-symbolic" , Gtk . IconSize . MENU ) ;
this . _lockImage . set _tooltip _text ( null ) ;
break ;
case PortalHelperSecurityLevel . INSECURE :
this . _lockImage . show ( ) ;
this . _lockImage . set _from _icon _name ( "channel-insecure-symbolic" , Gtk . IconSize . MENU ) ;
this . _lockImage . set _tooltip _text ( _ ( 'Your connection to this hotspot login is not secure. Passwords or other information you enter on this page can be viewed by people nearby.' ) ) ;
break ;
}
2017-10-31 01:23:39 +00:00
}
2017-02-10 20:08:01 +00:00
} ) ;
2017-10-31 01:23:39 +00:00
var PortalWindow = GObject . registerClass (
class PortalWindow extends Gtk . ApplicationWindow {
2017-10-31 00:03:21 +00:00
_init ( application , url , timestamp , doneCallback ) {
2019-08-19 19:06:04 +00:00
super . _init ( { application } ) ;
2017-01-23 07:32:30 +00:00
2017-12-02 00:27:35 +00:00
this . connect ( 'delete-event' , this . destroyWindow . bind ( this ) ) ;
2017-02-10 20:08:01 +00:00
this . _headerBar = new PortalHeaderBar ( ) ;
this . _headerBar . setSecurityIcon ( PortalHelperSecurityLevel . NOT _YET _DETERMINED ) ;
2017-01-23 07:32:30 +00:00
this . set _titlebar ( this . _headerBar ) ;
this . _headerBar . show ( ) ;
2014-02-17 16:19:18 +00:00
2014-07-28 07:36:40 +00:00
if ( ! url ) {
2022-02-07 14:14:06 +00:00
url = CONNECTIVITY _CHECK _URI ;
2014-07-28 07:36:40 +00:00
this . _originalUrlWasGnome = true ;
} else {
this . _originalUrlWasGnome = false ;
2014-02-17 16:19:18 +00:00
}
2021-08-07 01:38:15 +00:00
this . _uri = GLib . Uri . parse ( url , HTTP _URI _FLAGS ) ;
2014-07-28 07:36:40 +00:00
this . _everSeenRedirect = false ;
2014-02-17 16:19:18 +00:00
this . _originalUrl = url ;
this . _doneCallback = doneCallback ;
this . _lastRecheck = 0 ;
this . _recheckAtExit = false ;
2017-03-23 15:54:28 +00:00
this . _webContext = WebKit . WebContext . new _ephemeral ( ) ;
2017-02-13 09:44:01 +00:00
this . _webContext . set _cache _model ( WebKit . CacheModel . DOCUMENT _VIEWER ) ;
2017-05-18 16:13:12 +00:00
this . _webContext . set _network _proxy _settings ( WebKit . NetworkProxyMode . NO _PROXY , null ) ;
2020-02-05 22:18:11 +00:00
if ( this . _webContext . set _sandbox _enabled ) {
// We have WebKitGTK 2.26 or newer.
this . _webContext . set _sandbox _enabled ( true ) ;
}
2017-01-23 06:14:17 +00:00
2017-02-13 09:44:01 +00:00
this . _webView = WebKit . WebView . new _with _context ( this . _webContext ) ;
2017-12-02 00:27:35 +00:00
this . _webView . connect ( 'decide-policy' , this . _onDecidePolicy . bind ( this ) ) ;
this . _webView . connect ( 'load-changed' , this . _onLoadChanged . bind ( this ) ) ;
this . _webView . connect ( 'insecure-content-detected' , this . _onInsecureContentDetected . bind ( this ) ) ;
this . _webView . connect ( 'load-failed-with-tls-errors' , this . _onLoadFailedWithTlsErrors . bind ( this ) ) ;
2014-02-17 16:19:18 +00:00
this . _webView . load _uri ( url ) ;
2017-12-02 00:27:35 +00:00
this . _webView . connect ( 'notify::uri' , this . _syncUri . bind ( this ) ) ;
2017-01-23 07:32:30 +00:00
this . _syncUri ( ) ;
2014-02-17 16:19:18 +00:00
this . add ( this . _webView ) ;
this . _webView . show ( ) ;
2017-01-23 07:32:05 +00:00
this . set _size _request ( 600 , 450 ) ;
2014-02-17 16:19:18 +00:00
this . maximize ( ) ;
this . present _with _time ( timestamp ) ;
2017-01-17 14:01:01 +00:00
this . application . set _accels _for _action ( 'app.quit' , [ '<Primary>q' , '<Primary>w' ] ) ;
2017-10-31 01:23:39 +00:00
}
2014-02-17 16:19:18 +00:00
2017-10-31 00:03:21 +00:00
destroyWindow ( ) {
2017-01-23 06:14:17 +00:00
this . destroy ( ) ;
2017-10-31 01:23:39 +00:00
}
2017-01-23 06:14:17 +00:00
2017-10-31 00:03:21 +00:00
_syncUri ( ) {
2017-01-23 07:32:30 +00:00
let uri = this . _webView . uri ;
if ( uri )
2017-02-10 20:08:01 +00:00
this . _headerBar . setSubtitle ( GLib . uri _unescape _string ( uri , null ) ) ;
2017-01-23 07:32:30 +00:00
else
2017-02-13 10:10:19 +00:00
this . _headerBar . setSubtitle ( '' ) ;
2017-10-31 01:23:39 +00:00
}
2014-02-17 16:19:18 +00:00
2017-10-31 00:03:21 +00:00
refresh ( ) {
2014-02-17 16:19:18 +00:00
this . _everSeenRedirect = false ;
this . _webView . load _uri ( this . _originalUrl ) ;
2017-10-31 01:23:39 +00:00
}
2014-02-17 16:19:18 +00:00
2019-02-04 11:30:53 +00:00
vfunc _delete _event ( _event ) {
2014-02-17 16:19:18 +00:00
if ( this . _recheckAtExit )
this . _doneCallback ( PortalHelperResult . RECHECK ) ;
else
this . _doneCallback ( PortalHelperResult . CANCELLED ) ;
return false ;
2017-10-31 01:23:39 +00:00
}
2014-02-17 16:19:18 +00:00
2017-10-31 00:03:21 +00:00
_onLoadChanged ( view , loadEvent ) {
2017-03-23 17:27:40 +00:00
if ( loadEvent == WebKit . LoadEvent . STARTED ) {
2017-02-10 20:08:01 +00:00
this . _headerBar . setSecurityIcon ( PortalHelperSecurityLevel . NOT _YET _DETERMINED ) ;
2017-03-23 17:27:40 +00:00
} else if ( loadEvent == WebKit . LoadEvent . COMMITTED ) {
2017-02-10 20:08:01 +00:00
let tlsInfo = this . _webView . get _tls _info ( ) ;
let ret = tlsInfo [ 0 ] ;
let flags = tlsInfo [ 2 ] ;
if ( ret && flags == 0 )
this . _headerBar . setSecurityIcon ( PortalHelperSecurityLevel . SECURE ) ;
else
this . _headerBar . setSecurityIcon ( PortalHelperSecurityLevel . INSECURE ) ;
}
2017-10-31 01:23:39 +00:00
}
2017-02-10 20:08:01 +00:00
2017-10-31 00:03:21 +00:00
_onInsecureContentDetected ( ) {
2017-02-10 20:08:01 +00:00
this . _headerBar . setSecurityIcon ( PortalHelperSecurityLevel . INSECURE ) ;
2017-10-31 01:23:39 +00:00
}
2017-02-10 20:08:01 +00:00
2019-01-31 14:08:10 +00:00
_onLoadFailedWithTlsErrors ( view , failingURI , certificate , _errors ) {
2017-02-13 09:44:01 +00:00
this . _headerBar . setSecurityIcon ( PortalHelperSecurityLevel . INSECURE ) ;
2021-08-07 01:38:15 +00:00
let uri = GLib . Uri . parse ( failingURI , HTTP _URI _FLAGS ) ;
2017-02-13 09:44:01 +00:00
this . _webContext . allow _tls _certificate _for _host ( certificate , uri . get _host ( ) ) ;
this . _webView . load _uri ( failingURI ) ;
return true ;
2017-10-31 01:23:39 +00:00
}
2017-02-13 09:44:01 +00:00
2017-10-31 00:03:21 +00:00
_onDecidePolicy ( view , decision , type ) {
2014-02-17 16:19:18 +00:00
if ( type == WebKit . PolicyDecisionType . NEW _WINDOW _ACTION ) {
2017-03-24 17:57:29 +00:00
let navigationAction = decision . get _navigation _action ( ) ;
if ( navigationAction . is _user _gesture ( ) ) {
// Even though the portal asks for a new window,
// perform the navigation in the current one. Some
// portals open a window as their last login step and
// ignoring that window causes them to not let the
// user go through. We don't risk popups taking over
// the page because we check that the navigation is
// user initiated.
this . _webView . load _request ( navigationAction . get _request ( ) ) ;
}
2014-02-17 16:19:18 +00:00
decision . ignore ( ) ;
return true ;
}
if ( type != WebKit . PolicyDecisionType . NAVIGATION _ACTION )
return false ;
let request = decision . get _request ( ) ;
2021-08-07 01:38:15 +00:00
const uri = GLib . Uri . parse ( request . get _uri ( ) , HTTP _URI _FLAGS ) ;
2014-02-17 16:19:18 +00:00
2021-08-07 01:38:15 +00:00
if ( uri . get _host ( ) !== this . _uri . get _host ( ) && this . _originalUrlWasGnome ) {
2016-10-24 14:58:24 +00:00
if ( uri . get _host ( ) == CONNECTIVITY _CHECK _HOST && this . _everSeenRedirect ) {
2014-02-17 16:19:18 +00:00
// Yay, we got to gnome!
decision . ignore ( ) ;
this . _doneCallback ( PortalHelperResult . COMPLETED ) ;
return true ;
2016-10-24 14:58:24 +00:00
} else if ( uri . get _host ( ) != CONNECTIVITY _CHECK _HOST ) {
2014-02-17 16:19:18 +00:00
this . _everSeenRedirect = true ;
}
}
2014-07-28 07:36:40 +00:00
// We *may* have finished here, but we don't know for
// sure. Tell gnome-shell to run another connectivity check
// (but ratelimit the checks, we don't want to spam
// nmcheck.gnome.org for portals that have 10 or more internal
// redirects - and unfortunately they exist)
// If we hit the rate limit, we also queue a recheck
// when the window is closed, just in case we miss the
// final check and don't realize we're connected
// This should not be a problem in the cancelled logic,
// because if the user doesn't want to start the login,
// we should not see any redirect at all, outside this._uri
let now = GLib . get _monotonic _time ( ) ;
let shouldRecheck = ( now - this . _lastRecheck ) >
CONNECTIVITY _RECHECK _RATELIMIT _TIMEOUT ;
if ( shouldRecheck ) {
this . _lastRecheck = now ;
this . _recheckAtExit = false ;
this . _doneCallback ( PortalHelperResult . RECHECK ) ;
} else {
this . _recheckAtExit = true ;
}
// Update the URI, in case of chained redirects, so we still
// think we're doing the login until gnome-shell kills us
this . _uri = uri ;
2014-02-17 16:19:18 +00:00
decision . use ( ) ;
return true ;
2017-10-31 01:23:39 +00:00
}
2014-02-17 16:19:18 +00:00
} ) ;
2017-10-31 01:23:39 +00:00
var WebPortalHelper = GObject . registerClass (
class WebPortalHelper extends Gtk . Application {
2017-10-31 00:03:21 +00:00
_init ( ) {
2020-03-29 21:51:13 +00:00
super . _init ( {
application _id : 'org.gnome.Shell.PortalHelper' ,
flags : Gio . ApplicationFlags . IS _SERVICE ,
inactivity _timeout : 30000 ,
} ) ;
2014-02-17 16:19:18 +00:00
this . _dbusImpl = Gio . DBusExportedObject . wrapJSObject ( HelperDBusInterface , this ) ;
this . _queue = [ ] ;
2017-01-17 14:01:01 +00:00
let action = new Gio . SimpleAction ( { name : 'quit' } ) ;
2019-01-28 00:42:00 +00:00
action . connect ( 'activate' , ( ) => this . active _window . destroyWindow ( ) ) ;
2017-01-17 14:01:01 +00:00
this . add _action ( action ) ;
2017-10-31 01:23:39 +00:00
}
2014-02-17 16:19:18 +00:00
2017-10-31 00:03:21 +00:00
vfunc _dbus _register ( connection , path ) {
2014-02-17 16:19:18 +00:00
this . _dbusImpl . export ( connection , path ) ;
2017-10-31 01:23:39 +00:00
super . vfunc _dbus _register ( connection , path ) ;
2014-02-17 16:19:18 +00:00
return true ;
2017-10-31 01:23:39 +00:00
}
2014-02-17 16:19:18 +00:00
2017-10-31 00:03:21 +00:00
vfunc _dbus _unregister ( connection , path ) {
2014-02-17 16:19:18 +00:00
this . _dbusImpl . unexport _from _connection ( connection ) ;
2017-10-31 01:23:39 +00:00
super . vfunc _dbus _unregister ( connection , path ) ;
}
2014-02-17 16:19:18 +00:00
2017-10-31 00:03:21 +00:00
vfunc _activate ( ) {
2014-02-17 16:19:18 +00:00
// If launched manually (for example for testing), force a dummy authentication
// session with the default url
this . Authenticate ( '/org/gnome/dummy' , '' , 0 ) ;
2017-10-31 01:23:39 +00:00
}
2014-02-17 16:19:18 +00:00
2017-10-31 00:03:21 +00:00
Authenticate ( connection , url , timestamp ) {
2019-08-19 19:06:04 +00:00
this . _queue . push ( { connection , url , timestamp } ) ;
2014-02-17 16:19:18 +00:00
this . _processQueue ( ) ;
2017-10-31 01:23:39 +00:00
}
2014-02-17 16:19:18 +00:00
2017-10-31 00:03:21 +00:00
Close ( connection ) {
2014-02-17 16:19:18 +00:00
for ( let i = 0 ; i < this . _queue . length ; i ++ ) {
let obj = this . _queue [ i ] ;
if ( obj . connection == connection ) {
if ( obj . window )
2017-01-23 06:14:17 +00:00
obj . window . destroyWindow ( ) ;
2014-02-17 16:19:18 +00:00
this . _queue . splice ( i , 1 ) ;
break ;
}
}
this . _processQueue ( ) ;
2017-10-31 01:23:39 +00:00
}
2014-02-17 16:19:18 +00:00
2017-10-31 00:03:21 +00:00
Refresh ( connection ) {
2014-02-17 16:19:18 +00:00
for ( let i = 0 ; i < this . _queue . length ; i ++ ) {
let obj = this . _queue [ i ] ;
if ( obj . connection == connection ) {
if ( obj . window )
obj . window . refresh ( ) ;
break ;
}
}
2017-10-31 01:23:39 +00:00
}
2014-02-17 16:19:18 +00:00
2017-10-31 00:03:21 +00:00
_processQueue ( ) {
2014-02-17 16:19:18 +00:00
if ( this . _queue . length == 0 )
return ;
let top = this . _queue [ 0 ] ;
if ( top . window != null )
return ;
2017-10-31 00:38:18 +00:00
top . window = new PortalWindow ( this , top . url , top . timestamp , result => {
2014-02-17 16:19:18 +00:00
this . _dbusImpl . emit _signal ( 'Done' , new GLib . Variant ( '(ou)' , [ top . connection , result ] ) ) ;
2017-10-31 00:38:18 +00:00
} ) ;
2017-10-31 01:23:39 +00:00
}
2014-02-17 16:19:18 +00:00
} ) ;
function initEnvironment ( ) {
String . prototype . format = Format . format ;
}
function main ( argv ) {
initEnvironment ( ) ;
2017-03-23 15:54:28 +00:00
if ( ! WebKit . WebContext . new _ephemeral ) {
log ( 'WebKitGTK 2.16 is required for the portal-helper, see https://bugzilla.gnome.org/show_bug.cgi?id=780453' ) ;
return 1 ;
}
2014-02-17 16:19:18 +00:00
Gettext . bindtextdomain ( Config . GETTEXT _PACKAGE , Config . LOCALEDIR ) ;
Gettext . textdomain ( Config . GETTEXT _PACKAGE ) ;
let app = new WebPortalHelper ( ) ;
return app . run ( argv ) ;
}