php 8.4.3 + latest update = Timestamp format issue with s3

briansol

Well-known member
Affected version
2.3.5
Upgraded to the latest xf release and php 8.4.3 and started logging 100s of these. I am using s3 as a storage bucket.

Server error log
  • ErrorException: [E_USER_WARNING] Failed to parse the expires header as a timestamp due to an invalid timestamp format. Please refer to ExpiresString for the unparsed string format of this header.
  • src/vendor/aws/aws-sdk-php/src/S3/ExpiresParsingMiddleware.php:45
  • Generated by: Unknown account
  • Feb 5, 2025 at 9:01 PM

Stack trace​

#0 [internal function]: XF::handlePhpError(512, '[E_USER_WARNING...', '/home/nginx/dom...', 45)
#1 src/vendor/aws/aws-sdk-php/src/S3/ExpiresParsingMiddleware.php(45): trigger_error('Failed to parse...', 512)
#2 src/vendor/guzzlehttp/promises/src/Promise.php(209): Aws\S3\ExpiresParsingMiddleware->{closure:Aws\S3\ExpiresParsingMiddleware::__invoke():43}(Object(Aws\Result))
#3 src/vendor/guzzlehttp/promises/src/Promise.php(158): GuzzleHttp\Promise\Promise::callHandler(1, Object(Aws\Result), NULL)
#4 src/vendor/guzzlehttp/promises/src/TaskQueue.php(52): GuzzleHttp\Promise\Promise::{closure:GuzzleHttp\Promise\Promise::settle():156}()
#5 src/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(163): GuzzleHttp\Promise\TaskQueue->run()
#6 src/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(189): GuzzleHttp\Handler\CurlMultiHandler->tick()
#7 src/vendor/guzzlehttp/promises/src/Promise.php(251): GuzzleHttp\Handler\CurlMultiHandler->execute(true)
#8 src/vendor/guzzlehttp/promises/src/Promise.php(227): GuzzleHttp\Promise\Promise->invokeWaitFn()
#9 src/vendor/guzzlehttp/promises/src/Promise.php(272): GuzzleHttp\Promise\Promise->waitIfPending()
#10 src/vendor/guzzlehttp/promises/src/Promise.php(229): GuzzleHttp\Promise\Promise->invokeWaitList()
#11 src/vendor/guzzlehttp/promises/src/Promise.php(272): GuzzleHttp\Promise\Promise->waitIfPending()
#12 src/vendor/guzzlehttp/promises/src/Promise.php(229): GuzzleHttp\Promise\Promise->invokeWaitList()
#13 src/vendor/guzzlehttp/promises/src/Promise.php(69): GuzzleHttp\Promise\Promise->waitIfPending()
#14 src/vendor/aws/aws-sdk-php/src/AwsClientTrait.php(58): GuzzleHttp\Promise\Promise->wait()
#15 src/vendor/aws/aws-sdk-php/src/S3/S3ClientTrait.php(363): Aws\AwsClient->execute(Object(Aws\Command))
#16 src/vendor/aws/aws-sdk-php/src/S3/S3ClientTrait.php(306): Aws\S3\S3Client->checkExistenceWithCommand(Object(Aws\Command))
#17 src/vendor/league/flysystem-aws-s3-v3/src/AwsS3Adapter.php(234): Aws\S3\S3Client->doesObjectExist('hss3data', 'pdata/attachmen...', Array)
#18 src/vendor/league/flysystem/src/Filesystem.php(57): League\Flysystem\AwsS3v3\AwsS3Adapter->has('attachments/23/...')
#19 src/vendor/league/flysystem-eventable-filesystem/src/EventableFilesystem.php(430): League\Flysystem\Filesystem->has('attachments/23/...', Array)
#20 src/vendor/league/flysystem-eventable-filesystem/src/EventableFilesystem.php(395): League\Flysystem\EventableFilesystem\EventableFilesystem->callFilesystemMethod('has', Array)
#21 src/vendor/league/flysystem-eventable-filesystem/src/EventableFilesystem.php(128): League\Flysystem\EventableFilesystem\EventableFilesystem->delegateMethodCall('has', Array)
#22 src/vendor/league/flysystem/src/MountManager.php(313): League\Flysystem\EventableFilesystem\EventableFilesystem->has('attachments/23/...')
#23 src/XF/Entity/AttachmentData.php(241): League\Flysystem\MountManager->has('attachments/23/...')
#24 src/XF/ControllerPlugin/AttachmentPlugin.php(12): XF\Entity\AttachmentData->isDataAvailable()
#25 src/XF/Pub/Controller/AttachmentController.php(49): XF\ControllerPlugin\AttachmentPlugin->displayAttachment(Object(XFMG\XF\Entity\Attachment))
#26 src/XF/Mvc/Dispatcher.php(362): XF\Pub\Controller\AttachmentController->actionIndex(Object(XF\Mvc\ParameterBag))
#27 src/XF/Mvc/Dispatcher.php(264): XF\Mvc\Dispatcher->dispatchClass('XF:Attachment', 'Index', Object(XF\Mvc\RouteMatch), Object(XF\Pub\Controller\AttachmentController), NULL)
#28 src/XF/Mvc/Dispatcher.php(121): XF\Mvc\Dispatcher->dispatchFromMatch(Object(XF\Mvc\RouteMatch), Object(XF\Pub\Controller\AttachmentController), NULL)
#29 src/XF/Mvc/Dispatcher.php(63): XF\Mvc\Dispatcher->dispatchLoop(Object(XF\Mvc\RouteMatch))
#30 src/XF/App.php(2826): XF\Mvc\Dispatcher->run()
#31 src/XF.php(806): XF\App->run()
#32 index.php(23): XF::runApp('XF\\Pub\\App')
#33 {main}

