@@ -9,7 +9,7 @@ class MutualAuthUploadUtility
99 /**
1010 * Uploads an encrypted file using mTLS with a PKCS#12 (.p12/.pfx) client certificate.
1111 *
12- * @param string $encryptedPgpBytes The encrypted file content (ASCII-armored) .
12+ * @param string $encryptedPgpBytes The encrypted file content.
1313 * @param string $endpointUrl The full API endpoint URL.
1414 * @param string $fileName The file name to use in the multipart upload.
1515 * @param string $p12FilePath Path to the PKCS#12 client certificate file.
@@ -28,103 +28,25 @@ public static function uploadWithP12(
2828 $ verify_ssl = true ,
2929 $ logger = null
3030 ) {
31- $ tmpFile = tempnam (sys_get_temp_dir (), 'pgp_ ' );
32- file_put_contents ($ tmpFile , $ encryptedPgpBytes );
33-
34- $ ch = curl_init ();
35- $ cfile = new \CURLFile ($ tmpFile , 'application/octet-stream ' , $ fileName );
36- $ postFields = ['file ' => $ cfile ];
37-
38- curl_setopt ($ ch , CURLOPT_URL , $ endpointUrl );
39- curl_setopt ($ ch , CURLOPT_POST , 1 );
40- curl_setopt ($ ch , CURLOPT_POSTFIELDS , $ postFields );
41-
42- // mTLS: client cert (PKCS#12)
43- curl_setopt ($ ch , CURLOPT_SSLCERT , $ p12FilePath );
44- curl_setopt ($ ch , CURLOPT_SSLCERTTYPE , 'P12 ' );
45- curl_setopt ($ ch , CURLOPT_SSLCERTPASSWD , $ p12Password );
46-
47- // CA cert for server validation
48- if ($ caCertPath ) {
49- curl_setopt ($ ch , CURLOPT_CAINFO , $ caCertPath );
50- }
51-
52- // SSL verification
53- curl_setopt ($ ch , CURLOPT_SSL_VERIFYPEER , $ verify_ssl );
54- curl_setopt ($ ch , CURLOPT_SSL_VERIFYHOST , $ verify_ssl ? 2 : 0 );
55-
56- curl_setopt ($ ch , CURLOPT_RETURNTRANSFER , true );
57-
58- // Optional: Add correlation ID for logging/tracing
59- $ correlationId = self ::generateCorrelationId ();
60- curl_setopt ($ ch , CURLOPT_HTTPHEADER , [
61- "v-c-correlation-id: $ correlationId "
62- ]);
63-
64- // Use CURLOPT_HEADERFUNCTION to parse headers
65- $ responseHeaders = [];
66- curl_setopt ($ ch , CURLOPT_HEADERFUNCTION , function ($ curl , $ header_line ) use (&$ responseHeaders ) {
67- $ len = strlen ($ header_line );
68- $ header_line = trim ($ header_line );
69- if (empty ($ header_line ) || strpos ($ header_line , ': ' ) === false ) {
70- return $ len ;
71- }
72-
73- list ($ key , $ value ) = explode (': ' , $ header_line , 2 );
74- $ key = trim ($ key );
75- $ value = trim ($ value );
76-
77- if (isset ($ responseHeaders [$ key ])) {
78- if (is_array ($ responseHeaders [$ key ])) {
79- $ responseHeaders [$ key ][] = $ value ;
80- } else {
81- $ responseHeaders [$ key ] = [$ responseHeaders [$ key ], $ value ];
82- }
83- } else {
84- $ responseHeaders [$ key ] = $ value ;
85- }
86-
87- return $ len ;
88- });
89-
90- $ body = curl_exec ($ ch );
91-
92- if ($ body === false ) {
93- $ err = curl_error ($ ch );
94- curl_close ($ ch );
95- unlink ($ tmpFile );
96- $ error_message = "cURL error: $ err " ;
97- if ($ logger ) $ logger ->error ($ error_message );
98- throw new ApiException ($ error_message , 500 );
99- }
100-
101- $ httpCode = curl_getinfo ($ ch , CURLINFO_HTTP_CODE );
102- $ err = curl_error ($ ch );
103-
104- curl_close ($ ch );
105- unlink ($ tmpFile );
106-
107- if ($ err || $ httpCode === 0 ) {
108- $ error_message = $ err ? "cURL error: $ err " : "API call failed, but for an unknown reason. This could happen if you are disconnected from the network. " ;
109- if ($ logger ) $ logger ->error ($ error_message );
110- throw new ApiException ($ error_message , 500 );
111- } elseif ($ httpCode >= 200 && $ httpCode < 300 ) {
112- if ($ logger ) $ logger ->info ("Upload completed for correlationId: $ correlationId. Status: $ httpCode " );
113- // Check if response is JSON and return decoded JSON if it is, otherwise return as string
114- $ jsonResponse = json_decode ($ body , true );
115- $ processedBody = (json_last_error () === JSON_ERROR_NONE ) ? $ jsonResponse : $ body ;
116- return [$ processedBody , $ httpCode , $ responseHeaders ];
117- } else {
118- $ msg = "File upload failed. Status code: $ httpCode, body: $ body " ;
119- if ($ logger ) $ logger ->error ($ msg );
120- throw new ApiException ($ msg , $ httpCode , $ responseHeaders , $ body );
121- }
31+ return self ::doMultipartUpload (
32+ $ encryptedPgpBytes ,
33+ $ endpointUrl ,
34+ $ fileName ,
35+ [
36+ 'type ' => 'p12 ' ,
37+ 'cert ' => $ p12FilePath ,
38+ 'password ' => $ p12Password ,
39+ ],
40+ $ caCertPath ,
41+ $ verify_ssl ,
42+ $ logger
43+ );
12244 }
12345
12446 /**
12547 * Uploads an encrypted file using mTLS with client key/cert PEM files.
12648 *
127- * @param string $encryptedPgpBytes The encrypted file content (ASCII-armored) .
49+ * @param string $encryptedPgpBytes The encrypted file content.
12850 * @param string $endpointUrl The full API endpoint URL.
12951 * @param string $fileName The file name to use in the multipart upload.
13052 * @param string $clientCertPath Path to the client certificate (PEM).
@@ -145,22 +67,67 @@ public static function uploadWithKeyAndCert(
14567 $ verify_ssl = true ,
14668 $ logger = null
14769 ) {
148- $ tmpFile = tempnam (sys_get_temp_dir (), 'pgp_ ' );
149- file_put_contents ($ tmpFile , $ encryptedPgpBytes );
70+ return self ::doMultipartUpload (
71+ $ encryptedPgpBytes ,
72+ $ endpointUrl ,
73+ $ fileName ,
74+ [
75+ 'type ' => 'pem ' ,
76+ 'cert ' => $ clientCertPath ,
77+ 'key ' => $ clientKeyPath ,
78+ 'password ' => $ clientKeyPassword ,
79+ ],
80+ $ caCertPath ,
81+ $ verify_ssl ,
82+ $ logger
83+ );
84+ }
15085
86+ /**
87+ * Shared logic for multipart upload with mTLS.
88+ */
89+ private static function doMultipartUpload (
90+ $ encryptedPgpBytes ,
91+ $ endpointUrl ,
92+ $ fileName ,
93+ $ tlsConfig ,
94+ $ caCertPath ,
95+ $ verify_ssl ,
96+ $ logger
97+ ){
98+ $ boundary = '-------------------------- ' . microtime (true );
99+ $ eol = "\r\n" ;
100+ // works without this header too: Content-Transfer-Encoding
101+ $ multipartBody =
102+ "-- $ boundary$ eol " .
103+ "Content-Disposition: form-data; name= \"file \"; filename= \"$ fileName \"$ eol " .
104+ "Content-Type: application/octet-stream $ eol " .
105+ "Content-Transfer-Encoding: binary $ eol$ eol " .
106+ $ encryptedPgpBytes . $ eol .
107+ "-- $ boundary-- $ eol " ;
108+
109+ $ correlationId = self ::generateCorrelationId ();
110+
151111 $ ch = curl_init ();
152- $ cfile = new \CURLFile ($ tmpFile , 'application/octet-stream ' , $ fileName );
153- $ postFields = ['file ' => $ cfile ];
154-
155112 curl_setopt ($ ch , CURLOPT_URL , $ endpointUrl );
156113 curl_setopt ($ ch , CURLOPT_POST , 1 );
157- curl_setopt ($ ch , CURLOPT_POSTFIELDS , $ postFields );
114+ curl_setopt ($ ch , CURLOPT_POSTFIELDS , $ multipartBody );
115+ curl_setopt ($ ch , CURLOPT_HTTPHEADER , [
116+ "Content-Type: multipart/form-data; boundary= $ boundary " ,
117+ "v-c-correlation-id: $ correlationId "
118+ ]);
158119
159- // mTLS: client cert and key
160- curl_setopt ($ ch , CURLOPT_SSLCERT , $ clientCertPath );
161- curl_setopt ($ ch , CURLOPT_SSLKEY , $ clientKeyPath );
162- if ($ clientKeyPassword ) {
163- curl_setopt ($ ch , CURLOPT_KEYPASSWD , $ clientKeyPassword );
120+ // mTLS: handle both PKCS#12 and cert and key
121+ if ($ tlsConfig ['type ' ] === 'p12 ' ) {
122+ curl_setopt ($ ch , CURLOPT_SSLCERT , $ tlsConfig ['cert ' ]);
123+ curl_setopt ($ ch , CURLOPT_SSLCERTTYPE , 'P12 ' );
124+ curl_setopt ($ ch , CURLOPT_SSLCERTPASSWD , $ tlsConfig ['password ' ]);
125+ } else {
126+ curl_setopt ($ ch , CURLOPT_SSLCERT , $ tlsConfig ['cert ' ]);
127+ curl_setopt ($ ch , CURLOPT_SSLKEY , $ tlsConfig ['key ' ]);
128+ if (!empty ($ tlsConfig ['password ' ])) {
129+ curl_setopt ($ ch , CURLOPT_KEYPASSWD , $ tlsConfig ['password ' ]);
130+ }
164131 }
165132
166133 // CA cert for server validation
@@ -173,12 +140,6 @@ public static function uploadWithKeyAndCert(
173140 curl_setopt ($ ch , CURLOPT_SSL_VERIFYHOST , $ verify_ssl ? 2 : 0 );
174141
175142 curl_setopt ($ ch , CURLOPT_RETURNTRANSFER , true );
176-
177- // Optional: Add correlation ID for logging/tracing
178- $ correlationId = self ::generateCorrelationId ();
179- curl_setopt ($ ch , CURLOPT_HTTPHEADER , [
180- "v-c-correlation-id: $ correlationId "
181- ]);
182143
183144 // Use CURLOPT_HEADERFUNCTION to parse headers
184145 $ responseHeaders = [];
@@ -211,18 +172,15 @@ public static function uploadWithKeyAndCert(
211172 if ($ body === false ) {
212173 $ err = curl_error ($ ch );
213174 curl_close ($ ch );
214- unlink ($ tmpFile );
215175 $ error_message = "cURL error: $ err " ;
216176 if ($ logger ) $ logger ->error ($ error_message );
217177 throw new ApiException ($ error_message , 500 );
218178 }
219179
220180 $ httpCode = curl_getinfo ($ ch , CURLINFO_HTTP_CODE );
221181 $ err = curl_error ($ ch );
222- $ http_header_size = curl_getinfo ($ ch , CURLINFO_HEADER_SIZE );
223- $ http_body = substr ($ body , $ http_header_size );
182+
224183 curl_close ($ ch );
225- unlink ($ tmpFile );
226184
227185 if ($ err || $ httpCode === 0 ) {
228186 if ($ err ) {
@@ -236,13 +194,14 @@ public static function uploadWithKeyAndCert(
236194 throw new ApiException ($ error_message , 500 );
237195 } elseif ($ httpCode >= 200 && $ httpCode < 300 ) {
238196 if ($ logger ) $ logger ->info ("Upload completed for correlationId: $ correlationId. Status: $ httpCode " );
239- $ jsonResponse = json_decode ($ http_body );
197+ // Check if response is JSON and return decoded JSON if it is, otherwise return as string
198+ $ jsonResponse = json_decode ($ body , true );
240199 $ processedBody = (json_last_error () === JSON_ERROR_NONE ) ? $ jsonResponse : $ body ;
241200 return [$ processedBody , $ httpCode , $ responseHeaders ];
242201 } else {
243- $ msg = "File upload failed. Status code: $ httpCode, body: $ http_body " ;
202+ $ msg = "File upload failed. Status code: $ httpCode, body: $ body " ;
244203 if ($ logger ) $ logger ->error ($ msg );
245- throw new ApiException ($ msg , $ httpCode , $ responseHeaders , $ http_body );
204+ throw new ApiException ($ msg , $ httpCode , $ responseHeaders , $ body );
246205 }
247206 }
248207
0 commit comments