From 8763c7ab44c404ecbbbe8c2b4822e35ef955da94 Mon Sep 17 00:00:00 2001 From: Enrico Colasante Date: Thu, 22 Aug 2024 10:39:18 +0200 Subject: [PATCH] fix: Set CSP header for attachments [DHIS2-17310](2.39) (#18400) * fix: Set CSP header for attachments [DHIS2-17310] * Do not send attachment header for images * Update dhis-2/dhis-web-api/src/test/java/org/hisp/dhis/webapi/controller/event/TrackedEntityInstanceControllerTest.java --- .../TrackedEntityInstanceController.java | 16 ++++- .../hisp/dhis/webapi/utils/HeaderUtils.java | 7 +- .../TrackedEntityInstanceControllerTest.java | 68 +++++++++++++++++- .../src/test/resources/images/dhis2.png | Bin 0 -> 11417 bytes 4 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 dhis-2/dhis-web-api/src/test/resources/images/dhis2.png diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/TrackedEntityInstanceController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/TrackedEntityInstanceController.java index 85facf7e7671..50eeef64c5bf 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/TrackedEntityInstanceController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/TrackedEntityInstanceController.java @@ -69,6 +69,8 @@ import org.hisp.dhis.dxf2.importsummary.ImportSummary; import org.hisp.dhis.dxf2.webmessage.WebMessage; import org.hisp.dhis.dxf2.webmessage.WebMessageException; +import org.hisp.dhis.external.conf.ConfigurationKey; +import org.hisp.dhis.external.conf.DhisConfigurationProvider; import org.hisp.dhis.fieldfilter.FieldFilterParams; import org.hisp.dhis.fieldfilter.FieldFilterService; import org.hisp.dhis.fileresource.FileResource; @@ -98,6 +100,7 @@ import org.hisp.dhis.webapi.strategy.old.tracker.imports.request.TrackerEntityInstanceRequest; import org.hisp.dhis.webapi.utils.ContextUtils; import org.hisp.dhis.webapi.utils.FileResourceUtils; +import org.hisp.dhis.webapi.utils.HeaderUtils; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.DeleteMapping; @@ -146,6 +149,8 @@ public class TrackedEntityInstanceController { private final TrackedEntityInstanceStrategyHandler trackedEntityInstanceStrategyHandler; + private final DhisConfigurationProvider config; + // ------------------------------------------------------------------------- // READ // ------------------------------------------------------------------------- @@ -261,9 +266,7 @@ public void getAttributeImage( FileResourceUtils.setImageFileDimensions( fileResource, MoreObjects.firstNonNull(dimension, ImageFileDimension.ORIGINAL)); - response.setContentType(fileResource.getContentType()); - response.setContentLengthLong(fileResource.getContentLength()); - response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "filename=" + fileResource.getName()); + setHttpResponse(response, fileResource); try (InputStream inputStream = fileResourceService.getFileResourceContent(fileResource)) { BufferedImage img = ImageIO.read(inputStream); @@ -538,4 +541,11 @@ private TrackedEntityInstanceParams getTrackedEntityInstanceParams(List return params; } + + private void setHttpResponse(HttpServletResponse response, FileResource fileResource) { + response.setContentType(fileResource.getContentType()); + response.setContentLengthLong(fileResource.getContentLength()); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "filename=" + fileResource.getName()); + HeaderUtils.setSecurityHeaders(response, config.getProperty(ConfigurationKey.CSP_HEADER_VALUE)); + } } diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/HeaderUtils.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/HeaderUtils.java index 5f97dcb6226b..fa4833a9d8b5 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/HeaderUtils.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/HeaderUtils.java @@ -30,9 +30,12 @@ import javax.servlet.http.HttpServletResponse; public class HeaderUtils { + public static final String X_CONTENT_TYPE_OPTIONS_VALUE = "nosniff"; + public static final String X_XSS_PROTECTION_VALUE = "1; mode=block"; + public static void setSecurityHeaders(HttpServletResponse response, String cspHeaders) { response.setHeader("Content-Security-Policy", cspHeaders); - response.setHeader("X-Content-Type-Options", "nosniff"); - response.setHeader("X-XSS-Protection", "1; mode=block"); + response.setHeader("X-Content-Type-Options", X_CONTENT_TYPE_OPTIONS_VALUE); + response.setHeader("X-XSS-Protection", X_XSS_PROTECTION_VALUE); } } diff --git a/dhis-2/dhis-web-api/src/test/java/org/hisp/dhis/webapi/controller/event/TrackedEntityInstanceControllerTest.java b/dhis-2/dhis-web-api/src/test/java/org/hisp/dhis/webapi/controller/event/TrackedEntityInstanceControllerTest.java index 6ecf6367e988..26de69c2943f 100644 --- a/dhis-2/dhis-web-api/src/test/java/org/hisp/dhis/webapi/controller/event/TrackedEntityInstanceControllerTest.java +++ b/dhis-2/dhis-web-api/src/test/java/org/hisp/dhis/webapi/controller/event/TrackedEntityInstanceControllerTest.java @@ -27,31 +27,50 @@ */ package org.hisp.dhis.webapi.controller.event; +import static org.hisp.dhis.webapi.utils.HeaderUtils.X_CONTENT_TYPE_OPTIONS_VALUE; +import static org.hisp.dhis.webapi.utils.HeaderUtils.X_XSS_PROTECTION_VALUE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.util.Set; +import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.ValueType; import org.hisp.dhis.dxf2.events.trackedentity.TrackedEntityInstanceService; import org.hisp.dhis.dxf2.importsummary.ImportSummaries; +import org.hisp.dhis.external.conf.ConfigurationKey; +import org.hisp.dhis.external.conf.DhisConfigurationProvider; +import org.hisp.dhis.fileresource.FileResource; +import org.hisp.dhis.fileresource.FileResourceDomain; +import org.hisp.dhis.fileresource.FileResourceService; +import org.hisp.dhis.fileresource.FileResourceStorageStatus; import org.hisp.dhis.schema.descriptors.TrackedEntityInstanceSchemaDescriptor; +import org.hisp.dhis.trackedentity.TrackedEntityAttribute; import org.hisp.dhis.trackedentity.TrackedEntityInstance; import org.hisp.dhis.trackedentity.TrackerAccessManager; +import org.hisp.dhis.trackedentityattributevalue.TrackedEntityAttributeValue; import org.hisp.dhis.user.CurrentUserService; import org.hisp.dhis.user.User; import org.hisp.dhis.webapi.controller.exception.BadRequestException; import org.hisp.dhis.webapi.strategy.old.tracker.imports.impl.TrackedEntityInstanceAsyncStrategyImpl; import org.hisp.dhis.webapi.strategy.old.tracker.imports.impl.TrackedEntityInstanceStrategyImpl; import org.hisp.dhis.webapi.strategy.old.tracker.imports.impl.TrackedEntityInstanceSyncStrategyImpl; +import org.hisp.dhis.webapi.utils.ContextUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ClassPathResource; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -70,12 +89,16 @@ class TrackedEntityInstanceControllerTest { @Mock private TrackedEntityInstanceSyncStrategyImpl trackedEntityInstanceSyncStrategy; + @Mock private DhisConfigurationProvider config; + @Mock private User user; @Mock private org.hisp.dhis.trackedentity.TrackedEntityInstanceService instanceService; @Mock private TrackerAccessManager trackerAccessManager; + @Mock private FileResourceService fileResourceService; + @Mock private TrackedEntityInstance trackedEntityInstance; private static final String ENDPOINT = TrackedEntityInstanceSchemaDescriptor.API_ENDPOINT; @@ -90,16 +113,55 @@ public void setUp() throws BadRequestException, IOException { null, null, currentUserService, - null, + fileResourceService, trackerAccessManager, null, null, new TrackedEntityInstanceStrategyImpl( - trackedEntityInstanceSyncStrategy, trackedEntityInstanceAsyncStrategy)); + trackedEntityInstanceSyncStrategy, trackedEntityInstanceAsyncStrategy), + config); mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); when(currentUserService.getCurrentUser()).thenReturn(user); - when(user.getUid()).thenReturn("userId"); + } + + @Test + void shouldRetrieveImageAsAnAttachment() throws Exception { + String teUid = CodeGenerator.generateUid(); + String attributeUid = CodeGenerator.generateUid(); + TrackedEntityAttribute attribute = new TrackedEntityAttribute(); + attribute.setUid(attributeUid); + attribute.setValueType(ValueType.IMAGE); + TrackedEntityAttributeValue attributeValue = new TrackedEntityAttributeValue(); + attributeValue.setAttribute(attribute); + attributeValue.setValue("fileName"); + FileResource fileResource = new FileResource(); + fileResource.setDomain(FileResourceDomain.DATA_VALUE); + fileResource.setStorageStatus(FileResourceStorageStatus.STORED); + fileResource.setContentType("image/png"); + fileResource.setName("dhis2.png"); + + File file = new ClassPathResource("images/dhis2.png").getFile(); + + when(instanceService.getTrackedEntityInstance(teUid)).thenReturn(trackedEntityInstance); + when(trackedEntityInstance.getTrackedEntityAttributeValues()) + .thenReturn(Set.of(attributeValue)); + when(fileResourceService.getFileResource("fileName")).thenReturn(fileResource); + when(fileResourceService.getFileResourceContent(fileResource)) + .thenReturn(new FileInputStream(file)); + when(config.getProperty(ConfigurationKey.CSP_HEADER_VALUE)).thenReturn("script-src 'none';"); + + mockMvc + .perform( + get(ENDPOINT + "/" + teUid + "/" + attributeUid + "/image") + .contentType(MediaType.APPLICATION_JSON) + .content("{}")) + .andExpect(status().isOk()) + .andExpect(header().string(ContextUtils.HEADER_CONTENT_DISPOSITION, "filename=dhis2.png")) + .andExpect(header().string("Content-Security-Policy", "script-src 'none';")) + .andExpect(header().string("X-XSS-Protection", X_XSS_PROTECTION_VALUE)) + .andExpect(header().string("X-Content-Type-Options", X_CONTENT_TYPE_OPTIONS_VALUE)) + .andReturn(); } @Test diff --git a/dhis-2/dhis-web-api/src/test/resources/images/dhis2.png b/dhis-2/dhis-web-api/src/test/resources/images/dhis2.png new file mode 100644 index 0000000000000000000000000000000000000000..73f41ec54212864c380449218cf6413decfb5593 GIT binary patch literal 11417 zcmd^lcQjnl_pek*gb+qAK|&BjnCPS^K@e>WV#FXAEqX7J=q-re5~9uw2BVi?^j>E4 z-ohwR$Ea`e`@Z+jd+V+D-rw)8v(DPtn*q5k%3 zzuV9T3bRHE^F~UGMk=L%dmo!{bsOfX4D8`B=xqW*1}}l z%=|8x*{+4zzJMCvgdx?vojS|5;_ zV=Jd4h6@zV<=n>YjCll(eB|2p*sY!4C`Q1e;|b)OkXNU$N$gY8I1y;PsBf2;S-hCp zcc6JZ(EPi2K)1xlL`lme>A)UYSjvmwzL)lCilP0=PML2a2Grbss6`EGd>htu&(ieB z){Gg{^3K(X8`kyC(e=&M`%(t-$pQHn== ztkgUdY5uLwGNQyXYYY}yY!ijDjjOiJ9=D4rvrA}n{8s81SK*jD>5@?CmR##rFy)bm z_DHJsNUrvP&wFOHdL>tSrPh0=)_TLIeSft1rPumr)CUyL2Ih8t`O)w-yZLMRQb=KU zNZDLy`FvPzOGM>jB)m7epf$RBF}n6oOvykjye+9Kq2W&=syDg3Ke=%^1=XEW zIg(o1p4PIF{(B{(q9+60`=hGwXZ?5%X7yLy@UQmu+}eTMuFd@4lLeiB3LA!tdUxQR ztBBS~ME5$f8H;S2E$!PV9o$8=V$0el%7^zWI;YV+OO=E8>h77E4qQ#wWKG{f4R*J- zcdmA1tFC9Nv2VI@Xr&3a-86aFJUG`nv4a_2XqzRpPyOx0E_IJDchBthOs@BiFZGTu z_s#DQOs)(}tqx8s4=)o(X8w#W?c?S*aDPt67x3e&hf_qoQ88?&q1b9lns z=IQ*}&cY^PasBV|#_sa=@ygEe+UDNcU*h^MabxEIzjv~^d$@gYva@%zb9l0QK-fJx z-90+nJ3QVaob4YG4+z9V!U^HzjCgu}a&~@3YUdZ{7yr2~FD@@GE-(MnS!_OVCnMw2 zS5bJW>oK`Cz_X%vC1=#_wVbaN%>ODX?gSJrwH%UXJNDa^^~6v}C3aLPYCYZ} zrl&j0@!s~7CnR|fK2+b^SUWpTA0S5GCVSQ5{+FKY0f_7cf-Dq3MlJA9#@zYuGW5oO zmr+;A{!0!3Um^Q1nFJ@v|AGI%sQ!oP|3&pbOeEI-Yt{eB$p0JF|IPgVH>v?_5qwx* zq*hda=q53tb`o#XS{K-ZC-so`>W!nU!=M1!}(K;mg07W?+t&$Ng`Ul8uli7PGM$9c{AZRg-Uda!nrDt5_BaxXMY4?sERv zJ9!AtU)6llGkA}QEA|b*DM;y5P<_W=?NOXjFk^W)k1OI5TWlb@I=cL!hY=N+%+g4d z-?H8DTtx+!Js}F-uhGsa7kM*m{aq`z>ai>!Dn@ohv~Hib#)F(jAhc}Vs2qmH@qPrY z6^1DC0hL0c*p%y)_*m$Z^kw%8PwV_7XT!i(3cAy0XD)P|^=97`zBj8N4*W<|L;E(n zxv9JHa)HSB@6YH|%GO(H4_Vc`>UlH@k7TalYA9F;3z~wZhXP(4pO5b4Gq~xlLUa+ln`WR;{F+;Znnr$=3WpB<;U`7zp-sL9THnWmdGW_%p$ng zD(5jDWObt2!tgot#=O1lnln;~5AR&~UUD(FSkY*8_A~fOHJ|NV=?AfM-IC>^Bnuf- z?v{5b;J5$8)_|asii=y?8?iVWN^cu^q^8tMWH0Ex|gTz8_6;fV>8* zYebpY%@D!TO)tCIZdb`rz=x-aIxy|OzGRWwuh?+oU?`s6vSl!a0<)KrEVD+y~ zZMUs11=3J~kNd}8JZ<#0+M{vNWrrK4H04L6&z&$r!tc?JUA+<~unec*^R zpXLHnxqqXU1BIVsm9RHyebLx_!fed$u>Aq1L*6>0Po(|Fm^tnsCv8zG5l1yN0#M3e z@Qef15kKTt)w%eQc)XaBk=4_JG=c>;2*Z2~CU?ND;j=4SFYU{p=^Ab!^=jr_c%X`i z&(T0s#Dawf?}(=cp_#~k#R$EDW=?p;8FP~OzEHR0jvHqT1Z{4@Pr_b_Mjwlf;3bq+ z1U6(OO!(iyK^dw-VX96RfbR)rvV!btOb7uoPwg(T6R` z`Yy6-$K}o4s}Gk41#*Xv>%%8kEsO~4^3JQZ`WF+*bI4)gU@Rnz-s$D1inOUm`CLDZ?0na}?_WsXQ z)AzsJlWQm5KR%rFas#01(<&$76m_=AxplE~PHNqeH$nzZC8W@uMazeq z8$>uY=@pN5*2cR8D2+#yOeL)opO_Vlh_jxzbhHi`LcEK2t>yi0kvKoF8Qh3+ZP~oL zT^^3`nlTRPKRK2xf1=*HG!T?GzpMRk0-|O1+`e@|GR7jSUC*}NJD*y~*bE#WOTWXI zfyxYYX4v*@4!LpEQLexRxUa$QoaU~XsC4l_B=hT5Wx1C=;F488yG$+VCKhgzRc{bq zZwUC9+^WV_!BW29l}aJ&2J5ulK}rC^706n36D`T87Z|ApwN;ZP*XEB;AC1ap<}QFk zLB_75YqX`HM+B6fL1`{42}Xw5@QWahQQM~t)Rd_9?@kx-=dJ_}@LD1H`^kg0hJ&e_ z-czB1H}u4(Qrr#j*S)j1qaR0q?kvZhUUzGJ8H@3((yR3Y{96N|u7OGGuBcd?W z4*JeDdu^uQ=^GYlr}H1dipLftzUPd6HzYk(fWJY~`C9?Sw=}lg#~%{D_AGOT2o)X4 za@+_pPS>252nKFyDk4Jl&cM!0!8mN7Y|n-zR}CgMJGL@?R}1wle4o|AY`OUPzvo(7 zyCC}ocM|FaORKj!0_fA}#3v*DnI(#n?K7(i*A6E%jE3 zO|HDo_eO>24e8W=qAi@e%oX_0Gr_U)mRU*L-N7S}#81gov0MXE^Ep;3qwbOee`37$ z*#8kap5E2D=*K%BdU4$UB+Ec3iKw=9D!M~8Vv8j>2F0Cjt?;`&{O3oZU~k@S`T>K( z)i437ek85il@s!;Z%(PTCB`p+pgAQb0)Ax~&-)LW47P66F=CxQq5{??=$f4uaD|^r zy)&4~AoLTPMGqDbs9{rJU47f+l$2q-j(l^0bJieKq&ry>(IaLd$-; zRu0HvrRUfT9}6>^))~)CIZwSlmXxIRrd8^AnM`j)F@SWIpl08q+{X55EuG>bJ>DGu z!6n{b5quV;$}*)4kD~N8WR zyivM`b%gH_dIN0hVn1_Z@Xu+7z?^-=t**mlYIrGX+kN(l!^jBjl3o4y(lB`zi=(Mo zgWaDO3~^J)XU08`oDgjHxAP5=yM3IVCHxqETf~$@qC21S>zi{%%+k7wU+jkMi-zCa zG~#b(7qB^D5W!`-P!ja>gzj_!PDpz;+u&k~QhAiH;#NRX7n@aR(o?qjaQ){d57|=| z(IYTF1SQEQ(w5a#eV^@sF5K0h!;KuKFVB8S8W)+)E{ql?eLFH5&{N#?Ff9sqrN=Pe zVetq}u$}ZlYr7^G=7fzfRlomIe7RkAu`@Wyl{>5?CGo3ENn6>oszg8jdr|lDu+Wr^n)M4%4HP z+Q#OkseK=FhBgN(c4m&LV?tKNgPbJ*^_N% zaJAD6Z8cdX5!{M+V(muf>6eTBh@H*!=H1@SW$~2FU4mA|l0FBKcK3T%khA41WFk39 z#IOWdipLgJpY~RH!*eEPgI`-3DH(=$by@}{=v|C!WB6;AYsDEERt`MpvYgHxPgWM& zQe{>ulIQ~5@8utgo2Cq@b?pmjsXUS zYCuq0_96{06ySMU72H?V>up?2@|KfY5uckq*Du7HFW={j$SoSkLj`??vb0Nzaix`V z!s`92jrP0UxJH`O7Tgp64ejr~uYjP}o8|E|@Pcs7(ldL{5(7_5p*C>IOeH@d=d*kQ z`D5I&x5lx=iMfScCXcGxti0HfP_Mt+OIw7=KD`WiK|3rWy@nAL?m6$ocgk8nGxr_? zRu;io&ox%tH?_lUAX&*MdX6P`t{OF5*Pr-6K+-cOxUB<&%kMqRL(M+X(XN5p@8Q1#gZOt z2MHR2xPckJ%rYjA&3q1<4Y>uwhIb_layT%{f>Li(tWVafCC}j~u(cXuDf3HJ@Ka)l zKEi)HBRu_7F3X{@N+S|XTl>`pO;FMTEtu(fC+}ca-iL#&1?2md;I6e# zf2JEEP6hKutB!pSHO(Wa@h&BwkPsN~;cX6h*&m7hyG73=ch*8TlY_y0&P~nuo|V1~ zr!Wx0`)yvxu^-WWI0k)O5pWHke?w`(2;C*n|15J*E;WDooVBPdM2AMe*hhS!QvN{S z5M?h8*k>;X&z8k1Bf3s+*kY^rqLdKBH&XhNd5&jJlzdSVpn8ebK7uVOUO!)r%msN?CRjyCr1pZ1sb=ChCUpL+B0 zMOPS3=DZH_mHEZoRk~CMlw&S9!tg{D4R z+glWia(>h;`Pw_ZSc}cSMhCBgOB_zNM)h}(jUP^umN5CPf%>6GdIqNqR?#|L-v%bv*;9)J*MLJYvrZzbK3MR#=j24(kl>cq4XMZtwKX#s>B&;VMQSa)FrJ%a7wOEnqO07BYDjHs_9` z2xz+(^e;J7w5}!&geVamxXaJ1!g%E3Xl+1QH*lc-w0892&!?p@=7IaGrlS<@wQo#z zMI#ZqwmkkF@+C|_*gS^@W5T#6q#iuSXMP-UPDWg_e5f6sKs;%w8tIh&`SFWLuK7d= zgHMR8YO@a7M&_51_7?V*c=J#);UP4)4Dgoy+l3Z`ENn1~16ZU}0iNq#BU)NY=Eh>X z>Wc_J-z0VfjIJX)7~b4ynr$!Eci>`Olh+qmE;Qb94owJl+Pm0zeHVFzofOnYKw8eP zm&-qpZD}-DMLlycV+WSauvHXAt+!|{XvS5>J@-GlQ=5g5o>xH7$*9ZJOaf^84#;kc zY@20cR2%(#n0MVBN5X&!#M0wAn+XmpAjKR^)x0hLmjF5cb>mF+CLk?J`vq%MeNAf$ z2PfwVyB2ij^_g*8SVx}oAB?KR@8v1}F-hCc&kW=I(hnLDx_3@a4g~gT;D4WmMahWC zd?s?~+|OzyOjG892gL6IZx-R`YGA_acAf*=n_SO!1#?67JZM815)G87 zg*8|qUZ4FC8BhFJ7U^b&6{JvcA47aV-Vx6qJw+DL*$BtaPANr|#>Y}pY`;V-?vz1N z*@kZvHl~r;yIEWy( z;4Bj-8Q7L{H|wn|&JN+1!FMGbol$Qw7|*~Iz^0$`>w1G2Vg(qUjN~Z{??e^@+wb^cUnqKWK>^Ix z`>vWUchf>p3U6~*@%(O0F)N3^F&pOYHS@$AL7^OjdI#l=z&@utg!R%q zZqNcv|GmZp+P-Z^D73cm7ZaYn68}~A=3|_Xe`0NQMP0YWyLG;*BKKIMLqQ#8+fd*` ziQ5!UHd#c%XRbX`kWp|oH|{+W=cjy!NF@0BNlxmT$J;#KZjf@>wJ}K7RLw!9N{r`Z``Vkp%V#$ArKBkPs;$Pg)`8uNkzS<=$W}HHxh)NRPO4z%6jyeH591ZKmF7{ zlVO$0^7Kt>hp&bxZr9c%MEV^{O7clpQ(fT;PSL^6Hky1M&G&o0-+6zElHW&Zzw-<8 z44naggW)S{RRA>igVcUL z(cV#PN;%wZCx=l0zohm#0=u~ER+Xj+0`-L4lDRiz8j0X7!JiFGnpWh=lXfB0v#en0 z)ksq|xP^2Zc*6hSGmC+`j^u3GMoW9UQONrB_qZwhNQCTbzgc=FbdFJI!qt{Nl@dzL z55yN?IZ7^Ck!S%r=!#?f)Jzm}%7hRY9=FjHn^m4L*(~@r{KukV3mQSQ$-$o;4@G2f z(!kSBWetTJwK*P*BIL{Kk3{K_utPT(Dpi(+8s3R=L&-7PGAjacN8duRy1h$0NK3D7 z-IiWhIKSD@hF4DXYLKUUpCaPbRM%OU5F7hLZv4SuC|2zAfM9~W;p+Vl+VOe@2kgX} z=e1Uqlip#|zM*={;H2^xAm`%CaMUa<7b>-VxAw}()FCtX0N;hmMqK=pcI@4#D0@V8 zrru`X{K`cnV&rkVArn8Uto}9bLqD=_n31@tYKVS>Nx18qd;IomHIta!{|mLe4PmUg;@9MSGj9`M zxcd{V9tL#So20YXvZ=ws^vlO@9wE`N%?dNDLtk4bzZareq%0VOXb{ME-(!BCq)=Eh zn#x5ewFW}e{i77mLE=z=yXf&gAESX}+oX58HA|LZ+6wDOj_7Dr-s1Y5x_S`&zA4JF zFI*SH|CD0i@QG!5?TSIFTVB27*%pSM9IoLR&WJ=56w6&%!FA8?)& zq5Q#og&EFQ6Mb6w*V_};WQ2}2Iv?Z8d5mPHpyvWL)9@(DY;|~oRuze@PjR#OA-sk& z($zf`at!h=DIW@TYRxdR*-?9e;Of}u_!X+Ks+ipJ3^)7b>QeRUq|221xn$)-=Ta-Q zo<(WIP?7qS_Lo?_P4IatOFbis>O20ref&cWqBCtfohNe|$n|ELgf6FxH!jU03ronB z*PnTSdwH}PQCGcL?*=vy_O;Tl?L`aXY|AW`b zhv7W>(Ci)_j5YA?qSz6)YGc;-=9S%kLjtEYpWSX^YIT~JpRLP^nG6P#%RZH<^V+KE z@x#>yY`rMxZs=u4!Jiv9%g{mrtftKs@-s}WM+FdCygowN zAQKj)X3~|XH`I=uHOA$6)6&4_->Ah#Tl!|ZDlCYQ(5Z^CyGr^!UFCk569PZT(!s?# zBGl|?mp?(P9%QE_pe9mwC#JNMP_vGRc^dWDj{!wh=RN{JTJInBm|CmmjHRr6$@IZ+xp)SnTW&>Z77v)7jg7kcK~ zoR27erB7|B_pJlV9;3SqvU}}k5(>5kYaAn{Ce$O|uxf26BJQ@cvlljfY}Ybpia2HU zuTki)!y>6XNhjOJ(&Zl|gxDyJ+@|ahV%yCu?19X?>WY4D67Z7j=V!bSP2p7H!CvgI8(}~r}%!ZO1b+JD>xtg z!XEhAj-gJk<6Hj5+t7rYRY}!sy&tJYLZ`)P1ok!XJ$uLa#t|j!A-)!G|2Hk4e5(+< zToE9n;skEi$+uSfAE|5~nJ%)p(-S63npSll!&!d?2C^74Rh+%8t zdot7(L);n|)ein)nN%j4WzS5J#C%eaZ!pz+v2H+_R^1L86}xXKB=oH4+F+3#xFb>l zqpAp{To;{Nsv+2FPn{X@Mc1&c6`{<#)Pt&>lJIXyFm5GSFp7;Zzh??P+z^zr?*s|DL>O> zaZY=qhgtIQ=%$MZI}Q2Xh0!RHXo=#?!lcw|BSnxyCrW5;-glXVkFH{^_V7niKYJLC zP>nlCZo6{}`S-sz<;$MMd=nr~rZW{UGN|Cc{kG_NT|M+CDO`;2h5}0}X@QM)Y@9FAYtu@Ch{@3-%5f>mPn8el311p{pe2+%m>lWbR;V zWP?R0)>$DjovU%Iq(Y`!&5U`>kS#6@|0ERnd#bsyM>(%ULBYgbSAz4Shr$IkQEf3; z4dJUiFh$N{;xB1(YIlI; zPzD6|Tp6H-Kc41zfcp`S9G|ujRHHZks3in-`K9eXgb5+>N z3;?&q_xQd(-rH*=_h3fdG2iNpjUEiw)2VC?1S?{EVVDa6j!_Q`{~#3{BKceUUNbA) zxb|N%Iow*R!%)-5^*nF?0Tj@3Wh}rwhGG%SR+g8qPGT(5&_HK)UYvAHQNzie*W7Fr zw4v@`PP%v{1f>izoa?GN86omg3pn)LRW%B2gC!sxL^(l2SdW^jZkH2sRN1~`4#mWrw0BfvW)dr<}MKofAtP^BY_B z+X3hE4DGXzBdETOf<$= za_n9q!^O7GAIDNzj)^3P~g+Xt$pG zEyte-(K7NgRP)DuO>P;s1uw&+D#Kn=zaQkcW-$_|#gsIvB%N$InVuBy?hbX8G@=_xGORS88YUXO((t?^p+H zYYI`9*Ajo+M!jNWtZOy9wE{v;mR+uCC?Ke*cm{AGPKe!N6W!p8kO1)Kgj|6H&UzZ# zdjDt=KDpG^K`RIbS2>Bs@?h?mww}L2cQdC-BOIfq;%NlFVqfO*dul(fEJFATkxmZI zblu`Eineo*URUZXT4+#doP?*E(syo*wrl4eFOd}CWY#4f^O|!p3=Gpxr?a;S0FAu9BruS0p=D$-m0Z9Y;m*=Gr0JBkx zn`d|G`9GUvDjRKaUt7jE-sRbiTfRb2w?FCGsS0XA_(kDk+enB7H;QJH-{#VvY$>g-ks z1XTJ7+eeJ%2F)X!)zEKg-=n2;wc;Vi?j+Lc*%(Mh$(=kb_a>Oh)2;n(BcY%=f2vdA8pfEDBzfH4kf3~P8V@a*+RA{XYecd z%FU(s_1UV-e{)*gGN8l3m1(Cpl|um@rx(!{Rs2_t;KnHzy?qkaw^4zEU+}{~z2W2E zD;tB#SchcqkvVZ{R3P;o!l4J<>WeKTfA~jvLPOdmDIx#hR?q01%tp9qxc@Ma50Zxd z;|^T-%gzm=egj)e4UUv(sID<|b!6%rKnHcOZPN(c;wkrbL#uHw3J%9FiZLC=xJ$M! zpDQ9B{5UH*0PGz@1xGXGr6}Fpgb9V(y3;w7>N}yNBmx=E9y;hPZD0n04L%jZ_ zSQlite!T{8nV7|Vw8I6x5$pD0ri~ysah^rN10Yo-vZ;7wG=C6d_Kv8pocc8z?R{zT z<)!5tRTcM|aAtbjnbPXw@i4+YRA6;n$M;KwxA8#PYH-}u7+&tyl~PF;Sf3uucgMf( z*)ti>8x~Q00+vW4@dmMuz$!}AtAOBf%;s6@<)29zd{BZKJy5(a9qcxyXK^T+mdYS# zL~PmY)097UFcaPVNOH`ZiKA!IYWXwL7S>}`QacUqFh_fPekC2uo&TwH?f=vIx_@Pe p|EJQ!|Ftyle^f2LNG4w5o*bB(|Ly-wDmf-oQPfZnzX9t>u&e+8 literal 0 HcmV?d00001