Upstream EasyDeflate library from VisualEditor

The library is used to compress large documents before
sending to the server, as HTTP doesn't support upload compression.
(See T68914 for context.)

N.B. Many of the polyfills in the original are omitted as
MediaWiki doesn't support browsers which require them.

Co-Authored-by: Kunal Mehta <legoktm@member.fsf.org>
Change-Id: I12a5879188f46f17dc4c33bdad52317d218d4172
This commit is contained in:
Ed Sanders 2017-09-15 16:25:52 +01:00 committed by Esanders
parent e03b96c9a4
commit 230e90b2af
10 changed files with 4668 additions and 2 deletions

View file

@ -428,6 +428,7 @@ $wgAutoloadLocalClasses = [
'DumpRev' => __DIR__ . '/maintenance/storage/dumpRev.php',
'DumpStringOutput' => __DIR__ . '/includes/export/DumpStringOutput.php',
'DuplicateJob' => __DIR__ . '/includes/jobqueue/jobs/DuplicateJob.php',
'EasyDeflate' => __DIR__ . '/includes/libs/EasyDeflate.php',
'EditAction' => __DIR__ . '/includes/actions/EditAction.php',
'EditCLI' => __DIR__ . '/maintenance/edit.php',
'EditPage' => __DIR__ . '/includes/EditPage.php',

View file

@ -0,0 +1,72 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
/**
* Server-side helper for the easy-deflate library
*
* @since 1.32
*/
class EasyDeflate {
/**
* Whether the content is deflated
*
* @param string $data
*
* @return bool
*/
public static function isDeflated( $data ) {
return substr( $data, 0, 11 ) === 'rawdeflate,';
}
/**
* For content that has been compressed with deflate in the client,
* try to uncompress it with inflate.
*
* If data is not prefixed with 'rawdeflate,' it will be returned unmodified.
*
* Data can be compressed in the client using the 'easy-deflate.deflate'
* module:
*
* @code
* mw.loader.using( 'easy-deflate.deflate' ).then( function () {
* var deflated = EasyDeflate.deflate( myContent );
* } );
* @endcode
*
* @param string $data Deflated data
* @return StatusValue Inflated data will be set as the value
* @throws InvalidArgumentException If the data wasn't deflated
*/
public static function inflate( $data ) {
if ( !self::isDeflated( $data ) ) {
throw new InvalidArgumentException( 'Data does not begin with deflated prefix' );
}
$deflated = base64_decode( substr( $data, 11 ), true );
if ( $deflated === false ) {
return StatusValue::newFatal( 'easydeflate-invaliddeflate' );
}
Wikimedia\suppressWarnings();
$inflated = gzinflate( $deflated );
Wikimedia\restoreWarnings();
if ( $inflated === false ) {
return StatusValue::newFatal( 'easydeflate-invaliddeflate' );
}
return StatusValue::newGood( $inflated );
}
}

View file

@ -4481,5 +4481,6 @@
"passwordpolicies-policy-passwordcannotmatchusername": "Password cannot be the same as username",
"passwordpolicies-policy-passwordcannotmatchblacklist": "Password cannot match specifically blacklisted passwords",
"passwordpolicies-policy-maximalpasswordlength": "Password must be less than $1 {{PLURAL:$1|character|characters}} long",
"passwordpolicies-policy-passwordcannotbepopular": "Password cannot be {{PLURAL:$1|the popular password|in the list of $1 popular passwords}}"
"passwordpolicies-policy-passwordcannotbepopular": "Password cannot be {{PLURAL:$1|the popular password|in the list of $1 popular passwords}}",
"easydeflate-invaliddeflate": "Content provided is not properly deflated"
}

View file

@ -4680,5 +4680,6 @@
"passwordpolicies-policy-passwordcannotmatchusername": "Password policy that enforces that the password of the account cannot be the same as the username",
"passwordpolicies-policy-passwordcannotmatchblacklist": "Password policy that enforces that passwords are not on a list of blacklisted passwords (often previously used during MediaWiki automated testing)",
"passwordpolicies-policy-maximalpasswordlength": "Password policy that enforces a maximum number of characters a password must be. $1 - maximum number of characters that a password can be",
"passwordpolicies-policy-passwordcannotbepopular": "Password policy that enforces that a password is not in a list of $1 number of \"popular\" passwords. $1 - number of popular passwords the password will be checked against"
"passwordpolicies-policy-passwordcannotbepopular": "Password policy that enforces that a password is not in a list of $1 number of \"popular\" passwords. $1 - number of popular passwords the password will be checked against",
"easydeflate-invaliddeflate": "Error message if the content passed to easydeflate was not deflated (compressed) properly"
}

View file

@ -2792,6 +2792,25 @@ return [
'raw' => true,
],
/* EasyDeflate */
'easy-deflate.core' => [
'scripts' => [ 'resources/lib/easy-deflate/easydeflate.js' ],
'targets' => [ 'desktop', 'mobile' ],
],
'easy-deflate.deflate' => [
'scripts' => [ 'resources/lib/easy-deflate/deflate.js' ],
'dependencies' => [ 'easy-deflate.core' ],
'targets' => [ 'desktop', 'mobile' ],
],
'easy-deflate.inflate' => [
'scripts' => [ 'resources/lib/easy-deflate/inflate.js' ],
'dependencies' => [ 'easy-deflate.core' ],
'targets' => [ 'desktop', 'mobile' ],
],
/* OOjs */
'oojs' => [
'scripts' => [

View file

@ -0,0 +1,38 @@
Modified version of Easy-Deflate https://github.com/Jacob-Christian-Munch-Andersen/Easy-Deflate
This version: https://github.com/edg2s/Easy-Deflate
* Added semi-colons to easydeflate.js so it can be minified
* Namespaced functions inside global EasyDeflate object
* Base64 lib replaced with one with detailed license info
Modifications by Ed Sanders, Public Domain.
Easy-Deflate
============
Library for compressing and decompressing strings in JavaScript, feature full Unicode support and is compatible with most browsers.
Use:
====
Copy the script inclusion from demo.html.<br>
Call EasyDeflate.deflate(foo) in order to compress a string.<br>
Call EasyDeflate.inflate(bar) in order to decompress a string compressed in this manner.<br>
Both functions return a string, or null in case of illegal input.
The compression works by first UTF-8 encoding the input, then compressing it to a raw deflate stream. The stream is then base64 encoded, and finally the identifier "rawdeflate," is prepended.
Credits:
========
Gildas Lormeau made the JavaScript conversion of a Deflate utility: https://github.com/gildas-lormeau/zip.js<br>
Jacob Christian Munch-Andersen made this package in order to make simple use easier and compatible with older browsers.
The following shims are included:<br>
es5-shim by Kristopher Michael Kowal https://github.com/kriskowal/es5-shim<br>
JSON 3 by Kit Cambridge http://bestiejs.github.com/json3/<br>
Typed arrays light shim by Jacob Christian Munch-Andersen https://github.com/Jacob-Christian-Munch-Andersen/Typed-arrays-light-shim<br>
<s>base64 by Yaffle https://gist.github.com/1284012</s>
License:
========
Main packages come with a BSD licence, the shims, except for base64 that include no license text, each has a permissive license.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,219 @@
/**
Copyright (c) 2013, Specialisterne.
http://specialisterne.com/dk/
All rights reserved.
Authors:
Jacob Christian Munch-Andersen
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**/
// For information and latest version see: https://github.com/Jacob-Christian-Munch-Andersen/Easy-Deflate
(function(){
var zip={};
function UTF8encode(str){
var out=[];
var a;
var c,c2;
for(a=0;a<str.length;a++){
c=str.charCodeAt(a);
if(c<128){
out.push(c);
}
else if(c<2048){
out.push((c >> 6)+192);
out.push((c & 63)+128);
}
else if(c<65536){
if(c>=0xD800 && c<0xDC00){
a++;
if(a>=str.length){
return null;
}
c2=str.charCodeAt(a);
if(c2>=0xDC00 && c2<0xE000){
c=65536+(c-0xD800)*1024+c2-0xDC00;
out.push((c >> 18)+240);
out.push(((c >> 12) & 63)+128);
out.push(((c >> 6) & 63)+128);
out.push((c & 63)+128);
}
else{
return null;
}
}
else if(c>=0xDC00 && c<0xE000){
return null;
}
else{
out.push((c >> 12)+224);
out.push(((c >> 6) & 63)+128);
out.push((c & 63)+128);
}
}
else{
return null;
}
}
return new Uint8Array(out);
}
function UTF8decodeA(arrarr){
var result="";
var intermediate;
var minvalue;
var missing=0;
var a,b;
var arr;
var c;
var lower,upper;
for(a=0;a<arrarr.length;a++){
arr=arrarr[a];
for(b=0;b<arr.length;b++){
c=arr[b];
if(missing){
if(c>127 && c<192){
intermediate=intermediate*64+c-128;
missing--;
if(!missing){
if(intermediate>=minvalue){
if(intermediate>=65536){
if(intermediate>0x10FFFF){
return null;
}
upper=(intermediate-65536)>>10;
lower=intermediate%1024;
result+=String.fromCharCode(upper+0xD800,lower+0xDC00);
}
else{
result+=String.fromCharCode(intermediate);
}
}
else{
return null;
}
}
}
else{
return null;
}
}
else if(c<128){
result+=String.fromCharCode(c);
}
else if(c>191 && c<248){
if(c<224){
intermediate=c-192;
minvalue=128;
missing=1;
}
else if(c<240){
intermediate=c-224;
minvalue=2048;
missing=2;
}
else{
intermediate=c-240;
minvalue=65536;
missing=3;
}
}
else{
return null;
}
}
}
if(missing){
return null;
}
return result;
}
function deflate(str){
var a,c;
var readlen=50000;
var resulta=[];
var results="";
var b,d;
var zipper=new zip.Deflater(9);
for(a=0;a<str.length;a+=readlen){
d=UTF8encode(str.substr(a,readlen));
if(d===null){ //This error may be due to a 4 byte charachter being split, retry with a string that is 1 longer to fix it.
d=UTF8encode(str.substr(a,readlen+1));
a+=1;
if(d===null){
return null;
}
}
b=zipper.append(d);
if(b.length!==0){
resulta.push(b);
}
}
b=zipper.flush();
if(b.length!==0){
resulta.push(b);
}
for(a=0;a<resulta.length;a++){
for(c=0;c<resulta[a].length;c++){
results+=String.fromCharCode(resulta[a][c]);
}
}
return "rawdeflate,"+btoa(results);
}
function inflate(dfl){
var unzipper=new zip.Inflater();
var resulta=[];
var dfls;
var a,c;
var b,d;
if(dfl.slice(0,11)!="rawdeflate,"){
return null;
}
try{
dfls=atob(dfl.slice(11));
}
catch(e){
return null;
}
try{
for(a=0;a<dfls.length;a+=50000){
b=new Uint8Array(Math.min(50000,dfls.length-a));
for(c=0;c<b.length;c++){
b[c]=dfls.charCodeAt(c+a);
}
d=unzipper.append(b);
if(d.length){
resulta.push(d);
}
}
return UTF8decodeA(resulta);
}
catch(e){
return null;
}
}
window.EasyDeflate = {
'zip': zip,
'inflate': inflate,
'deflate': deflate
};
})();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,64 @@
<?php
/**
* Copyright (C) 2018 Kunal Mehta <legoktm@member.fsf.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
/**
* @covers EasyDeflate
*/
class EasyDeflateTest extends PHPUnit\Framework\TestCase {
public function provideIsDeflated() {
return [
[ 'rawdeflate,S8vPT0osAgA=', true ],
[ 'abcdefghijklmnopqrstuvwxyz', false ],
];
}
/**
* @dataProvider provideIsDeflated
*/
public function testIsDeflated( $data, $expected ) {
$actual = EasyDeflate::isDeflated( $data );
$this->assertSame( $expected, $actual );
}
public function provideInflate() {
return [
[ 'rawdeflate,S8vPT0osAgA=', true, 'foobar' ],
// Fails base64_decode
[ 'rawdeflate,🌻', false, 'easydeflate-invaliddeflate' ],
// Fails gzinflate
[ 'rawdeflate,S8vPT0dfdAgB=', false, 'easydeflate-invaliddeflate' ],
];
}
/**
* @dataProvider provideInflate
*/
public function testInflate( $data, $ok, $value ) {
$actual = EasyDeflate::inflate( $data );
if ( $ok ) {
$this->assertTrue( $actual->isOK() );
$this->assertSame( $value, $actual->getValue() );
} else {
$this->assertFalse( $actual->isOK() );
$this->assertTrue( $actual->hasMessage( $value ) );
}
}
}