Completed
Push — master ( e4d488...d7a2df )
by Sander
30s
created

js/background/inject/inject.js (2 issues)

1
/* global API */
2
var $j = jQuery.noConflict();
3
4
$j(document).ready(function () {
5
6
    $j(document).click(function (event) {
7
        var passwordPickerRef = '.passwordPickerIframe';
8
        if (!$j(event.target).closest(passwordPickerRef).length) {
9
            if ($j(passwordPickerRef).is(":visible")) {
10
                removePasswordPicker();
11
            }
12
        }
13
    });
14
15
    var _this = this;
16
    Array.prototype.findUrl = function (match) {
17
        return this.filter(function (item) {
18
            var matchParse = processURL(match, false, false, true, false);
19
            return typeof item === 'string' && item.indexOf(matchParse) > -1;
20
        });
21
    };
22
23
    function removePasswordPicker() {
24
        activeForm = undefined;
25
        $j('.passwordPickerIframe').remove();
26
    }
27
28
    _this.removePasswordPicker = removePasswordPicker;
29
30
    function enterLoginDetails(login, allowSubmit) {
31
        var username;
32
33
        if (login.hasOwnProperty('username')) {
34
            username = (login.username !== '' ) ? login.username : login.email;
35
        }
36
        if (!username) {
37
            username = null;
38
        }
39
40
        fillPassword(username, login.password);
41
42
        if (activeForm) {
43
            API.runtime.sendMessage(API.runtime.id, {method: 'isAutoSubmitEnabled'}).then(function (isEnabled) {
44
                if (isEnabled && allowSubmit) {
45
                    submitLoginForm(username);
46
                }
47
            });
48
        }
49
    }
50
51
    _this.enterLoginDetails = enterLoginDetails;
52
53
    function enterCustomFields(login, settings) {
54
        var customFieldPattern = /^\#(.*)$/;
55
        var elementId;
56
        var element = false;
0 ignored issues
show
The variable element seems to be never used. Consider removing it.
Loading history...
57
58
        /* parhaps wise to try / catch this as this is non essential and no reason to abort previous processing */
59
        try {
60
            /* do we have custom_fields for this entry */
61
            if (login.hasOwnProperty('custom_fields') && login.custom_fields.length) {
62
                /* yes we do, iterate over all the custom_fields values */
63
                for (var i = 0, len = login.custom_fields.length; i < len; i++) {
64
                    /* does this custom field label begin with a hash? */
65
                    if (customFieldPattern.test(login.custom_fields[i].label)) {
66
                        /* set variable elementid to whatever element we are trying to auto fill */
67
                        elementId = customFieldPattern.exec(login.custom_fields[i].label)[1];
68
                        enterCustomFieldElement(elementId, login.custom_fields[i].value);
69
                    }
70
                    else if ($j('label[for]:contains(' + login.custom_fields[i].label + ')').length) {
71
                        elementId = $j('label[for]:contains(' + login.custom_fields[i].label + ')').attr('for');
72
                        enterCustomFieldElement(elementId, login.custom_fields[i].value);
73
                    }
74
                }
75
            }
76
        }
77
        catch (e) {
78
            if (settings.debug) {
79
                console.log('While attempting to auto fill custom fields the following exception was thrown: ' + e);
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
80
            }
81
        }
82
    }
83
84
    function enterCustomFieldElement(elementId, value) {
85
        /* check to see if element id exist */
86
        if ($j('#' + elementId).length) {
87
            element = $j('#' + elementId);
88
        }
89
        else if ($j('input[name$="' + elementId + '"]').length) { /* maybe element name exist */
90
            element = $j('input[name$="' + elementId + '"]');
91
        }
92
        else { /* neither element id or name exist */
93
            element = false;
94
        }
95
        /* if we have an element and it is type text, number or password, lets auto fill it */
96
        if (element && (element[0].type === 'text' || element[0].type === 'number' || element[0].type === 'password')) {
97
            element.val(value);
98
        }
99
    }
100
101
    function submitLoginForm(username) {
102
        if (!activeForm) {
103
            // @TODO detect login form on the current page
104
            return;
105
        }
106
107
        var formEl = $j(activeForm).closest('form');
108
        var iframeUrl = API.extension.getURL('/html/inject/auto_login.html');
109
        $j('#loginPopupIframe').remove();
110
        var loginPopup = $j('<iframe class="loginPopupIframe" scrolling="no" frameborder="0" src="' + iframeUrl + '"></iframe>');
111
        var padding = parseInt($j(formEl).css('padding').replace('px', ''));
112
        var margin = parseInt($j(formEl).css('margin').replace('px', ''));
113
        var height = Math.round($j(formEl).height() + (padding * 2) + (margin * 2));
114
        var width = Math.round($j(formEl).width() + (padding * 2) + (margin * 2));
115
        loginPopup.attr('height', height);
116
        loginPopup.attr('width', width);
117
        loginPopup.css('position', 'absolute');
118
        loginPopup.css('z-index', getMaxZ() + 1);
119
        loginPopup.css('background-color', 'rgba(0, 0, 0, 0.73)');
120
        loginPopup.css('left', Math.floor($j(formEl).offset().left - padding - margin));
121
        loginPopup.css('top', Math.floor($j(formEl).offset().top - padding - margin));
122
        removePasswordPicker();
123
        $j(document.body).prepend(loginPopup);
124
        API.runtime.sendMessage(API.runtime.id, {'setIframeUsername': username}).then(function () {
125
            $j(formEl).submit();
126
            setTimeout(function () {
127
                loginPopup.remove();
128
            }, 2000);
129
        });
130
    }
131
132
    function getMaxZ() {
133
        return Math.max.apply(null,
134
            $j.map($j('body *'), function (e) {
135
                if ($j(e).css('position') !== 'static')
136
                    return parseInt($j(e).css('z-index')) || 1;
137
            }));
138
    }
139
140
    var activeForm;
141
142
    function showPasswordPicker(form) {
143
        var jPasswordPicker = $j('.passwordPickerIframe');
144
        if (jPasswordPicker.length > 1) {
145
            return;
146
        }
147
        var loginField = $j(form[0]);
148
        var loginFieldPos = loginField.offset();
149
        var loginFieldVisible = loginField.is(':visible');
150
151
        var position = $j(form[1]).position();
152
        var passwordField = $j(form[1]);
153
        var passwordFieldPos = passwordField.offset();
154
        var passwordFieldVisible = loginField.is(':visible');
155
        var left = (loginFieldPos) ? loginFieldPos.left : passwordFieldPos.left;
156
        var top = (loginFieldPos) ? loginFieldPos.top : passwordFieldPos.top;
157
        var maxZ = getMaxZ();
158
159
        if (loginFieldPos && passwordFieldPos.top > loginFieldPos.top) {
160
            //console.log('login fields below each other')
161
            top = passwordFieldPos.top + passwordField.height() + 10;
162
        } else {
163
            // console.log('login fields next to each other')
164
            if (loginFieldPos) {
165
                top = top + loginField.height() + 10;
166
            } else {
167
                top = top + passwordField.height() + 10;
168
            }
169
        }
170
        if (!loginFieldVisible) {
171
            left = passwordFieldPos.left;
172
        }
173
174
        var pickerUrl = API.extension.getURL('/html/inject/password_picker.html');
175
176
        var picker = $j('<iframe class="passwordPickerIframe" scrolling="no" height="385" width="350" frameborder="0" src="' + pickerUrl + '"></iframe>');
177
        picker.css('position', 'absolute');
178
        picker.css('left', left);
179
        picker.css('z-index', maxZ + 10);
180
        picker.css('top', top);
181
        $j('body').prepend($j(picker));
182
        activeForm = form;
183
        // picker.css('width', $j(form).width());
184
        $j('.passwordPickerIframe:not(:last)').remove();
185
    }
186
187
    function onFormIconClick(e) {
188
        e.preventDefault();
189
        e.stopPropagation();
190
        var offsetX = e.offsetX;
191
        var offsetRight = (e.data.width - offsetX);
192
        if (offsetRight < e.data.height) {
193
            showPasswordPicker(e.data.form);
194
        }
195
    }
196
197
    function createFormIcon(el, form) {
198
        var offset = el.offset();
199
        var width = el.width();
200
        var height = el.height() * 1;
201
        var margin = (el.css('margin')) ? parseInt(el.css('margin').replace('px', '')) : 0;
202
        var padding = (el.css('padding')) ? parseInt(el.css('padding').replace('px', '')) : 0;
203
204
        var pickerIcon = API.extension.getURL('/icons/icon.svg');
205
        $j(el).css('background-image', 'url("' + pickerIcon + '")');
206
        $j(el).css('background-repeat', 'no-repeat');
207
        //$j(el).css('background-position', '');
208
        $j(el).css('cssText', el.attr('style') + ' background-position: right 3px center !important;');
209
210
        $j(el).unbind('click', onFormIconClick);
211
        $j(el).click({width: width, height: height, form: form}, onFormIconClick);
212
    }
213
214
    function createPasswordPicker(form) {
215
        for (var i = 0; i < form.length; i++) {
216
            var el = $j(form[i]);
217
            createFormIcon(el, form);
218
        }
219
    }
220
221
    function formSubmitted(fields) {
222
        var user = fields[0].value;
223
        var pass = fields[1].value;
224
        var params = {
225
            username: user,
226
            password: pass
227
        };
228
        //Disable password mining
229
        //$j(fields[1]).attr('type', 'hidden');
230
        API.runtime.sendMessage(API.runtime.id, {method: "minedForm", args: params});
231
232
    }
233
234
    function inIframe() {
235
        try {
236
            return window.self !== window.top;
237
        } catch (e) {
238
            return true;
239
        }
240
    }
241
242
    function showDoorhanger(data) {
243
        if (inIframe()) {
244
            return;
245
        }
246
        data.data.currentLocation = window.location.href;
247
        API.runtime.sendMessage(API.runtime.id, {method: "setDoorhangerData", args: data});
248
        var pickerUrl = API.extension.getURL('/html/inject/doorhanger.html');
249
250
        var doorhanger = $j('<iframe id="password-toolbarIframe" style="display: none;" scrolling="no" height="60" width="100%" frameborder="0" src="' + pickerUrl + '"></iframe>');
251
        $j('#password-toolbarIframe').remove();
252
        doorhanger.css('z-index', getMaxZ() + 1);
253
        $j('body').prepend(doorhanger);
254
        $j('#password-toolbarIframe').fadeIn();
255
    }
256
257
    _this.showDoorhanger = showDoorhanger;
258
259
    function showUrlUpdateDoorhanger(data) {
260
        var buttons = ['cancel', 'updateUrl'];
261
        showDoorhanger({
262
            data: data.data,
263
            buttons: buttons
264
        });
265
    }
266
267
    _this.showUrlUpdateDoorhanger = showUrlUpdateDoorhanger;
268
269
    function checkForMined() {
270
        if (inIframe()) {
271
            return;
272
        }
273
274
        API.runtime.sendMessage(API.runtime.id, {method: "getMinedData"}).then(function (data) {
275
            if (!data) {
276
                return;
277
            }
278
            if (data.hasOwnProperty('username') && data.hasOwnProperty('password') && data.hasOwnProperty('url')) {
279
                var buttons = ['cancel', 'ignore', 'save'];
280
                showDoorhanger({data: data, buttons: buttons});
281
            }
282
        });
283
    }
284
285
286
    function closeDoorhanger() {
287
        $j('#password-toolbarIframe').hide(400);
288
        $j('#password-toolbarIframe').remove();
289
    }
290
291
    _this.closeDoorhanger = closeDoorhanger;
292
293
    var flagFilledForm = false;
294
    function initForms() {
295
        API.runtime.sendMessage(API.runtime.id, {method: 'getRuntimeSettings'}).then(function (settings) {
296
            var enablePasswordPicker = settings.enablePasswordPicker;
297
            var url = window.location.href;
298
            var loginFields = getLoginFields();
299
            if (!settings.hasOwnProperty('ignored_sites') || settings.ignored_sites.findUrl(url).length !== 0) {
300
                return;
301
            }
302
303
            if (loginFields.length > 0) {
304
                for (var i = 0; i < loginFields.length; i++) {
305
                    var form = getFormFromElement(loginFields[i][0]);
306
                    if (enablePasswordPicker) {
307
                        createPasswordPicker(loginFields[i], form);
308
                    }
309
310
                    //Password miner
311
                    /* jshint ignore:start */
312
                    $j(form).submit((function (loginFields) {
313
                        return function () {
314
                            formSubmitted(loginFields);
315
                        };
316
                    })(loginFields[i]));
317
                    /* jshint ignore:end */
318
                }
319
320
                API.runtime.sendMessage(API.runtime.id, {
321
                    method: "getCredentialsByUrl",
322
                    args: url
323
                }).then(function (logins) {
324
                    console.log('Found ' + logins.length + ' logins for this site');
325
                    if (logins.length === 1) {
326
                        API.runtime.sendMessage(API.runtime.id, {method: 'isAutoFillEnabled'}).then(function (isEnabled) {
327
                            if (isEnabled && !flagFilledForm) {
328
                                enterLoginDetails(logins[0], false);
329
                                flagFilledForm = true;
330
                            }
331
                        });
332
                    }
333
                });
334
            }
335
336
            API.runtime.sendMessage(API.runtime.id, {
337
                method: "getCredentialsByUrl",
338
                args: url
339
            }).then(function (logins) {
340
                if (logins.length === 1) {
341
                    API.runtime.sendMessage(API.runtime.id, {method: 'isAutoFillEnabled'}).then(function (isEnabled) {
342
                        if (isEnabled) {
343
                            enterCustomFields(logins[0], settings);
344
                        }
345
                    });
346
                }
347
            });
348
349
        });
350
    }
351
352
    function minedLoginSaved(args) {
353
        // If the login added by the user then this is true
354
        if (args.selfAdded) {
355
            showDoorhanger({
356
                data: args,
357
                buttons: ['cancel']
358
            });
359
            enterLoginDetails(args.credential, false);
360
        }
361
    }
362
363
    _this.minedLoginSaved = minedLoginSaved;
364
365
    function resizeIframe(height) {
366
        $j('#password-toolbarIframe').height(60 + height);
367
    }
368
369
    _this.resizeIframe = resizeIframe;
370
371
    function copyText(text) {
372
        var txtToCopy = document.createElement('input');
373
        txtToCopy.style.left = '-300px';
374
        txtToCopy.style.position = 'absolute';
375
        txtToCopy.value = text;
376
        document.body.appendChild(txtToCopy);
377
        txtToCopy.select();
378
        document.execCommand('copy');
379
        txtToCopy.parentNode.removeChild(txtToCopy);
380
    }
381
382
    _this.copyText = copyText;
383
384
    function init() {
385
        checkForMined();
386
        initForms();
387
    }
388
389
    var readyStateCheckInterval = setInterval(function () {
390
        if (document.readyState === "complete") {
391
            clearInterval(readyStateCheckInterval);
392
            API.runtime.sendMessage(API.runtime.id, {method: 'getMasterPasswordSet'}).then(function (result) {
393
                if (result) {
394
                    init();
395
                    var body = document.getElementsByTagName('body')[0];
396
                    if (body) {
397
                        observeDOM(body, initForms);
398
                    }
399
                } else {
400
                    console.log('[Passman extension] Stopping, vault key not set');
401
                }
402
            });
403
        }
404
    }, 10);
405
406
    API.runtime.onMessage.addListener(function (msg, sender) {
407
        //console.log('Method call', msg.method);
408
        if (_this[msg.method]) {
409
            _this[msg.method](msg.args, sender);
410
        }
411
    });
412
});
413