From a9455b95ac754be02bc02f1670340d7e093be79e Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 17 Jul 2025 16:50:22 -0400 Subject: [PATCH] Inline long text attachments into backup files. --- ...tem_standard_message_long_text_00.binproto | Bin 729 -> 724 bytes ...tem_standard_message_long_text_01.binproto | Bin 738 -> 130628 bytes ...tem_standard_message_long_text_02.binproto | Bin 810 -> 757 bytes ...tem_standard_message_long_text_03.binproto | Bin 706 -> 130608 bytes ...tem_standard_message_long_text_04.binproto | Bin 748 -> 788 bytes ...tem_standard_message_long_text_05.binproto | Bin 787 -> 130639 bytes ...tem_standard_message_long_text_06.binproto | Bin 738 -> 729 bytes ...tem_standard_message_long_text_07.binproto | Bin 728 -> 130627 bytes ...tem_standard_message_long_text_08.binproto | Bin 798 -> 749 bytes ...tem_standard_message_long_text_09.binproto | Bin 713 -> 130606 bytes ...tem_standard_message_long_text_10.binproto | Bin 762 -> 798 bytes ...tem_standard_message_long_text_11.binproto | Bin 776 -> 130637 bytes ...tem_standard_message_long_text_12.binproto | Bin 726 -> 721 bytes ...tem_standard_message_long_text_13.binproto | Bin 737 -> 130627 bytes ...tem_standard_message_long_text_14.binproto | Bin 812 -> 759 bytes .../securesms/backup/v2/ArchiveErrorCases.kt | 16 ++++ .../v2/exporters/ChatItemArchiveExporter.kt | 77 ++++++++++++---- .../v2/importer/ChatItemArchiveImporter.kt | 83 +++++++++++++++--- .../signal/core/util/CollectionsExtensions.kt | 7 ++ .../java/org/signal/core/util/StringUtil.kt | 3 +- .../signal/core/util/StringExtensionsTest.kt | 4 +- 21 files changed, 158 insertions(+), 32 deletions(-) diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_00.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_00.binproto index 07832b33ba1a254ee560c2658b5bbf3c2d8aacac..47acad9917a85ee9d3d124a9655ba1e056bcfc15 100644 GIT binary patch delta 150 zcmV;H0BQf(1=Izg0zA?IBF_N`0uTfk_My~>kuyRN2(Hnb?ej1Yp}?7pk})6wTCV{T z6ABS>WpH(5X<=?;VPzmfNDwmTz0v8K@3P4$hDRTUUJf@Ra0ysVx&BdC61tJMq zP;P5sS4l!hC;%{o?!fNxFnE3QvZbBRG%28uHF%jC E0F$&jGXMYp delta 173 zcmV;e08;zBFIz%fD)2(Hnb?ej1Yp}?7pk})6wTC@QY z6bceza&>fNAa-SPX>@gSAa!naZ7Q1qA`^6Fcyup#EpusSZeeUKaBN{|Zfb!63P6)y z0ys%pvCnd_2RPvvs@dm=1|ke@YhhPOLP$eeQcrX!05I=_u;%tLS|EH18dRr>Q)-#( bCzo%srJc_-DWH!vc$t|^DN$+jbwwHgR5wFs diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_01.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_01.binproto index a9b70d9345df79400de551abeba8f2f1d41e8c6f..ff862615b50eebbb6cbebda57e0b169445700c7c 100644 GIT binary patch literal 130628 zcmeIu%WK?K6bJC%oy=s$@iiSt9LFxkv{)M<{Yd1cGjZ`NJD zW?$1V@MZDv9Tj(-Y>3S_wvELO{ZR)+%tK4;J1(1UaU;1kFTkWX%J z4F_8$UtfD__Z^+(x>DD58>hOzn>oB;s(fX(YE$R!xzOHTSvllFAzT|8!;Q}^FNRVv z^c2I^b)h44g$F`!cr^(d1T|Z*|E0Y6BB)JrBC;7I=bqK`KxBS zWvP!L1_ delta 235 zcmV;b7634Wo{sJa%ppPX>oOBAah}CWh#yVA`^6Fcyup#EpusSZeeUKaBN{|ZaN2R zOh`;^bWCb&3P1p|-sOP@g-R)-vhkI$_B+(b7)geP2^ZB>8U15h$%6VmWY_Mfmp_mw z{)8Q#FY(J*oBaJ(4hj&qAkkW#y<937hMI`D1~>pxATF3qWiHnVmX}KPr=(eB3MG*5 l(TpUX+r;_`S%b;x0~!D_5efwGpOSzO0T_muk?y}TAOU$K+HBE=AOi&02`i?5(4wIs7NSD`d1 zv#>N(Yd)ira7k)KiGGD{ab|j6VvcS>PGV-B6`M~*ig$otRvnkaiu4y-LQ0(@uNYU^ z9Qn{5{z*H1<}?98ml-AV?Js4#{^zqj*>B})<8RFx8-v(|CeLINN|9J~^LYoG5@%#U zUUp)*r;~>Uvq8(d1MfZ>Mk&Z0D`#Kp{OMx)G>i6CvTQcJTg!}pg*1PbuSn8a?=5;s kior;bi{-=oNew_Rw10fGZlAdVBSdIouKR6Pa=_4wjf;hYMT#M+pGnA;%Q_{qxS+Hou_QA; zFH@ngG%>ZXG*uxnPobbFwIs7NSD`d16C_fU3KYml%}XiGD*-CYECvc{EuXB} z;vL{u>K0L1kd@WQ$mK9OlS!lg+J64ua>vTq*E)Z?m_E&-eU&VmP4Ct+V~ICQHeF*^ z;>pWS4EJ>MaE=P{FVSE&c;B+&**C)|1^J33o%P4EF diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_03.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_03.binproto index b7c10f46b9ca5bd190f5f41b5b16d1b2660d7b07..5d9444420080eeb4c71d28669847607a8178badf 100644 GIT binary patch literal 130608 zcmeIu%WIrf7zg0*OlC5lqqXBeVjQ~|r^VU`Ar}b}UFfL24Ae$qX$)#j%4mYABbq=J z7p+j5NI~NbENHD&j1;j5ZrY6(P^%A< z*&K)L!XGalJkymgJbvujrKj)R_3}q6o?Q3M(f&hg6Y16GeZH-{wC0w}U)cWDo5hvu ztJNFE$13+t6uLV)J38C{6?B~qPKV47AyXJm%pCdYz0#(Q&mTU}b>fw|=jKjUf8E!8 zYIJ$q>1guHh1>-r)sf-tcULARI(mEay_rJgp4%V%40namaDUhpc84$(5(h#$4lPj>)z5Z`Mpys4Yifz=Q9aHz$DutDpWmEI z#)iY4q9hH&Pz|OIW(FZO|eLuPP$d;W;Gkw3M4vxK*IXS-djkOOgU%EKB z_~EhEJ6ms_4WTcJ^UZN0o9qv-*V4t;YK_erB)=c}bZVxjyfU*QPG*<)zy1DfXE70n zq3K#Wx3`wg?5U-1t11En2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N P0t5&UAV7e?IRbwHSfd48 delta 203 zcmV;+05t!w{0G7XpaOru0V2Ht2m%lR819$3)W9)H4+;$k1Q6qlz{aRBA_52jTAl$C z6bceza&>fNAa-SPX>@gSAa!naZ7PKUA`^6Fcyup#EpusSZeeUKaBN{|Zfa}_K-jY$ z3yok&m0hTrE{?K!A4c+=n`fP|yrjU%ewl-qVgbfb0lRz1tn5Poay>`eMAi~;pxi$2 zvbJ~B;O?(8aRL~s+2@D`I0I53t1VYKD4|N*E|8FMnH5j%m!7GQ8Q4g{dw5^?4bst% F8UQKpRWbko diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_04.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_04.binproto index 16d52312cdd71a8ff028860fb8dc249b4cb39dd9..233abedbccfe22946fbf338be51e36661c0852b0 100644 GIT binary patch delta 214 zcmaFEI)!ZkQ|2TlrG6$3CILo?Ukk5vOf>f5<>KaG5$HL1;r2Ae zusBeW*7V7?Oyb;iTn>@#nnizFZI(@rV6q65ID2$f6RQ$uWI$eaVz{T1hX#W|%ew>b zJ{m?TY+$!|sQSLRpoU8-WO-L`*38%H)AKhioqOIyb3wmp#Y`y%BS9{f5A!EA03F}{ L@zJ_{<_e4edgf5? delta 192 zcmV;x06+hf2J8i(0$Az+BIf}J0uTWh@0YpMz%fb<3JVAX5RAdl-=sAn09wic5*G>; zb7634Wo{sJa%ppPX>oOBAah}CWh$EiA`^6Fcyup#EpusSZeeUKaBN{|Zfb!63P6*K z0ytb+vCnd_2RPvvs@dm=1|ke@YhhPOLP$eeQcrX!05I=_u;%tLS|EH18dRr>Q)-#( uCzo%srJc_-DWH!vc$t|^DN$+jbwwHgG7$;{@Sl=^5CIs5n33+kG9Upb!blzf diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_05.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_05.binproto index c486c623dbc39d1c430c169aa3df5b55602bc504..fd35913ba7a0bf459b153166026b917459a2021d 100644 GIT binary patch literal 130639 zcmeIu%WIrf7zg0*OlC5l<83;SIF4P6X|Xm!$VH+=7dmP$1GSM@8iQJsGMZp&L=&ju zA{9zfQ8e`e7BtpYEEH@J+_WI53tKfoikFBQOtSJ;s-O*u#Hrb67ybp$%UQhq&N(l< z&!#wJXa0C?@@z|?`0TM8=U#Yl=fRII-n;hOqn-QLB+{!c_+o2$ZuK2ky}Iq|w;L{9 zH#Bt1;6UY};bLo3b5nEUzk-%?!Re6sF=UFpiHXBMe^A=E;gtjXT28((_43rIq2I<@ z=ld77o{mPon#o<%H`Ld=?f%N}a8rAGp*>TqJaG4;pI&k_n_4*jM0x$MFMV9O^X#5o ztH$fTubWx@SJU&kZ0~#F_2~1`WqaOF;Gmia#Ci|M1$j zLeqh^sW;c3eEPPoN<(k=HJhh4JrV^k6@ zNv$Z+uh1>dOwUWq(JjbH%*?Z5&-U@~$u058%BkmaVAyo;X(M~9m*(P4AE$2kW_{&^ zglAhDr}))S$-mJjn||4)Tz|J>y8Q%=e=V}}^go`CnEmH(I3Jh5Hie5(bN9w*Nwm%C z*uiGO5Tu|t!#7p$I_I?MUSF3liA?2Eo$&r*kIKB;M}BcdHlKXSEX80X$i?zu{-g$= bciU!6e7E07RDls9HnCTLMWP)nroaRMUI}qX diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_06.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_06.binproto index 6355df9ce7e0f4f2051279eef9de8eaa93f37510..567adf547bb3c869561f77a0f30059323eccb6a7 100644 GIT binary patch delta 201 zcmV;)05<>P1=$6l0)N*5BGds00uTWh{Grr{kuypS3JVAX5aW!%#;7nV09v;J5)%p$ za%FIJWNBe;WMO3>VsdqKWh$QmA`^6Fcyup#EpusSZeeUKaBN{|ZaM}`cw|dZPHKJ% zK&WTvv{ZFNS=2LnJjC#aSMn=onU)X{M3{7+KhkLH{!F)HPLpi{96lJT+2@D`A_-Yg zZfjvzNkT{{12BZ{!0z!eS|A?AcL%LQ^3i9OIESho20V?mb~E}^gYzGFVl1yq71A02 DXZ238 delta 198 zcmV;%06G8J1>yyu0%70*BHaN90uTWh@t3*Oz%fb<3JVAX5aW!%#;7nV09wES5)=v& zVsdqKWgvECa%psRb0BqYb!{rA0U{H0Wq5Qicr9~jXKrC^EpTjMX>K|SWJ^#^bw*fu zaB6CS0SZ8qa{?$?*1r$?9>#YEtwZwBXO=jJsvQPAjkR_&80@67)(0XCZfjvzNkT|N zT2fDRC<8F>gs|rJF=g;HDL^y3i^KrFe;`(IhKxr3%_0lsMImcHv*}yqv|$@0|0( z`)r6qcKXj(C(bt46`nqN)9iB(?l}0#lD(_HJ<_p%RU*COqA#|TW>?;E)hk=Se!G6@ zn!&+a2l~qo4Ha4%n;M%M{uMNz4^D^7Pa#w2NsJ%<<-_9obuS;-*L>pj$rmS24gNmb zGS@f1<#aUs)pYKX-of6Ut@oFQh8o-2>e@1e@&k82dh*gE+0^{8CrWF7d*Rdaoo9FN zTrpPjL(TNcbB)jDvOUMcYtiS$%XWW|%1^I<{7gPspDb3UG$hwl#+`;Li(;jMSuVS0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF k5FkK+009C72oShHpk-Mmd1hkoNH*k0f1P{tz3U6%FUJE6>Hq)$ delta 225 zcmV<703QFt{0GZXk4WX>)XGadl-Nb75>{Dun?e6Le*GbT4=O0me`PyL(y4tn2`CJxAL_))H`_ z+&=KKws+Lv?yoa(0vM{<=ZFS415zNXEmt`xp-S5>kdSei6;JJ#o~e%+*hs;9cwhJp b($S9^05TB@1n{4dfDi!~hM1A=zcL^JBOYC~ diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_08.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_08.binproto index eed617322043adb95ffc1a6c9aeba8fa08a0bbc1..63e18781fe302fc14ad566823eab7ac634ae3322 100644 GIT binary patch delta 193 zcmbQo_Lg-4Q{Zbxr5B7GOahD&{}x{9m}u<9#>K+HV#E-2iBU*^i?5(4wIs7NSD`d1 zv#>N(YdWKna7k)KiGGD{ab|j6VvcS>PGV+WRvni^WV>e3pH`b?lgpVbQY6kEoz=vu z#2Fcomz@~y>ExloV9@gJz`KuzQ3@N_Egq`AFD|Izk_uVg6`VEmwfgk@O-tvVH_=?s oZ(1=^ior;bi{-=oNew_pwSRoHZlAdVBSdIouKZXG*uxnPobbFwIs7NSD`d16C_fU3KYml%}XiGD*-CYECvc{&7Q2!B+k;v z$mK9OkjX+lYUBB$4eS;VC05;h-od8Clb4+s?&;*=92MkWqQPMBzGcI+Z-!9{lh-my u7|it549xso>MF%xB*?|`Vg95Bpaa`xOnkTBNP!U|G_hBJMWP)d!~_6C;7D}< diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_09.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_09.binproto index 302f3f18ce0ca04a4f35042ef9915d5d37c27220..30dc682fc4a595c847a7108f7f8f961856df473a 100644 GIT binary patch literal 130606 zcmeIu%WIrf7zg0*OlC5lQ>)`Z;y89Orp4L_Ar}b}UFfL24Ae$qX$)$el+grJBbq=J z7p+j5NI~NbENHD&8py&TxM??DK&_G>#Y;pDrdfF_B4`Ie#;Iz zaeVxivC-;76UFY1&W_IZe+6CVgR>#?W5^UMiJ7^d-!E<1{PNL5U8mlde`)^o_-_Zg z&y1|>I2%oVwV1njczn3B^Zx3@L`QFLp*K^kK5+M=pI&l2n_4;fM0wM%FMd+J^W5~l zwNowMw=Ayvv*Wp3w(@RxE&9B4`Sg3K{Nk3!f5|7?lBLF!_TKD@4{ z&~db9{@A8d2X7mwwp9kN-L^3F-O}vVh3XZ#rtJf_=0myMSUKcFFgT&eqpSbjHMK*fr^TBLqUoY%OqXVQFqU2Ww17Ol@>b zYHSKX0J7fYfd_?3DWkISm9X|Z)W{e~hK30j)m0h&Tgig@K4ePQ?x>ePkSP9y9iA`o z%UGNI{Z|eO5Vj!ETAjUIDj0^Eh`0tg08$_>m`!Ca*9n%FO7*9tS!D_(knhoqB%a&E M`U+Wt$>{?c0C^`@3IG5A diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_10.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_10.binproto index 35bfe232ee990bf692a71a1049cb99fe05c4c2e2..3e487e55de02fac494e33ea7ee1e57b8376dfc07 100644 GIT binary patch delta 240 zcmeyxI*)AuQ}!GtrI}0|OahD&4U4XHOf>f5=i=pH5$HL1;r4Wks~j+L{ob^dfQeVRr4Dp@w0-mPWE mze1Wn%U2}ntoIhZB*kDP$i?zu{-g$=x7t5GTDQ+!fe`@7J6iPs delta 247 zcmV3JnMZ5RAdl-=sA<0tf+G)&UY1 z3KerY%OqXVQFqU3S>)A zPIX3Dd2niKfdL9Ym~@{%(rD}cOt)iBsjD;ggDA052NJcdtCPM04{X-I5Bna*cL%LQ z^3i9OIESho20V?mb~704q_WlrA`EV8VOL2)NJCmuPjn~)Fz@^~#|Q_pSf#Xy@ze66rM;e7U_mzxIx+UfuD{I}Mj^ z7#g}|aG>(gaIv+ixv9DFUqQ>c;B?6R6f(u$#N^>$J}hnC^vc2gEhpZbetG)T(C_1| z3;m1RPe&tP&*m=b8|v%baerlaxT(Fp(4Hw)9=Q9_&n`KdO)VaOqP+39mp-lBd3NvE znu)p}>Sove)%1KW+xvbv5Pea)Z0`rD{Osn(&*YO0$x?MnV{$`vOvn06@#o|HA79&6 zXgb(7{no}4Pv6#6Y3S{~X6sDP_j8A~%v3JV)okm!H6P05>dGM>is9kIMhW^w0y2hRJ-MWx|+IZIa-c$wQ*=n#241) zl5wInTAeGzH4XKdZ1ux`s*uRnJQZ@|Ax`F=4AuM0s+lOdDH>gQ6>{e%iQ;jO#oCp-Q~O%1%8IW@TL$hyZCubAte z`{Y>tz4f;r3ZWy43$<|~o9qlHm(mR%Emb$Gl8hbrd}6Y#T$@S8$x_sFWGS87vy{$^ zEv4@oQUnMPAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB=E> j1X@>Rl4qv&jb=lB{MUuI-@C3DR-(ygvdR3!N|g8;mB$b6 delta 274 zcmX^6hrNSs0aJY&lTtGi2a^D!#JA}?uN*M+V&h`rU~yuIddnze%VnLCSzJ(Bl30?N zpO>jnSelqxSemMkn5R%slv#dJ`rT&J^{3JAK~De#sji2dgXMfAC(q*e}IkB*?|`Vg95BpdZ_2OnkTBNP!U| PG_hBJi$$UxBE|#&Fg|a3 diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_12.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_12.binproto index 6f7a66aa68276baf861b22a94e9c59f12d56fb77..2f023f9318fafe5afc168a9e13a76c801951cde9 100644 GIT binary patch delta 165 zcmV;W09yan1VsdqKWh$2eA`^6Fcyup#EpusSZeeUKaBN{|ZfbrCKv{<=a{Prn zrju0yI6fH7#hQWzA_-YgZfjvzNkT{{05F8^!0z!eS|G3oIN>Glb8vhL8dRr>Q)-#( TCzo%srJc_-DWH!vc$pdioOV5N delta 170 zcmV;b09F6d1=aMwP0SZ8qTLL&q zTCvY^um?Ed7^>Olhz24IZfjvzNkT|NT2fDRC;%|;gs|rJFnE3Q YvZbBRG%28uHF%ksO({`n^L0fU07d#g{r~^~ diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_13.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_13.binproto index 96f84920b498c64b7c312ac4cf1a91b59dba7de3..1b378324a2cd29e975a9038d4b47da451d3d851f 100644 GIT binary patch literal 130627 zcmeIu%WIrf7zg0*OlC5lqqWn4#4&a;rp4L_As3AjUFfL24782J(iqg5l+grJBbq=J z7pYQ`N=g;HDL^y3i^KrFe;`(IhKxr3%_0NSx|MyYMe~Ue4m>cg}g? zeKy1)JN?Hi6K9+23QrxqVfNYkcO3k9$==o99O-y?;J#mig2daHiv zn!&-F2l~qo3>8`$n;M%M{uMNz3r>g3k0Dd&NsJ%<`Gey6buS&**L>o&$rmP14gNOT zGS@f1<#aUs<#g_%-of6Ut@oCPh8o-2>e@1e@_lzceDdNW+0^{8$4YB|eg2d3?PquI zTrpPjea-aBKO3LPWqXc?{n2N|OLo7X%1^I<^h`ckpDb3UG$hwl#Zbz!&LdwT-D~zTk@e)s;nIHp%AVNP2swymls1% zG4vM0wsoN+bcK6DUwAO=2qPhkg~Yy)jzdipMT_UUM5SB&yQ`{+7Nf;DR~?6jM0{a& zE*U3UqUE`|xT?N3ldXLCPt_&zRZoW8Xo!=!$3x{lvurAgZj46$c@=W!CyByyb6+oB zUn+cY*YN+I?$k{K{oBgDsm|^FLwyfjp88>U*WpduXUE%rPfhf{ojEnI`OQ_2%wIOs zHS^KY+PiBv9txp7itDQ5L^jzG-djl5zq3%;tU~g{{!hooTT4qbOX6g9&+(Jr9=xWM zh(q^_3+ddhg>+`;Li&zDMSuVS0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF k5FkK+009C72oN|=pk-Mmd1hkoNH*k0f0=vZ-D?ZsFDWAoi2wiq delta 234 zcmVZXk4WX>)XGadl-Nb75>{Dvkjn6Le*GbT4=XOloWjKmfAd<$(u;0E|^VaF4qZ`mrC`gq*-MOC6MpY kj3l1h#QF+ZgURUw8UQj83Iy<1aU?!Pi10o6obrT_o{ diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_14.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_long_text_14.binproto index 2411bb0fe5b8acecbc0d533bdf9252b32c02ef06..533599954693c82c3af03d96d30d24f0144fb26b 100644 GIT binary patch delta 186 zcmZ3(_MLSBli^oJrB93;OahD&EsL&nOf>f5;9}!o@nYg&in_%pB*4X2P?TDdS(>X* znv_{snyNK_avYPmGMi6Dig$otRvnkaiu4y-LQ0(@uNYU^9Qn{5{z*H1<}?98m&vV6 z8Zr{AZa(i|Q{s#a$jeR)_jK~mU^ZxZci`Pe!zhKx8<|8*euXrDmaj>-KLs9?$ delta 254 zcmey)x`u55Q`{;hrR7WjnSelqxSemMkn5R%slv}#DrT}+>5(Y{KS&8By2nX$y1C7Z6X zEAixICx&}Ec{oP}`Il%g8@zAX@a&sml!AOklFoW>(Mv*4uNxdbq;^M1uK$gx($Xhq s^(XRvO>1(OVlWcqV)-zCQUlOOZ8Ijm+i#@62oajtE5IVr4iRDk03K^z-T(jq diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveErrorCases.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveErrorCases.kt index 433e6e3bc0..ddc0f7bdb3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveErrorCases.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveErrorCases.kt @@ -170,6 +170,22 @@ object ExportOddities { return log(sentTimestamp, "Invalid e164 in sessions switchover event. Exporting an empty event.") } + fun undownloadedLongTextAttachment(sentTimestamp: Long): String { + return log(sentTimestamp, "Long text attachment was not yet downloaded. Falling back to the known body with an attachment pointer.") + } + + fun unreadableLongTextAttachment(sentTimestamp: Long): String { + return log(sentTimestamp, "Long text attachment was unreadable. Falling back to the known body with an attachment pointer.") + } + + fun unopenableLongTextAttachment(sentTimestamp: Long): String { + return log(sentTimestamp, "Long text attachment failed to open. Falling back to the known body with an attachment pointer.") + } + + fun bodyGreaterThanMaxLength(sentTimestamp: Long, length: Int): String { + return log(sentTimestamp, "The body length was greater than the max allowed ($length bytes). Trimming to fit.") + } + private fun log(sentTimestamp: Long, message: String): String { return "[ODDITY][$sentTimestamp] $message" } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt index 5ab938daae..ab7458536b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt @@ -13,13 +13,17 @@ import org.signal.core.util.Base64 import org.signal.core.util.EventTimer import org.signal.core.util.Hex import org.signal.core.util.ParallelEventTimer +import org.signal.core.util.StringUtil import org.signal.core.util.concurrent.SignalExecutors +import org.signal.core.util.emptyIfNull import org.signal.core.util.isNotNullOrBlank +import org.signal.core.util.kibiBytes import org.signal.core.util.logging.Log import org.signal.core.util.logging.logW import org.signal.core.util.nullIfBlank import org.signal.core.util.nullIfEmpty import org.signal.core.util.orNull +import org.signal.core.util.readFully import org.signal.core.util.requireBlob import org.signal.core.util.requireBoolean import org.signal.core.util.requireInt @@ -82,8 +86,10 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent +import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.linkpreview.LinkPreview +import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.mms.QuoteModel import org.thoughtcrime.securesms.payments.FailureReason import org.thoughtcrime.securesms.payments.State @@ -105,6 +111,8 @@ import org.thoughtcrime.securesms.backup.v2.proto.BodyRange as BackupBodyRange import org.thoughtcrime.securesms.backup.v2.proto.GiftBadge as BackupGiftBadge private val TAG = Log.tag(ChatItemArchiveExporter::class.java) +private val MAX_INLINED_BODY_SIZE = 128.kibiBytes.bytes.toInt() +private val MAX_INLINED_BODY_SIZE_WITH_LONG_ATTACHMENT_POINTER = 2.kibiBytes.bytes.toInt() /** * An iterator for chat items with a clever performance twist: rather than do the extra queries one at a time (for reactions, @@ -361,11 +369,6 @@ class ChatItemArchiveExporter( else -> { val attachments = extraData.attachmentsById[record.id] - if (attachments?.isNotEmpty() == true && attachments.any { it.contentType == MediaUtil.LONG_TEXT } && record.body.isNullOrBlank()) { - Log.w(TAG, ExportSkips.invalidLongTextChatItem(record.dateSent)) - continue - } - val sticker = attachments?.firstOrNull { dbAttachment -> dbAttachment.isSticker } if (sticker?.stickerLocator != null) { @@ -963,6 +966,8 @@ private fun BackupMessageRecord.toRemoteDirectStoryReplyMessage(mediaArchiveEnab val isReaction = MessageTypes.isStoryReaction(this.type) + val (bodyText, longTextAttachment) = this.getBodyText(attachments) + return DirectStoryReplyMessage( emoji = if (isReaction) { this.body @@ -972,10 +977,10 @@ private fun BackupMessageRecord.toRemoteDirectStoryReplyMessage(mediaArchiveEnab textReply = if (!isReaction) { DirectStoryReplyMessage.TextReply( text = Text( - body = this.body, + body = bodyText, bodyRanges = this.bodyRanges?.toRemoteBodyRanges(this.dateSent) ?: emptyList() ), - longText = attachments?.firstOrNull { it.contentType == MediaUtil.LONG_TEXT }?.toRemoteFilePointer(mediaArchiveEnabled) + longText = longTextAttachment?.toRemoteFilePointer(mediaArchiveEnabled) ) } else { null @@ -985,23 +990,25 @@ private fun BackupMessageRecord.toRemoteDirectStoryReplyMessage(mediaArchiveEnab } private fun BackupMessageRecord.toRemoteStandardMessage(exportState: ExportState, mediaArchiveEnabled: Boolean, reactionRecords: List?, mentions: List?, attachments: List?): StandardMessage { - val text = body.nullIfBlank()?.let { + val linkPreviews = this.toRemoteLinkPreviews(attachments) + val linkPreviewAttachments = linkPreviews.mapNotNull { it.thumbnail.orElse(null) }.toSet() + val quotedAttachments = attachments?.filter { it.quote } ?: emptyList() + val messageAttachments = attachments + ?.filterNot { it.quote } + ?.filterNot { linkPreviewAttachments.contains(it) } + ?.filterNot { MediaUtil.isLongTextType(it.contentType) } + ?: emptyList() + val hasVoiceNote = messageAttachments.any { it.voiceNote } + + val (bodyText, longTextAttachment) = this.getBodyText(attachments) + + val text = bodyText.nullIfBlank()?.let { Text( body = it, bodyRanges = (this.bodyRanges?.toRemoteBodyRanges(this.dateSent) ?: emptyList()) + (mentions?.toRemoteBodyRanges(exportState) ?: emptyList()) ) } - val linkPreviews = this.toRemoteLinkPreviews(attachments) - val linkPreviewAttachments = linkPreviews.mapNotNull { it.thumbnail.orElse(null) }.toSet() - val quotedAttachments = attachments?.filter { it.quote } ?: emptyList() - val longTextAttachment = attachments?.firstOrNull { it.contentType == "text/x-signal-plain" } - val messageAttachments = attachments - ?.filterNot { it.quote } - ?.filterNot { linkPreviewAttachments.contains(it) } - ?.filterNot { it == longTextAttachment } - ?: emptyList() - val hasVoiceNote = messageAttachments.any { it.voiceNote } return StandardMessage( quote = this.toRemoteQuote(exportState, mediaArchiveEnabled, quotedAttachments), text = text.takeUnless { hasVoiceNote }, @@ -1012,6 +1019,40 @@ private fun BackupMessageRecord.toRemoteStandardMessage(exportState: ExportState ) } +/** + * Retrieves the body text, reading from a long text attachment if necessary. Will return an optional [DatabaseAttachment] that, if present, indicates that + * you should set it as the value for [StandardMessage.longText]. + */ +private fun BackupMessageRecord.getBodyText(attachments: List?): Pair { + val longTextAttachment = attachments?.firstOrNull { it.contentType == "text/x-signal-plain" } + if (longTextAttachment == null) { + return this.body.emptyIfNull() to null + } + + if (longTextAttachment.uri == null || longTextAttachment.transferState != AttachmentTable.TRANSFER_PROGRESS_DONE) { + return StringUtil.trimToFit(this.body.emptyIfNull(), MAX_INLINED_BODY_SIZE_WITH_LONG_ATTACHMENT_POINTER) to longTextAttachment + } + + val longText = try { + PartAuthority.getAttachmentStream(AppDependencies.application, longTextAttachment.uri!!)?.readFully()?.toString(Charsets.UTF_8) + } catch (e: IOException) { + Log.w(TAG, ExportOddities.unreadableLongTextAttachment(this.dateSent)) + return this.body.emptyIfNull() to longTextAttachment + } + + if (longText == null) { + Log.w(TAG, ExportOddities.unopenableLongTextAttachment(this.dateSent)) + return StringUtil.trimToFit(this.body.emptyIfNull(), MAX_INLINED_BODY_SIZE_WITH_LONG_ATTACHMENT_POINTER) to longTextAttachment + } + + val trimmed = StringUtil.trimToFit(longText, MAX_INLINED_BODY_SIZE) + if (trimmed.length != longText.length) { + Log.w(TAG, ExportOddities.bodyGreaterThanMaxLength(this.dateSent, longText.length)) + } + + return trimmed to null +} + private fun BackupMessageRecord.toRemoteQuote(exportState: ExportState, mediaArchiveEnabled: Boolean, attachments: List? = null): Quote? { if (this.quoteTargetSentTimestamp == MessageTable.QUOTE_NOT_PRESENT_ID || this.quoteAuthor <= 0 || exportState.groupRecipientIds.contains(this.quoteAuthor)) { return null diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt index e778abcd86..2603ed5a2b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt @@ -10,6 +10,7 @@ import androidx.core.content.contentValuesOf import org.signal.core.util.Base64 import org.signal.core.util.Hex import org.signal.core.util.SqlUtil +import org.signal.core.util.asList import org.signal.core.util.forEach import org.signal.core.util.logging.Log import org.signal.core.util.orNull @@ -40,6 +41,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.Sticker import org.thoughtcrime.securesms.backup.v2.proto.ViewOnceMessage import org.thoughtcrime.securesms.backup.v2.util.toLocalAttachment import org.thoughtcrime.securesms.contactshare.Contact +import org.thoughtcrime.securesms.database.AttachmentTable import org.thoughtcrime.securesms.database.CallTable import org.thoughtcrime.securesms.database.GroupReceiptTable import org.thoughtcrime.securesms.database.MessageTable @@ -63,6 +65,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.PaymentTombstone import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent +import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.mms.QuoteModel import org.thoughtcrime.securesms.payments.CryptoValueUtil import org.thoughtcrime.securesms.payments.Direction @@ -74,6 +77,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.stickers.StickerLocator import org.thoughtcrime.securesms.util.JsonUtils import org.thoughtcrime.securesms.util.MediaUtil +import org.thoughtcrime.securesms.util.MessageUtil import org.whispersystems.signalservice.api.payments.Money import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.util.UuidUtil @@ -371,14 +375,17 @@ class ChatItemArchiveImporter( } if (this.directStoryReplyMessage != null) { - val longTextAttachment: Attachment? = this.directStoryReplyMessage.textReply?.longText?.toLocalAttachment( - importState = importState, - contentType = "text/x-signal-plain" - ) + val (trimmedBodyText, longTextAttachment) = this.directStoryReplyMessage.parseBodyText(importState) + if (trimmedBodyText != null) { + contentValues.put(MessageTable.BODY, trimmedBodyText) + } if (longTextAttachment != null) { followUps += { messageRowId -> - SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, listOf(longTextAttachment), emptyList()) + val ids = SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, listOf(longTextAttachment), emptyList()) + ids.values.firstOrNull()?.let { attachmentId -> + SignalDatabase.attachments.setTransferState(messageRowId, attachmentId, AttachmentTable.TRANSFER_PROGRESS_DONE) + } } } } @@ -396,23 +403,29 @@ class ChatItemArchiveImporter( attachment.toLocalAttachment() } - val longTextAttachments: List = this.standardMessage.longText?.toLocalAttachment( - importState = importState, - contentType = "text/x-signal-plain" - )?.let { listOf(it) } ?: emptyList() + val (trimmedBodyText, longTextAttachment) = this.standardMessage.parseBodyText(importState) + if (trimmedBodyText != null) { + contentValues.put(MessageTable.BODY, trimmedBodyText) + } val quoteAttachments: List = this.standardMessage.quote?.toLocalAttachments() ?: emptyList() - val hasAttachments = attachments.isNotEmpty() || linkPreviewAttachments.isNotEmpty() || quoteAttachments.isNotEmpty() || longTextAttachments.isNotEmpty() + val hasAttachments = attachments.isNotEmpty() || linkPreviewAttachments.isNotEmpty() || quoteAttachments.isNotEmpty() || longTextAttachment != null if (hasAttachments || linkPreviews.isNotEmpty()) { followUps += { messageRowId -> val attachmentMap = if (hasAttachments) { - SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, attachments + linkPreviewAttachments + longTextAttachments, quoteAttachments) + SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, attachments + linkPreviewAttachments + longTextAttachment.asList(), quoteAttachments) } else { emptyMap() } + if (longTextAttachment != null) { + attachmentMap[longTextAttachment]?.let { attachmentId -> + SignalDatabase.attachments.setTransferState(messageRowId, attachmentId, AttachmentTable.TRANSFER_PROGRESS_DONE) + } + } + if (linkPreviews.isNotEmpty()) { db.update(MessageTable.TABLE_NAME) .values(MessageTable.LINK_PREVIEWS to SignalDatabase.messages.getSerializedLinkPreviews(attachmentMap, linkPreviews)) @@ -453,6 +466,54 @@ class ChatItemArchiveImporter( return MessageInsert(contentValues, followUp) } + /** + * Text that we import from the [StandardMessage.text] field may be too long to put in a database column, needing to instead be broken into a separate + * attachment. This handles looking at the state of the frame and giving back the components we need to insert. + * + * @return If the returned String is non-null, then that means you should replace what we currently have stored as the body with this new, trimmed string. + * If the attachment is non-null, then you should store it along with the message, as it contains the long text. + */ + private fun StandardMessage.parseBodyText(importState: ImportState): Pair { + if (this.longText != null) { + return null to this.longText.toLocalAttachment(importState, contentType = "text/x-signal-plain") + } + + if (this.text?.body == null) { + return null to null + } + + val splitResult = MessageUtil.getSplitMessage(AppDependencies.application, this.text.body) + if (splitResult.textSlide.isPresent) { + return splitResult.body to splitResult.textSlide.get().asAttachment() + } + + return null to null + } + + /** + * Text that we import from the [DirectStoryReplyMessage.textReply] field may be too long to put in a database column, needing to instead be broken into a separate + * attachment. This handles looking at the state of the frame and giving back the components we need to insert. + * + * @return If the returned String is non-null, then that means you should replace what we currently have stored as the body with this new, trimmed string. + * If the attachment is non-null, then you should store it along with the message, as it contains the long text. + */ + private fun DirectStoryReplyMessage.parseBodyText(importState: ImportState): Pair { + if (this.textReply?.longText != null) { + return null to this.textReply.longText.toLocalAttachment(importState, contentType = "text/x-signal-plain") + } + + if (this.textReply?.text == null) { + return null to null + } + + val splitResult = MessageUtil.getSplitMessage(AppDependencies.application, this.textReply.text.body) + if (splitResult.textSlide.isPresent) { + return splitResult.body to splitResult.textSlide.get().asAttachment() + } + + return null to null + } + private fun ChatItem.toMessageContentValues(fromRecipientId: RecipientId, chatRecipientId: RecipientId, threadId: Long): ContentValues { val contentValues = ContentValues() diff --git a/core-util/src/main/java/org/signal/core/util/CollectionsExtensions.kt b/core-util/src/main/java/org/signal/core/util/CollectionsExtensions.kt index 5f06e46951..ba39ac6cc9 100644 --- a/core-util/src/main/java/org/signal/core/util/CollectionsExtensions.kt +++ b/core-util/src/main/java/org/signal/core/util/CollectionsExtensions.kt @@ -22,3 +22,10 @@ fun List.swap(i: Int, j: Int): List { Collections.swap(mutableCopy, i, j) return mutableCopy.toList() } + +/** + * Returns the item wrapped in a list, or an empty list of the item is null. + */ +fun E?.asList(): List { + return if (this == null) emptyList() else listOf(this) +} diff --git a/core-util/src/main/java/org/signal/core/util/StringUtil.kt b/core-util/src/main/java/org/signal/core/util/StringUtil.kt index 7bdc21c59c..47211e53fc 100644 --- a/core-util/src/main/java/org/signal/core/util/StringUtil.kt +++ b/core-util/src/main/java/org/signal/core/util/StringUtil.kt @@ -1,6 +1,7 @@ package org.signal.core.util import android.text.SpannableStringBuilder +import okio.utf8Size import java.io.ByteArrayOutputStream import java.io.IOException import java.nio.charset.StandardCharsets @@ -27,7 +28,7 @@ object StringUtil { return "" } - if (name.toByteArray(StandardCharsets.UTF_8).size <= maxByteLength) { + if (name.utf8Size() <= maxByteLength) { return name } diff --git a/core-util/src/test/java/org/signal/core/util/StringExtensionsTest.kt b/core-util/src/test/java/org/signal/core/util/StringExtensionsTest.kt index d286ee698e..3efa760aaf 100644 --- a/core-util/src/test/java/org/signal/core/util/StringExtensionsTest.kt +++ b/core-util/src/test/java/org/signal/core/util/StringExtensionsTest.kt @@ -12,7 +12,7 @@ import org.junit.Test class StringExtensionsTest { @Test - fun `splitByByteLength fuzzing`() { + fun `splitByByteLength - fuzzing`() { val characterSet = "日月木山川田水火金土空海花風雨雪星森犬猫鳥魚虫人子女男友学校車電話本書時分先生愛夢楽音話語映画新古長短高低東西南北春夏秋冬雨雲星夜朝昼電気手足目耳口心頭体家国町村道橋山川本店仕事時間会話思考知識感情自動車飛行機船馬牛羊豚鶏鳥猫犬虎龍" for (stringSize in 2100..2500) { @@ -28,7 +28,7 @@ class StringExtensionsTest { } @Test - fun idk() { + fun `splitByByteLength - long string`() { val myString = """ すべての人間は生まれながらにして自由であり、尊厳と権利において平等である。彼らは理性と良心を授けられており、互いに兄弟愛の精神をもって行動しなければならない。