From 38547c53066feda1be186ff120c76faa9dd836c3 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Tue, 12 Nov 2019 17:18:10 -0600 Subject: [PATCH] Fix: Stop splitting surrogate-pairs in diff-match-patch deltas See https://github.com/google/diff-match-patch/pull/80 There have been certain cases where consecutive surrogate pairs crash `diff-match-patch` when it's building the _delta string_. This is because the diffing algorithm finds a common prefix up to and including half of the surrogate pair match when three consecutive code points share the same high surrogate. ``` // before - \ud83d\ude4b\ud83d\ue4b // after - \ud83d\ude4b\ud83d\ue4c\ud83d\ude4b // deltas - eq \ud83d\ude4b\ud83d -> add \ude4c -> eq \ud83d \ude4b ``` This crashes when trying to encode the invalid sequence of UTF-16 code units into `URIEncode`, which expects a valid Unicode string. After the fix the delta groups are normalized to prevent this situation and the `node-simperium` library should be able to process those problematic changesets. --- RELEASE-NOTES.txt | 4 + package-lock.json | 2 +- package.json | 2 +- src/simperium/jsondiff/diff_match_patch.js | 302 +++++++++++++-------- 4 files changed, 189 insertions(+), 121 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 80a172c..47533a5 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,5 +1,9 @@ # Changelog +## 1.0.2 + + - Update diff-match-patch to fix problem when encoding consecutive surrogate pairs + ## 1.0.1 - Disconnect from existing socket connection when connecting [#90](https://github.com/Simperium/node-simperium/pull/90) diff --git a/package-lock.json b/package-lock.json index 565642d..af2b448 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "simperium", - "version": "1.0.0", + "version": "1.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1097483..e34e82a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "simperium", - "version": "1.0.1", + "version": "1.0.2", "description": "A simperium client for node.js", "main": "./lib/simperium/index.js", "repository": { diff --git a/src/simperium/jsondiff/diff_match_patch.js b/src/simperium/jsondiff/diff_match_patch.js index 9bdccea..883e77e 100644 --- a/src/simperium/jsondiff/diff_match_patch.js +++ b/src/simperium/jsondiff/diff_match_patch.js @@ -1,9 +1,7 @@ - /** * Diff Match and Patch - * - * Copyright 2006 Google Inc. - * http://code.google.com/p/google-diff-match-patch/ + * Copyright 2018 The diff-match-patch Authors. + * https://github.com/google/diff-match-patch * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +26,7 @@ * Class containing the diff, match and patch methods. * @constructor */ -export default function diff_match_patch() { +export default diff_match_patch = function() { // Defaults. // Redefine these in your program to override the defaults. @@ -53,7 +51,7 @@ export default function diff_match_patch() { // The number of bits in an int. this.Match_MaxBits = 32; -} +}; // DIFF FUNCTIONS @@ -68,8 +66,27 @@ export const DIFF_DELETE = -1; export const DIFF_INSERT = 1; export const DIFF_EQUAL = 0; -/** @typedef {{0: number, 1: string}} */ -diff_match_patch.Diff; +/** + * Class representing one diff tuple. + * Attempts to look like a two-element array (which is what this used to be). + * @param {number} op Operation, one of: DIFF_DELETE, DIFF_INSERT, DIFF_EQUAL. + * @param {string} text Text to be deleted, inserted, or retained. + * @constructor + */ +diff_match_patch.Diff = function(op, text) { + this[0] = op; + this[1] = text; +}; + +diff_match_patch.Diff.prototype.length = 2; + +/** + * Emulate the output of a two-element array. + * @return {string} Diff operation as a string. + */ +diff_match_patch.Diff.prototype.toString = function() { + return this[0] + ',' + this[1]; +}; /** @@ -80,7 +97,7 @@ diff_match_patch.Diff; * @param {boolean=} opt_checklines Optional speedup flag. If present and false, * then don't run a line-level diff first to identify the changed areas. * Defaults to true, which does a faster, slightly less optimal diff. - * @param {number} opt_deadline Optional time when the diff should be complete + * @param {number=} opt_deadline Optional time when the diff should be complete * by. Used internally for recursive calls. Users should set DiffTimeout * instead. * @return {!Array.} Array of diff tuples. @@ -105,7 +122,7 @@ diff_match_patch.prototype.diff_main = function(text1, text2, opt_checklines, // Check for equality (speedup). if (text1 == text2) { if (text1) { - return [[DIFF_EQUAL, text1]]; + return [new diff_match_patch.Diff(DIFF_EQUAL, text1)]; } return []; } @@ -132,10 +149,10 @@ diff_match_patch.prototype.diff_main = function(text1, text2, opt_checklines, // Restore the prefix and suffix. if (commonprefix) { - diffs.unshift([DIFF_EQUAL, commonprefix]); + diffs.unshift(new diff_match_patch.Diff(DIFF_EQUAL, commonprefix)); } if (commonsuffix) { - diffs.push([DIFF_EQUAL, commonsuffix]); + diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, commonsuffix)); } this.diff_cleanupMerge(diffs); return diffs; @@ -160,12 +177,12 @@ diff_match_patch.prototype.diff_compute_ = function(text1, text2, checklines, if (!text1) { // Just add some text (speedup). - return [[DIFF_INSERT, text2]]; + return [new diff_match_patch.Diff(DIFF_INSERT, text2)]; } if (!text2) { // Just delete some text (speedup). - return [[DIFF_DELETE, text1]]; + return [new diff_match_patch.Diff(DIFF_DELETE, text1)]; } var longtext = text1.length > text2.length ? text1 : text2; @@ -173,9 +190,10 @@ diff_match_patch.prototype.diff_compute_ = function(text1, text2, checklines, var i = longtext.indexOf(shorttext); if (i != -1) { // Shorter text is inside the longer text (speedup). - diffs = [[DIFF_INSERT, longtext.substring(0, i)], - [DIFF_EQUAL, shorttext], - [DIFF_INSERT, longtext.substring(i + shorttext.length)]]; + diffs = [new diff_match_patch.Diff(DIFF_INSERT, longtext.substring(0, i)), + new diff_match_patch.Diff(DIFF_EQUAL, shorttext), + new diff_match_patch.Diff(DIFF_INSERT, + longtext.substring(i + shorttext.length))]; // Swap insertions for deletions if diff is reversed. if (text1.length > text2.length) { diffs[0][0] = diffs[2][0] = DIFF_DELETE; @@ -186,7 +204,8 @@ diff_match_patch.prototype.diff_compute_ = function(text1, text2, checklines, if (shorttext.length == 1) { // Single character string. // After the previous speedup, the character can't be an equality. - return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; + return [new diff_match_patch.Diff(DIFF_DELETE, text1), + new diff_match_patch.Diff(DIFF_INSERT, text2)]; } // Check to see if the problem can be split in two. @@ -202,7 +221,8 @@ diff_match_patch.prototype.diff_compute_ = function(text1, text2, checklines, var diffs_a = this.diff_main(text1_a, text2_a, checklines, deadline); var diffs_b = this.diff_main(text1_b, text2_b, checklines, deadline); // Merge the results. - return diffs_a.concat([[DIFF_EQUAL, mid_common]], diffs_b); + return diffs_a.concat([new diff_match_patch.Diff(DIFF_EQUAL, mid_common)], + diffs_b); } if (checklines && text1.length > 100 && text2.length > 100) { @@ -239,7 +259,7 @@ diff_match_patch.prototype.diff_lineMode_ = function(text1, text2, deadline) { // Rediff any replacement blocks, this time character-by-character. // Add a dummy entry at the end. - diffs.push([DIFF_EQUAL, '']); + diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, '')); var pointer = 0; var count_delete = 0; var count_insert = 0; @@ -260,13 +280,14 @@ diff_match_patch.prototype.diff_lineMode_ = function(text1, text2, deadline) { if (count_delete >= 1 && count_insert >= 1) { // Delete the offending records and add the merged ones. diffs.splice(pointer - count_delete - count_insert, - count_delete + count_insert); + count_delete + count_insert); pointer = pointer - count_delete - count_insert; - var a = this.diff_main(text_delete, text_insert, false, deadline); - for (var j = a.length - 1; j >= 0; j--) { - diffs.splice(pointer, 0, a[j]); + var subDiff = + this.diff_main(text_delete, text_insert, false, deadline); + for (var j = subDiff.length - 1; j >= 0; j--) { + diffs.splice(pointer, 0, subDiff[j]); } - pointer = pointer + a.length; + pointer = pointer + subDiff.length; } count_insert = 0; count_delete = 0; @@ -336,7 +357,7 @@ diff_match_patch.prototype.diff_bisect_ = function(text1, text2, deadline) { } var y1 = x1 - k1; while (x1 < text1_length && y1 < text2_length && - text1.charAt(x1) == text2.charAt(y1)) { + text1.charAt(x1) == text2.charAt(y1)) { x1++; y1++; } @@ -371,8 +392,8 @@ diff_match_patch.prototype.diff_bisect_ = function(text1, text2, deadline) { } var y2 = x2 - k2; while (x2 < text1_length && y2 < text2_length && - text1.charAt(text1_length - x2 - 1) == - text2.charAt(text2_length - y2 - 1)) { + text1.charAt(text1_length - x2 - 1) == + text2.charAt(text2_length - y2 - 1)) { x2++; y2++; } @@ -400,7 +421,8 @@ diff_match_patch.prototype.diff_bisect_ = function(text1, text2, deadline) { } // Diff took too long and hit the deadline or // number of diffs equals number of characters, no commonality at all. - return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; + return [new diff_match_patch.Diff(DIFF_DELETE, text1), + new diff_match_patch.Diff(DIFF_INSERT, text2)]; }; @@ -472,21 +494,29 @@ diff_match_patch.prototype.diff_linesToChars_ = function(text1, text2) { lineEnd = text.length - 1; } var line = text.substring(lineStart, lineEnd + 1); - lineStart = lineEnd + 1; if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : (lineHash[line] !== undefined)) { chars += String.fromCharCode(lineHash[line]); } else { + if (lineArrayLength == maxLines) { + // Bail out at 65535 because + // String.fromCharCode(65536) == String.fromCharCode(0) + line = text.substring(lineStart); + lineEnd = text.length; + } chars += String.fromCharCode(lineArrayLength); lineHash[line] = lineArrayLength; lineArray[lineArrayLength++] = line; } + lineStart = lineEnd + 1; } return chars; } - + // Allocate 2/3rds of the space for text1, the rest for text2. + var maxLines = 40000; var chars1 = diff_linesToCharsMunge_(text1); + maxLines = 65535; var chars2 = diff_linesToCharsMunge_(text2); return {chars1: chars1, chars2: chars2, lineArray: lineArray}; }; @@ -500,13 +530,13 @@ diff_match_patch.prototype.diff_linesToChars_ = function(text1, text2) { * @private */ diff_match_patch.prototype.diff_charsToLines_ = function(diffs, lineArray) { - for (var x = 0; x < diffs.length; x++) { - var chars = diffs[x][1]; + for (var i = 0; i < diffs.length; i++) { + var chars = diffs[i][1]; var text = []; - for (var y = 0; y < chars.length; y++) { - text[y] = lineArray[chars.charCodeAt(y)]; + for (var j = 0; j < chars.length; j++) { + text[j] = lineArray[chars.charCodeAt(j)]; } - diffs[x][1] = text.join(''); + diffs[i][1] = text.join(''); } }; @@ -524,7 +554,7 @@ diff_match_patch.prototype.diff_commonPrefix = function(text1, text2) { return 0; } // Binary search. - // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ var pointermin = 0; var pointermax = Math.min(text1.length, text2.length); var pointermid = pointermax; @@ -556,7 +586,7 @@ diff_match_patch.prototype.diff_commonSuffix = function(text1, text2) { return 0; } // Binary search. - // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ var pointermin = 0; var pointermax = Math.min(text1.length, text2.length); var pointermid = pointermax; @@ -605,7 +635,7 @@ diff_match_patch.prototype.diff_commonOverlap_ = function(text1, text2) { // Start by looking for a single character match // and increase length until no match is found. - // Performance analysis: http://neil.fraser.name/news/2010/11/04/ + // Performance analysis: https://neil.fraser.name/news/2010/11/04/ var best = 0; var length = 1; while (true) { @@ -667,9 +697,9 @@ diff_match_patch.prototype.diff_halfMatch_ = function(text1, text2) { var best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b; while ((j = shorttext.indexOf(seed, j + 1)) != -1) { var prefixLength = dmp.diff_commonPrefix(longtext.substring(i), - shorttext.substring(j)); + shorttext.substring(j)); var suffixLength = dmp.diff_commonSuffix(longtext.substring(0, i), - shorttext.substring(0, j)); + shorttext.substring(0, j)); if (best_common.length < suffixLength + prefixLength) { best_common = shorttext.substring(j - suffixLength, j) + shorttext.substring(j, j + prefixLength); @@ -681,7 +711,7 @@ diff_match_patch.prototype.diff_halfMatch_ = function(text1, text2) { } if (best_common.length * 2 >= longtext.length) { return [best_longtext_a, best_longtext_b, - best_shorttext_a, best_shorttext_b, best_common]; + best_shorttext_a, best_shorttext_b, best_common]; } else { return null; } @@ -689,10 +719,10 @@ diff_match_patch.prototype.diff_halfMatch_ = function(text1, text2) { // First check if the second quarter is the seed for a half-match. var hm1 = diff_halfMatchI_(longtext, shorttext, - Math.ceil(longtext.length / 4)); + Math.ceil(longtext.length / 4)); // Check again based on the third quarter. var hm2 = diff_halfMatchI_(longtext, shorttext, - Math.ceil(longtext.length / 2)); + Math.ceil(longtext.length / 2)); var hm; if (!hm1 && !hm2) { return null; @@ -732,7 +762,7 @@ diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) { var equalities = []; // Stack of indices where equalities are found. var equalitiesLength = 0; // Keeping our own length var is faster in JS. /** @type {?string} */ - var lastequality = null; + var lastEquality = null; // Always equal to diffs[equalities[equalitiesLength - 1]][1] var pointer = 0; // Index of current position. // Number of characters that changed prior to the equality. @@ -748,7 +778,7 @@ diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) { length_deletions1 = length_deletions2; length_insertions2 = 0; length_deletions2 = 0; - lastequality = diffs[pointer][1]; + lastEquality = diffs[pointer][1]; } else { // An insertion or deletion. if (diffs[pointer][0] == DIFF_INSERT) { length_insertions2 += diffs[pointer][1].length; @@ -757,13 +787,13 @@ diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) { } // Eliminate an equality that is smaller or equal to the edits on both // sides of it. - if (lastequality && (lastequality.length <= + if (lastEquality && (lastEquality.length <= Math.max(length_insertions1, length_deletions1)) && - (lastequality.length <= Math.max(length_insertions2, - length_deletions2))) { + (lastEquality.length <= Math.max(length_insertions2, + length_deletions2))) { // Duplicate record. diffs.splice(equalities[equalitiesLength - 1], 0, - [DIFF_DELETE, lastequality]); + new diff_match_patch.Diff(DIFF_DELETE, lastEquality)); // Change second copy to insert. diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; // Throw away the equality we just deleted. @@ -775,7 +805,7 @@ diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) { length_deletions1 = 0; length_insertions2 = 0; length_deletions2 = 0; - lastequality = null; + lastEquality = null; changes = true; } } @@ -806,8 +836,8 @@ diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) { if (overlap_length1 >= deletion.length / 2 || overlap_length1 >= insertion.length / 2) { // Overlap found. Insert an equality and trim the surrounding edits. - diffs.splice(pointer, 0, - [DIFF_EQUAL, insertion.substring(0, overlap_length1)]); + diffs.splice(pointer, 0, new diff_match_patch.Diff(DIFF_EQUAL, + insertion.substring(0, overlap_length1))); diffs[pointer - 1][1] = deletion.substring(0, deletion.length - overlap_length1); diffs[pointer + 1][1] = insertion.substring(overlap_length1); @@ -818,8 +848,8 @@ diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) { overlap_length2 >= insertion.length / 2) { // Reverse overlap found. // Insert an equality and swap and trim the surrounding edits. - diffs.splice(pointer, 0, - [DIFF_EQUAL, deletion.substring(0, overlap_length2)]); + diffs.splice(pointer, 0, new diff_match_patch.Diff(DIFF_EQUAL, + deletion.substring(0, overlap_length2))); diffs[pointer - 1][0] = DIFF_INSERT; diffs[pointer - 1][1] = insertion.substring(0, insertion.length - overlap_length2); @@ -977,7 +1007,7 @@ diff_match_patch.prototype.diff_cleanupEfficiency = function(diffs) { var equalities = []; // Stack of indices where equalities are found. var equalitiesLength = 0; // Keeping our own length var is faster in JS. /** @type {?string} */ - var lastequality = null; + var lastEquality = null; // Always equal to diffs[equalities[equalitiesLength - 1]][1] var pointer = 0; // Index of current position. // Is there an insertion operation before the last equality. @@ -996,11 +1026,11 @@ diff_match_patch.prototype.diff_cleanupEfficiency = function(diffs) { equalities[equalitiesLength++] = pointer; pre_ins = post_ins; pre_del = post_del; - lastequality = diffs[pointer][1]; + lastEquality = diffs[pointer][1]; } else { // Not a candidate, and can never become one. equalitiesLength = 0; - lastequality = null; + lastEquality = null; } post_ins = post_del = false; } else { // An insertion or deletion. @@ -1017,16 +1047,16 @@ diff_match_patch.prototype.diff_cleanupEfficiency = function(diffs) { * AXCD * ABXC */ - if (lastequality && ((pre_ins && pre_del && post_ins && post_del) || - ((lastequality.length < this.Diff_EditCost / 2) && - (pre_ins + pre_del + post_ins + post_del) == 3))) { + if (lastEquality && ((pre_ins && pre_del && post_ins && post_del) || + ((lastEquality.length < this.Diff_EditCost / 2) && + (pre_ins + pre_del + post_ins + post_del) == 3))) { // Duplicate record. diffs.splice(equalities[equalitiesLength - 1], 0, - [DIFF_DELETE, lastequality]); + new diff_match_patch.Diff(DIFF_DELETE, lastEquality)); // Change second copy to insert. diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; equalitiesLength--; // Throw away the equality we just deleted; - lastequality = null; + lastEquality = null; if (pre_ins && pre_del) { // No changes made which could affect previous entry, keep going. post_ins = post_del = true; @@ -1055,7 +1085,8 @@ diff_match_patch.prototype.diff_cleanupEfficiency = function(diffs) { * @param {!Array.} diffs Array of diff tuples. */ diff_match_patch.prototype.diff_cleanupMerge = function(diffs) { - diffs.push([DIFF_EQUAL, '']); // Add a dummy entry at the end. + // Add a dummy entry at the end. + diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, '')); var pointer = 0; var count_delete = 0; var count_insert = 0; @@ -1087,8 +1118,8 @@ diff_match_patch.prototype.diff_cleanupMerge = function(diffs) { diffs[pointer - count_delete - count_insert - 1][1] += text_insert.substring(0, commonlength); } else { - diffs.splice(0, 0, [DIFF_EQUAL, - text_insert.substring(0, commonlength)]); + diffs.splice(0, 0, new diff_match_patch.Diff(DIFF_EQUAL, + text_insert.substring(0, commonlength))); pointer++; } text_insert = text_insert.substring(commonlength); @@ -1106,19 +1137,19 @@ diff_match_patch.prototype.diff_cleanupMerge = function(diffs) { } } // Delete the offending records and add the merged ones. - if (count_delete === 0) { - diffs.splice(pointer - count_insert, - count_delete + count_insert, [DIFF_INSERT, text_insert]); - } else if (count_insert === 0) { - diffs.splice(pointer - count_delete, - count_delete + count_insert, [DIFF_DELETE, text_delete]); - } else { - diffs.splice(pointer - count_delete - count_insert, - count_delete + count_insert, [DIFF_DELETE, text_delete], - [DIFF_INSERT, text_insert]); + pointer -= count_delete + count_insert; + diffs.splice(pointer, count_delete + count_insert); + if (text_delete.length) { + diffs.splice(pointer, 0, + new diff_match_patch.Diff(DIFF_DELETE, text_delete)); + pointer++; + } + if (text_insert.length) { + diffs.splice(pointer, 0, + new diff_match_patch.Diff(DIFF_INSERT, text_insert)); + pointer++; } - pointer = pointer - count_delete - count_insert + - (count_delete ? 1 : 0) + (count_insert ? 1 : 0) + 1; + pointer++; } else if (pointer !== 0 && diffs[pointer - 1][0] == DIFF_EQUAL) { // Merge this equality with the previous one. diffs[pointer - 1][1] += diffs[pointer][1]; @@ -1152,7 +1183,7 @@ diff_match_patch.prototype.diff_cleanupMerge = function(diffs) { // Shift the edit over the previous equality. diffs[pointer][1] = diffs[pointer - 1][1] + diffs[pointer][1].substring(0, diffs[pointer][1].length - - diffs[pointer - 1][1].length); + diffs[pointer - 1][1].length); diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1]; diffs.splice(pointer - 1, 1); changes = true; @@ -1308,6 +1339,15 @@ diff_match_patch.prototype.diff_levenshtein = function(diffs) { return levenshtein; }; +diff_match_patch.prototype.isHighSurrogate = function(c) { + var v = c.charCodeAt(0); + return v >= 0xD800 && v <= 0xDBFF; +} + +diff_match_patch.prototype.isLowSurrogate = function(c) { + var v = c.charCodeAt(0); + return v >= 0xDC00 && v <= 0xDFFF; +} /** * Crush the diff into an encoded string which describes the operations @@ -1319,7 +1359,26 @@ diff_match_patch.prototype.diff_levenshtein = function(diffs) { */ diff_match_patch.prototype.diff_toDelta = function(diffs) { var text = []; + var lastEnd; for (var x = 0; x < diffs.length; x++) { + + var thisDiff = diffs[x]; + var thisTop = thisDiff[1][0]; + var thisEnd = thisDiff[1][thisDiff[1].length - 1]; + + if (thisEnd && this.isHighSurrogate(thisEnd)) { + thisDiff[1] = thisDiff[1].slice(0, -1); + } + + if (lastEnd && thisTop && this.isHighSurrogate(lastEnd) && this.isLowSurrogate(thisTop)) { + thisDiff[1] = lastEnd + thisDiff[1]; + } + + lastEnd = thisEnd; + if ( 0 === thisDiff[1].length ) { + continue; + } + switch (diffs[x][0]) { case DIFF_INSERT: text[x] = '+' + encodeURI(diffs[x][1]); @@ -1356,7 +1415,8 @@ diff_match_patch.prototype.diff_fromDelta = function(text1, delta) { switch (tokens[x].charAt(0)) { case '+': try { - diffs[diffsLength++] = [DIFF_INSERT, decodeURI(param)]; + diffs[diffsLength++] = + new diff_match_patch.Diff(DIFF_INSERT, decodeURI(param)); } catch (ex) { // Malformed URI sequence. throw new Error('Illegal escape in diff_fromDelta: ' + param); @@ -1371,9 +1431,9 @@ diff_match_patch.prototype.diff_fromDelta = function(text1, delta) { } var text = text1.substring(pointer, pointer += n); if (tokens[x].charAt(0) == '=') { - diffs[diffsLength++] = [DIFF_EQUAL, text]; + diffs[diffsLength++] = new diff_match_patch.Diff(DIFF_EQUAL, text); } else { - diffs[diffsLength++] = [DIFF_DELETE, text]; + diffs[diffsLength++] = new diff_match_patch.Diff(DIFF_DELETE, text); } break; default: @@ -1381,7 +1441,7 @@ diff_match_patch.prototype.diff_fromDelta = function(text1, delta) { // Anything else is an error. if (tokens[x]) { throw new Error('Invalid diff operation in diff_fromDelta: ' + - tokens[x]); + tokens[x]); } } } @@ -1513,8 +1573,8 @@ diff_match_patch.prototype.match_bitap_ = function(text, pattern, loc) { rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; } else { // Subsequent passes: fuzzy match. rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) | - (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | - last_rd[j + 1]; + (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | + last_rd[j + 1]; } if (rd[j] & matchmask) { var score = match_bitapScore_(d, j - 1); @@ -1576,17 +1636,20 @@ diff_match_patch.prototype.patch_addContext_ = function(patch, text) { if (text.length == 0) { return; } + if (patch.start2 === null) { + throw Error('patch not initialized'); + } var pattern = text.substring(patch.start2, patch.start2 + patch.length1); var padding = 0; // Look for the first and last matches of pattern in text. If two different // matches are found, increase the pattern length. while (text.indexOf(pattern) != text.lastIndexOf(pattern) && - pattern.length < this.Match_MaxBits - this.Patch_Margin - - this.Patch_Margin) { + pattern.length < this.Match_MaxBits - this.Patch_Margin - + this.Patch_Margin) { padding += this.Patch_Margin; pattern = text.substring(patch.start2 - padding, - patch.start2 + patch.length1 + padding); + patch.start2 + patch.length1 + padding); } // Add one chunk for good luck. padding += this.Patch_Margin; @@ -1594,13 +1657,13 @@ diff_match_patch.prototype.patch_addContext_ = function(patch, text) { // Add the prefix. var prefix = text.substring(patch.start2 - padding, patch.start2); if (prefix) { - patch.diffs.unshift([DIFF_EQUAL, prefix]); + patch.diffs.unshift(new diff_match_patch.Diff(DIFF_EQUAL, prefix)); } // Add the suffix. var suffix = text.substring(patch.start2 + patch.length1, - patch.start2 + patch.length1 + padding); + patch.start2 + patch.length1 + padding); if (suffix) { - patch.diffs.push([DIFF_EQUAL, suffix]); + patch.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, suffix)); } // Roll back the start points. @@ -1628,9 +1691,9 @@ diff_match_patch.prototype.patch_addContext_ = function(patch, text) { * * @param {string|!Array.} a text1 (methods 1,3,4) or * Array of diff tuples for text1 to text2 (method 2). - * @param {string|!Array.} opt_b text2 (methods 1,4) or + * @param {string|!Array.=} opt_b text2 (methods 1,4) or * Array of diff tuples for text1 to text2 (method 3) or undefined (method 2). - * @param {string|!Array.} opt_c Array of diff tuples + * @param {string|!Array.=} opt_c Array of diff tuples * for text1 to text2 (method 4) or undefined (methods 1,2,3). * @return {!Array.} Array of Patch objects. */ @@ -1695,14 +1758,14 @@ diff_match_patch.prototype.patch_make = function(a, opt_b, opt_c) { patch.diffs[patchDiffLength++] = diffs[x]; patch.length2 += diff_text.length; postpatch_text = postpatch_text.substring(0, char_count2) + diff_text + - postpatch_text.substring(char_count2); + postpatch_text.substring(char_count2); break; case DIFF_DELETE: patch.length1 += diff_text.length; patch.diffs[patchDiffLength++] = diffs[x]; postpatch_text = postpatch_text.substring(0, char_count2) + - postpatch_text.substring(char_count2 + - diff_text.length); + postpatch_text.substring(char_count2 + + diff_text.length); break; case DIFF_EQUAL: if (diff_text.length <= 2 * this.Patch_Margin && @@ -1719,7 +1782,7 @@ diff_match_patch.prototype.patch_make = function(a, opt_b, opt_c) { patch = new diff_match_patch.patch_obj(); patchDiffLength = 0; // Unlike Unidiff, our patch lists have a rolling context. - // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff + // https://github.com/google/diff-match-patch/wiki/Unidiff // Update prepatch text & pos to reflect the application of the // just completed patch. prepatch_text = postpatch_text; @@ -1760,7 +1823,8 @@ diff_match_patch.prototype.patch_deepCopy = function(patches) { var patchCopy = new diff_match_patch.patch_obj(); patchCopy.diffs = []; for (var y = 0; y < patch.diffs.length; y++) { - patchCopy.diffs[y] = patch.diffs[y].slice(); + patchCopy.diffs[y] = + new diff_match_patch.Diff(patch.diffs[y][0], patch.diffs[y][1]); } patchCopy.start1 = patch.start1; patchCopy.start2 = patch.start2; @@ -1807,7 +1871,7 @@ diff_match_patch.prototype.patch_apply = function(patches, text) { // patch_splitMax will only provide an oversized pattern in the case of // a monster delete. start_loc = this.match_main(text, text1.substring(0, this.Match_MaxBits), - expected_loc); + expected_loc); if (start_loc != -1) { end_loc = this.match_main(text, text1.substring(text1.length - this.Match_MaxBits), @@ -1838,8 +1902,8 @@ diff_match_patch.prototype.patch_apply = function(patches, text) { if (text1 == text2) { // Perfect match, just shove the replacement text in. text = text.substring(0, start_loc) + - this.diff_text2(patches[x].diffs) + - text.substring(start_loc + text1.length); + this.diff_text2(patches[x].diffs) + + text.substring(start_loc + text1.length); } else { // Imperfect match. Run a diff to get a framework of equivalent // indices. @@ -1860,11 +1924,11 @@ diff_match_patch.prototype.patch_apply = function(patches, text) { } if (mod[0] === DIFF_INSERT) { // Insertion text = text.substring(0, start_loc + index2) + mod[1] + - text.substring(start_loc + index2); + text.substring(start_loc + index2); } else if (mod[0] === DIFF_DELETE) { // Deletion text = text.substring(0, start_loc + index2) + - text.substring(start_loc + this.diff_xIndex(diffs, - index1 + mod[1].length)); + text.substring(start_loc + this.diff_xIndex(diffs, + index1 + mod[1].length)); } if (mod[0] !== DIFF_DELETE) { index1 += mod[1].length; @@ -1904,7 +1968,7 @@ diff_match_patch.prototype.patch_addPadding = function(patches) { var diffs = patch.diffs; if (diffs.length == 0 || diffs[0][0] != DIFF_EQUAL) { // Add nullPadding equality. - diffs.unshift([DIFF_EQUAL, nullPadding]); + diffs.unshift(new diff_match_patch.Diff(DIFF_EQUAL, nullPadding)); patch.start1 -= paddingLength; // Should be 0. patch.start2 -= paddingLength; // Should be 0. patch.length1 += paddingLength; @@ -1924,7 +1988,7 @@ diff_match_patch.prototype.patch_addPadding = function(patches) { diffs = patch.diffs; if (diffs.length == 0 || diffs[diffs.length - 1][0] != DIFF_EQUAL) { // Add nullPadding equality. - diffs.push([DIFF_EQUAL, nullPadding]); + diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, nullPadding)); patch.length1 += paddingLength; patch.length2 += paddingLength; } else if (paddingLength > diffs[diffs.length - 1][1].length) { @@ -1965,10 +2029,10 @@ diff_match_patch.prototype.patch_splitMax = function(patches) { patch.start2 = start2 - precontext.length; if (precontext !== '') { patch.length1 = patch.length2 = precontext.length; - patch.diffs.push([DIFF_EQUAL, precontext]); + patch.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, precontext)); } while (bigpatch.diffs.length !== 0 && - patch.length1 < patch_size - this.Patch_Margin) { + patch.length1 < patch_size - this.Patch_Margin) { var diff_type = bigpatch.diffs[0][0]; var diff_text = bigpatch.diffs[0][1]; if (diff_type === DIFF_INSERT) { @@ -1978,13 +2042,13 @@ diff_match_patch.prototype.patch_splitMax = function(patches) { patch.diffs.push(bigpatch.diffs.shift()); empty = false; } else if (diff_type === DIFF_DELETE && patch.diffs.length == 1 && - patch.diffs[0][0] == DIFF_EQUAL && - diff_text.length > 2 * patch_size) { + patch.diffs[0][0] == DIFF_EQUAL && + diff_text.length > 2 * patch_size) { // This is a large deletion. Let it pass in one chunk. patch.length1 += diff_text.length; start1 += diff_text.length; empty = false; - patch.diffs.push([diff_type, diff_text]); + patch.diffs.push(new diff_match_patch.Diff(diff_type, diff_text)); bigpatch.diffs.shift(); } else { // Deletion or equality. Only take as much as we can stomach. @@ -1998,7 +2062,7 @@ diff_match_patch.prototype.patch_splitMax = function(patches) { } else { empty = false; } - patch.diffs.push([diff_type, diff_text]); + patch.diffs.push(new diff_match_patch.Diff(diff_type, diff_text)); if (diff_text == bigpatch.diffs[0][1]) { bigpatch.diffs.shift(); } else { @@ -2013,7 +2077,7 @@ diff_match_patch.prototype.patch_splitMax = function(patches) { precontext.substring(precontext.length - this.Patch_Margin); // Append the end context for this patch. var postcontext = this.diff_text1(bigpatch.diffs) - .substring(0, this.Patch_Margin); + .substring(0, this.Patch_Margin); if (postcontext !== '') { patch.length1 += postcontext.length; patch.length2 += postcontext.length; @@ -2021,7 +2085,7 @@ diff_match_patch.prototype.patch_splitMax = function(patches) { patch.diffs[patch.diffs.length - 1][0] === DIFF_EQUAL) { patch.diffs[patch.diffs.length - 1][1] += postcontext; } else { - patch.diffs.push([DIFF_EQUAL, postcontext]); + patch.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, postcontext)); } } if (!empty) { @@ -2100,13 +2164,13 @@ diff_match_patch.prototype.patch_fromText = function(textline) { } if (sign == '-') { // Deletion. - patch.diffs.push([DIFF_DELETE, line]); + patch.diffs.push(new diff_match_patch.Diff(DIFF_DELETE, line)); } else if (sign == '+') { // Insertion. - patch.diffs.push([DIFF_INSERT, line]); + patch.diffs.push(new diff_match_patch.Diff(DIFF_INSERT, line)); } else if (sign == ' ') { // Minor equality. - patch.diffs.push([DIFF_EQUAL, line]); + patch.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, line)); } else if (sign == '@') { // Start of next patch. break; @@ -2142,9 +2206,9 @@ diff_match_patch.patch_obj = function() { /** - * Emmulate GNU diff's format. + * Emulate GNU diff's format. * Header: @@ -382,8 +481,9 @@ - * Indicies are printed as 1-based, not 0-based. + * Indices are printed as 1-based, not 0-based. * @return {string} The GNU diff string. */ diff_match_patch.patch_obj.prototype.toString = function() {