Commit 30e1ca95 authored by Ola Bini's avatar Ola Bini

Refactor numerous files - use new modules and break out most

functionality. Remove EnigmailCommon, and rename modules and file names
to match each other. Enable all tests.

Contribution from Fan Jiang, Iván Pazmiño and Ola Bini.
parent 1e0e0aa7
......@@ -7,7 +7,7 @@ DEPTH = ..
include $(DEPTH)/config/autoconf.mk
TST =
TST =
ifeq ($(TESTS),yes)
TST = tst
......@@ -25,17 +25,57 @@ COMPFILES = \
PREFFILES = prefs/enigmail.js
MODFILES = \
commonFuncs.jsm \
gpgAgentHandler.jsm \
keyManagement.jsm \
pipeConsole.jsm \
app.jsm \
armor.jsm \
attachment.jsm \
card.jsm \
commandLine.jsm \
configure.jsm \
constants.jsm \
data.jsm \
decryption.jsm \
decryptPermanently.jsm \
dialog.jsm \
encryption.jsm \
enigmailCore.jsm \
enigmailCommon.jsm \
enigmailConvert.jsm \
passwordCheck.jsm \
installGnuPG.jsm \
enigmailErrorHandling.jsm \
enigmailFuncs.jsm \
enigmailGpgAgent.jsm \
enigmailProtocolHandler.jsm \
events.jsm \
execution.jsm \
files.jsm \
filters.jsm \
fixExchangeMsg.jsm \
mimeVerify.jsm
glodaMime.jsm \
glodaUtils.jsm \
gpg.jsm \
hash.jsm \
httpProxy.jsm \
installGnuPG.jsm \
key.jsm \
keyEditor.jsm \
keyRing.jsm \
keyserver.jsm \
lazy.jsm \
locale.jsm \
log.jsm \
mime.jsm \
mimeVerify.jsm \
os.jsm \
passwordCheck.jsm \
passwords.jsm \
pipeConsole.jsm \
prefs.jsm \
promise.jsm \
rules.jsm \
streams.jsm \
time.jsm \
timer.jsm \
trust.jsm \
uris.jsm \
verify.jsm \
windows.jsm
all: deploy $(TST)
......
This diff is collapsed.
/*global Components XPCOMUtils */
/* jshint -W097 */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
......@@ -16,6 +16,9 @@
* Copyright (C) 2003 Patrick Brunschwig. All Rights Reserved.
*
* Contributor(s):
* Fan Jiang <fanjiang@thoughtworks.com>
* Iván Pazmiño <iapazmino@thoughtworks.com>
* Ola Bini <obini@thoughtworks.com>
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU General Public License version 2 or later (the
......@@ -63,4 +66,3 @@ var components = [EnigmailPrefService];
const NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
dump("Enigmail account manager extension registered\n");
This diff is collapsed.
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public
* License Version 1.1 (the "MPL"); you may not use this file
* except in compliance with the MPL. You may obtain a copy of
* the MPL at http://www.mozilla.org/MPL/
*
* Software distributed under the MPL is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the MPL for the specific language governing
* rights and limitations under the MPL.
*
* The Original Code is Enigmail.
*
* The Initial Developer of the Original Code is Patrick Brunschwig.
* Portions created by Patrick Brunschwig <patrick@enigmail.net> are
* Copyright (C) 2013 Patrick Brunschwig. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
* ***** END LICENSE BLOCK ***** */
'use strict';
Components.utils.import("resource://enigmail/subprocess.jsm");
var EXPORTED_SYMBOLS = [ "EnigmailGpgAgent" ];
const Cc = Components.classes;
const Ci = Components.interfaces;
const NS_FILE_CONTRACTID = "@mozilla.org/file/local;1";
var gIsGpgAgent = -1;
var Ec = null;
function DEBUG_LOG(str) {
if (Ec) Ec.DEBUG_LOG(str);
}
var EnigmailGpgAgent = {
setEnigmailCommon: function(enigCommon) {
Ec = enigCommon;
},
resetGpgAgent: function() {
DEBUG_LOG("gpgAgentHandler.jsm: resetGpgAgent\n");
gIsGpgAgent = -1;
},
readString: function(data, length) {
var r = '';
for(var i = 0; i < length; i++) {
r += String.fromCharCode(data[i]);
}
return r;
},
isAbsolutePath: function (filePath, isDosLike) {
// Check if absolute path
if (isDosLike) {
return ((filePath.search(/^\w+:\\/) == 0) || (filePath.search(/^\\\\/) == 0)
|| (filePath.search(/^\/\//) == 0));
} else {
return (filePath.search(/^\//) == 0);
}
},
initPath: function (localFileObj, pathStr) {
localFileObj.initWithPath(pathStr);
if (! localFileObj.exists()) {
localFileObj.persistentDescriptor = pathStr;
}
},
resolvePath: function (filePath, envPath, isDosLike) {
DEBUG_LOG("gpgAgentHandler.jsm: resolvePath: filePath="+filePath+"\n");
if (this.isAbsolutePath(filePath, isDosLike))
return filePath;
if (!envPath)
return null;
var fileNames = filePath.split(";");
var pathDirs = envPath.split(isDosLike ? ";" : ":");
for (var i=0; i < fileNames.length; i++) {
for (var j=0; j<pathDirs.length; j++) {
try {
var pathDir = Cc[NS_FILE_CONTRACTID].createInstance(Ci.nsIFile);
DEBUG_LOG("gpgAgentHandler.jsm: resolvePath: checking for "+pathDirs[j]+"/"+fileNames[i]+"\n");
this.initPath(pathDir, pathDirs[j]);
try {
if (pathDir.exists() && pathDir.isDirectory()) {
pathDir.appendRelativePath(fileNames[i]);
if (pathDir.exists() && !pathDir.isDirectory()) {
return pathDir;
}
}
}
catch (ex) {}
}
catch (ex) {}
}
}
return null;
},
isCmdGpgAgent: function(pid) {
DEBUG_LOG("gpgAgentHandler.jsm: isCmdGpgAgent:\n");
var environment = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
var ret = false;
var path = environment.get("PATH");
if (! path || path.length == 0) {
path = "/bin:/usr/bin:/usr/local/bin";
}
var psCmd = this.resolvePath("ps", path, false);
var proc = {
command: psCmd,
arguments: [ "-o", "comm", "-p", pid ],
environment: Ec.envList,
charset: null,
done: function(result) {
DEBUG_LOG("gpgAgentHandler.jsm: isCmdGpgAgent: got data: '"+result.stdout+"'\n");
var data = result.stdout.replace(/[\r\n]/g, " ");
if (data.search(/gpg-agent/) >= 0)
ret = true;
}
};
try {
subprocess.call(proc).wait();
}
catch (ex) {}
return ret;
},
isAgentTypeGpgAgent: function() {
// determine if the used agent is a gpg-agent
DEBUG_LOG("gpgAgentHandler.jsm: isAgentTypeGpgAgent:\n");
// to my knowledge there is no other agent than gpg-agent on Windows
if (Ec.getOS() == "WINNT") return true;
if (gIsGpgAgent >= 0)
return gIsGpgAgent == 1;
var pid = -1;
var exitCode = -1;
var svc = Ec.getService();
if (! svc) return false;
var proc = {
command: svc.connGpgAgentPath,
arguments: [],
charset: null,
environment: Ec.envList,
stdin: function(pipe) {
pipe.write("/subst\n");
pipe.write("/serverpid\n");
pipe.write("/echo pid: ${get serverpid}\n");
pipe.write("/bye\n");
pipe.close();
},
done: function(result) {
exitCode = result.exitCode;
var data = result.stdout.replace(/[\r\n]/g, "");
if (data.search(/^pid: [0-9]+$/) == 0) {
pid = data.replace(/^pid: /, "");
}
}
};
try {
subprocess.call(proc).wait();
if (exitCode) pid = -2;
}
catch (ex) {}
DEBUG_LOG("gpgAgentHandler.jsm: isAgentTypeGpgAgent: pid="+pid+"\n");
this.isCmdGpgAgent(pid);
var isAgent = false;
try {
isAgent = this.isCmdGpgAgent(pid);
gIsGpgAgent = isAgent ? 1 : 0;
}
catch(ex) {}
return isAgent;
},
getAgentMaxIdle: function() {
DEBUG_LOG("gpgAgentHandler.jsm: getAgentMaxIdle:\n");
var svc = Ec.getService();
var maxIdle = -1;
if (! svc) return maxIdle;
const DEFAULT = 7;
const CFGVALUE = 9;
var proc = {
command: svc.gpgconfPath,
arguments: [ "--list-options", "gpg-agent" ],
charset: null,
environment: Ec.envList,
done: function(result) {
var lines = result.stdout.split(/[\r\n]/);
var i;
for (i=0; i < lines.length; i++) {
DEBUG_LOG("gpgAgentHandler.jsm: getAgentMaxIdle: line: "+lines[i]+"\n");
if (lines[i].search(/^default-cache-ttl:/) == 0) {
var m = lines[i].split(/:/);
if (m[CFGVALUE].length == 0) {
maxIdle = Math.round(m[DEFAULT] / 60);
}
else
maxIdle = Math.round(m[CFGVALUE] / 60);
break;
}
}
}
};
subprocess.call(proc).wait();
return maxIdle;
},
setAgentMaxIdle: function(idleMinutes) {
DEBUG_LOG("gpgAgentHandler.jsm: setAgentMaxIdle:\n");
var svc = Ec.getService();
if (! svc) return;
const RUNTIME = 8;
var proc = {
command: svc.gpgconfPath,
arguments: [ "--runtime", "--change-options", "gpg-agent" ],
environment: Ec.envList,
charset: null,
mergeStderr: true,
stdin: function(pipe) {
pipe.write("default-cache-ttl:"+ RUNTIME +":" + (idleMinutes * 60) +"\n");
pipe.write("max-cache-ttl:"+ RUNTIME +":" + (idleMinutes * 600) +"\n");
pipe.close();
},
stdout: function (data) {
DEBUG_LOG("gpgAgentHandler.jsm: setAgentMaxIdle.stdout: "+data+"\n");
},
done: function(result) {
DEBUG_LOG("gpgAgentHandler.jsm: setAgentMaxIdle.stdout: gpgconf exitCode="+result.exitCode+"\n");
}
};
try {
subprocess.call(proc);
}
catch (ex) {
DEBUG_LOG("gpgAgentHandler.jsm: setAgentMaxIdle: exception: "+ex.toString()+"\n");
}
},
getMaxIdlePref: function(win) {
let maxIdle = Ec.getPref("maxIdleMinutes");
try {
var svc = Ec.getService(win);
if (svc) {
if (svc.gpgconfPath &&
svc.connGpgAgentPath) {
if (this.isAgentTypeGpgAgent()) {
let m = this.getAgentMaxIdle();
if (m > -1) maxIdle = m;
}
}
}
}
catch(ex) {}
return maxIdle;
},
setMaxIdlePref: function(minutes) {
Ec.setPref("maxIdleMinutes", minutes);
if (this.isAgentTypeGpgAgent()) {
try {
this.setAgentMaxIdle(minutes);
}
catch(ex) {}
}
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/*jshint -W097 */
/*
Taken from jquery.complexify (WTFPL 2.0)
*/
/*
* Import into a JS component using
* 'Components.utils.import("resource://enigmail/passwordCheck.jsm");'
*/
"use strict";
var EXPORTED_SYMBOLS = [ "passwordCheck" ];
const EXPORTED_SYMBOLS = [ "passwordCheck" ];
var passwordCheck = {
const passwordCheck = {
/**
* Check password quality
*
......@@ -21,29 +19,29 @@ var passwordCheck = {
checkQuality: function (password) {
return evaluateSecurity(password);
}
}
};
/*********
Helper functions and variables
*/
var COMPLEXIFY_BANLIST = '123456|password|12345678|1234|pussy|12345|dragon|qwerty|696969|mustang|letmein|baseball|master|michael|football|shadow|monkey|abc123|pass|fuckme|6969|jordan|harley|ranger|iwantu|jennifer|hunter|fuck|2000|test|batman|trustno1|thomas|tigger|robert|access|love|buster|1234567|soccer|hockey|killer|george|sexy|andrew|charlie|superman|asshole|fuckyou|dallas|jessica|panties|pepper|1111|austin|william|daniel|golfer|summer|heather|hammer|yankees|joshua|maggie|biteme|enter|ashley|thunder|cowboy|silver|richard|fucker|orange|merlin|michelle|corvette|bigdog|cheese|matthew|121212|patrick|martin|freedom|ginger|blowjob|nicole|sparky|yellow|camaro|secret|dick|falcon|taylor|111111|131313|123123|bitch|hello|scooter|please|porsche|guitar|chelsea|black|diamond|nascar|jackson|cameron|654321|computer|amanda|wizard|xxxxxxxx|money|phoenix|mickey|bailey|knight|iceman|tigers|purple|andrea|horny|dakota|aaaaaa|player|sunshine|morgan|starwars|boomer|cowboys|edward|charles|girls|booboo|coffee|xxxxxx|bulldog|ncc1701|rabbit|peanut|john|johnny|gandalf|spanky|winter|brandy|compaq|carlos|tennis|james|mike|brandon|fender|anthony|blowme|ferrari|cookie|chicken|maverick|chicago|joseph|diablo|sexsex|hardcore|666666|willie|welcome|chris|panther|yamaha|justin|banana|driver|marine|angels|fishing|david|maddog|hooters|wilson|butthead|dennis|fucking|captain|bigdick|chester|smokey|xavier|steven|viking|snoopy|blue|eagles|winner|samantha|house|miller|flower|jack|firebird|butter|united|turtle|steelers|tiffany|zxcvbn|tomcat|golf|bond007|bear|tiger|doctor|gateway|gators|angel|junior|thx1138|porno|badboy|debbie|spider|melissa|booger|1212|flyers|fish|porn|matrix|teens|scooby|jason|walter|cumshot|boston|braves|yankee|lover|barney|victor|tucker|princess|mercedes|5150|doggie|'+
const COMPLEXIFY_BANLIST = '123456|password|12345678|1234|pussy|12345|dragon|qwerty|696969|mustang|letmein|baseball|master|michael|football|shadow|monkey|abc123|pass|fuckme|6969|jordan|harley|ranger|iwantu|jennifer|hunter|fuck|2000|test|batman|trustno1|thomas|tigger|robert|access|love|buster|1234567|soccer|hockey|killer|george|sexy|andrew|charlie|superman|asshole|fuckyou|dallas|jessica|panties|pepper|1111|austin|william|daniel|golfer|summer|heather|hammer|yankees|joshua|maggie|biteme|enter|ashley|thunder|cowboy|silver|richard|fucker|orange|merlin|michelle|corvette|bigdog|cheese|matthew|121212|patrick|martin|freedom|ginger|blowjob|nicole|sparky|yellow|camaro|secret|dick|falcon|taylor|111111|131313|123123|bitch|hello|scooter|please|porsche|guitar|chelsea|black|diamond|nascar|jackson|cameron|654321|computer|amanda|wizard|xxxxxxxx|money|phoenix|mickey|bailey|knight|iceman|tigers|purple|andrea|horny|dakota|aaaaaa|player|sunshine|morgan|starwars|boomer|cowboys|edward|charles|girls|booboo|coffee|xxxxxx|bulldog|ncc1701|rabbit|peanut|john|johnny|gandalf|spanky|winter|brandy|compaq|carlos|tennis|james|mike|brandon|fender|anthony|blowme|ferrari|cookie|chicken|maverick|chicago|joseph|diablo|sexsex|hardcore|666666|willie|welcome|chris|panther|yamaha|justin|banana|driver|marine|angels|fishing|david|maddog|hooters|wilson|butthead|dennis|fucking|captain|bigdick|chester|smokey|xavier|steven|viking|snoopy|blue|eagles|winner|samantha|house|miller|flower|jack|firebird|butter|united|turtle|steelers|tiffany|zxcvbn|tomcat|golf|bond007|bear|tiger|doctor|gateway|gators|angel|junior|thx1138|porno|badboy|debbie|spider|melissa|booger|1212|flyers|fish|porn|matrix|teens|scooby|jason|walter|cumshot|boston|braves|yankee|lover|barney|victor|tucker|princess|mercedes|5150|doggie|'+
'zzzzzz|gunner|horney|bubba|2112|fred|johnson|xxxxx|tits|member|boobs|donald|bigdaddy|bronco|penis|voyager|rangers|birdie|trouble|white|topgun|bigtits|bitches|green|super|qazwsx|magic|lakers|rachel|slayer|scott|2222|asdf|video|london|7777|marlboro|srinivas|internet|action|carter|jasper|monster|teresa|jeremy|11111111|bill|crystal|peter|pussies|cock|beer|rocket|theman|oliver|prince|beach|amateur|7777777|muffin|redsox|star|testing|shannon|murphy|frank|hannah|dave|eagle1|11111|mother|nathan|raiders|steve|forever|angela|viper|ou812|jake|lovers|suckit|gregory|buddy|whatever|young|nicholas|lucky|helpme|jackie|monica|midnight|college|baby|cunt|brian|mark|startrek|sierra|leather|232323|4444|beavis|bigcock|happy|sophie|ladies|naughty|giants|booty|blonde|fucked|golden|0|fire|sandra|pookie|packers|einstein|dolphins|chevy|winston|warrior|sammy|slut|8675309|zxcvbnm|nipples|power|victoria|asdfgh|vagina|toyota|travis|hotdog|paris|rock|xxxx|extreme|redskins|erotic|dirty|ford|freddy|arsenal|access14|wolf|nipple|iloveyou|alex|florida|eric|legend|movie|success|rosebud|jaguar|great|cool|cooper|1313|scorpio|mountain|madison|987654|brazil|lauren|japan|naked|squirt|stars|apple|alexis|aaaa|bonnie|peaches|jasmine|kevin|matt|qwertyui|danielle|beaver|4321|4128|runner|swimming|dolphin|gordon|casper|stupid|shit|saturn|gemini|apples|august|3333|canada|blazer|cumming|hunting|kitty|rainbow|112233|arthur|cream|calvin|shaved|surfer|samson|kelly|paul|mine|king|racing|5555|eagle|hentai|newyork|little|redwings|smith|sticky|cocacola|animal|broncos|private|skippy|marvin|blondes|enjoy|girl|apollo|parker|qwert|time|sydney|women|voodoo|magnum|juice|abgrtyu|777777|dreams|maxwell|music|rush2112|russia|scorpion|rebecca|tester|mistress|phantom|billy|6666|albert|111111|11111111|112233|'+
'121212|123123|123456|1234567|12345678|131313|232323|654321|666666|696969|777777|7777777|8675309|987654|abcdef|password1|password12|password123|twitter'.split('|');
var options = {
const options = {
minimumChars: 8,
strengthScaleFactor: 1,
bannedPasswords: COMPLEXIFY_BANLIST,
banMode: 'strict' // (strict|loose)
};
var MIN_COMPLEXITY = 30; // 8 chars with Upper, Lower and Number
const MIN_COMPLEXITY = 30; // 8 chars with Upper, Lower and Number
//var MAX_COMPLEXITY = 120; // 25 chars, all charsets
var MAX_COMPLEXITY = 60;
const MAX_COMPLEXITY = 60;
var CHARSETS = [
const CHARSETS = [
// Commonly Used
////////////////////
[0x0020, 0x0020], // Space
......@@ -193,4 +191,3 @@ function evaluateSecurity(password) {
return { valid: valid, complexity: complexity};
}
/*jshint -W097 */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
......@@ -32,18 +33,15 @@
* the terms of any one of the MPL, the GPL or the LGPL.
* ***** END LICENSE BLOCK ***** */
/*
* Import into a JS component using
* 'Components.utils.import("resource://enigmail/pipeConsole.jsm");'
*/
"use strict";
var EXPORTED_SYMBOLS = [ "EnigmailConsole" ];
const EXPORTED_SYMBOLS = [ "EnigmailConsole" ];
const MAX_SIZE = 32768;
var dataCache = "";
var gotNewData = false;
var EnigmailConsole = {
const EnigmailConsole = {
write: function(data) {
dataCache += data;
if (dataCache.length > MAX_SIZE) {
......@@ -60,4 +58,4 @@ var EnigmailConsole = {
gotNewData = false;
return dataCache;
}
}
};
function run_test() {
var md = do_get_cwd().parent;
md.append("enigmailCommon.jsm");
do_load_module("file://" + md.path);
shouldHandleErrorOutput_test();
}
function shouldHandleErrorOutput_test() {
var errorOutput = "[GNUPG:] USERID_HINT 781617319CE311C4 anonymous strike <strike.devtest@gmail.com>\n" +
"[GNUPG:] NEED_PASSPHRASE 781617319CE311C4 781617319CE311C4 1 0\n" +
"gpg-agent[14654]: command get_passphrase failed: Operation cancelled\n" +
"gpg: cancelled by user\n" +
"[GNUPG:] MISSING_PASSPHRASE\n" +
"gpg: skipped \"<strike.devtest@gmail.com>\": Operation cancelled\n" +
"[GNUPG:] INV_SGNR 0 <strike.devtest@gmail.com>\n" +
"gpg: [stdin]: clearsign failed: Operation cancelled\n";
EnigmailCommon.parseErrorOutput(errorOutput, retStatusObj = {});
Assert.assertContains(retStatusObj.statusMsg,"Missing Passphrase");
Assert.equal(retStatusObj.extendedStatus, "");
}
Assert.assertContains = function(actual, expected, message) {
var msg = message || "Searching for <".concat(expected).concat("> to be contained within actual string.");
Assert.equal(actual.search(expected) > -1, true, msg);
};
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
function run_test() {
var md = do_get_cwd().parent;
md.append("enigmailCore.jsm");
do_load_module("file://" + md.path);
shouldReadProperty_test();
shouldSetGetPreference_test()
shouldCreateLogFile_test()
}
function shouldReadProperty_test() {
var importBtnProp = "enigHeader";
var importBtnValue = EnigmailCore.getString(importBtnProp);
Assert.equal("Enigmail:", importBtnValue);
}
function shouldSetGetPreference_test() {
var prefName = "mypref";
EnigmailCore.setPref(prefName, "yourpref");
Assert.equal("yourpref", EnigmailCore.getPref(prefName));
}
function shouldCreateLogFile_test() {
EnigmailCore.setLogDirectory(do_get_cwd().path);
EnigmailCore.setLogLevel(5);
EnigmailCore.createLogFiles();
var filePath = EnigmailCore._logDirectory + "enigdbug.txt";
var localFile = Cc[NS_LOCAL_FILE_CONTRACTID].createInstance(Ci.nsIFile);
initPath(localFile, filePath);
Assert.equal(localFile.exists(), true);
if (localFile.exists()) {
localFile.remove(false)
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment