From 6260fc09a3965ab18e2e13b3b0d6459694264c20 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Thu, 26 May 2016 17:34:00 +0200 Subject: [PATCH] Attempt to recover valid `format 3` FDSelect data from broken CFF fonts (bug 1146106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to the CFF specification, see http://partners.adobe.com/public/developer/en/font/5176.CFF.pdf#G3.46884, for `format 3` FDSelect data: "The first range must have a ‘first’ GID of 0". Since the PDF file (attached in the bug) violates that part of the specification, this patch tries to recover valid FDSelect data to prevent OTS from rejecting the font. Fixes https://bugzilla.mozilla.org/show_bug.cgi?id=1146106. --- src/core/cff_parser.js | 23 ++++++++++++++++++----- test/pdfs/.gitignore | 1 + test/pdfs/bug1146106.pdf | Bin 0 -> 4517 bytes test/test_manifest.json | 7 +++++++ test/unit/cff_parser_spec.js | 35 ++++++++++++++++++++++++++++------- 5 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 test/pdfs/bug1146106.pdf diff --git a/src/core/cff_parser.js b/src/core/cff_parser.js index 2a9f3cb0ea5de..2c7578f480c53 100644 --- a/src/core/cff_parser.js +++ b/src/core/cff_parser.js @@ -856,8 +856,8 @@ var CFFParser = (function CFFParserClosure() { var start = pos; var bytes = this.bytes; var format = bytes[pos++]; - var fdSelect = []; - var i; + var fdSelect = [], rawBytes; + var i, invalidFirstGID = false; switch (format) { case 0: @@ -865,11 +865,18 @@ var CFFParser = (function CFFParserClosure() { var id = bytes[pos++]; fdSelect.push(id); } + rawBytes = bytes.subarray(start, pos); break; case 3: var rangesCount = (bytes[pos++] << 8) | bytes[pos++]; for (i = 0; i < rangesCount; ++i) { var first = (bytes[pos++] << 8) | bytes[pos++]; + if (i === 0 && first !== 0) { + warn('parseFDSelect: The first range must have a first GID of 0' + + ' -- trying to recover.'); + invalidFirstGID = true; + first = 0; + } var fdIndex = bytes[pos++]; var next = (bytes[pos] << 8) | bytes[pos + 1]; for (var j = first; j < next; ++j) { @@ -878,13 +885,19 @@ var CFFParser = (function CFFParserClosure() { } // Advance past the sentinel(next). pos += 2; + rawBytes = bytes.subarray(start, pos); + + if (invalidFirstGID) { + rawBytes[3] = rawBytes[4] = 0; // Adjust the first range, first GID. + } break; default: - error('Unknown fdselect format ' + format); + error('parseFDSelect: Unknown format "' + format + '".'); break; } - var end = pos; - return new CFFFDSelect(fdSelect, bytes.subarray(start, end)); + assert(fdSelect.length === length, 'parseFDSelect: Invalid font data.'); + + return new CFFFDSelect(fdSelect, rawBytes); } }; return CFFParser; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index f9f1c41021217..d0b08ac3012d2 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -37,6 +37,7 @@ !bug1050040.pdf !bug1200096.pdf !bug1068432.pdf +!bug1146106.pdf !issue5564_reduced.pdf !canvas.pdf !bug1132849.pdf diff --git a/test/pdfs/bug1146106.pdf b/test/pdfs/bug1146106.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c0d05387603d895da9472f6ef058863e41cd0fbc GIT binary patch literal 4517 zcmcIod0Z369?wH9g2V$5JgP2K5aih1Y!VXXgF?a)1%rg67%4A=B?NMr4RVQB<#Bjb zJfPL8wDnWPql)#axA;(0sHIxPTR>h>1W{2yjyJO$a`pAExB0-%d}ro2zwm>yGz+rqe-TzhX>P? zGU!Q}nrWJ}<96=DwHEl;3y-bet;kbcTw=f3KW6T5>6kfB#_oCYWwW7a*r^x!wjCeJ zjwfbVQetCKh5AH^CMR;aGAG<9yQ=+qa@6M=c18ZDw&XtNa>hTKqSA6J1<$X%6a@yY z-tW+s_toASqujt#yYsfa?!2+*vfa(nru*(`NyAU4sWXbL*Exh0T-voKz2VK4Cjk%a zc%9a|V-@nVDIKkqDevPG+EY)opIM)}9c|q5eW+7m!<`&#-X|qalkQA13;%7aP)2dY z;whJQ8?tSwxUoq+wiQEuv%RXEftSnD!vvg74e>U}-yz0u#=H;m;mW`=KeU2WU zy=%ky&nl;Ota%+owOw1_P=`-0es{;dzD_)fHTy$t^K9FCV#P#nXOrk92JYsI?Z>iC zH}5yu8u6ulNc6^+D#MC^O4p8g>ueTgRb1e=lBYJ*|0H^FW!LVRl2!ie>bLnscHX<3 z)}CdOBdjTDo^4-$v}59;C81xLFWDMA#bdd~Jh4$RqU7Leomu=rcH6uAM)e;W=FiM4 zxb{jr4N4B)B%c`Lx8r7EAuW;g7MU z#pAA{4=bNG9b1`tcWo~6;6FYS8brQh+9sX!t*I|N`lhM$PUM6XY6sJl)I|4$A2jLY zfeAx5*nA#6f$k7JsJnYlX0c&r!Gszjvj8kfh%zMWNCXv#gg#o0AsBQp!GMGdnXmoS zd+eity2crY&5d~0=5t5>;FoD(zSt|!Z*+-CV~&@eSTLP?vCRM7(~68MXR98Gv}(nJ zol`d6*_jo2baa+k;n*zOaW=MPpSBFE&g3=ZFf$)5ZW&P<6c%z~USw`T>x;4&pTe(I z+|)m-B#H$)+v8$d@A(qxwfojeic00}VXeNgZMQpqYb+{VJg3guM_Tn$OO^I*p>gW3 z8_(ZPt-U#}>Ou4NuiGmQG+uf!F*fqtY-4Au7y>HAiQI4#V&THWcu5q1~Ow4-dcsxM3t#FNr zC_0PU{f5o6Gah3TbMoKLw@UA+N_>tj-WxvJIVL64;ybcD-M;L^CqF7*#ao14e|vCj zXz=qb@+~>nN>i+zn}U|aXKv3n-o0yI;-YuUSN&PQ_CNCArE0o~rg(e2)TaFK{-q<& zPi}XqDoI~K%)0-@e#Hh>)fStp?{h7Uj-5UB{xg+acQen$>QK0qfBZT7kxs>tv#wI% zBSzi6zH9kAZfi#BirCCG=5s>*R&T|!Z-$50@0~ES(PEcf5&#O(t<~sdU@2UF5E!@cJ*BP_Qzi`ahBa43Ovs1VJ^_@{>-l6)Mg2U<`&jhZN zzBRqH+EVDX^5<fMC;_M`AJXGjnv~7`Xxob-7xuae8uh8SDP;;)ucS0{qEO? zCKa>GA63*v$!9RNrw^a1%1&laiE^>N#}6TwC(ax{H@)CtHX<0u$+fXXt8J3*GY#{aE-ak2=XsR(J##WV$9uIX?Ck!8 zyc_!0A$i9`W>znBwmeYz_VBs!n}$7$#cYiuS61uhiqAj${sR*6GIUd@ZJ2r7>f{|& zDMeMSlQzvibU?tWeWbIAzqt7FO_}=8@MZEVBcm;snja{RKT&m7@oCn{x68&n|E2R* zQe3Q6`qJTR#2!p1pL4JBTV9{MH+$A^ozyx_;}&)Pql2ouFC4DyDf@lCreOa!<>f!1 zBiK^*h^g~pO_k?jS6jwC3vV|K&Q7qnQJKtYe0q2DOPsZJ{G!#d}slkxMOXw>UI*A2- zo+7zcLwRWvG|^- z^O8{{ROo*>XGmIyssNgoBvZgJktRlqK-GgFEDS?%41+ESL!eD0e-Z7>rbaVd@A~4DW+{Ej#NXj&Y*1>(j${sKBdxi((1=Q|uEryeNO{R}90C;P_ z?Fo7v?eV&cAAq}J9xVeA1x}<8sMi2hajc&*1CtMdx(-~@=URtC4N$Sb7o~I9p|iuE zA!8&nNQRz4F&GR-G=!2VNN`3Az?}|cJdDN19^&9l>6M75ULGLRfv~u;z=z;)SdP#} zIu#uJWyz%8(Sv?I`VjBE6#!|2lvv70U>F#YKm{duCxDiW5d%Jv3>}Tff&YmNIX%O` z&;n)Epltwe89?Jf8)tAB7=r+B0`yqmje!qbulv86)#q9R0!ZgR9U>3|G*GXVOGpDE zMZpalL4(O8P#7XL@UK_*ThRR$!8`@ib)?>)Bq`t`u>eDG7`Pw=v~h&?Ul0O|yKjNx zy;Mf$W*{hKgG3H!1?JWQt)}~i-jm6AAByXWg0HvOS0F*d<_9};HKn$o9~tH=iNyIl zwym?!GdLtbYAfsmNdo=6gTx}C)b`^vF5$+*2{%{FnZ@V1vN&!m7Qx{X5fRRTL81U} zAi-BplA1)NJl573bHyT@Ll%gFfm~7na2mqh*c^hzXK`>opG|7)D{Y~M`;G8q`-5ofEzIAKG3HBdXNazH!xTN$k@%*4QCTP1m|*GIV_CJM_l-> zU|Sw{NXU)r%HwkQT%^y+UMvFjheH2mUx5Un{U6jXfJ3oqAV?9A5CLSgQbwO0&<;18 zMvADChD|$ z0}78PpgWLC?}4+q9N_JUzhsDR&jU9Btzb$5W%`-aOQ(JK5}HH-o#rc}DMX6XEDMbg zfI}15FhIgkj4P0V<5H^tYiL;??G_9ARgMr=4?NYRNKXYxd&(yw!ovfy9|e%E;_9j^ z0C&p`0N-j6K!Rjf@u1L4q&`qj#*o0A9Icz5kbL_uG5>SC@iXklEE129}55nbe2o3^Ax*y0s@If@a7>5tY zcL2tP%+Vji-P}Or24Fl`Mg1|L=@1wwje+wBYzT}1p&uZR?KT+WumV2|m`x4uf6>Ze{xJ-%=#X6p{`dN?&g1 ie4$HJs|DHL=g_7MGQFWI)@%#|zscCyd5e9RrvC-$9|S)D literal 0 HcmV?d00001 diff --git a/test/test_manifest.json b/test/test_manifest.json index 2281e0c78ffaf..ac342b40c4183 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -210,6 +210,13 @@ "link": false, "type": "eq" }, + { "id": "bug1146106", + "file": "pdfs/bug1146106.pdf", + "md5": "a323d3766da49ee40f7d5dff0aeb0cc1", + "rounds": 1, + "link": false, + "type": "eq" + }, { "id": "issue1512", "file": "pdfs/issue1512r.pdf", "md5": "af48ede2658d99cca423147085c6609b", diff --git a/test/unit/cff_parser_spec.js b/test/unit/cff_parser_spec.js index 67594d3f30e5d..754f4630eb517 100644 --- a/test/unit/cff_parser_spec.js +++ b/test/unit/cff_parser_spec.js @@ -262,10 +262,12 @@ describe('CFFParser', function() { var bytes = new Uint8Array([0x00, // format 0x00, // gid: 0 fd: 0 0x01 // gid: 1 fd: 1 - ]); - parser.bytes = bytes; + ]); + parser.bytes = bytes.slice(); var fdSelect = parser.parseFDSelect(0, 2); + expect(fdSelect.fdSelect).toEqual([0, 1]); + expect(fdSelect.raw).toEqual(bytes); }); it('parses fdselect format 3', function() { @@ -273,13 +275,32 @@ describe('CFFParser', function() { 0x00, 0x02, // range count 0x00, 0x00, // first gid 0x09, // font dict 1 id - 0x00, 0x02, // nex gid - 0x0a, // font dict 2 gid + 0x00, 0x02, // next gid + 0x0a, // font dict 2 id 0x00, 0x04 // sentinel (last gid) - ]); - parser.bytes = bytes; - var fdSelect = parser.parseFDSelect(0, 2); + ]); + parser.bytes = bytes.slice(); + var fdSelect = parser.parseFDSelect(0, 4); + + expect(fdSelect.fdSelect).toEqual([9, 9, 0xa, 0xa]); + expect(fdSelect.raw).toEqual(bytes); + }); + + it('parses invalid fdselect format 3 (bug 1146106)', function() { + var bytes = new Uint8Array([0x03, // format + 0x00, 0x02, // range count + 0x00, 0x01, // first gid (invalid) + 0x09, // font dict 1 id + 0x00, 0x02, // next gid + 0x0a, // font dict 2 id + 0x00, 0x04 // sentinel (last gid) + ]); + parser.bytes = bytes.slice(); + var fdSelect = parser.parseFDSelect(0, 4); + expect(fdSelect.fdSelect).toEqual([9, 9, 0xa, 0xa]); + bytes[3] = bytes[4] = 0x00; // The adjusted first range, first gid. + expect(fdSelect.raw).toEqual(bytes); }); // TODO fdArray