From 6260fc09a3965ab18e2e13b3b0d6459694264c20 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
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 2a9f3cb0e..2c7578f48 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 f9f1c4102..d0b08ac30 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}ro2zw<kj*aZrG
zTyR$|(-cQAL>m>yG<A1pnxg)sMq!9WSO5$HT_2^&K<W_$^-;+Tq>z+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;w<l7>hJQ8?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`Lx8r7<V^PCY2<U}@#hXgM!S1Mvd{GGG$o{DI6xjVy>EAuW;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!<Hs0#bmzCf@V@Pwr9Ds8tjPRYRg+{-*=|llk4Ao|
z@Q;~J6pHU3N&DdYcuM>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(PE<DKq@Z2P}Op5k#X7U#j(Z?
z#>cf5&#O(t<~sdU@2UF5E!@cJ*BP_Qzi`ahBa43Ovs1VJ^_@{>-l6)Mg2U<`&jhZN
zzBRqH+EVDX^5<<v4_=vN)T9?ZO<ITEy12N^-FT|So9{lUU9YeBYU)LN0^e)e4fotP
z|Jo3bZS=e~!osY4Q$p>fMC;_M`AJXGjnv~7`Xxob-7xuae8uh8SDP;;)ucS0{qEO?
zCKa<iT35BXpDSGJ^tj@5)R>>GA63*v$!9RNrw^a1%1&laiE^>N#<cRX9v5kqFGPmz
zv9!uv_R=%X&M5Qv$TePqu(eLF?81&b&2HgL<E&T^I+B0wRG4a+<XcyNanX(=`t<n@
zUljNkiq1?+E8KiK<NEu8_kVg+x8FWnKc%j&-Y3<JBI}!rJeu2lhIxeTEO%;9hHZPh
zZ`EAC>}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!<H@kg`PpF9&T%J8q_
zmfL)OqDf-We(P?2eN;iq*n^3cA=6*PPkyOhvu|Fl)o8QxV~XZP`JeMcf4@w1{-dF~
zB4#Ltg2Y3U5{_}Pj}*n}f$8H9(hcAyKs-W{lIxW^gH{iZK>#d}slkxMOXw>UI*A2-
zo+7zcLwRWvG|^-<oTHK{D1=~jPxtcDCLvN69C0BCgo6_Zn}<h$F_A&0Qp!Cw3KfX}
zHBe7VPHGHb6n5aIu;)UH5y*6Yq*4(JM{zJJF_7v|dJ)tI5GY9?kYT|=4GFS`in;wP
z3L+IsK-S5KgC}5W(h&iI#QJDbuhb|I2T?TGPH9M{VM&5cry|u*0U)BkP`7?UKe`D>
z^O8{{ROo*>XGmIyssNgoBvZgJktRlqK-GgFEDS?%41+ESL!<yOLBPYsOr#KvfE|R(
z>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|<JAc=Yv@55wWiU*J~
zDnU>7)D{Y~M`;G8q`-5ofEzIAKG3HBdXNazH!xTN$k@%*4QCTP1m|*GIV_CJM_l->
zU|Sw{NXU)r%HwkQT%^y+UMvFjheH2mUx5Un{U6jXfJ3oqAV?9A5CLSgQbwO0&<;18
z<I2MLY!1R<vFLn}=^;Pk-7J9$0A~PzqAiB`7erFp1ihRDNcY)*h6YM|>MvADChD|$
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#*o0<v@hweE&UZ{^=IX7?)+=EgoZ<YRcbXt@MRit
z5YA#@I0qwGIL>A9Icz5kbL_uG5>SC@iXklEE129}55nbe2o3^Ax*y0s@If@a7>5tY
zcL2tP%+Vji-P}Or24Fl`Mg1|L=@1wwje+wBYzT}1p&uZR?KT+Wum<VE;&KP?%X1sF
z2Z3ROY$GtvptS^x7`zrx&_MkNE_bjzH)4>V2|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 2281e0c78..ac342b40c 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 67594d3f3..754f4630e 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