@@ -102,17 +102,7 @@ public function request(TransportRequest $request): TransportResponse
102102 }
103103 // @codeCoverageIgnoreEnd
104104
105- // Needed to store the JSESSIONID
106- $ cookieFile = @tmpfile (); // Silenced as tmpfile can throw notices like "creating file in system temporary directory".
107- // @codeCoverageIgnoreStart
108- if (false === $ cookieFile ) {
109- throw new RuntimeException ('Could not create temporary file for cookies. ' );
110- }
111- // @codeCoverageIgnoreEnd
112- $ cookieFilePath = stream_get_meta_data ($ cookieFile )['uri ' ];
113-
114105 $ options = self ::mergeCurlOptions (
115- self ::buildCookieOptions ($ cookieFilePath ),
116106 self ::buildUrlOptions ($ request ),
117107 self ::buildPostOptions ($ request ),
118108 self ::INTERNAL_CURL_OPTIONS ,
@@ -123,12 +113,7 @@ public function request(TransportRequest $request): TransportResponse
123113 curl_setopt ($ ch , $ option , $ value );
124114 }
125115
126- $ data = curl_exec ($ ch );
127- // @codeCoverageIgnoreStart
128- if ($ data === false ) {
129- throw new NetworkException ('Error during curl_exec. Error: ' . curl_error ($ ch ));
130- }
131- // @codeCoverageIgnoreEnd
116+ [$ headers , $ data ] = self ::getHeadersAndContentFromCurlHandle ($ ch );
132117
133118 $ httpCode = curl_getinfo ($ ch , CURLINFO_HTTP_CODE );
134119 if ($ httpCode < 200 || $ httpCode >= 300 ) {
@@ -137,29 +122,14 @@ public function request(TransportRequest $request): TransportResponse
137122
138123 curl_close ($ ch );
139124
140- $ cookies = file_get_contents ($ cookieFilePath );
141125 $ sessionId = null ;
142-
143- if (strpos ($ cookies , 'JSESSIONID ' ) !== false ) {
144- preg_match ('/(?:JSESSIONID\s*)(?<JSESSIONID>.*)/ ' , $ cookies , $ output );
145- $ sessionId = $ output ['JSESSIONID ' ];
126+ if (isset ($ headers ['set-cookie ' ])) {
127+ $ sessionId = Cookie::extractJsessionId ($ headers ['set-cookie ' ]);
146128 }
147129
148130 return new TransportResponse ($ data , $ sessionId );
149131 }
150132
151- /**
152- * @param string $cookieFilePath
153- * @return mixed[]
154- */
155- private static function buildCookieOptions (string $ cookieFilePath ): array
156- {
157- return [
158- CURLOPT_COOKIEFILE => $ cookieFilePath ,
159- CURLOPT_COOKIEJAR => $ cookieFilePath ,
160- ];
161- }
162-
163133 /**
164134 * @param TransportRequest $request
165135 * @return mixed[]
@@ -169,7 +139,6 @@ private static function buildPostOptions(TransportRequest $request): array
169139 $ options = [];
170140
171141 if ('' !== $ payload = $ request ->getPayload ()) {
172- $ options [CURLOPT_HEADER ] = 0 ;
173142 $ options [CURLOPT_CUSTOMREQUEST ] = 'POST ' ;
174143 $ options [CURLOPT_POST ] = 1 ;
175144 $ options [CURLOPT_POSTFIELDS ] = $ payload ;
@@ -222,4 +191,65 @@ private static function mergeCurlOptions(array ...$options): array
222191
223192 return ArrayHelper::mergeRecursive (true , $ headerOptions , ...$ options );
224193 }
194+
195+ /**
196+ * A raw response as returned from cURL will contain the headers followed by "\r\n\r\n" and the content.
197+ *
198+ * @param \CurlHandle|resource $curlHandle
199+ * @return array{0: string, 1: string[]} First key headers, second key is content
200+ * @throws NetworkException
201+ * @link https://stackoverflow.com/questions/10589889/returning-header-as-array-using-curl
202+ */
203+ private static function getHeadersAndContentFromCurlHandle ($ curlHandle ): array
204+ {
205+ /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */
206+ // @codeCoverageIgnoreStart
207+ if (\PHP_VERSION_ID >= 80000 && !$ curlHandle instanceof \CurlHandle) {
208+ /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */
209+ throw new \InvalidArgumentException (sprintf ('$curlHandle must be "%s". "%s" given. ' , \CurlHandle::class, get_debug_type ($ curlHandle )));
210+ } elseif (\PHP_VERSION_ID < 80000 && !is_resource ($ curlHandle )) {
211+ throw new \InvalidArgumentException (sprintf ('$curlHandle must be resource. "%s" given. ' , is_object ($ curlHandle ) ? get_class ($ curlHandle ) : gettype ($ curlHandle )));
212+ }
213+ // @codeCoverageIgnoreEnd
214+
215+ $ headers = [];
216+
217+ curl_setopt ($ curlHandle , CURLOPT_HEADER , 1 );
218+ $ responseContent = curl_exec ($ curlHandle );
219+
220+ // @codeCoverageIgnoreStart
221+ if (false === $ responseContent ) {
222+ throw new NetworkException ('Error during curl_exec. Error: ' . curl_error ($ curlHandle ));
223+ }
224+ // @codeCoverageIgnoreEnd
225+
226+ $ headerSize = curl_getinfo ($ curlHandle , CURLINFO_HEADER_SIZE );
227+ $ headerContent = substr ($ responseContent , 0 , $ headerSize );
228+
229+ // Split the string on every "double" new line.
230+ $ headerParts = explode ("\r\n\r\n" , $ headerContent , 2 ); // only split once to mitigate scrapping content if it contains newlines with carriage return
231+
232+ // Loop of response headers. The "count() -1" is to avoid an empty row for the extra line break before the body of the response.
233+ for ($ index = 0 ; $ index < count ($ headerParts ) - 1 ; $ index ++) {
234+ foreach (explode ("\r\n" , $ headerParts [$ index ]) as $ i => $ line ) {
235+ if (0 === $ i ) {
236+ // HTTP code
237+ continue ;
238+ }
239+
240+ $ splitHeader = explode (': ' , $ line , 2 );
241+ // @codeCoverageIgnoreStart
242+ if (!isset ($ splitHeader [0 ], $ splitHeader [1 ])) {
243+ throw new \InvalidArgumentException (sprintf ('Header value "%s" is invalid. Expected format is "Header-Name: value". ' , $ line ));
244+ }
245+ // @codeCoverageIgnoreEnd
246+
247+ [$ header , $ value ] = $ splitHeader ;
248+ // Use lower case to have an always predictable result
249+ $ headers [strtolower ($ header )][] = $ value ;
250+ }
251+ }
252+
253+ return [$ headers , substr ($ responseContent , $ headerSize )];
254+ }
225255}
0 commit comments