Request state​

array(4) {
["url"] => string(43) "/attachments/img_20180529_215947-jpg.26760/"
["referrer"] => string(65) "https://domain.com/threads/yadda-yadda.123456/"
["_GET"] => array(1) {
["/attachments/img_20180529_215947-jpg_26760/"] => string(0) ""
}
["_POST"] => array(0) {
}
}
 
seems to be coming out of:

Code:
public function __invoke(CommandInterface $command, ?RequestInterface $request = null)
    {
        $next = $this->nextHandler;
        return $next($command, $request)->then(
            function (ResultInterface $result) {
                if (empty($result['Expires']) && !empty($result['ExpiresString'])) {
                    trigger_error(
                        "Failed to parse the `expires` header as a timestamp due to "
                        . " an invalid timestamp format.\nPlease refer to `ExpiresString` "
                        . "for the unparsed string format of this header.\n"
                        , E_USER_WARNING
                    );
                }
                return $result;
            }
        );
    }
 
Am I the only one having this issue?

I tried to optimize attachments to see if that would be a thing to help here and the job died instantly as well.

1739396176594.webp
 
Troubleshooting this a bit more:
  1. it seems to only effect old attachments. I haven't found the exact cut-off point yet, but new threads work fine. old attachments do not load at all. It's not consistent, but seems to be the 80/20 that older don't work. Some 'newer' ones (2007) don't work yet the 2006 one does, so it's not a hard cut off. Something is different abotu the files. It's effecting inline posts and in article display as dead images: 1739465729529.webp
  2. it effects all types (pdf, zip, and images) all have the same error message and don't load.
  3. The same error message exists when trying to load it through the admin panel as well with the attachment viewer.
  4. Thumbnails are not affected in the admin portal, but they are inline on threads.
  5. It effects XFMG as well. same issue with thumbnails available, full size not.


So i dived in more, and something deleted images i think.

Here's the admin view of a random image i found that loads the thumbnail:

1739466455298.webp

view host content,
the image is missing:
1739466497441.webp

ignore the fact that the avatar is the same picture here... totally coicidence.


So, if i edit the post, the attachment is there:

1739466569294.webp

but when i go to s3, it's missing. Image 25311 isn't there.
the admin url hints to this number: admin.php?attachments/img_20161007_150311-jpg.25311/view

1739466612464.webp


So, maybe it's not a php thing? the image is actually missing. But how? why did half of my attachments suddenly go missing from s3 at the exact moment i upgraded XF to 2.3.6 and php 8.4.3 ?

S3 shows 0 loss in the past 4 weeks (and further back). Nice steady uptrack as more attachments come in. This doesn't make any sense.


1739467328507.webp
 
Last edited:
Backtrace error and the code from ExpiresParsingMiddleware.php, this is a known warning that occurs when the AWS S3 SDK can't parse the Expires header timestamp from S3 responses. This warning is not an issue so maybe.

Try to use this:
  1. Add this warning:
    PHP:
    error_reporting(E_ALL & ~E_USER_WARNING);

  2. Robust solution & modify Amazon S3 client configuration:
    PHP:
    $s3Client = new Aws\S3\S3Client([
        'version' => 'latest',
        'region'  => 'your-region',
        'http'    => [
            'headers' => [
                'Cache-Control' => 'max-age=3600'
            ]
        ]
    ]);

  3. Alternative for handle expires header different:
    PHP:
    $s3Config = [
        'params' => [
            'CacheControl' => 'max-age=3600',
            'Expires' => gmdate('D, d M Y H:i:s T', strtotime('+1 hour'))
        ]
    ];

The AWS SDK attempts to parse the Expires header from S3 responses. This header comes in an unexpected format, ExpiresParsingMiddleware class detects this and triggers a warning, this is more of a logging noise issue than a functional problem. I think your S3 storage is working, I am not sure that there are warning. Try using options 2 or 3 as they address the root cause issue than just hiding the warning. These solutions ensure proper cache control a headers.
 
Back
Top Bottom