Initial Commit
This commit is contained in:
LICENSEautoload.phpconstants.phppsalm.xmlautoload.phpscoper-autoload.php
assets
css
admin-screen.cssadmin-screen.min.cssadmin.cssadmin.min.cssapexcharts.cssapexcharts.min.csscheckout-editor.csscheckout-editor.min.csscheckout.csscheckout.min.csscompat-admin-themes.csscompat-admin-themes.min.cssfields.cssfields.min.cssflags.cssflags.min.css
flags
framework.cssframework.css.mapframework.min.cssjumper.cssjumper.min.csslegacy-admin-tabs.csslegacy-admin-tabs.min.csslegacy-shortcodes.csslegacy-shortcodes.min.csslegacy-signup.csslegacy-signup.min.csstemplate-previewer.csstemplate-previewer.min.csswhitelabel.csswhitelabel.min.cssfonts
img
badge.pngbg-setup.png
checkout-forms
clean-order-summary.pngclean-period-selection.pngclean-steps.pngclean-template-selection.pnglegacy-period-selection.pnglegacy-pricing-table.pnglegacy-steps.pnglegacy-template-selection.pnglist-pricing-table.pngminimal-steps.pngminimal-template-selection.pngurl-preview.png
empty-state-bg.pngerasmo-carlos.jpgflags
1x1
ad.svgae.svgaf.svgag.svgai.svgal.svgam.svgao.svgaq.svgar.svgas.svgat.svgau.svgaw.svgax.svgaz.svgba.svgbb.svgbd.svgbe.svgbf.svgbg.svgbh.svgbi.svgbj.svgbl.svgbm.svgbn.svgbo.svgbq.svgbr.svgbs.svgbt.svgbv.svgbw.svgby.svgbz.svgca.svgcc.svgcd.svgcf.svgcg.svgch.svgci.svgck.svgcl.svgcm.svgcn.svgco.svgcr.svgcu.svgcv.svgcw.svgcx.svgcy.svgcz.svgde.svgdj.svgdk.svgdm.svgdo.svgdz.svgec.svgee.svgeg.svgeh.svger.svges-ca.svges-ga.svges.svget.svgeu.svgfi.svgfj.svgfk.svgfm.svgfo.svgfr.svgga.svggb-eng.svggb-nir.svggb-sct.svggb-wls.svggb.svggd.svgge.svggf.svggg.svggh.svggi.svggl.svggm.svggn.svggp.svggq.svggr.svggs.svggt.svggu.svggw.svggy.svghk.svghm.svghn.svghr.svght.svghu.svgid.svgie.svgil.svgim.svgin.svgio.svgiq.svgir.svgis.svgit.svgje.svgjm.svgjo.svgjp.svgke.svgkg.svgkh.svgki.svgkm.svgkn.svgkp.svgkr.svgkw.svgky.svgkz.svgla.svglb.svglc.svgli.svglk.svglr.svgls.svglt.svglu.svglv.svgly.svgma.svgmc.svgmd.svgme.svgmf.svgmg.svgmh.svgmk.svgml.svgmm.svgmn.svgmo.svgmp.svgmq.svgmr.svgms.svgmt.svgmu.svgmv.svgmw.svgmx.svgmy.svgmz.svgna.svgnc.svgne.svgnf.svgng.svgni.svgnl.svgno.svgnp.svgnr.svgnu.svgnz.svgom.svgpa.svgpe.svgpf.svgpg.svgph.svgpk.svgpl.svgpm.svgpn.svgpr.svgps.svgpt.svgpw.svgpy.svgqa.svgre.svgro.svgrs.svgru.svgrw.svgsa.svgsb.svgsc.svgsd.svgse.svgsg.svgsh.svgsi.svgsj.svgsk.svgsl.svgsm.svgsn.svgso.svgsr.svgss.svgst.svgsv.svgsx.svgsy.svgsz.svgtc.svgtd.svgtf.svgtg.svgth.svgtj.svgtk.svgtl.svgtm.svgtn.svgto.svgtr.svgtt.svgtv.svgtw.svgtz.svgua.svgug.svgum.svgun.svgus.svguy.svguz.svgva.svgvc.svgve.svgvg.svgvi.svgvn.svgvu.svgwf.svgws.svgxk.svgye.svgyt.svgza.svgzm.svgzw.svg
4x3
ad.svgae.svgaf.svgag.svgai.svgal.svgam.svgao.svgaq.svgar.svgas.svgat.svgau.svgaw.svgax.svgaz.svgba.svgbb.svgbd.svgbe.svgbf.svgbg.svgbh.svgbi.svgbj.svgbl.svgbm.svgbn.svgbo.svgbq.svgbr.svgbs.svgbt.svgbv.svgbw.svgby.svgbz.svgca.svgcc.svgcd.svgcf.svgcg.svgch.svgci.svgck.svgcl.svgcm.svgcn.svgco.svgcr.svgcu.svgcv.svgcw.svgcx.svgcy.svgcz.svgde.svgdj.svgdk.svgdm.svgdo.svgdz.svgec.svgee.svgeg.svgeh.svger.svges-ca.svges-ga.svges.svget.svgeu.svgfi.svgfj.svgfk.svgfm.svgfo.svgfr.svgga.svggb-eng.svggb-nir.svggb-sct.svggb-wls.svggb.svggd.svgge.svggf.svggg.svggh.svggi.svggl.svggm.svggn.svggp.svggq.svggr.svggs.svggt.svggu.svggw.svggy.svghk.svghm.svghn.svghr.svght.svghu.svgid.svgie.svgil.svgim.svgin.svgio.svgiq.svgir.svgis.svgit.svgje.svgjm.svgjo.svgjp.svgke.svgkg.svgkh.svgki.svgkm.svgkn.svgkp.svgkr.svgkw.svgky.svgkz.svgla.svglb.svglc.svgli.svglk.svglr.svgls.svglt.svglu.svglv.svgly.svgma.svgmc.svgmd.svgme.svgmf.svgmg.svgmh.svgmk.svgml.svgmm.svgmn.svgmo.svgmp.svgmq.svgmr.svgms.svgmt.svgmu.svgmv.svgmw.svgmx.svgmy.svgmz.svgna.svgnc.svgne.svgnf.svgng.svgni.svgnl.svgno.svgnp.svgnr.svgnu.svgnz.svgom.svgpa.svgpe.svgpf.svgpg.svgph.svgpk.svgpl.svgpm.svgpn.svgpr.svgps.svgpt.svgpw.svgpy.svgqa.svgre.svgro.svgrs.svgru.svgrw.svgsa.svgsb.svgsc.svgsd.svgse.svgsg.svgsh.svgsi.svgsj.svgsk.svgsl.svgsm.svgsn.svgso.svgsr.svgss.svgst.svgsv.svgsx.svgsy.svgsz.svgtc.svgtd.svgtf.svgtg.svgth.svgtj.svgtk.svgtl.svgtm.svgtn.svgto.svgtr.svgtt.svgtv.svgtw.svgtz.svgua.svgug.svgum.svgun.svgus.svguy.svguz.svgva.svgvc.svgve.svgvg.svgvi.svgvn.svgvu.svgwf.svgws.svgxk.svgye.svgyt.svgza.svgzm.svgzw.svg
hosts
closte.svgcloudflare.svgcloudways.pngcpanel.svggridpane-1.pnggridpane-2.pnggridpane.pngruncloud-1.pngruncloud-2.pngruncloud-3.pngruncloud-4.pngruncloud.svgserverpilot-1.pngserverpilot-2.pngserverpilot-3.pngserverpilot.svgwpengine.svgwpmudev.jpg
loader.svglogo.pngno-preview.pngpattern-wp-ultimo.pngsettings
sidebar
add-ons.pngcheckout-forms.pngemail-template.pnggateway-add-ons.pnginvoice-template.pnginvoices.pngsite-template.pngsystem-emails.pngtemplate-placeholders.png
site-placeholder-image.pngwizards
wp-ultimo-screenshot.pngjs
addons.jsaddons.min.jsadmin-notices.jsadmin-notices.min.jsadmin-screen.jsadmin-screen.min.jsadmin.jsadmin.min.jsapp.jsapp.min.jscheckout-form-editor-modal.jscheckout-form-editor-modal.min.jscheckout-forms-editor.jscheckout-forms-editor.min.jscheckout.jscheckout.min.jscookie-helpers.jscookie-helpers.min.jscustomizer.jscustomizer.min.jsdashboard-statistics.jsdashboard-statistics.min.jsedit-placeholders.jsedit-placeholders.min.jsemail-edit-page.jsemail-edit-page.min.jsevent-view-page.jsevent-view-page.min.jsfields.jsfields.min.jsfunctions.jsfunctions.min.js
gateways
gutenberg-support.jsgutenberg-support.min.jsjumper.jsjumper.min.jslegacy-signup.jslegacy-signup.min.jslib
accounting.jsaccounting.min.jsapexcharts.jsapexcharts.min.jsdetectincognito.jsdetectincognito.min.jses6-promise.auto.min.jsflatpicker.jsflatpicker.min.jsjquery.blockUI.jsjquery.blockUI.min.jsjquery.fonticonpicker.jsjquery.fonticonpicker.min.jsmousetrap.jsmousetrap.min.jsselectize.jsselectize.min.jsshepherd.jsshepherd.min.jssweetalert2.all.jssweetalert2.all.min.jstiptip.jstiptip.min.jsv-money.jsv-money.min.jsvue-apexcharts.jsvue-apexcharts.min.jsvue-the-mask.jsvue-the-mask.min.jsvue.jsvue.min.js
list-tables.jslist-tables.min.jsscreenshot-scraper.jsscreenshot-scraper.min.jsselectizer.jsselectizer.min.jssetup-wizard-polyfill.jssetup-wizard-polyfill.min.jssetup-wizard.jssetup-wizard.min.jssite-maintenance.jssite-maintenance.min.jssso.jssso.min.jssupport.jssupport.min.jstax-rates.jstax-rates.min.jstax-statistics.jstax-statistics.min.jstemplate-previewer.jstemplate-previewer.min.jstemplate-switching.jstemplate-switching.min.jsthank-you.jsthank-you.min.jstours.jstours.min.jsurl-preview.jsurl-preview.min.jsview-logs.jsview-logs.min.jsvisits-counter.jsvisits-counter.min.jsvue-apps.jsvue-apps.min.jswebhook-list-page.jswebhook-list-page.min.jswebhook-page.jswebhook-page.min.jswubox.jswubox.min.jsdata
dependencies
amphp
amp
lib
CallableMaker.phpCancellationToken.phpCancellationTokenSource.phpCancelledException.phpCoroutine.phpDeferred.phpDelayed.phpEmitter.phpFailure.php
Internal
InvalidYieldError.phpIterator.phpLazyPromise.phpLoop.phpLoop
Driver.phpDriverFactory.phpEvDriver.phpEventDriver.php
MultiReasonException.phpNullCancellationToken.phpProducer.phpPromise.phpStruct.phpSuccess.phpTimeoutCancellationToken.phpTimeoutException.phpfunctions.phpInternal
InvalidWatcherError.phpNativeDriver.phpTracingDriver.phpUnsupportedFeatureException.phpUvDriver.phpWatcher.phpbyte-stream
lib
Base64
Base64DecodingInputStream.phpBase64DecodingOutputStream.phpBase64EncodingInputStream.phpBase64EncodingOutputStream.php
ClosedException.phpInMemoryStream.phpInputStream.phpInputStreamChain.phpIteratorStream.phpLineReader.phpMessage.phpOutputBuffer.phpOutputStream.phpPayload.phpPendingReadError.phpResourceInputStream.phpResourceOutputStream.phpStreamException.phpZlibInputStream.phpZlibOutputStream.phpfunctions.phpcache
lib
ArrayCache.phpAtomicCache.phpCache.phpCacheException.phpFileCache.phpNullCache.phpPrefixCache.phpSerializedCache.php
psalm.xmldns
appveyor.yml
lib
hpack
http-client
psalm.xml
src
ApplicationInterceptor.php
Body
Connection
Connection.phpConnectionFactory.phpConnectionLimitingPool.phpConnectionPool.phpDefaultConnectionFactory.phpHttp1Connection.phpHttp2Connection.phpHttp2ConnectionException.phpHttp2StreamException.phpHttpStream.phpInterceptedStream.php
DelegateHttpClient.phpEventListener.phpInternal
LimitedConnectionPool.phpStream.phpStreamLimitingPool.phpUnlimitedConnectionPool.phpUnprocessedRequestException.phpUpgradedSocket.phpEventListener
HttpClient.phpHttpClientBuilder.phpHttpException.phpInterceptedHttpClient.phpInterceptor
AddRequestHeader.phpAddResponseHeader.phpDecompressResponse.phpFollowRedirects.phpForbidUriUserInfo.phpLogHttpArchive.phpMatchOrigin.phpModifyRequest.phpModifyResponse.phpRemoveRequestHeader.phpRemoveResponseHeader.phpRetryRequests.phpSetRequestHeader.phpSetRequestHeaderIfUnset.phpSetRequestTimeout.phpSetResponseHeader.phpSetResponseHeaderIfUnset.phpTooManyRedirectsException.php
Internal
ForbidCloning.phpForbidSerialization.phpHarAttributes.phpResponseBodyStream.phpSizeLimitingInputStream.phpfunctions.php
InvalidRequestException.phpMissingAttributeError.phpNetworkInterceptor.phpParseException.phpPooledHttpClient.phpRequest.phpRequestBody.phpResponse.phpSocketException.phpTimeoutException.phpTrailers.phphttp
src
parser
process
lib
serialization
src
socket
psalm.xml
src
BindContext.phpCertificate.phpClientTlsContext.phpConnectContext.phpConnectException.phpConnector.phpDatagramSocket.phpDnsConnector.phpEncryptableSocket.php
Internal
PendingAcceptError.phpPendingReceiveError.phpResourceSocket.phpServer.phpServerTlsContext.phpSocket.phpSocketAddress.phpSocketException.phpSocketPool.phpStaticConnector.phpTlsException.phpTlsInfo.phpUnlimitedSocketPool.phpfunctions.phpsync
composer-require-check.json
src
Barrier.php
ConcurrentIterator
FileMutex.phpInternal
KeyedMutex.phpKeyedSemaphore.phpLocalKeyedMutex.phpLocalKeyedSemaphore.phpLocalMutex.phpLocalSemaphore.phpLock.phpMutex.phpPosixSemaphore.phpPrefixedKeyedMutex.phpPrefixedKeyedSemaphore.phpSemaphore.phpSemaphoreMutex.phpStaticKeyMutex.phpSyncException.phpThreadedMutex.phpThreadedSemaphore.phpfunctions.phpwindows-registry
berlindb
core
composer
ClassLoader.phpInstalledVersions.phpLICENSEautoload_classmap.phpautoload_files.phpautoload_namespaces.phpautoload_psr4.phpautoload_real.phpautoload_static.phpinstalled.jsoninstalled.php
daverandom
libdns
src
Decoder
Encoder
Enumeration.phpMessages
Packets
Records
Question.phpQuestionFactory.phpRData.phpRDataBuilder.phpRDataFactory.phpRecord.phpRecordCollection.phpRecordCollectionFactory.phpRecordTypes.phpResource.phpResourceBuilder.phpResourceBuilderFactory.phpResourceClasses.phpResourceFactory.phpResourceQClasses.phpResourceQTypes.phpResourceTypes.php
functions.phpTypeDefinitions
FieldDefinition.phpFieldDefinitionFactory.phpTypeDefinition.phpTypeDefinitionFactory.phpTypeDefinitionManager.phpTypeDefinitionManagerFactory.php
Types
tools
delight-im
doctrine
deprecations
lib
Doctrine
Deprecations
guzzlehttp
guzzle
src
BodySummarizer.phpBodySummarizerInterface.phpClient.phpClientInterface.phpClientTrait.php
Cookie
Exception
BadResponseException.phpClientException.phpConnectException.phpGuzzleException.phpInvalidArgumentException.phpRequestException.phpServerException.phpTooManyRedirectsException.phpTransferException.php
Handler
CurlFactory.phpCurlFactoryInterface.phpCurlHandler.phpCurlMultiHandler.phpEasyHandle.phpHeaderProcessor.phpMockHandler.phpProxy.phpStreamHandler.php
HandlerStack.phpMessageFormatter.phpMessageFormatterInterface.phpMiddleware.phpPool.phpPrepareBodyMiddleware.phpRedirectMiddleware.phpRequestOptions.phpRetryMiddleware.phpTransferStats.phpUtils.phpfunctions.phpfunctions_include.phppromises
src
psr7
src
AppendStream.phpBufferStream.phpCachingStream.phpDroppingStream.php
Exception
FnStream.phpHeader.phpHttpFactory.phpInflateStream.phpLazyOpenStream.phpLimitStream.phpMessage.phpMessageTrait.phpMimeType.phpMultipartStream.phpNoSeekStream.phpPumpStream.phpQuery.phpRequest.phpResponse.phpRfc7230.phpServerRequest.phpStream.phpStreamDecoratorTrait.phpStreamWrapper.phpUploadedFile.phpUri.phpUriComparator.phpUriNormalizer.phpUriResolver.phpUtils.phphashids
ifsnop
mysqldump-php
src
Ifsnop
Mysqldump
jasny
immutable
sso
kelunik
certificate
league
uri-interfaces
Contracts
AuthorityInterface.phpDataPathInterface.phpDomainHostInterface.phpFragmentInterface.phpHostInterface.phpIpHostInterface.phpPathInterface.phpPortInterface.phpQueryInterface.phpSegmentedPathInterface.phpUriAccess.phpUriComponentInterface.phpUriException.phpUriInterface.phpUserInfoInterface.php
Encoder.phpExceptions
FeatureDetection.phpIPv4
Idna
KeyValuePair
QueryString.phpUriString.phpuri-parser
uri
mexitek
phpcolors
mpdf
mpdf
CREDITS.txt
data
CJKdata.php
phpunit.xmlruleset.xmlcollations
Afrikaans_South_Africa.phpAlbanian_Albania.phpAlsatian_France.phpArabic_Algeria.phpArabic_Bahrain.phpArabic_Egypt.phpArabic_Iraq.phpArabic_Jordan.phpArabic_Kuwait.phpArabic_Lebanon.phpArabic_Libya.phpArabic_Morocco.phpArabic_Oman.phpArabic_Pseudo_RTL.phpArabic_Qatar.phpArabic_Saudi_Arabia.phpArabic_Syria.phpArabic_Tunisia.phpArabic_Yemen.phpAzeri_(Cyrillic)_Azerbaijan.phpAzeri_(Latin)_Azerbaijan.phpBashkir_Russia.phpBasque_Spain.phpBelarusian_Belarus.phpBosnian_(Cyrillic)_Bosnia_and_Herzegovina.phpBosnian_(Latin)_Bosnia_and_Herzegovina.phpBreton_France.phpBulgarian_Bulgaria.phpCatalan_Spain.phpCorsican_France.phpCroatian_(Latin)_Bosnia_and_Herzegovina.phpCroatian_Croatia.phpCzech_Czech_Republic.phpDanish_Denmark.phpDari_Afghanistan.phpDutch_Belgium.phpDutch_Netherlands.phpEnglish_Australia.phpEnglish_Belize.phpEnglish_Canada.phpEnglish_Caribbean.phpEnglish_India.phpEnglish_Ireland.phpEnglish_Jamaica.phpEnglish_Malaysia.phpEnglish_New_Zealand.phpEnglish_Republic_of_the_Philippines.phpEnglish_Singapore.phpEnglish_South_Africa.phpEnglish_Trinidad_and_Tobago.phpEnglish_United_Kingdom.phpEnglish_United_States.phpEnglish_Zimbabwe.phpEstonian_Estonia.phpFaroese_Faroe_Islands.phpFilipino_Philippines.phpFinnish_Finland.phpFrench_Belgium.phpFrench_Canada.phpFrench_France.phpFrench_Luxembourg.phpFrench_Principality_of_Monaco.phpFrench_Switzerland.phpFrisian_Netherlands.phpGalician_Spain.phpGerman_Austria.phpGerman_Germany.phpGerman_Liechtenstein.phpGerman_Luxembourg.phpGerman_Switzerland.phpGreek_Greece.phpGreenlandic_Greenland.phpHausa_(Latin)_Nigeria.phpHebrew_Israel.phpHungarian_Hungary.phpIcelandic_Iceland.phpIgbo_Nigeria.phpIndonesian_Indonesia.phpInuktitut_(Latin)_Canada.phpInvariant_Language_Invariant_Country.phpIrish_Ireland.phpItalian_Italy.phpItalian_Switzerland.phpKinyarwanda_Rwanda.phpKiswahili_Kenya.phpKyrgyz_Kyrgyzstan.phpLatvian_Latvia.phpLithuanian_Lithuania.phpLower_Sorbian_Germany.phpLuxembourgish_Luxembourg.phpMacedonian_(FYROM)_Macedonia_(FYROM).phpMalay_Brunei_Darussalam.phpMalay_Malaysia.phpMapudungun_Chile.phpMohawk_Canada.phpMongolian_(Cyrillic)_Mongolia.phpNorwegian_(Nynorsk)_Norway.phpOccitan_France.phpPersian_Iran.phpPolish_Poland.phpPortuguese_Brazil.phpPortuguese_Portugal.phpQuechua_Bolivia.phpQuechua_Ecuador.phpQuechua_Peru.phpRomanian_Romania.phpRomansh_Switzerland.phpRussian_Russia.phpSami_(Inari)_Finland.phpSami_(Lule)_Norway.phpSami_(Lule)_Sweden.phpSami_(Northern)_Finland.phpSami_(Northern)_Norway.phpSami_(Northern)_Sweden.phpSami_(Skolt)_Finland.phpSami_(Southern)_Norway.phpSami_(Southern)_Sweden.phpSerbian_(Cyrillic)_Bosnia_and_Herzegovina.phpSerbian_(Cyrillic)_Serbia.phpSerbian_(Latin)_Bosnia_and_Herzegovina.phpSerbian_(Latin)_Serbia.phpSesotho_sa_Leboa_South_Africa.phpSetswana_South_Africa.phpSlovak_Slovakia.phpSlovenian_Slovenia.phpSpanish_Argentina.phpSpanish_Bolivia.phpSpanish_Chile.phpSpanish_Colombia.phpSpanish_Costa_Rica.phpSpanish_Dominican_Republic.phpSpanish_Ecuador.phpSpanish_El_Salvador.phpSpanish_Guatemala.phpSpanish_Honduras.phpSpanish_Mexico.phpSpanish_Nicaragua.phpSpanish_Panama.phpSpanish_Paraguay.phpSpanish_Peru.phpSpanish_Puerto_Rico.phpSpanish_Spain.phpSpanish_United_States.phpSpanish_Uruguay.phpSpanish_Venezuela.phpSwedish_Finland.phpSwedish_Sweden.phpTajik_(Cyrillic)_Tajikistan.phpTamazight_(Latin)_Algeria.phpTatar_Russia.phpTurkish_Turkey.phpTurkmen_Turkmenistan.phpUkrainian_Ukraine.phpUpper_Sorbian_Germany.phpUrdu_Islamic_Republic_of_Pakistan.phpUzbek_(Cyrillic)_Uzbekistan.phpUzbek_(Latin)_Uzbekistan.phpVietnamese_Vietnam.phpWelsh_United_Kingdom.phpWolof_Senegal.phpYakut_Russia.phpYoruba_Nigeria.phpisiXhosa_South_Africa.phpisiZulu_South_Africa.php
entity_substitutions.phpfont
ccourier.phpccourierb.phpccourierbi.phpccourieri.phpchelvetica.phpchelveticab.phpchelveticabi.phpchelveticai.phpcsymbol.phpctimes.phpctimesb.phpctimesbi.phpctimesi.phpczapfdingbats.php
iccprofiles
lang2fonts.csslinebrdictK.datlinebrdictL.datlinebrdictT.datmpdf.cssno_image.jpgout.phppatterns
subs_core.phpsubs_win-1252.phpupperCase.phpsrc
AssetFetcher.phpBarcode.php
Barcode
AbstractBarcode.phpBarcodeException.phpBarcodeInterface.phpCodabar.phpCode11.phpCode128.phpCode39.phpCode93.phpEanExt.phpEanUpc.phpI25.phpImb.phpMsi.phpPostnet.phpRm4Scc.phpS25.php
Cache.phpColor
Config
Container
Conversion
Css
CssManager.phpDirectWrite.phpException
File
Fonts
Form.phpFpdiTrait.phpGif
Gradient.phpHTMLParserMode.phpHttp
Hyphenator.phpImage
Language
Log
Mpdf.phpMpdfException.phpMpdfImageException.phpOtl.phpOtlDump.phpOutput
PageBox.phpPageFormat.phpPdf
ServiceFactory.phpShaper
SizeConverter.phpStrict.phpTTFontFile.phpTTFontFileAnalysis.phpTableOfContents.phpTag.phpTag
A.phpAcronym.phpAddress.phpAnnotation.phpArticle.phpAside.phpB.phpBarCode.phpBdi.phpBdo.phpBig.phpBlockQuote.phpBlockTag.phpBookmark.phpBr.phpCaption.phpCenter.phpCite.phpCode.phpColumnBreak.phpColumns.phpDd.phpDel.phpDetails.phpDiv.phpDl.phpDotTab.phpDt.phpEm.phpFieldSet.phpFigCaption.phpFigure.phpFont.phpFooter.phpForm.phpFormFeed.phpH1.phpH2.phpH3.phpH4.phpH5.phpH6.phpHGroup.phpHeader.phpHr.phpI.phpImg.phpIndexEntry.phpIndexInsert.phpInlineTag.phpInput.phpIns.phpKbd.phpLegend.phpLi.phpMain.phpMark.phpMeter.phpNav.phpNewColumn.phpNewPage.phpOl.phpOption.phpP.phpPageBreak.phpPageFooter.phpPageHeader.phpPre.phpProgress.phpQ.phpS.phpSamp.phpSection.phpSelect.phpSetHtmlPageFooter.phpSetHtmlPageHeader.phpSetPageFooter.phpSetPageHeader.phpSmall.phpSpan.phpStrike.phpStrong.phpSub.phpSubstituteTag.phpSummary.phpSup.phpTBody.phpTFoot.phpTHead.phpTable.phpTag.phpTd.phpTextArea.phpTextCircle.phpTh.phpTime.phpToc.phpTocEntry.phpTocPageBreak.phpTr.phpTt.phpTta.phpTts.phpTtz.phpU.phpUl.phpVarTag.phpWatermarkImage.phpWatermarkText.php
Ucdn.phpUtils
Watermark.phpWatermarkImage.phpWatermarkText.phpWriter
BackgroundWriter.phpBaseWriter.phpBookmarkWriter.phpColorWriter.phpFontWriter.phpFormWriter.phpImageWriter.phpJavaScriptWriter.phpMetadataWriter.phpObjectWriter.phpOptionalContentWriter.phpPageWriter.phpResourceWriter.php
functions-dev.phpfunctions.phpttfonts
AboriginalSansREGULAR.ttfAbyssinica_SIL.ttfAegean.otfAegyptus.otfAkkadian.otfDBSILBR.ttfDejaVuSans-Bold.ttfDejaVuSans-BoldOblique.ttfDejaVuSans-Oblique.ttfDejaVuSans.ttfDejaVuSansCondensed-Bold.ttfDejaVuSansCondensed-BoldOblique.ttfDejaVuSansCondensed-Oblique.ttfDejaVuSansCondensed.ttfDejaVuSansMono-Bold.ttfDejaVuSansMono-BoldOblique.ttfDejaVuSansMono-Oblique.ttfDejaVuSansMono.ttfDejaVuSerif-Bold.ttfDejaVuSerif-BoldItalic.ttfDejaVuSerif-Italic.ttfDejaVuSerif.ttfDejaVuSerifCondensed-Bold.ttfDejaVuSerifCondensed-BoldItalic.ttfDejaVuSerifCondensed-Italic.ttfDejaVuSerifCondensed.ttfDejaVuinfo.txtDhyana-Bold.ttfDhyana-Regular.ttfDhyanaOFL.txtFreeMono.ttfFreeMonoBold.ttfFreeMonoBoldOblique.ttfFreeMonoOblique.ttfFreeSans.ttfFreeSansBold.ttfFreeSansBoldOblique.ttfFreeSansOblique.ttfFreeSerif.ttfFreeSerifBold.ttfFreeSerifBoldItalic.ttfFreeSerifItalic.ttfGNUFreeFontinfo.txtGaruda-Bold.ttfGaruda-BoldOblique.ttfGaruda-Oblique.ttfGaruda.ttfJomolhari-OFL.txtJomolhari.ttfKhmerOFL.txtKhmerOS.ttfLateef font OFL.txtLateefRegOT.ttfLohit-Kannada.ttfLohitKannadaOFL.txtPadauk-book.ttfPothana2000.ttfQuivira.otfSundaneseUnicode-1.0.5.ttfSyrCOMEdessa.otfSyrCOMEdessa_license.txtTaameyDavidCLM-Medium.ttfTaiHeritagePro.ttfTharlon-Regular.ttfTharlonOFL.txtUthman.otfXB Riyaz.ttfXB RiyazBd.ttfXB RiyazBdIt.ttfXB RiyazIt.ttfXW Zar Font Info.txtZawgyiOne.ttfayar.ttfdamase_v.2.ttfkaputaunicode.ttflannaalif-v1-03.ttfocrb10.ttf
psr-http-message-shim
psr-log-aware-trait
myclabs
deep-copy
src
DeepCopy
DeepCopy.php
Exception
Filter
Matcher
Reflection
TypeFilter
TypeMatcher
deep_copy.phpnesbot
carbon
extension.neonsponsors.php
lazy
Carbon
src
Carbon
AbstractTranslator.phpCarbon.phpCarbonConverterInterface.phpCarbonImmutable.phpCarbonInterface.phpCarbonInterval.phpCarbonPeriod.phpCarbonPeriodImmutable.phpCarbonTimeZone.php
Cli
Doctrine
CarbonDoctrineType.phpCarbonImmutableType.phpCarbonType.phpCarbonTypeConverter.phpDateTimeDefaultPrecision.phpDateTimeImmutableType.phpDateTimeType.php
Exceptions
BadComparisonUnitException.phpBadFluentConstructorException.phpBadFluentSetterException.phpBadMethodCallException.phpEndLessPeriodException.phpException.phpImmutableException.phpInvalidArgumentException.phpInvalidCastException.phpInvalidDateException.phpInvalidFormatException.phpInvalidIntervalException.phpInvalidPeriodDateException.phpInvalidPeriodParameterException.phpInvalidTimeZoneException.phpInvalidTypeException.phpNotACarbonClassException.phpNotAPeriodException.phpNotLocaleAwareException.phpOutOfRangeException.phpParseErrorException.phpRuntimeException.phpUnitException.phpUnitNotConfiguredException.phpUnknownGetterException.phpUnknownMethodException.phpUnknownSetterException.phpUnknownUnitException.phpUnreachableException.php
Factory.phpFactoryImmutable.phpLang
aa.phpaa_DJ.phpaa_ER.phpaa_ER@saaho.phpaa_ET.phpaf.phpaf_NA.phpaf_ZA.phpagq.phpagr.phpagr_PE.phpak.phpak_GH.phpam.phpam_ET.phpan.phpan_ES.phpanp.phpanp_IN.phpar.phpar_AE.phpar_BH.phpar_DJ.phpar_DZ.phpar_EG.phpar_EH.phpar_ER.phpar_IL.phpar_IN.phpar_IQ.phpar_JO.phpar_KM.phpar_KW.phpar_LB.phpar_LY.phpar_MA.phpar_MR.phpar_OM.phpar_PS.phpar_QA.phpar_SA.phpar_SD.phpar_SO.phpar_SS.phpar_SY.phpar_Shakl.phpar_TD.phpar_TN.phpar_YE.phpas.phpas_IN.phpasa.phpast.phpast_ES.phpayc.phpayc_PE.phpaz.phpaz_AZ.phpaz_Cyrl.phpaz_IR.phpaz_Latn.phpbas.phpbe.phpbe_BY.phpbe_BY@latin.phpbem.phpbem_ZM.phpber.phpber_DZ.phpber_MA.phpbez.phpbg.phpbg_BG.phpbhb.phpbhb_IN.phpbho.phpbho_IN.phpbi.phpbi_VU.phpbm.phpbn.phpbn_BD.phpbn_IN.phpbo.phpbo_CN.phpbo_IN.phpbr.phpbr_FR.phpbrx.phpbrx_IN.phpbs.phpbs_BA.phpbs_Cyrl.phpbs_Latn.phpbyn.phpbyn_ER.phpca.phpca_AD.phpca_ES.phpca_ES_Valencia.phpca_FR.phpca_IT.phpccp.phpccp_IN.phpce.phpce_RU.phpcgg.phpchr.phpchr_US.phpckb.phpcmn.phpcmn_TW.phpcrh.phpcrh_UA.phpcs.phpcs_CZ.phpcsb.phpcsb_PL.phpcu.phpcv.phpcv_RU.phpcy.phpcy_GB.phpda.phpda_DK.phpda_GL.phpdav.phpde.phpde_AT.phpde_BE.phpde_CH.phpde_DE.phpde_IT.phpde_LI.phpde_LU.phpdje.phpdoi.phpdoi_IN.phpdsb.phpdsb_DE.phpdua.phpdv.phpdv_MV.phpdyo.phpdz.phpdz_BT.phpebu.phpee.phpee_TG.phpel.phpel_CY.phpel_GR.phpen.phpen_001.phpen_150.phpen_AG.phpen_AI.phpen_AS.phpen_AT.phpen_AU.phpen_BB.phpen_BE.phpen_BI.phpen_BM.phpen_BS.phpen_BW.phpen_BZ.phpen_CA.phpen_CC.phpen_CH.phpen_CK.phpen_CM.phpen_CX.phpen_CY.phpen_DE.phpen_DG.phpen_DK.phpen_DM.phpen_ER.phpen_FI.phpen_FJ.phpen_FK.phpen_FM.phpen_GB.phpen_GD.phpen_GG.phpen_GH.phpen_GI.phpen_GM.phpen_GU.phpen_GY.phpen_HK.phpen_IE.phpen_IL.phpen_IM.phpen_IN.phpen_IO.phpen_ISO.phpen_JE.phpen_JM.phpen_KE.phpen_KI.phpen_KN.phpen_KY.phpen_LC.phpen_LR.phpen_LS.phpen_MG.phpen_MH.phpen_MO.phpen_MP.phpen_MS.phpen_MT.phpen_MU.phpen_MW.phpen_MY.phpen_NA.phpen_NF.phpen_NG.phpen_NL.phpen_NR.phpen_NU.phpen_NZ.phpen_PG.phpen_PH.phpen_PK.phpen_PN.phpen_PR.phpen_PW.phpen_RW.phpen_SB.phpen_SC.phpen_SD.phpen_SE.phpen_SG.phpen_SH.phpen_SI.phpen_SL.phpen_SS.phpen_SX.phpen_SZ.phpen_TC.phpen_TK.phpen_TO.phpen_TT.phpen_TV.phpen_TZ.phpen_UG.phpen_UM.phpen_US.phpen_US_Posix.phpen_VC.phpen_VG.phpen_VI.phpen_VU.phpen_WS.phpen_ZA.phpen_ZM.phpen_ZW.phpeo.phpes.phpes_419.phpes_AR.phpes_BO.phpes_BR.phpes_BZ.phpes_CL.phpes_CO.phpes_CR.phpes_CU.phpes_DO.phpes_EA.phpes_EC.phpes_ES.phpes_GQ.phpes_GT.phpes_HN.phpes_IC.phpes_MX.phpes_NI.phpes_PA.phpes_PE.phpes_PH.phpes_PR.phpes_PY.phpes_SV.phpes_US.phpes_UY.phpes_VE.phpet.phpet_EE.phpeu.phpeu_ES.phpewo.phpfa.phpfa_AF.phpfa_IR.phpff.phpff_CM.phpff_GN.phpff_MR.phpff_SN.phpfi.phpfi_FI.phpfil.phpfil_PH.phpfo.phpfo_DK.phpfo_FO.phpfr.phpfr_BE.phpfr_BF.phpfr_BI.phpfr_BJ.phpfr_BL.phpfr_CA.phpfr_CD.phpfr_CF.phpfr_CG.phpfr_CH.phpfr_CI.phpfr_CM.phpfr_DJ.phpfr_DZ.phpfr_FR.phpfr_GA.phpfr_GF.phpfr_GN.phpfr_GP.phpfr_GQ.phpfr_HT.phpfr_KM.phpfr_LU.phpfr_MA.phpfr_MC.phpfr_MF.phpfr_MG.phpfr_ML.phpfr_MQ.phpfr_MR.phpfr_MU.phpfr_NC.phpfr_NE.phpfr_PF.phpfr_PM.phpfr_RE.phpfr_RW.phpfr_SC.phpfr_SN.phpfr_SY.phpfr_TD.phpfr_TG.phpfr_TN.phpfr_VU.phpfr_WF.phpfr_YT.phpfur.phpfur_IT.phpfy.phpfy_DE.phpfy_NL.phpga.phpga_IE.phpgd.phpgd_GB.phpgez.phpgez_ER.phpgez_ET.phpgl.phpgl_ES.phpgom.phpgom_Latn.phpgsw.phpgsw_CH.phpgsw_FR.phpgsw_LI.phpgu.phpgu_IN.phpguz.phpgv.phpgv_GB.phpha.phpha_GH.phpha_NE.phpha_NG.phphak.phphak_TW.phphaw.phphe.phphe_IL.phphi.phphi_IN.phphif.phphif_FJ.phphne.phphne_IN.phphr.phphr_BA.phphr_HR.phphsb.phphsb_DE.phpht.phpht_HT.phphu.phphu_HU.phphy.phphy_AM.phpi18n.phpia.phpia_FR.phpid.phpid_ID.phpig.phpig_NG.phpii.phpik.phpik_CA.phpin.phpis.phpis_IS.phpit.phpit_CH.phpit_IT.phpit_SM.phpit_VA.phpiu.phpiu_CA.phpiw.phpja.phpja_JP.phpjgo.phpjmc.phpjv.phpka.phpka_GE.phpkab.phpkab_DZ.phpkam.phpkde.phpkea.phpkhq.phpki.phpkk.phpkk_KZ.phpkkj.phpkl.phpkl_GL.phpkln.phpkm.phpkm_KH.phpkn.phpkn_IN.phpko.phpko_KP.phpko_KR.phpkok.phpkok_IN.phpks.phpks_IN.phpks_IN@devanagari.phpksb.phpksf.phpksh.phpku.phpku_TR.phpkw.phpkw_GB.phpky.phpky_KG.phplag.phplb.phplb_LU.phplg.phplg_UG.phpli.phpli_NL.phplij.phplij_IT.phplkt.phpln.phpln_AO.phpln_CD.phpln_CF.phpln_CG.phplo.phplo_LA.phplrc.phplrc_IQ.phplt.phplt_LT.phplu.phpluo.phpluy.phplv.phplv_LV.phplzh.phplzh_TW.phpmag.phpmag_IN.phpmai.phpmai_IN.phpmas.phpmas_TZ.phpmer.phpmfe.phpmfe_MU.phpmg.phpmg_MG.phpmgh.phpmgo.phpmhr.phpmhr_RU.phpmi.phpmi_NZ.phpmiq.phpmiq_NI.phpmjw.phpmjw_IN.phpmk.phpmk_MK.phpml.phpml_IN.phpmn.phpmn_MN.phpmni.phpmni_IN.phpmo.phpmr.phpmr_IN.phpms.phpms_BN.phpms_MY.phpms_SG.phpmt.phpmt_MT.phpmua.phpmy.phpmy_MM.phpmzn.phpnan.phpnan_TW.phpnan_TW@latin.phpnaq.phpnb.phpnb_NO.phpnb_SJ.phpnd.phpnds.phpnds_DE.phpnds_NL.phpne.phpne_IN.phpne_NP.phpnhn.phpnhn_MX.phpniu.phpniu_NU.phpnl.phpnl_AW.phpnl_BE.phpnl_BQ.phpnl_CW.phpnl_NL.phpnl_SR.phpnl_SX.phpnmg.phpnn.phpnn_NO.phpnnh.phpno.phpnr.phpnr_ZA.phpnso.phpnso_ZA.phpnus.phpnyn.phpoc.phpoc_FR.phpom.phpom_ET.phpom_KE.phpor.phpor_IN.phpos.phpos_RU.phppa.phppa_Arab.phppa_Guru.phppa_IN.phppa_PK.phppap.phppap_AW.phppap_CW.phppl.phppl_PL.phpprg.phpps.phpps_AF.phppt.phppt_AO.phppt_BR.phppt_CH.phppt_CV.phppt_GQ.phppt_GW.phppt_LU.phppt_MO.phppt_MZ.phppt_PT.phppt_ST.phppt_TL.phpqu.phpqu_BO.phpqu_EC.phpquz.phpquz_PE.phpraj.phpraj_IN.phprm.phprn.phpro.phpro_MD.phpro_RO.phprof.phpru.phpru_BY.phpru_KG.phpru_KZ.phpru_MD.phpru_RU.phpru_UA.phprw.phprw_RW.phprwk.phpsa.phpsa_IN.phpsah.phpsah_RU.phpsaq.phpsat.phpsat_IN.phpsbp.phpsc.phpsc_IT.phpsd.phpsd_IN.phpsd_IN@devanagari.phpse.phpse_FI.phpse_NO.phpse_SE.phpseh.phpses.phpsg.phpsgs.phpsgs_LT.phpsh.phpshi.phpshi_Latn.phpshi_Tfng.phpshn.phpshn_MM.phpshs.phpshs_CA.phpsi.phpsi_LK.phpsid.phpsid_ET.phpsk.phpsk_SK.phpsl.phpsl_SI.phpsm.phpsm_WS.phpsmn.phpsn.phpso.phpso_DJ.phpso_ET.phpso_KE.phpso_SO.phpsq.phpsq_AL.phpsq_MK.phpsq_XK.phpsr.phpsr_Cyrl.phpsr_Cyrl_BA.phpsr_Cyrl_ME.phpsr_Cyrl_XK.phpsr_Latn.phpsr_Latn_BA.phpsr_Latn_ME.phpsr_Latn_XK.phpsr_ME.phpsr_RS.phpsr_RS@latin.phpss.phpss_ZA.phpst.phpst_ZA.phpsv.phpsv_AX.phpsv_FI.phpsv_SE.phpsw.phpsw_CD.phpsw_KE.phpsw_TZ.phpsw_UG.phpszl.phpszl_PL.phpta.phpta_IN.phpta_LK.phpta_MY.phpta_SG.phptcy.phptcy_IN.phpte.phpte_IN.phpteo.phpteo_KE.phptet.phptg.phptg_TJ.phpth.phpth_TH.phpthe.phpthe_NP.phpti.phpti_ER.phpti_ET.phptig.phptig_ER.phptk.phptk_TM.phptl.phptl_PH.phptlh.phptn.phptn_ZA.phpto.phpto_TO.phptpi.phptpi_PG.phptr.phptr_CY.phptr_TR.phpts.phpts_ZA.phptt.phptt_RU.phptt_RU@iqtelif.phptwq.phptzl.phptzm.phptzm_Latn.phpug.phpug_CN.phpuk.phpuk_UA.phpunm.phpunm_US.phpur.phpur_IN.phpur_PK.phpuz.phpuz_Arab.phpuz_Cyrl.phpuz_Latn.phpuz_UZ.phpuz_UZ@cyrillic.phpvai.phpvai_Latn.phpvai_Vaii.phpve.phpve_ZA.phpvi.phpvi_VN.phpvo.phpvun.phpwa.phpwa_BE.phpwae.phpwae_CH.phpwal.phpwal_ET.phpwo.phpwo_SN.phpxh.phpxh_ZA.phpxog.phpyav.phpyi.phpyi_US.phpyo.phpyo_BJ.phpyo_NG.phpyue.phpyue_HK.phpyue_Hans.phpyue_Hant.phpyuw.phpyuw_PG.phpzgh.phpzh.phpzh_CN.phpzh_HK.phpzh_Hans.phpzh_Hans_HK.phpzh_Hans_MO.phpzh_Hans_SG.phpzh_Hant.phpzh_Hant_HK.phpzh_Hant_MO.phpzh_Hant_TW.phpzh_MO.phpzh_SG.phpzh_TW.phpzh_YUE.phpzu.phpzu_ZA.php
Laravel
List
MessageFormatter
PHPStan
Traits
Boundaries.phpCast.phpComparison.phpConverter.phpCreator.phpDate.phpDeprecatedProperties.phpDifference.phpIntervalRounding.phpIntervalStep.phpLocalization.phpMacro.phpMagicParameter.phpMixin.phpModifiers.phpMutability.phpObjectInitialisation.phpOptions.phpRounding.phpSerialization.phpTest.phpTimestamp.phpToStringFormat.phpUnits.phpWeek.php
Translator.phpTranslatorImmutable.phpTranslatorStrongTypeInterface.phpnyholm
psr7
pablo-sg-pacheco
wp-namespace-autoloader
paragonie
constant_time_encoding
src
random_compat
phpdocumentor
reflection-common
reflection-docblock
src
DocBlock.php
DocBlock
Description.phpDescriptionFactory.phpExampleFinder.phpSerializer.phpStandardTagFactory.phpTag.phpTagFactory.php
DocBlockFactory.phpDocBlockFactoryInterface.phpTags
Exception
Utils.phptype-resolver
src
FqsenResolver.phpPseudoType.php
PseudoTypes
ArrayShape.phpArrayShapeItem.phpCallableString.phpConstExpression.phpFalse_.phpFloatValue.phpHtmlEscapedString.phpIntegerRange.phpIntegerValue.phpList_.phpLiteralString.phpLowercaseString.phpNegativeInteger.phpNonEmptyList.phpNonEmptyLowercaseString.phpNonEmptyString.phpNumericString.phpNumeric_.phpPositiveInteger.phpStringValue.phpTraitString.phpTrue_.php
Type.phpTypeResolver.phpTypes
AbstractList.phpAggregatedType.phpArrayKey.phpArray_.phpBoolean.phpCallableParameter.phpCallable_.phpClassString.phpCollection.phpCompound.phpContext.phpContextFactory.phpExpression.phpFloat_.phpInteger.phpInterfaceString.phpIntersection.phpIterable_.phpMixed_.phpNever_.phpNull_.phpNullable.phpObject_.phpParent_.phpResource_.phpScalar.phpSelf_.phpStatic_.phpString_.phpThis.phpVoid_.php
phpseclib
bcmath_compat
phpseclib
AUTHORSbootstrap.phpopenssl.cnf
phpseclib
Common
Functions
Crypt
AES.phpBlowfish.phpChaCha20.phpParameters.phpPrivateKey.phpPublicKey.phpHash.phpPublicKeyLoader.phpRC2.phpRC4.phpRSA.php
Common
AsymmetricKey.phpBlockCipher.php
DES.phpDH.phpFormats
PrivateKey.phpPublicKey.phpStreamCipher.phpSymmetricKey.phpTraits
DH
DSA.phpDSA
EC.phpEC
BaseCurves
Curves
Curve25519.phpCurve448.phpEd25519.phpEd448.phpbrainpoolP160r1.phpbrainpoolP160t1.phpbrainpoolP192r1.phpbrainpoolP192t1.phpbrainpoolP224r1.phpbrainpoolP224t1.phpbrainpoolP256r1.phpbrainpoolP256t1.phpbrainpoolP320r1.phpbrainpoolP320t1.phpbrainpoolP384r1.phpbrainpoolP384t1.phpbrainpoolP512r1.phpbrainpoolP512t1.phpnistb233.phpnistb409.phpnistk163.phpnistk233.phpnistk283.phpnistk409.phpnistp192.phpnistp224.phpnistp256.phpnistp384.phpnistp521.phpnistt571.phpprime192v1.phpprime192v2.phpprime192v3.phpprime239v1.phpprime239v2.phpprime239v3.phpprime256v1.phpsecp112r1.phpsecp112r2.phpsecp128r1.phpsecp128r2.phpsecp160k1.phpsecp160r1.phpsecp160r2.phpsecp192k1.phpsecp192r1.phpsecp224k1.phpsecp224r1.phpsecp256k1.phpsecp256r1.phpsecp384r1.phpsecp521r1.phpsect113r1.phpsect113r2.phpsect131r1.phpsect131r2.phpsect163k1.phpsect163r1.phpsect163r2.phpsect193r1.phpsect193r2.phpsect233k1.phpsect233r1.phpsect239k1.phpsect283k1.phpsect283r1.phpsect409k1.phpsect409r1.phpsect571k1.phpsect571r1.php
Formats
Keys
Common.phpJWK.phpMontgomeryPrivate.phpMontgomeryPublic.phpOpenSSH.phpPKCS1.phpPKCS8.phpPuTTY.phpXML.phplibsodium.php
Signature
RSA
Random.phpRijndael.phpSalsa20.phpTripleDES.phpTwofish.phpException
BadConfigurationException.phpBadDecryptionException.phpBadModeException.phpConnectionClosedException.phpFileNotFoundException.phpInconsistentSetupException.phpInsufficientSetupException.phpNoKeyLoadedException.phpNoSupportedAlgorithmsException.phpUnableToConnectException.phpUnsupportedAlgorithmException.phpUnsupportedCurveException.phpUnsupportedFormatException.phpUnsupportedOperationException.php
File
ANSI.phpASN1.php
ASN1
Element.php
X509.phpMaps
AccessDescription.phpAdministrationDomainName.phpAlgorithmIdentifier.phpAnotherName.phpAttribute.phpAttributeType.phpAttributeTypeAndValue.phpAttributeValue.phpAttributes.phpAuthorityInfoAccessSyntax.phpAuthorityKeyIdentifier.phpBaseDistance.phpBasicConstraints.phpBuiltInDomainDefinedAttribute.phpBuiltInDomainDefinedAttributes.phpBuiltInStandardAttributes.phpCPSuri.phpCRLDistributionPoints.phpCRLNumber.phpCRLReason.phpCertPolicyId.phpCertificate.phpCertificateIssuer.phpCertificateList.phpCertificatePolicies.phpCertificateSerialNumber.phpCertificationRequest.phpCertificationRequestInfo.phpCharacteristic_two.phpCountryName.phpCurve.phpDHParameter.phpDSAParams.phpDSAPrivateKey.phpDSAPublicKey.phpDigestInfo.phpDirectoryString.phpDisplayText.phpDistributionPoint.phpDistributionPointName.phpDssSigValue.phpECParameters.phpECPoint.phpECPrivateKey.phpEDIPartyName.phpEcdsaSigValue.phpEncryptedData.phpEncryptedPrivateKeyInfo.phpExtKeyUsageSyntax.phpExtension.phpExtensionAttribute.phpExtensionAttributes.phpExtensions.phpFieldElement.phpFieldID.phpGeneralName.phpGeneralNames.phpGeneralSubtree.phpGeneralSubtrees.phpHashAlgorithm.phpHoldInstructionCode.phpInvalidityDate.phpIssuerAltName.phpIssuingDistributionPoint.phpKeyIdentifier.phpKeyPurposeId.phpKeyUsage.phpMaskGenAlgorithm.phpName.phpNameConstraints.phpNetworkAddress.phpNoticeReference.phpNumericUserIdentifier.phpORAddress.phpOneAsymmetricKey.phpOrganizationName.phpOrganizationalUnitNames.phpOtherPrimeInfo.phpOtherPrimeInfos.phpPBEParameter.phpPBES2params.phpPBKDF2params.phpPBMAC1params.phpPKCS9String.phpPentanomial.phpPersonalName.phpPolicyInformation.phpPolicyMappings.phpPolicyQualifierId.phpPolicyQualifierInfo.phpPostalAddress.phpPrime_p.phpPrivateDomainName.phpPrivateKey.phpPrivateKeyInfo.phpPrivateKeyUsagePeriod.phpPublicKey.phpPublicKeyAndChallenge.phpPublicKeyInfo.phpRC2CBCParameter.phpRDNSequence.phpRSAPrivateKey.phpRSAPublicKey.phpRSASSA_PSS_params.phpReasonFlags.phpRelativeDistinguishedName.phpRevokedCertificate.phpSignedPublicKeyAndChallenge.phpSpecifiedECDomain.phpSubjectAltName.phpSubjectDirectoryAttributes.phpSubjectInfoAccessSyntax.phpSubjectPublicKeyInfo.phpTBSCertList.phpTBSCertificate.phpTerminalIdentifier.phpTime.phpTrinomial.phpUniqueIdentifier.phpUserNotice.phpValidity.phpnetscape_ca_policy_url.phpnetscape_cert_type.phpnetscape_comment.php
Math
BigInteger.phpBinaryField.php
BigInteger
Engines
BinaryField
Common
PrimeField.phpPrimeField
Net
System
SSH
phpstan
phpdoc-parser
phpstan-baseline.neon
src
Ast
AbstractNodeVisitor.phpAttribute.php
ConstExpr
ConstExprArrayItemNode.phpConstExprArrayNode.phpConstExprFalseNode.phpConstExprFloatNode.phpConstExprIntegerNode.phpConstExprNode.phpConstExprNullNode.phpConstExprStringNode.phpConstExprTrueNode.phpConstFetchNode.phpDoctrineConstExprStringNode.phpQuoteAwareConstExprStringNode.php
Node.phpNodeAttributes.phpNodeTraverser.phpNodeVisitor.phpNodeVisitor
PhpDoc
AssertTagMethodValueNode.phpAssertTagPropertyValueNode.phpAssertTagValueNode.phpDeprecatedTagValueNode.php
Doctrine
DoctrineAnnotation.phpDoctrineArgument.phpDoctrineArray.phpDoctrineArrayItem.phpDoctrineTagValueNode.php
ExtendsTagValueNode.phpGenericTagValueNode.phpImplementsTagValueNode.phpInvalidTagValueNode.phpMethodTagValueNode.phpMethodTagValueParameterNode.phpMixinTagValueNode.phpParamOutTagValueNode.phpParamTagValueNode.phpPhpDocChildNode.phpPhpDocNode.phpPhpDocTagNode.phpPhpDocTagValueNode.phpPhpDocTextNode.phpPropertyTagValueNode.phpReturnTagValueNode.phpSelfOutTagValueNode.phpTemplateTagValueNode.phpThrowsTagValueNode.phpTypeAliasImportTagValueNode.phpTypeAliasTagValueNode.phpTypelessParamTagValueNode.phpUsesTagValueNode.phpVarTagValueNode.phpType
ArrayShapeItemNode.phpArrayShapeNode.phpArrayTypeNode.phpCallableTypeNode.phpCallableTypeParameterNode.phpConditionalTypeForParameterNode.phpConditionalTypeNode.phpConstTypeNode.phpGenericTypeNode.phpIdentifierTypeNode.phpIntersectionTypeNode.phpInvalidTypeNode.phpNullableTypeNode.phpObjectShapeItemNode.phpObjectShapeNode.phpOffsetAccessTypeNode.phpThisTypeNode.phpTypeNode.phpUnionTypeNode.php
Lexer
Parser
ConstExprParser.phpParserException.phpPhpDocParser.phpStringUnescaper.phpTokenIterator.phpTypeParser.php
Printer
psr
cache
clock
container
event-dispatcher
http-client
src
http-factory
src
http-message
src
log
src
simple-cache
rakit
validation
phpcs.xml
src
Attribute.phpErrorBag.phpHelper.phpMimeTypeGuesser.phpMissingRequiredParameterException.phpRule.phpRuleNotFoundException.phpRuleQuashException.php
Rules
Accepted.phpAfter.phpAlpha.phpAlphaDash.phpAlphaNum.phpAlphaSpaces.phpBefore.phpBetween.phpBoolean.phpCallback.phpDate.phpDefaults.phpDifferent.phpDigits.phpDigitsBetween.phpEmail.phpExtension.phpIn.phpInteger.php
Interfaces
Ip.phpIpv4.phpIpv6.phpJson.phpLowercase.phpMax.phpMimes.phpMin.phpNotIn.phpNullable.phpNumeric.phpPresent.phpRegex.phpRequired.phpRequiredIf.phpRequiredUnless.phpRequiredWith.phpRequiredWithAll.phpRequiredWithout.phpRequiredWithoutAll.phpSame.phpTraits
TypeArray.phpUploadedFile.phpUppercase.phpUrl.phpTraits
Validation.phpValidator.phpralouphie
getallheaders
remotelyliving
php-dns
_config.yml
bootstrap
churn.ymlphpstan.neonpsalm.xmlrector.phpsrc
Entities
CAAData.phpCNAMEData.phpDNSRecord.phpDNSRecordCollection.phpDNSRecordType.phpDataAbstract.phpEntityAbstract.phpHostname.phpIPAddress.php
Interfaces
MXData.phpNSData.phpPTRData.phpSOAData.phpSRVData.phpTXTData.phpExceptions
Factories
Mappers
Observability
Events
Interfaces
Performance
Subscribers
Traits
Resolvers
Cached.phpChain.phpCloudFlare.phpDig.php
Exceptions
GoogleDNS.phpInterfaces
LocalSystem.phpResolverAbstract.phpTraits
Services
rpnzl
arrch
scssphp
scssphp
scss.inc.php
src
Base
Block.phpBlock
AtRootBlock.phpCallableBlock.phpContentBlock.phpDirectiveBlock.phpEachBlock.phpElseBlock.phpElseifBlock.phpForBlock.phpIfBlock.phpMediaBlock.phpNestedPropertyBlock.phpWhileBlock.php
Cache.phpColors.phpCompilationResult.phpCompiler.phpCompiler
Exception
CompilerException.phpParserException.phpRangeException.phpSassException.phpSassScriptException.phpServerException.php
Formatter.phpFormatter
Logger
Node.phpNode
OutputStyle.phpParser.phpSourceMap
Type.phpUtil.phpUtil
ValueConverter.phpVersion.phpWarn.phpsetasign
fpdi
src
FpdfTpl.phpFpdfTplTrait.phpFpdfTrait.phpFpdi.phpFpdiException.phpFpdiTrait.phpGraphicsState.php
Math
PdfParser
CrossReference
AbstractReader.phpCrossReference.phpCrossReferenceException.phpFixedReader.phpLineReader.phpReaderInterface.php
Filter
Ascii85.phpAscii85Exception.phpAsciiHex.phpFilterException.phpFilterInterface.phpFlate.phpFlateException.phpLzw.phpLzwException.php
PdfParser.phpPdfParserException.phpStreamReader.phpTokenizer.phpType
PdfReader
Tcpdf
TcpdfFpdi.phpTfpdf
autoload.phpspatie
dns
src
macroable
ssl-certificate
stripe
stripe-php
OPENAPI_VERSIONVERSION
data
init.phplib
Account.phpAccountLink.php
ApiOperations
All.phpCreate.phpDelete.phpNestedResource.phpRequest.phpRetrieve.phpSearch.phpSingletonRetrieve.phpUpdate.php
ApiRequestor.phpApiResource.phpApiResponse.phpApplePayDomain.phpApplicationFee.phpApplicationFeeRefund.phpApps
Balance.phpBalanceTransaction.phpBankAccount.phpBaseStripeClient.phpBaseStripeClientInterface.phpBillingPortal
Capability.phpCard.phpCashBalance.phpCharge.phpCheckout
Collection.phpCountrySpec.phpCoupon.phpCreditNote.phpCreditNoteLineItem.phpCustomer.phpCustomerBalanceTransaction.phpCustomerCashBalanceTransaction.phpDiscount.phpDispute.phpEphemeralKey.phpErrorObject.phpEvent.phpException
ApiConnectionException.phpApiErrorException.phpAuthenticationException.phpBadMethodCallException.phpCardException.phpExceptionInterface.phpIdempotencyException.phpInvalidArgumentException.phpInvalidRequestException.php
ExchangeRate.phpFile.phpFileLink.phpOAuth
ExceptionInterface.phpInvalidClientException.phpInvalidGrantException.phpInvalidRequestException.phpInvalidScopeException.phpOAuthErrorException.phpUnknownOAuthErrorException.phpUnsupportedGrantTypeException.phpUnsupportedResponseTypeException.php
PermissionException.phpRateLimitException.phpSignatureVerificationException.phpUnexpectedValueException.phpUnknownApiErrorException.phpFinancialConnections
FundingInstructions.phpHttpClient
Identity
Invoice.phpInvoiceItem.phpInvoiceLineItem.phpIssuing
LineItem.phpLoginLink.phpMandate.phpOAuth.phpOAuthErrorObject.phpPaymentIntent.phpPaymentLink.phpPaymentMethod.phpPayout.phpPerson.phpPlan.phpPrice.phpProduct.phpPromotionCode.phpQuote.phpRadar
RecipientTransfer.phpRefund.phpReporting
RequestTelemetry.phpReview.phpSearchResult.phpService
AbstractService.phpAbstractServiceFactory.phpAccountLinkService.phpAccountService.phpApplePayDomainService.phpApplicationFeeService.php
SetupAttempt.phpSetupIntent.phpShippingRate.phpApps
BalanceService.phpBalanceTransactionService.phpBillingPortal
ChargeService.phpCheckout
CoreServiceFactory.phpCountrySpecService.phpCouponService.phpCreditNoteService.phpCustomerService.phpDisputeService.phpEphemeralKeyService.phpEventService.phpExchangeRateService.phpFileLinkService.phpFileService.phpFinancialConnections
Identity
InvoiceItemService.phpInvoiceService.phpIssuing
AuthorizationService.phpCardService.phpCardholderService.phpDisputeService.phpIssuingServiceFactory.phpTransactionService.php
MandateService.phpOAuthService.phpPaymentIntentService.phpPaymentLinkService.phpPaymentMethodService.phpPayoutService.phpPlanService.phpPriceService.phpProductService.phpPromotionCodeService.phpQuoteService.phpRadar
RefundService.phpReporting
ReviewService.phpSetupAttemptService.phpSetupIntentService.phpShippingRateService.phpSigma
SourceService.phpSubscriptionItemService.phpSubscriptionScheduleService.phpSubscriptionService.phpTax
TaxCodeService.phpTaxRateService.phpTerminal
ConfigurationService.phpConnectionTokenService.phpLocationService.phpReaderService.phpTerminalServiceFactory.php
TestHelpers
CustomerService.php
TokenService.phpTopupService.phpTransferService.phpIssuing
RefundService.phpTerminal
TestClockService.phpTestHelpersServiceFactory.phpTreasury
Treasury
CreditReversalService.phpDebitReversalService.phpFinancialAccountService.phpInboundTransferService.phpOutboundPaymentService.phpOutboundTransferService.phpReceivedCreditService.phpReceivedDebitService.phpTransactionEntryService.phpTransactionService.phpTreasuryServiceFactory.php
WebhookEndpointService.phpSigma
SingletonApiResource.phpSource.phpSourceTransaction.phpStripe.phpStripeClient.phpStripeClientInterface.phpStripeObject.phpStripeStreamingClientInterface.phpSubscription.phpSubscriptionItem.phpSubscriptionSchedule.phpTax
TaxCode.phpTaxId.phpTaxRate.phpTerminal
TestHelpers
Token.phpTopup.phpTransfer.phpTransferReversal.phpTreasury
CreditReversal.phpDebitReversal.phpFinancialAccount.phpFinancialAccountFeatures.phpInboundTransfer.phpOutboundPayment.phpOutboundTransfer.phpReceivedCredit.phpReceivedDebit.phpTransaction.phpTransactionEntry.php
UsageRecord.phpUsageRecordSummary.phpUtil
ApiVersion.phpCaseInsensitiveArray.phpDefaultLogger.phpLoggerInterface.phpObjectTypes.phpRandomGenerator.phpRequestOptions.phpSet.phpUtil.php
Webhook.phpWebhookEndpoint.phpWebhookSignature.phpsymfony
cache-contracts
cache
Adapter
AbstractAdapter.phpAbstractTagAwareAdapter.phpAdapterInterface.phpApcuAdapter.phpArrayAdapter.phpChainAdapter.phpCouchbaseBucketAdapter.phpCouchbaseCollectionAdapter.phpDoctrineAdapter.phpDoctrineDbalAdapter.phpFilesystemAdapter.phpFilesystemTagAwareAdapter.phpMemcachedAdapter.phpNullAdapter.phpParameterNormalizer.phpPdoAdapter.phpPhpArrayAdapter.phpPhpFilesAdapter.phpProxyAdapter.phpPsr16Adapter.phpRedisAdapter.phpRedisTagAwareAdapter.phpTagAwareAdapter.phpTagAwareAdapterInterface.phpTraceableAdapter.phpTraceableTagAwareAdapter.php
CacheItem.phpDataCollector
DependencyInjection
DoctrineProvider.phpException
LockRegistry.phpMarshaller
DefaultMarshaller.phpDeflateMarshaller.phpMarshallerInterface.phpSodiumMarshaller.phpTagAwareMarshaller.php
Messenger
PruneableInterface.phpPsr16Cache.phpResettableInterface.phpTraits
deprecation-contracts
event-dispatcher-contracts
event-dispatcher
Attribute
Debug
DependencyInjection
EventDispatcher.phpEventDispatcherInterface.phpEventSubscriberInterface.phpGenericEvent.phpImmutableEventDispatcher.phpLegacyEventDispatcherProxy.phppolyfill-mbstring
polyfill-php73
polyfill-php80
polyfill-php81
process
Exception
ExceptionInterface.phpInvalidArgumentException.phpLogicException.phpProcessFailedException.phpProcessSignaledException.phpProcessTimedOutException.phpRuntimeException.php
ExecutableFinder.phpInputStream.phpPhpExecutableFinder.phpPhpProcess.phpPipes
Process.phpProcessUtils.phpservice-contracts
Attribute
ResetInterface.phpServiceLocatorTrait.phpServiceProviderInterface.phpServiceSubscriberInterface.phpServiceSubscriberTrait.phpTest
translation-contracts
translation
Catalogue
CatalogueMetadataAwareInterface.phpCommand
DataCollector
DataCollectorTranslator.phpDependencyInjection
Dumper
CsvFileDumper.phpDumperInterface.phpFileDumper.phpIcuResFileDumper.phpIniFileDumper.phpJsonFileDumper.phpMoFileDumper.phpPhpFileDumper.phpPoFileDumper.phpQtFileDumper.phpXliffFileDumper.phpYamlFileDumper.php
Exception
ExceptionInterface.phpIncompleteDsnException.phpInvalidArgumentException.phpInvalidResourceException.phpLogicException.phpMissingRequiredOptionException.phpNotFoundResourceException.phpProviderException.phpProviderExceptionInterface.phpRuntimeException.phpUnsupportedSchemeException.php
Extractor
AbstractFileExtractor.phpChainExtractor.phpExtractorInterface.phpPhpAstExtractor.phpPhpExtractor.phpPhpStringTokenParser.php
Visitor
Formatter
IdentityTranslator.phpLoader
ArrayLoader.phpCsvFileLoader.phpFileLoader.phpIcuDatFileLoader.phpIcuResFileLoader.phpIniFileLoader.phpJsonFileLoader.phpLoaderInterface.phpMoFileLoader.phpPhpFileLoader.phpPoFileLoader.phpQtFileLoader.phpXliffFileLoader.phpYamlFileLoader.php
LocaleSwitcher.phpLoggingTranslator.phpMessageCatalogue.phpMessageCatalogueInterface.phpMetadataAwareInterface.phpProvider
AbstractProviderFactory.phpDsn.phpFilteringProvider.phpNullProvider.phpNullProviderFactory.phpProviderFactoryInterface.phpProviderInterface.phpTranslationProviderCollection.phpTranslationProviderCollectionFactory.php
PseudoLocalizationTranslator.phpReader
Resources
Test
TranslatableMessage.phpTranslator.phpTranslatorBag.phpTranslatorBagInterface.phpUtil
Writer
var-exporter
webmozart
woocommerce
action-scheduler
action-scheduler.php
classes
ActionScheduler_ActionClaim.phpActionScheduler_ActionFactory.phpActionScheduler_AdminView.phpActionScheduler_AsyncRequest_QueueRunner.phpActionScheduler_Compatibility.phpActionScheduler_DataController.phpActionScheduler_DateTime.phpActionScheduler_Exception.phpActionScheduler_FatalErrorMonitor.phpActionScheduler_InvalidActionException.phpActionScheduler_ListTable.phpActionScheduler_LogEntry.phpActionScheduler_NullLogEntry.phpActionScheduler_OptionLock.phpActionScheduler_QueueCleaner.phpActionScheduler_QueueRunner.phpActionScheduler_Versions.phpActionScheduler_WPCommentCleaner.phpActionScheduler_wcSystemStatus.php
WP_CLI
ActionScheduler_WPCLI_Clean_Command.phpActionScheduler_WPCLI_QueueRunner.phpActionScheduler_WPCLI_Scheduler_command.phpMigration_Command.phpProgressBar.php
abstracts
ActionScheduler.phpActionScheduler_Abstract_ListTable.phpActionScheduler_Abstract_QueueRunner.phpActionScheduler_Abstract_RecurringSchedule.phpActionScheduler_Abstract_Schedule.phpActionScheduler_Abstract_Schema.phpActionScheduler_Lock.phpActionScheduler_Logger.phpActionScheduler_Store.phpActionScheduler_TimezoneHelper.php
actions
ActionScheduler_Action.phpActionScheduler_CanceledAction.phpActionScheduler_FinishedAction.phpActionScheduler_NullAction.php
data-stores
ActionScheduler_DBLogger.phpActionScheduler_DBStore.phpActionScheduler_HybridStore.phpActionScheduler_wpCommentLogger.phpActionScheduler_wpPostStore.phpActionScheduler_wpPostStore_PostStatusRegistrar.phpActionScheduler_wpPostStore_PostTypeRegistrar.phpActionScheduler_wpPostStore_TaxonomyRegistrar.php
migration
ActionMigrator.phpActionScheduler_DBStoreMigrator.phpBatchFetcher.phpConfig.phpController.phpDryRun_ActionMigrator.phpDryRun_LogMigrator.phpLogMigrator.phpRunner.phpScheduler.php
schedules
ActionScheduler_CanceledSchedule.phpActionScheduler_CronSchedule.phpActionScheduler_IntervalSchedule.phpActionScheduler_NullSchedule.phpActionScheduler_Schedule.phpActionScheduler_SimpleSchedule.php
schema
deprecated
ActionScheduler_Abstract_QueueRunner_Deprecated.phpActionScheduler_AdminView_Deprecated.phpActionScheduler_Schedule_Deprecated.phpActionScheduler_Store_Deprecated.phpfunctions.php
functions.phplib
WP_Async_Request.php
cron-expression
yahnis-elsts
plugin-update-checker
inc
admin-pages
class-about-admin-page.phpclass-addons-admin-page.phpclass-base-admin-page.phpclass-base-customer-facing-admin-page.phpclass-broadcast-edit-admin-page.phpclass-broadcast-list-admin-page.phpclass-checkout-form-edit-admin-page.phpclass-checkout-form-list-admin-page.phpclass-customer-edit-admin-page.phpclass-customer-list-admin-page.phpclass-customizer-admin-page.phpclass-dashboard-admin-page.phpclass-discount-code-edit-admin-page.phpclass-discount-code-list-admin-page.phpclass-domain-edit-admin-page.phpclass-domain-list-admin-page.phpclass-edit-admin-page.phpclass-email-edit-admin-page.phpclass-email-list-admin-page.phpclass-email-template-customize-admin-page.phpclass-event-list-admin-page.phpclass-event-view-admin-page.phpclass-hosting-integration-wizard-admin-page.phpclass-invoice-template-customize-admin-page.phpclass-jobs-list-admin-page.phpclass-list-admin-page.phpclass-membership-edit-admin-page.phpclass-membership-list-admin-page.phpclass-migration-alert-admin-page.phpclass-payment-edit-admin-page.phpclass-payment-list-admin-page.phpclass-placeholders-admin-page.phpclass-product-edit-admin-page.phpclass-product-list-admin-page.phpclass-rollback-admin-page.phpclass-settings-admin-page.phpclass-setup-wizard-admin-page.phpclass-shortcodes-admin-page.phpclass-site-edit-admin-page.phpclass-site-list-admin-page.phpclass-system-info-admin-page.phpclass-tax-rates-admin-page.phpclass-template-previewer-customize-admin-page.phpclass-top-admin-nav-menu.phpclass-view-logs-admin-page.phpclass-webhook-edit-admin-page.phpclass-webhook-list-admin-page.phpclass-wizard-admin-page.php
customer-panel
class-account-admin-page.phpclass-add-new-site-admin-page.phpclass-checkout-admin-page.phpclass-my-sites-admin-page.phpclass-template-switching-admin-page.php
debug
api
class-register-endpoint.php
schemas
broadcast-create.phpbroadcast-update.phpcheckout-form-create.phpcheckout-form-update.phpcustomer-create.phpcustomer-update.phpdiscount-code-create.phpdiscount-code-update.phpdomain-create.phpdomain-update.phpemail-create.phpemail-update.phpevent-create.phpevent-update.phpmembership-create.phpmembership-update.phppayment-create.phppayment-update.phpproduct-create.phpproduct-update.phpsite-create.phpsite-update.phpwebhook-create.phpwebhook-update.php
trait-rest-api.phptrait-wp-cli.phpbuilders
block-editor
checkout
class-cart.phpclass-checkout-pages.phpclass-checkout.phpclass-legacy-checkout.phpclass-line-item.php
class-admin-notices.phpclass-admin-themes-compatibility.phpclass-ajax.phpclass-api.phpclass-async-calls.phpclass-autoloader.phpclass-core-updates.phpclass-cron.phpclass-current.phpclass-dashboard-statistics.phpclass-dashboard-widgets.phpclass-documentation.phpclass-domain-mapping.phpclass-faker.phpclass-geolocation.phpclass-helper.phpclass-hooks.phpclass-license.phpclass-light-ajax.phpclass-logger.phpclass-maintenance-mode.phpclass-requirements.phpclass-scripts.phpclass-session-cookie.phpclass-settings.phpclass-sunrise.phpclass-unsupported.phpclass-user-switching.phpclass-views.phpclass-whitelabel.phpclass-wp-ultimo.phpsignup-fields
class-base-signup-field.phpclass-signup-field-billing-address.phpclass-signup-field-checkbox.phpclass-signup-field-color.phpclass-signup-field-discount-code.phpclass-signup-field-email.phpclass-signup-field-hidden.phpclass-signup-field-order-bump.phpclass-signup-field-order-summary.phpclass-signup-field-password.phpclass-signup-field-payment.phpclass-signup-field-period-selection.phpclass-signup-field-pricing-table.phpclass-signup-field-products.phpclass-signup-field-select.phpclass-signup-field-shortcode.phpclass-signup-field-site-title.phpclass-signup-field-site-url.phpclass-signup-field-steps.phpclass-signup-field-submit-button.phpclass-signup-field-template-selection.phpclass-signup-field-terms-of-use.phpclass-signup-field-text.phpclass-signup-field-username.php
field-templates
class-base-field-template.php
order-bump
order-summary
period-selection
pricing-table
steps
class-clean-steps-field-template.phpclass-legacy-steps-field-template.phpclass-minimal-steps-field-template.php
template-selection
compat
class-as-admin-view.phpclass-discount-code-compat.phpclass-domain-mapping-compat.phpclass-elementor-compat.phpclass-general-compat.phpclass-gutenberg-support.phpclass-legacy-shortcodes.phpclass-multiple-accounts-compat.phpclass-product-compat.php
contracts
country
br
ac.phpal.phpam.phpap.phpba.phpce.phpdf.phpes.phpgo.phpma.phpmg.phpms.phpmt.phppa.phppb.phppe.phppi.phppr.phprj.phprn.phpro.phprr.phprs.phpsc.phpse.phpsp.phpto.php
ca
class-country-br.phpclass-country-ca.phpclass-country-cn.phpclass-country-de.phpclass-country-default.phpclass-country-es.phpclass-country-fr.phpclass-country-gb.phpclass-country-in.phpclass-country-jp.phpclass-country-mx.phpclass-country-my.phpclass-country-ne.phpclass-country-nl.phpclass-country-ru.phpclass-country-sg.phpclass-country-tr.phpclass-country-us.phpclass-country-za.phpclass-country.phpcn
ah.phpbj.phpcq.phpfj.phpgd.phpgs.phpgx.phpgz.phpha.phphb.phphe.phphi.phphl.phphn.phpjl.phpjs.phpjx.phpln.phpnm.phpnx.phpqh.phpsc.phpsd.phpsh.phpsn.phpsx.phptj.phptw.phpxj.phpxz.phpyn.phpzj.php
de
es
an.phpar.phpcb.phpce.phpcm.phpcn.phpct.phpex.phpga.phple.phpmc.phpmd.phpml.phpnc.phppm.phppv.phpri.phpvc.php
fr
gb
in
an.phpap.phpar.phpas.phpbr.phpch.phpct.phpdh.phpdl.phpga.phpgj.phphp.phphr.phpjh.phpjk.phpka.phpkl.phpla.phpld.phpmh.phpml.phpmn.phpmp.phpmz.phpnl.phpor.phppb.phppy.phprj.phpsk.phptg.phptn.phptr.phpup.phput.phpwb.php
jp
01.php02.php03.php04.php05.php06.php07.php08.php09.php10.php11.php12.php13.php14.php15.php16.php17.php18.php19.php20.php21.php22.php23.php24.php25.php26.php27.php28.php29.php30.php31.php32.php33.php34.php35.php36.php37.php38.php39.php40.php41.php42.php43.php44.php45.php46.php47.php
mx
agu.phpbcn.phpbcs.phpcam.phpcdmx.phpchh.phpchp.phpcoa.phpcol.phpdur.phpgro.phpgua.phphid.phpjal.phpmex.phpmic.phpmor.phpnay.phpnle.phpoax.phppue.phpque.phproo.phpsin.phpslp.phpson.phptab.phptam.phptla.phpver.phpyuc.phpzac.php
my
ne
nl
ru
ad.phpal.phpalt.phpamu.phpark.phpast.phpba.phpbel.phpbry.phpbu.phpce.phpche.phpchu.phpcu.phpda.phpin.phpiva.phpkam.phpkb.phpkc.phpkda.phpkem.phpkgd.phpkgn.phpkha.phpkhm.phpkir.phpkk.phpkl.phpklu.phpko.phpkos.phpkr.phpkrs.phpkya.phplen.phplip.phpmag.phpme.phpmo.phpmos.phpmow.phpmur.phpnen.phpngr.phpniz.phpnvs.phpoms.phpore.phporl.phpper.phppnz.phppri.phppsk.phpros.phprya.phpsa.phpsak.phpsam.phpsar.phpse.phpsmo.phpspe.phpsta.phpsve.phpta.phptam.phptom.phptul.phptve.phpty.phptyu.phpua-40.phpud.phpuly.phpvgg.phpvla.phpvlg.phpvor.phpyan.phpyar.phpyev.phpzab.php
sg
tr
01.php02.php03.php04.php05.php06.php07.php08.php09.php10.php11.php12.php13.php14.php15.php16.php17.php18.php19.php20.php21.php22.php23.php24.php25.php26.php27.php28.php29.php30.php31.php32.php33.php34.php35.php36.php37.php38.php39.php40.php41.php42.php43.php44.php45.php46.php47.php48.php49.php50.php51.php52.php53.php54.php55.php56.php57.php58.php59.php60.php61.php62.php63.php64.php65.php66.php67.php68.php69.php70.php71.php72.php73.php74.php75.php76.php77.php78.php79.php80.php81.php
us
ak.phpal.phpar.phpaz.phpca.phpco.phpct.phpdc.phpde.phpfl.phpga.phphi.phpia.phpid.phpil.phpin.phpks.phpky.phpla.phpma.phpmd.phpme.phpmi.phpmn.phpmo.phpms.phpmt.phpnc.phpnd.phpne.phpnh.phpnj.phpnm.phpnv.phpny.phpoh.phpok.phpor.phppa.phppr.phpri.phpsc.phpsd.phptn.phptx.phput.phpva.phpvt.phpwa.phpwi.phpwv.phpwy.php
za
database
broadcasts
checkout-forms
class-checkout-form-query.phpclass-checkout-forms-meta-table.phpclass-checkout-forms-schema.phpclass-checkout-forms-table.php
customers
class-customer-query.phpclass-customers-meta-table.phpclass-customers-schema.phpclass-customers-table.php
discount-codes
class-discount-code-query.phpclass-discount-codes-meta-table.phpclass-discount-codes-schema.phpclass-discount-codes-table.php
domains
emails
engine
class-base.phpclass-column.phpclass-compare.phpclass-date.phpclass-enum.phpclass-meta.phpclass-query.phpclass-row.phpclass-schema.phpclass-table.php
events
memberships
class-membership-query.phpclass-membership-status.phpclass-memberships-meta-table.phpclass-memberships-schema.phpclass-memberships-table.php
payments
class-payment-query.phpclass-payment-status.phpclass-payments-meta-table.phpclass-payments-schema.phpclass-payments-table.php
posts
products
class-product-query.phpclass-product-type.phpclass-products-meta-table.phpclass-products-schema.phpclass-products-table.php
sites
class-site-query.phpclass-site-type.phpclass-sites-meta-table.phpclass-sites-schema.phpclass-sites-table.php
webhooks
debug
deprecated
development
domain-mapping
duplication
exception
functions
admin.phparray-helpers.phpassets.phpbroadcast.phpcheckout-form.phpcheckout.phpcolor.phpcountries.phpcurrency.phpcustomer.phpdanger.phpdate.phpdebug.phpdiscount-code.phpdocumentation.phpdomain.phpelement.phpemail.phpenv.phpevent.phpfinancial.phpform.phpfs.phpgateway.phpgenerator.phpgeolocation.phphelper.phphttp.phpinvoice.phplegacy.phplicensing.phplimitations.phpmarkup-helpers.phpmembership.phpmock.phpmodel.phpnumber-helpers.phpoptions.phppages.phppayment.phpproduct.phpreflection.phprest.phpscheduler.phpsession.phpsettings.phpsite-context.phpsite.phpsort.phpstring-helpers.phpsunrise.phptax.phptemplate.phptranslation.phpurl.phpuser.phpwebhook.php
gateways
class-base-gateway.phpclass-base-stripe-gateway.phpclass-free-gateway.phpclass-ignorable-exception.phpclass-manual-gateway.phpclass-paypal-gateway.phpclass-stripe-checkout-gateway.phpclass-stripe-gateway.php
helpers
class-arr.phpclass-hash.phpclass-screenshot.phpclass-sender.phpclass-site-duplicator.phpclass-validator.phpclass-wp-config.php
validation-rules
installers
class-base-installer.phpclass-core-installer.phpclass-default-content-installer.phpclass-migrator.php
integrations
host-providers
class-base-host-provider.phpclass-closte-host-provider.phpclass-cloudflare-host-provider.phpclass-cloudways-host-provider.phpclass-cpanel-host-provider.phpclass-gridpane-host-provider.phpclass-runcloud-host-provider.phpclass-serverpilot-host-provider.phpclass-wpengine-host-provider.phpclass-wpmudev-host-provider.php
cpanel-api
internal
invoices
limitations
class-limit-customer-user-role.phpclass-limit-disk-space.phpclass-limit-domain-mapping.phpclass-limit-plugins.phpclass-limit-post-types.phpclass-limit-site-templates.phpclass-limit-sites.phpclass-limit-subtype.phpclass-limit-themes.phpclass-limit-users.phpclass-limit-visits.phpclass-limit.php
limits
class-customer-user-role-limits.phpclass-disk-space-limits.phpclass-plugin-limits.phpclass-post-type-limits.phpclass-site-template-limits.phpclass-theme-limits.phpclass-trial-limits.php
list-tables
class-base-list-table.phpclass-broadcast-list-table.phpclass-checkout-form-list-table.phpclass-customer-list-table.phpclass-customers-membership-list-table.phpclass-customers-payment-list-table.phpclass-customers-site-list-table.phpclass-discount-code-list-table.phpclass-domain-list-table.phpclass-email-list-table.phpclass-event-list-table.phpclass-inside-events-list-table.phpclass-line-item-list-table.phpclass-membership-line-item-list-table.phpclass-membership-list-table-widget.phpclass-membership-list-table.phpclass-memberships-site-list-table.phpclass-payment-line-item-list-table.phpclass-payment-list-table-widget.phpclass-payment-list-table.phpclass-product-list-table.phpclass-site-customer-list-table.phpclass-site-list-table.phpclass-sites-domain-list-table.phpclass-webhook-list-table.php
customer-panel
loaders
managers
class-base-manager.phpclass-block-manager.phpclass-broadcast-manager.phpclass-cache-manager.phpclass-checkout-form-manager.phpclass-customer-manager.phpclass-discount-code-manager.phpclass-domain-manager.phpclass-email-manager.phpclass-event-manager.phpclass-field-templates-manager.phpclass-form-manager.phpclass-gateway-manager.phpclass-job-manager.phpclass-limitation-manager.phpclass-membership-manager.phpclass-notes-manager.phpclass-notification-manager.phpclass-payment-manager.phpclass-product-manager.phpclass-signup-fields-manager.phpclass-site-manager.phpclass-visits-manager.phpclass-webhook-manager.php
mercator
models
class-base-model.phpclass-broadcast.phpclass-checkout-form.phpclass-customer.phpclass-discount-code.phpclass-domain.phpclass-email.phpclass-event.phpclass-membership.phpclass-payment.phpclass-post-base-model.phpclass-product.phpclass-site.phpclass-webhook.php
traits
next
objects
rollback
site-templates
sso
tax
traits
trait-singleton.phptrait-wp-ultimo-coupon-deprecated.phptrait-wp-ultimo-deprecated.phptrait-wp-ultimo-plan-deprecated.phptrait-wp-ultimo-settings-deprecated.phptrait-wp-ultimo-site-deprecated.phptrait-wp-ultimo-subscription-deprecated.php
ui
class-account-summary-element.phpclass-base-element.phpclass-billing-info-element.phpclass-checkout-element.phpclass-current-membership-element.phpclass-current-site-element.phpclass-domain-mapping-element.phpclass-field.phpclass-form.phpclass-invoices-element.phpclass-jumper.phpclass-limits-element.phpclass-login-form-element.phpclass-my-sites-element.phpclass-payment-methods-element.phpclass-simple-text-element.phpclass-site-actions-element.phpclass-site-maintenance-element.phpclass-template-previewer.phpclass-template-switching-element.phpclass-thank-you-element.phpclass-toolbox.phpclass-tours.php
updater
lang
wp-ultimo-de_DE.mowp-ultimo-de_DE.powp-ultimo-de_DE_formal.mowp-ultimo-de_DE_formal.powp-ultimo-es_ES.mowp-ultimo-es_ES.powp-ultimo-fr.mowp-ultimo-fr.powp-ultimo-fr_FR.mowp-ultimo-fr_FR.powp-ultimo-it_IT.mowp-ultimo-it_IT.powp-ultimo-locations.potwp-ultimo-pt_BR.mowp-ultimo-pt_BR.powp-ultimo-sv_SE.mowp-ultimo-sv_SE.powp-ultimo.pot
loco.xmlreadme.txtsunrise.phpuninstall.phpviews
about.phpadmin-notices.php
wp-multisite-waas.phpadmin-pages
fields
field-actions.phpfield-code-editor.phpfield-color-picker.phpfield-dashicon.phpfield-group.phpfield-header.phpfield-hidden.phpfield-html.phpfield-image.phpfield-link.phpfield-multiselect.phpfield-note.phpfield-repeater.phpfield-select-icon.phpfield-select.phpfield-small-header.phpfield-submit.phpfield-tab-select.phpfield-text-display.phpfield-text-edit.phpfield-text.phpfield-textarea.phpfield-toggle.phpfield-wp-editor.phpform.php
partials
base
addons.php
addons
centered.phpcheckout-forms
customers
dash.phpedit.phpedit
empty-state.phpfilter.phpgrid.phplist.phpproducts
responsive-table-row.phpsettings.phpsites
wizard-body.phpwizard.phpwizard
broadcast
checkout
confirmation.php
classes.phpfields
field-checkbox-multi.phpfield-checkbox.phpfield-clear.phpfield-group.phpfield-hidden.phpfield-html.phpfield-note.phpfield-password.phpfield-payment-methods.phpfield-products.phpfield-radio.phpfield-select.phpfield-submit.phpfield-text.phpfield-toggle.phpform.php
form.phppartials
partials
paypal
register.phptemplates
order-bump
order-summary
period-selection
pricing-table
steps
template-selection
customers
dashboard-statistics
filter.phpwidget-countries.phpwidget-forms.phpwidget-most-visited-sites.phpwidget-mrr-growth.phpwidget-new-accounts.phpwidget-revenue.phpwidget-tax-by-code.phpwidget-tax-by-day.phpwidget-tax-graph.phpwidget-taxes.php
dashboard-widgets
account-summary.phpactivity-stream.phpbilling-info.phpcurrent-membership-product-details.phpcurrent-membership.phpcurrent-site.phpdomain-mapping.phpfirst-steps.phpinvoices.phplimits-and-quotas.phplogin-additional-forms.phplogin-form.phpmy-sites.phpnews.phpsimple-text.phpsite-actions.phpsite-maintenance.phpsummary.phpthank-you.php
domain
dynamic-styles
email
emails
admin
customer
events
invoice
legacy
signup
limitations
memberships
payments
phpcs.xmlrollback
settings
fields
field-ajax_button.phpfield-checkbox.phpfield-color.phpfield-heading.phpfield-heading_collapsible.phpfield-image.phpfield-multi_checkbox.phpfield-note.phpfield-select.phpfield-select2.phpfield-text.phpfield-textarea.phpfield-wp_editor.php
widget-settings-body.phpshortcodes
sites
system-info
taxes
ui
branding
container-toggle.phpjumper-trigger.phpjumper.phpselectize-templates.phptemplate-previewer.phptoolbox.phpwizards
51
dependencies/amphp/http-client/psalm.xml
vendored
Normal file
51
dependencies/amphp/http-client/psalm.xml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
totallyTyped="false"
|
||||
errorLevel="2"
|
||||
phpVersion="7.2"
|
||||
resolveFromConfigFile="true"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="https://getpsalm.org/schema/config"
|
||||
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||
>
|
||||
<projectFiles>
|
||||
<directory name="examples"/>
|
||||
<directory name="src"/>
|
||||
<ignoreFiles>
|
||||
<directory name="vendor"/>
|
||||
</ignoreFiles>
|
||||
</projectFiles>
|
||||
|
||||
<issueHandlers>
|
||||
<StringIncrement>
|
||||
<errorLevel type="suppress">
|
||||
<directory name="examples"/>
|
||||
<directory name="src"/>
|
||||
</errorLevel>
|
||||
</StringIncrement>
|
||||
|
||||
<DuplicateClass>
|
||||
<errorLevel type="suppress">
|
||||
<directory name="examples"/>
|
||||
</errorLevel>
|
||||
</DuplicateClass>
|
||||
|
||||
<RedundantConditionGivenDocblockType>
|
||||
<errorLevel type="suppress">
|
||||
<directory name="src"/>
|
||||
</errorLevel>
|
||||
</RedundantConditionGivenDocblockType>
|
||||
|
||||
<DocblockTypeContradiction>
|
||||
<errorLevel type="suppress">
|
||||
<directory name="src"/>
|
||||
</errorLevel>
|
||||
</DocblockTypeContradiction>
|
||||
|
||||
<MissingClosureParamType>
|
||||
<errorLevel type="suppress">
|
||||
<directory name="src"/>
|
||||
</errorLevel>
|
||||
</MissingClosureParamType>
|
||||
</issueHandlers>
|
||||
</psalm>
|
34
dependencies/amphp/http-client/src/ApplicationInterceptor.php
vendored
Normal file
34
dependencies/amphp/http-client/src/ApplicationInterceptor.php
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor\DecompressResponse;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/**
|
||||
* Allows intercepting an application request to an HTTP resource.
|
||||
*/
|
||||
interface ApplicationInterceptor
|
||||
{
|
||||
/**
|
||||
* Intercepts an application request to an HTTP resource.
|
||||
*
|
||||
* The implementation might modify the request, delegate the request handling to the `$httpClient`, and/or modify
|
||||
* the response after the promise returned from `$httpClient->request(...)` resolves.
|
||||
*
|
||||
* An interceptor might also short-circuit and not delegate to the `$httpClient` at all.
|
||||
*
|
||||
* Any retry or cloned follow-up request must be manually cloned from `$request` to ensure a properly working
|
||||
* interceptor chain, e.g. the {@see DecompressResponse} interceptor only decodes a response if the
|
||||
* `accept-encoding` header isn't set manually. If the request isn't cloned, the first attempt will set the header
|
||||
* and the second attempt will see the header and won't decode the response, because it thinks another interceptor
|
||||
* or the application itself will care about the decoding.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param CancellationToken $cancellation
|
||||
* @param DelegateHttpClient $httpClient
|
||||
*
|
||||
* @return Promise<Response>
|
||||
*/
|
||||
public function request(Request $request, CancellationToken $cancellation, DelegateHttpClient $httpClient) : Promise;
|
||||
}
|
69
dependencies/amphp/http-client/src/Body/FileBody.php
vendored
Normal file
69
dependencies/amphp/http-client/src/Body/FileBody.php
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Body;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\InputStream;
|
||||
use WP_Ultimo\Dependencies\Amp\File\Driver;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\RequestBody;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Success;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
use function WP_Ultimo\Dependencies\Amp\File\getSize;
|
||||
use function WP_Ultimo\Dependencies\Amp\File\open;
|
||||
use function WP_Ultimo\Dependencies\Amp\File\openFile;
|
||||
use function WP_Ultimo\Dependencies\Amp\File\size;
|
||||
final class FileBody implements RequestBody
|
||||
{
|
||||
/** @var string */
|
||||
private $path;
|
||||
/**
|
||||
* @param string $path The filesystem path for the file we wish to send
|
||||
*/
|
||||
public function __construct(string $path)
|
||||
{
|
||||
if (!\interface_exists(Driver::class)) {
|
||||
throw new \Error("File request bodies require amphp/file to be installed");
|
||||
}
|
||||
$this->path = $path;
|
||||
}
|
||||
public function createBodyStream() : InputStream
|
||||
{
|
||||
$handlePromise = \function_exists('WP_Ultimo\\Dependencies\\Amp\\File\\openFile') ? openFile($this->path, "r") : open($this->path, "r");
|
||||
return new class($handlePromise) implements InputStream
|
||||
{
|
||||
/** @var Promise<InputStream> */
|
||||
private $promise;
|
||||
/** @var InputStream|null */
|
||||
private $stream;
|
||||
public function __construct(Promise $promise)
|
||||
{
|
||||
$this->promise = $promise;
|
||||
$this->promise->onResolve(function ($error, $stream) {
|
||||
if ($error) {
|
||||
return;
|
||||
}
|
||||
$this->stream = $stream;
|
||||
});
|
||||
}
|
||||
public function read() : Promise
|
||||
{
|
||||
if (!$this->stream) {
|
||||
return call(function () {
|
||||
/** @var InputStream $stream */
|
||||
$stream = (yield $this->promise);
|
||||
return $stream->read();
|
||||
});
|
||||
}
|
||||
return $this->stream->read();
|
||||
}
|
||||
};
|
||||
}
|
||||
public function getHeaders() : Promise
|
||||
{
|
||||
return new Success([]);
|
||||
}
|
||||
public function getBodyLength() : Promise
|
||||
{
|
||||
return \function_exists('WP_Ultimo\\Dependencies\\Amp\\File\\getSize') ? getSize($this->path) : size($this->path);
|
||||
}
|
||||
}
|
218
dependencies/amphp/http-client/src/Body/FormBody.php
vendored
Normal file
218
dependencies/amphp/http-client/src/Body/FormBody.php
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Body;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\InMemoryStream;
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\InputStream;
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\IteratorStream;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\RequestBody;
|
||||
use WP_Ultimo\Dependencies\Amp\Producer;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Success;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
final class FormBody implements RequestBody
|
||||
{
|
||||
/** @var (array{0: string, 1: string, 2: string, 3: null}|array{0: string, 1: FileBody, 2: string, 3: string})[] */
|
||||
private $fields = [];
|
||||
/** @var string */
|
||||
private $boundary;
|
||||
/** @var bool */
|
||||
private $isMultipart = \false;
|
||||
/** @var string|null */
|
||||
private $cachedBody;
|
||||
/** @var Promise<int>|null */
|
||||
private $cachedLength;
|
||||
/** @var list<string|FileBody>|null */
|
||||
private $cachedFields;
|
||||
/**
|
||||
* @param string $boundary An optional multipart boundary string
|
||||
*/
|
||||
public function __construct(string $boundary = null)
|
||||
{
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$this->boundary = $boundary ?? \bin2hex(\random_bytes(16));
|
||||
}
|
||||
/**
|
||||
* Add a data field to the form entity body.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @param string $contentType
|
||||
*/
|
||||
public function addField(string $name, string $value, string $contentType = 'text/plain') : void
|
||||
{
|
||||
$this->fields[] = [$name, $value, $contentType, null];
|
||||
$this->resetCache();
|
||||
}
|
||||
/**
|
||||
* Add each element of a associative array as a data field to the form entity body.
|
||||
*
|
||||
* @param array $data
|
||||
* @param string $contentType
|
||||
*/
|
||||
public function addFields(array $data, string $contentType = 'text/plain') : void
|
||||
{
|
||||
foreach ($data as $key => $value) {
|
||||
$this->addField($key, $value, $contentType);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Add a file field to the form entity body.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $filePath
|
||||
* @param string $contentType
|
||||
*/
|
||||
public function addFile(string $name, string $filePath, string $contentType = 'application/octet-stream') : void
|
||||
{
|
||||
$fileName = \basename($filePath);
|
||||
$this->fields[] = [$name, new FileBody($filePath), $contentType, $fileName];
|
||||
$this->isMultipart = \true;
|
||||
$this->resetCache();
|
||||
}
|
||||
/**
|
||||
* Add each element of a associative array as a file field to the form entity body.
|
||||
*
|
||||
* @param array $data
|
||||
* @param string $contentType
|
||||
*/
|
||||
public function addFiles(array $data, string $contentType = 'application/octet-stream') : void
|
||||
{
|
||||
foreach ($data as $key => $value) {
|
||||
$this->addFile($key, $value, $contentType);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Add a file field to the form from a string.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $fileContent
|
||||
* @param string $fileName
|
||||
* @param string $contentType
|
||||
*/
|
||||
public function addFileFromString(string $name, string $fileContent, string $fileName, string $contentType = 'application/octet-stream') : void
|
||||
{
|
||||
$this->fields[] = [$name, $fileContent, $contentType, $fileName];
|
||||
$this->isMultipart = \true;
|
||||
$this->resetCache();
|
||||
}
|
||||
/**
|
||||
* Returns an array of fields, each being an array of [name, value, content-type, file-name|null].
|
||||
* Both fields and files are returned in the array. Files use a FileBody object as the value. The file-name is
|
||||
* always null for fields.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFields() : array
|
||||
{
|
||||
return $this->fields;
|
||||
}
|
||||
private function resetCache() : void
|
||||
{
|
||||
$this->cachedBody = null;
|
||||
$this->cachedLength = null;
|
||||
$this->cachedFields = null;
|
||||
}
|
||||
public function createBodyStream() : InputStream
|
||||
{
|
||||
if ($this->isMultipart) {
|
||||
return $this->generateMultipartStreamFromFields($this->getMultipartFieldArray());
|
||||
}
|
||||
return new InMemoryStream($this->getFormEncodedBodyString());
|
||||
}
|
||||
private function getMultipartFieldArray() : array
|
||||
{
|
||||
if (isset($this->cachedFields)) {
|
||||
return $this->cachedFields;
|
||||
}
|
||||
$fields = [];
|
||||
foreach ($this->fields as $fieldArr) {
|
||||
[$name, $field, $contentType, $fileName] = $fieldArr;
|
||||
$fields[] = "--{$this->boundary}\r\n";
|
||||
/** @psalm-suppress PossiblyNullArgument */
|
||||
$fields[] = $fileName !== null ? $this->generateMultipartFileHeader($name, $fileName, $contentType) : $this->generateMultipartFieldHeader($name, $contentType);
|
||||
$fields[] = $field;
|
||||
$fields[] = "\r\n";
|
||||
}
|
||||
$fields[] = "--{$this->boundary}--\r\n";
|
||||
return $this->cachedFields = $fields;
|
||||
}
|
||||
private function generateMultipartFileHeader(string $name, string $fileName, string $contentType) : string
|
||||
{
|
||||
$header = "Content-Disposition: form-data; name=\"{$name}\"; filename=\"{$fileName}\"\r\n";
|
||||
$header .= "Content-Type: {$contentType}\r\n";
|
||||
$header .= "Content-Transfer-Encoding: binary\r\n\r\n";
|
||||
return $header;
|
||||
}
|
||||
private function generateMultipartFieldHeader(string $name, string $contentType) : string
|
||||
{
|
||||
$header = "Content-Disposition: form-data; name=\"{$name}\"\r\n";
|
||||
if ($contentType !== "") {
|
||||
$header .= "Content-Type: {$contentType}\r\n\r\n";
|
||||
} else {
|
||||
$header .= "\r\n";
|
||||
}
|
||||
return $header;
|
||||
}
|
||||
private function generateMultipartStreamFromFields(array $fields) : InputStream
|
||||
{
|
||||
foreach ($fields as $key => $field) {
|
||||
$fields[$key] = $field instanceof FileBody ? $field->createBodyStream() : new InMemoryStream($field);
|
||||
}
|
||||
return new IteratorStream(new Producer(static function (callable $emit) use($fields) {
|
||||
/** @psalm-var callable(string) $emit */
|
||||
foreach ($fields as $stream) {
|
||||
/** @var InputStream $stream */
|
||||
while (null !== ($chunk = (yield $stream->read()))) {
|
||||
(yield $emit($chunk));
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
private function getFormEncodedBodyString() : string
|
||||
{
|
||||
if ($this->cachedBody) {
|
||||
return $this->cachedBody;
|
||||
}
|
||||
$fields = [];
|
||||
foreach ($this->fields as $fieldArr) {
|
||||
[$name, $value] = $fieldArr;
|
||||
$fields[$name][] = $value;
|
||||
}
|
||||
foreach ($fields as $key => $value) {
|
||||
$fields[$key] = isset($value[1]) ? $value : $value[0];
|
||||
}
|
||||
return $this->cachedBody = \http_build_query($fields);
|
||||
}
|
||||
public function getHeaders() : Promise
|
||||
{
|
||||
return new Success(['Content-Type' => $this->determineContentType()]);
|
||||
}
|
||||
private function determineContentType() : string
|
||||
{
|
||||
return $this->isMultipart ? "multipart/form-data; boundary={$this->boundary}" : 'application/x-www-form-urlencoded';
|
||||
}
|
||||
public function getBodyLength() : Promise
|
||||
{
|
||||
if ($this->cachedLength) {
|
||||
return $this->cachedLength;
|
||||
}
|
||||
if (!$this->isMultipart) {
|
||||
return $this->cachedLength = new Success(\strlen($this->getFormEncodedBodyString()));
|
||||
}
|
||||
/** @var Promise<int> $lengthPromise */
|
||||
$lengthPromise = call(function () : \Generator {
|
||||
$fields = $this->getMultipartFieldArray();
|
||||
$length = 0;
|
||||
foreach ($fields as $field) {
|
||||
if (\is_string($field)) {
|
||||
$length += \strlen($field);
|
||||
} else {
|
||||
$length += (yield $field->getBodyLength());
|
||||
}
|
||||
}
|
||||
return $length;
|
||||
});
|
||||
return $this->cachedLength = $lengthPromise;
|
||||
}
|
||||
}
|
44
dependencies/amphp/http-client/src/Body/JsonBody.php
vendored
Normal file
44
dependencies/amphp/http-client/src/Body/JsonBody.php
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Body;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\InMemoryStream;
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\InputStream;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\HttpException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\RequestBody;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Success;
|
||||
final class JsonBody implements RequestBody
|
||||
{
|
||||
/** @var string */
|
||||
private $json;
|
||||
/**
|
||||
* JsonBody constructor.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param int $options
|
||||
* @param int<1, 2147483647> $depth
|
||||
*
|
||||
* @throws HttpException
|
||||
*/
|
||||
public function __construct($data, int $options = 0, int $depth = 512)
|
||||
{
|
||||
$this->json = \json_encode($data, $options, $depth);
|
||||
if (\json_last_error() !== \JSON_ERROR_NONE) {
|
||||
throw new HttpException('Failed to encode data to JSON');
|
||||
}
|
||||
}
|
||||
public function getHeaders() : Promise
|
||||
{
|
||||
return new Success(['content-type' => 'application/json; charset=utf-8']);
|
||||
}
|
||||
public function createBodyStream() : InputStream
|
||||
{
|
||||
return new InMemoryStream($this->json);
|
||||
}
|
||||
public function getBodyLength() : Promise
|
||||
{
|
||||
return new Success(\strlen($this->json));
|
||||
}
|
||||
}
|
29
dependencies/amphp/http-client/src/Body/StringBody.php
vendored
Normal file
29
dependencies/amphp/http-client/src/Body/StringBody.php
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Body;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\InMemoryStream;
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\InputStream;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\RequestBody;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Success;
|
||||
final class StringBody implements RequestBody
|
||||
{
|
||||
private $body;
|
||||
public function __construct(string $body)
|
||||
{
|
||||
$this->body = $body;
|
||||
}
|
||||
public function createBodyStream() : InputStream
|
||||
{
|
||||
return new InMemoryStream($this->body !== '' ? $this->body : null);
|
||||
}
|
||||
public function getHeaders() : Promise
|
||||
{
|
||||
return new Success([]);
|
||||
}
|
||||
public function getBodyLength() : Promise
|
||||
{
|
||||
return new Success(\strlen($this->body));
|
||||
}
|
||||
}
|
28
dependencies/amphp/http-client/src/Connection/Connection.php
vendored
Normal file
28
dependencies/amphp/http-client/src/Connection/Connection.php
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\SocketAddress;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\TlsInfo;
|
||||
interface Connection
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Promise<Stream|null> Returns a stream for the given request, or null if no stream is available or if
|
||||
* the connection is not suited for the given request. The first request for a stream
|
||||
* on a new connection MUST resolve the promise with a Stream instance.
|
||||
*/
|
||||
public function getStream(Request $request) : Promise;
|
||||
/**
|
||||
* @return string[] Array of supported protocol versions.
|
||||
*/
|
||||
public function getProtocolVersions() : array;
|
||||
public function close() : Promise;
|
||||
public function onClose(callable $onClose) : void;
|
||||
public function getLocalAddress() : SocketAddress;
|
||||
public function getRemoteAddress() : SocketAddress;
|
||||
public function getTlsInfo() : ?TlsInfo;
|
||||
}
|
26
dependencies/amphp/http-client/src/Connection/ConnectionFactory.php
vendored
Normal file
26
dependencies/amphp/http-client/src/Connection/ConnectionFactory.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
interface ConnectionFactory
|
||||
{
|
||||
/**
|
||||
* During connection establishment, the factory must call the {@see EventListener::startConnectionCreation()},
|
||||
* {@see EventListener::startTlsNegotiation()}, {@see EventListener::completeTlsNegotiation()}, and
|
||||
* {@see EventListener::completeConnectionCreation()} on all event listeners registered on the given request in the
|
||||
* order defined by {@see Request::getEventListeners()} as appropriate (TLS events are only invoked if TLS is
|
||||
* used). Before calling the next listener, the promise returned from the previous one must resolve successfully.
|
||||
*
|
||||
* Additionally, the factory may invoke {@see EventListener::startDnsResolution()} and
|
||||
* {@see EventListener::completeDnsResolution()}, but is not required to implement such granular events.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param CancellationToken $cancellationToken
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function create(Request $request, CancellationToken $cancellationToken) : Promise;
|
||||
}
|
282
dependencies/amphp/http-client/src/Connection/ConnectionLimitingPool.php
vendored
Normal file
282
dependencies/amphp/http-client/src/Connection/ConnectionLimitingPool.php
vendored
Normal file
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Coroutine;
|
||||
use WP_Ultimo\Dependencies\Amp\Deferred;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
use WP_Ultimo\Dependencies\Amp\MultiReasonException;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Success;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
use function WP_Ultimo\Dependencies\Amp\coroutine;
|
||||
final class ConnectionLimitingPool implements ConnectionPool
|
||||
{
|
||||
use ForbidSerialization;
|
||||
/**
|
||||
* Create a connection pool that limits the number of connections per authority.
|
||||
*
|
||||
* @param int $connectionLimit Maximum number of connections allowed to a single authority.
|
||||
* @param ConnectionFactory|null $connectionFactory
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function byAuthority(int $connectionLimit, ?ConnectionFactory $connectionFactory = null) : self
|
||||
{
|
||||
return new self($connectionLimit, $connectionFactory);
|
||||
}
|
||||
private static function formatUri(Request $request) : string
|
||||
{
|
||||
$uri = $request->getUri();
|
||||
$scheme = $uri->getScheme();
|
||||
$isHttps = $scheme === 'https';
|
||||
$defaultPort = $isHttps ? 443 : 80;
|
||||
$host = $uri->getHost();
|
||||
$port = $uri->getPort() ?? $defaultPort;
|
||||
$authority = $host . ':' . $port;
|
||||
return $scheme . '://' . $authority;
|
||||
}
|
||||
/** @var int */
|
||||
private $connectionLimit;
|
||||
/** @var ConnectionFactory */
|
||||
private $connectionFactory;
|
||||
/** @var array<string, \ArrayObject<int, Promise<Connection>>> */
|
||||
private $connections = [];
|
||||
/** @var Connection[] */
|
||||
private $idleConnections = [];
|
||||
/** @var int[] */
|
||||
private $activeRequestCounts = [];
|
||||
/** @var Deferred[][] */
|
||||
private $waiting = [];
|
||||
/** @var bool[] */
|
||||
private $waitForPriorConnection = [];
|
||||
/** @var int */
|
||||
private $totalConnectionAttempts = 0;
|
||||
/** @var int */
|
||||
private $totalStreamRequests = 0;
|
||||
/** @var int */
|
||||
private $openConnectionCount = 0;
|
||||
private function __construct(int $connectionLimit, ?ConnectionFactory $connectionFactory = null)
|
||||
{
|
||||
if ($connectionLimit < 1) {
|
||||
throw new \Error('The connection limit must be greater than 0');
|
||||
}
|
||||
$this->connectionLimit = $connectionLimit;
|
||||
$this->connectionFactory = $connectionFactory ?? new DefaultConnectionFactory();
|
||||
}
|
||||
public function __clone()
|
||||
{
|
||||
$this->connections = [];
|
||||
$this->totalConnectionAttempts = 0;
|
||||
$this->totalStreamRequests = 0;
|
||||
$this->openConnectionCount = 0;
|
||||
}
|
||||
public function getTotalConnectionAttempts() : int
|
||||
{
|
||||
return $this->totalConnectionAttempts;
|
||||
}
|
||||
public function getTotalStreamRequests() : int
|
||||
{
|
||||
return $this->totalStreamRequests;
|
||||
}
|
||||
public function getOpenConnectionCount() : int
|
||||
{
|
||||
return $this->openConnectionCount;
|
||||
}
|
||||
public function getStream(Request $request, CancellationToken $cancellation) : Promise
|
||||
{
|
||||
return call(function () use($request, $cancellation) {
|
||||
$this->totalStreamRequests++;
|
||||
$uri = self::formatUri($request);
|
||||
// Using new Coroutine avoids a bug on PHP < 7.4, see #265
|
||||
/**
|
||||
* @var Stream $stream
|
||||
* @psalm-suppress all
|
||||
*/
|
||||
[$connection, $stream] = (yield new Coroutine($this->getStreamFor($uri, $request, $cancellation)));
|
||||
$connectionId = \spl_object_id($connection);
|
||||
$this->activeRequestCounts[$connectionId] = ($this->activeRequestCounts[$connectionId] ?? 0) + 1;
|
||||
unset($this->idleConnections[$connectionId]);
|
||||
return HttpStream::fromStream($stream, coroutine(function (Request $request, CancellationToken $cancellationToken) use($connection, $stream, $uri) {
|
||||
try {
|
||||
/** @var Response $response */
|
||||
$response = (yield $stream->request($request, $cancellationToken));
|
||||
} catch (\Throwable $e) {
|
||||
$this->onReadyConnection($connection, $uri);
|
||||
throw $e;
|
||||
}
|
||||
// await response being completely received
|
||||
$response->getTrailers()->onResolve(function () use($connection, $uri) : void {
|
||||
$this->onReadyConnection($connection, $uri);
|
||||
});
|
||||
return $response;
|
||||
}), function () use($connection, $uri) : void {
|
||||
$this->onReadyConnection($connection, $uri);
|
||||
});
|
||||
});
|
||||
}
|
||||
private function getStreamFor(string $uri, Request $request, CancellationToken $cancellation) : \Generator
|
||||
{
|
||||
$isHttps = $request->getUri()->getScheme() === 'https';
|
||||
$connections = $this->connections[$uri] ?? new \ArrayObject();
|
||||
do {
|
||||
foreach ($connections as $connectionPromise) {
|
||||
\assert($connectionPromise instanceof Promise);
|
||||
try {
|
||||
if ($isHttps && ($this->waitForPriorConnection[$uri] ?? \true)) {
|
||||
// Wait for first successful connection if using a secure connection (maybe we can use HTTP/2).
|
||||
$connection = (yield $connectionPromise);
|
||||
} else {
|
||||
$connection = (yield Promise\first([$connectionPromise, new Success()]));
|
||||
if ($connection === null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
continue;
|
||||
// Ignore cancellations and errors of other requests.
|
||||
}
|
||||
\assert($connection instanceof Connection);
|
||||
$stream = (yield $this->getStreamFromConnection($connection, $request));
|
||||
if ($stream === null) {
|
||||
if (!$this->isAdditionalConnectionAllowed($uri) && $this->isConnectionIdle($connection)) {
|
||||
$connection->close();
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
// No stream available for the given request.
|
||||
}
|
||||
return [$connection, $stream];
|
||||
}
|
||||
$deferred = new Deferred();
|
||||
$deferredId = \spl_object_id($deferred);
|
||||
$this->waiting[$uri][$deferredId] = $deferred;
|
||||
$deferredPromise = $deferred->promise();
|
||||
$deferredPromise->onResolve(function () use($uri, $deferredId) : void {
|
||||
$this->removeWaiting($uri, $deferredId);
|
||||
});
|
||||
if ($this->isAdditionalConnectionAllowed($uri)) {
|
||||
break;
|
||||
}
|
||||
$connection = (yield $deferredPromise);
|
||||
\assert($connection instanceof Connection);
|
||||
$stream = (yield $this->getStreamFromConnection($connection, $request));
|
||||
if ($stream === null) {
|
||||
continue;
|
||||
// Wait for a different connection to become available.
|
||||
}
|
||||
return [$connection, $stream];
|
||||
} while (\true);
|
||||
$this->totalConnectionAttempts++;
|
||||
$connectionPromise = $this->connectionFactory->create($request, $cancellation);
|
||||
$promiseId = \spl_object_id($connectionPromise);
|
||||
$this->connections[$uri] = $this->connections[$uri] ?? new \ArrayObject();
|
||||
$this->connections[$uri][$promiseId] = $connectionPromise;
|
||||
$connectionPromise->onResolve(function (?\Throwable $exception, ?Connection $connection) use(&$deferred, $uri, $promiseId, $isHttps) : void {
|
||||
if ($exception) {
|
||||
$this->dropConnection($uri, null, $promiseId);
|
||||
if ($deferred !== null) {
|
||||
$deferred->fail($exception);
|
||||
// Fail Deferred so Promise\first() below fails.
|
||||
}
|
||||
return;
|
||||
}
|
||||
\assert($connection !== null);
|
||||
$connectionId = \spl_object_id($connection);
|
||||
$this->openConnectionCount++;
|
||||
if ($isHttps) {
|
||||
$this->waitForPriorConnection[$uri] = \in_array('2', $connection->getProtocolVersions(), \true);
|
||||
}
|
||||
$connection->onClose(function () use($uri, $connectionId, $promiseId) : void {
|
||||
$this->openConnectionCount--;
|
||||
$this->dropConnection($uri, $connectionId, $promiseId);
|
||||
});
|
||||
});
|
||||
try {
|
||||
$connection = (yield Promise\first([$connectionPromise, $deferredPromise]));
|
||||
} catch (MultiReasonException $exception) {
|
||||
[$exception] = $exception->getReasons();
|
||||
// The first reason is why the connection failed.
|
||||
throw $exception;
|
||||
}
|
||||
$deferred = null;
|
||||
// Null reference so connection promise handler does not double-resolve the Deferred.
|
||||
$this->removeWaiting($uri, $deferredId);
|
||||
// Deferred no longer needed for this request.
|
||||
\assert($connection instanceof Connection);
|
||||
$stream = (yield $this->getStreamFromConnection($connection, $request));
|
||||
if ($stream === null) {
|
||||
// Reused connection did not have an available stream for the given request.
|
||||
$connection = (yield $connectionPromise);
|
||||
// Wait for new connection request instead.
|
||||
$stream = (yield $this->getStreamFromConnection($connection, $request));
|
||||
if ($stream === null) {
|
||||
// Other requests used the new connection first, so we need to go around again.
|
||||
// Using new Coroutine avoids a bug on PHP < 7.4, see #265
|
||||
return (yield new Coroutine($this->getStreamFor($uri, $request, $cancellation)));
|
||||
}
|
||||
}
|
||||
return [$connection, $stream];
|
||||
}
|
||||
private function getStreamFromConnection(Connection $connection, Request $request) : Promise
|
||||
{
|
||||
if (!\array_intersect($request->getProtocolVersions(), $connection->getProtocolVersions())) {
|
||||
return new Success();
|
||||
// Connection does not support any of the requested protocol versions.
|
||||
}
|
||||
return $connection->getStream($request);
|
||||
}
|
||||
private function isAdditionalConnectionAllowed(string $uri) : bool
|
||||
{
|
||||
return \count($this->connections[$uri] ?? []) < $this->connectionLimit;
|
||||
}
|
||||
private function onReadyConnection(Connection $connection, string $uri) : void
|
||||
{
|
||||
$connectionId = \spl_object_id($connection);
|
||||
if (isset($this->activeRequestCounts[$connectionId])) {
|
||||
$this->activeRequestCounts[$connectionId]--;
|
||||
if ($this->activeRequestCounts[$connectionId] === 0) {
|
||||
while (\count($this->idleConnections) > 64) {
|
||||
// not customizable for now
|
||||
$idleConnection = \reset($this->idleConnections);
|
||||
$key = \key($this->idleConnections);
|
||||
unset($this->idleConnections[$key]);
|
||||
$idleConnection->close();
|
||||
}
|
||||
$this->idleConnections[$connectionId] = $connection;
|
||||
}
|
||||
}
|
||||
if (empty($this->waiting[$uri])) {
|
||||
return;
|
||||
}
|
||||
$deferred = \reset($this->waiting[$uri]);
|
||||
// Deferred is removed from waiting list in onResolve callback attached above.
|
||||
$deferred->resolve($connection);
|
||||
}
|
||||
private function isConnectionIdle(Connection $connection) : bool
|
||||
{
|
||||
$connectionId = \spl_object_id($connection);
|
||||
\assert(!isset($this->activeRequestCounts[$connectionId]) || $this->activeRequestCounts[$connectionId] >= 0);
|
||||
return ($this->activeRequestCounts[$connectionId] ?? 0) === 0;
|
||||
}
|
||||
private function removeWaiting(string $uri, int $deferredId) : void
|
||||
{
|
||||
unset($this->waiting[$uri][$deferredId]);
|
||||
if (empty($this->waiting[$uri])) {
|
||||
unset($this->waiting[$uri]);
|
||||
}
|
||||
}
|
||||
private function dropConnection(string $uri, ?int $connectionId, int $promiseId) : void
|
||||
{
|
||||
unset($this->connections[$uri][$promiseId]);
|
||||
if ($connectionId !== null) {
|
||||
unset($this->activeRequestCounts[$connectionId], $this->idleConnections[$connectionId]);
|
||||
}
|
||||
if ($this->connections[$uri]->count() === 0) {
|
||||
unset($this->connections[$uri], $this->waitForPriorConnection[$uri]);
|
||||
}
|
||||
}
|
||||
}
|
19
dependencies/amphp/http-client/src/Connection/ConnectionPool.php
vendored
Normal file
19
dependencies/amphp/http-client/src/Connection/ConnectionPool.php
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
interface ConnectionPool
|
||||
{
|
||||
/**
|
||||
* Reserve a stream for a particular request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param CancellationToken $cancellation
|
||||
*
|
||||
* @return Promise<Stream>
|
||||
*/
|
||||
public function getStream(Request $request, CancellationToken $cancellation) : Promise;
|
||||
}
|
149
dependencies/amphp/http-client/src/Connection/DefaultConnectionFactory.php
vendored
Normal file
149
dependencies/amphp/http-client/src/Connection/DefaultConnectionFactory.php
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\StreamException;
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\CancelledException;
|
||||
use WP_Ultimo\Dependencies\Amp\CombinedCancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\InvalidRequestException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\SocketException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\TimeoutException;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\ClientTlsContext;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\ConnectContext;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\Connector;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\EncryptableSocket;
|
||||
use WP_Ultimo\Dependencies\Amp\TimeoutCancellationToken;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
use function WP_Ultimo\Dependencies\Amp\Socket\connector;
|
||||
final class DefaultConnectionFactory implements ConnectionFactory
|
||||
{
|
||||
/** @var Connector|null */
|
||||
private $connector;
|
||||
/** @var ConnectContext|null */
|
||||
private $connectContext;
|
||||
public function __construct(?Connector $connector = null, ?ConnectContext $connectContext = null)
|
||||
{
|
||||
$this->connector = $connector;
|
||||
$this->connectContext = $connectContext;
|
||||
}
|
||||
public function create(Request $request, CancellationToken $cancellationToken) : Promise
|
||||
{
|
||||
return call(function () use($request, $cancellationToken) {
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->startConnectionCreation($request));
|
||||
}
|
||||
$connector = $this->connector ?? connector();
|
||||
$connectContext = $this->connectContext ?? new ConnectContext();
|
||||
$uri = $request->getUri();
|
||||
$scheme = $uri->getScheme();
|
||||
if (!\in_array($scheme, ['http', 'https'], \true)) {
|
||||
throw new InvalidRequestException($request, 'Invalid scheme provided in the request URI: ' . $uri);
|
||||
}
|
||||
$isHttps = $scheme === 'https';
|
||||
$defaultPort = $isHttps ? 443 : 80;
|
||||
$host = $uri->getHost();
|
||||
$port = $uri->getPort() ?? $defaultPort;
|
||||
if ($host === '') {
|
||||
throw new InvalidRequestException($request, 'A host must be provided in the request URI: ' . $uri);
|
||||
}
|
||||
$authority = $host . ':' . $port;
|
||||
$protocolVersions = $request->getProtocolVersions();
|
||||
$isConnect = $request->getMethod() === 'CONNECT';
|
||||
if ($isHttps) {
|
||||
$protocols = [];
|
||||
if (!$isConnect && \in_array('2', $protocolVersions, \true)) {
|
||||
$protocols[] = 'h2';
|
||||
}
|
||||
if (\in_array('1.1', $protocolVersions, \true) || \in_array('1.0', $protocolVersions, \true)) {
|
||||
$protocols[] = 'http/1.1';
|
||||
}
|
||||
if (!$protocols) {
|
||||
throw new InvalidRequestException($request, \sprintf("None of the requested protocol versions (%s) are supported by %s (HTTP/2 is only supported on HTTPS)", \implode(', ', $protocolVersions), self::class));
|
||||
}
|
||||
$tlsContext = ($connectContext->getTlsContext() ?? new ClientTlsContext(''))->withPeerCapturing();
|
||||
// If we only have HTTP/1.1 available, don't set application layer protocols.
|
||||
// There are misbehaving sites like n11.com, see https://github.com/amphp/http-client/issues/255
|
||||
if ($protocols !== ['http/1.1'] && Socket\hasTlsAlpnSupport()) {
|
||||
$tlsContext = $tlsContext->withApplicationLayerProtocols($protocols);
|
||||
}
|
||||
if ($tlsContext->getPeerName() === '') {
|
||||
$tlsContext = $tlsContext->withPeerName($host);
|
||||
}
|
||||
$connectContext = $connectContext->withTlsContext($tlsContext);
|
||||
}
|
||||
try {
|
||||
/** @var EncryptableSocket $socket */
|
||||
$socket = (yield $connector->connect('tcp://' . $authority, $connectContext->withConnectTimeout($request->getTcpConnectTimeout()), $cancellationToken));
|
||||
} catch (Socket\ConnectException $e) {
|
||||
throw new UnprocessedRequestException(new SocketException(\sprintf("Connection to '%s' failed", $authority), 0, $e));
|
||||
} catch (CancelledException $e) {
|
||||
// In case of a user cancellation request, throw the expected exception
|
||||
$cancellationToken->throwIfRequested();
|
||||
// Otherwise we ran into a timeout of our TimeoutCancellationToken
|
||||
throw new UnprocessedRequestException(new TimeoutException(\sprintf("Connection to '%s' timed out, took longer than " . $request->getTcpConnectTimeout() . ' ms', $authority)));
|
||||
// don't pass $e
|
||||
}
|
||||
if ($isHttps) {
|
||||
try {
|
||||
$tlsState = $socket->getTlsState();
|
||||
// Error if anything enabled TLS on a new connection before we can do it
|
||||
if ($tlsState !== EncryptableSocket::TLS_STATE_DISABLED) {
|
||||
$socket->close();
|
||||
throw new UnprocessedRequestException(new SocketException('Failed to setup TLS connection, connection was in an unexpected TLS state (' . $tlsState . ')'));
|
||||
}
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->startTlsNegotiation($request));
|
||||
}
|
||||
$tlsCancellationToken = new CombinedCancellationToken($cancellationToken, new TimeoutCancellationToken($request->getTlsHandshakeTimeout()));
|
||||
(yield $socket->setupTls($tlsCancellationToken));
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->completeTlsNegotiation($request));
|
||||
}
|
||||
} catch (StreamException $exception) {
|
||||
$socket->close();
|
||||
throw new UnprocessedRequestException(new SocketException(\sprintf("Connection to '%s' @ '%s' closed during TLS handshake", $authority, $socket->getRemoteAddress()->toString()), 0, $exception));
|
||||
} catch (CancelledException $e) {
|
||||
$socket->close();
|
||||
// In case of a user cancellation request, throw the expected exception
|
||||
$cancellationToken->throwIfRequested();
|
||||
// Otherwise we ran into a timeout of our TimeoutCancellationToken
|
||||
throw new UnprocessedRequestException(new TimeoutException(\sprintf("TLS handshake with '%s' @ '%s' timed out, took longer than " . $request->getTlsHandshakeTimeout() . ' ms', $authority, $socket->getRemoteAddress()->toString())));
|
||||
// don't pass $e
|
||||
}
|
||||
$tlsInfo = $socket->getTlsInfo();
|
||||
if ($tlsInfo === null) {
|
||||
throw new UnprocessedRequestException(new SocketException(\sprintf("Socket closed after TLS handshake with '%s' @ '%s'", $authority, $socket->getRemoteAddress()->toString())));
|
||||
}
|
||||
if ($tlsInfo->getApplicationLayerProtocol() === 'h2') {
|
||||
$http2Connection = new Http2Connection($socket);
|
||||
(yield $http2Connection->initialize());
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->completeConnectionCreation($request));
|
||||
}
|
||||
return $http2Connection;
|
||||
}
|
||||
}
|
||||
// Treat the presence of only HTTP/2 as prior knowledge, see https://http2.github.io/http2-spec/#known-http
|
||||
if ($request->getProtocolVersions() === ['2']) {
|
||||
$http2Connection = new Http2Connection($socket);
|
||||
(yield $http2Connection->initialize());
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->completeConnectionCreation($request));
|
||||
}
|
||||
return $http2Connection;
|
||||
}
|
||||
if (!\array_intersect($request->getProtocolVersions(), ['1.0', '1.1'])) {
|
||||
$socket->close();
|
||||
throw new InvalidRequestException($request, \sprintf("None of the requested protocol versions (%s) are supported by '%s' @ '%s'", \implode(', ', $protocolVersions), $authority, $socket->getRemoteAddress()->toString()));
|
||||
}
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->completeConnectionCreation($request));
|
||||
}
|
||||
return new Http1Connection($socket);
|
||||
});
|
||||
}
|
||||
}
|
546
dependencies/amphp/http-client/src/Connection/Http1Connection.php
vendored
Normal file
546
dependencies/amphp/http-client/src/Connection/Http1Connection.php
vendored
Normal file
@@ -0,0 +1,546 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\IteratorStream;
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\StreamException;
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationTokenSource;
|
||||
use WP_Ultimo\Dependencies\Amp\CancelledException;
|
||||
use WP_Ultimo\Dependencies\Amp\CombinedCancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Deferred;
|
||||
use WP_Ultimo\Dependencies\Amp\Emitter;
|
||||
use WP_Ultimo\Dependencies\Amp\Http;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\Internal\Http1Parser;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\Internal\RequestNormalizer;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\HttpException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ResponseBodyStream;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\InvalidRequestException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\ParseException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\SocketException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\TimeoutException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\InvalidHeaderException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Rfc7230;
|
||||
use WP_Ultimo\Dependencies\Amp\Loop;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\EncryptableSocket;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\SocketAddress;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\TlsInfo;
|
||||
use WP_Ultimo\Dependencies\Amp\Success;
|
||||
use WP_Ultimo\Dependencies\Amp\TimeoutCancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\TimeoutException as PromiseTimeoutException;
|
||||
use function WP_Ultimo\Dependencies\Amp\asyncCall;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
use function WP_Ultimo\Dependencies\Amp\getCurrentTime;
|
||||
use function WP_Ultimo\Dependencies\Amp\Http\Client\Internal\normalizeRequestPathWithQuery;
|
||||
/**
|
||||
* Socket client implementation.
|
||||
*
|
||||
* @see Client
|
||||
*/
|
||||
final class Http1Connection implements Connection
|
||||
{
|
||||
use ForbidSerialization;
|
||||
use ForbidCloning;
|
||||
private const MAX_KEEP_ALIVE_TIMEOUT = 60;
|
||||
private const PROTOCOL_VERSIONS = ['1.0', '1.1'];
|
||||
/** @var EncryptableSocket|null */
|
||||
private $socket;
|
||||
/** @var bool */
|
||||
private $busy = \false;
|
||||
/** @var int Number of requests made on this connection. */
|
||||
private $requestCounter = 0;
|
||||
/** @var string|null Keep alive timeout watcher ID. */
|
||||
private $timeoutWatcher;
|
||||
/** @var int Keep-Alive timeout from last response. */
|
||||
private $priorTimeout = self::MAX_KEEP_ALIVE_TIMEOUT;
|
||||
/** @var callable[]|null */
|
||||
private $onClose = [];
|
||||
/** @var int */
|
||||
private $timeoutGracePeriod;
|
||||
/** @var int */
|
||||
private $lastUsedAt;
|
||||
/** @var bool */
|
||||
private $explicitTimeout = \false;
|
||||
/** @var SocketAddress */
|
||||
private $localAddress;
|
||||
/** @var SocketAddress */
|
||||
private $remoteAddress;
|
||||
/** @var TlsInfo|null */
|
||||
private $tlsInfo;
|
||||
/** @var Promise|null */
|
||||
private $idleRead;
|
||||
public function __construct(EncryptableSocket $socket, int $timeoutGracePeriod = 2000)
|
||||
{
|
||||
$this->socket = $socket;
|
||||
$this->localAddress = $socket->getLocalAddress();
|
||||
$this->remoteAddress = $socket->getRemoteAddress();
|
||||
$this->tlsInfo = $socket->getTlsInfo();
|
||||
$this->timeoutGracePeriod = $timeoutGracePeriod;
|
||||
$this->lastUsedAt = getCurrentTime();
|
||||
$this->watchIdleConnection();
|
||||
}
|
||||
public function __destruct()
|
||||
{
|
||||
$this->close();
|
||||
}
|
||||
public function onClose(callable $onClose) : void
|
||||
{
|
||||
if (!$this->socket || $this->socket->isClosed()) {
|
||||
Promise\rethrow(call($onClose, $this));
|
||||
return;
|
||||
}
|
||||
$this->onClose[] = $onClose;
|
||||
}
|
||||
public function close() : Promise
|
||||
{
|
||||
if ($this->socket) {
|
||||
$this->socket->close();
|
||||
}
|
||||
return $this->free();
|
||||
}
|
||||
public function getLocalAddress() : SocketAddress
|
||||
{
|
||||
return $this->localAddress;
|
||||
}
|
||||
public function getRemoteAddress() : SocketAddress
|
||||
{
|
||||
return $this->remoteAddress;
|
||||
}
|
||||
public function getTlsInfo() : ?TlsInfo
|
||||
{
|
||||
return $this->tlsInfo;
|
||||
}
|
||||
public function getProtocolVersions() : array
|
||||
{
|
||||
return self::PROTOCOL_VERSIONS;
|
||||
}
|
||||
public function getStream(Request $request) : Promise
|
||||
{
|
||||
if ($this->busy || $this->requestCounter && !$this->hasStreamFor($request)) {
|
||||
return new Success();
|
||||
}
|
||||
$this->busy = \true;
|
||||
return new Success(HttpStream::fromConnection($this, \Closure::fromCallable([$this, 'request']), \Closure::fromCallable([$this, 'release'])));
|
||||
}
|
||||
private function free() : Promise
|
||||
{
|
||||
$this->socket = null;
|
||||
$this->idleRead = null;
|
||||
$this->lastUsedAt = 0;
|
||||
if ($this->timeoutWatcher !== null) {
|
||||
Loop::cancel($this->timeoutWatcher);
|
||||
}
|
||||
if ($this->onClose !== null) {
|
||||
$onClose = $this->onClose;
|
||||
$this->onClose = null;
|
||||
foreach ($onClose as $callback) {
|
||||
asyncCall($callback, $this);
|
||||
}
|
||||
}
|
||||
return new Success();
|
||||
}
|
||||
private function hasStreamFor(Request $request) : bool
|
||||
{
|
||||
return !$this->busy && $this->socket && !$this->socket->isClosed() && ($this->getRemainingTime() > 0 || $request->isIdempotent());
|
||||
}
|
||||
/** @inheritdoc */
|
||||
private function request(Request $request, CancellationToken $cancellation, Stream $stream) : Promise
|
||||
{
|
||||
return call(function () use($request, $cancellation, $stream) {
|
||||
++$this->requestCounter;
|
||||
if ($this->socket !== null && !$this->socket->isClosed()) {
|
||||
$this->socket->reference();
|
||||
}
|
||||
if ($this->timeoutWatcher !== null) {
|
||||
Loop::cancel($this->timeoutWatcher);
|
||||
$this->timeoutWatcher = null;
|
||||
}
|
||||
(yield RequestNormalizer::normalizeRequest($request));
|
||||
$protocolVersion = $this->determineProtocolVersion($request);
|
||||
$request->setProtocolVersions([$protocolVersion]);
|
||||
if ($request->getTransferTimeout() > 0) {
|
||||
$timeoutToken = new TimeoutCancellationToken($request->getTransferTimeout());
|
||||
$combinedCancellation = new CombinedCancellationToken($cancellation, $timeoutToken);
|
||||
} else {
|
||||
$combinedCancellation = $cancellation;
|
||||
}
|
||||
$id = $combinedCancellation->subscribe([$this, 'close']);
|
||||
try {
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->startSendingRequest($request, $stream));
|
||||
}
|
||||
yield from $this->writeRequest($request, $protocolVersion, $combinedCancellation);
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->completeSendingRequest($request, $stream));
|
||||
}
|
||||
return yield from $this->readResponse($request, $cancellation, $combinedCancellation, $stream);
|
||||
} catch (\Throwable $e) {
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->abort($request, $e));
|
||||
}
|
||||
if ($this->socket !== null) {
|
||||
$this->socket->close();
|
||||
}
|
||||
throw $e;
|
||||
} finally {
|
||||
$combinedCancellation->unsubscribe($id);
|
||||
$cancellation->throwIfRequested();
|
||||
}
|
||||
});
|
||||
}
|
||||
private function release() : void
|
||||
{
|
||||
$this->busy = \false;
|
||||
}
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param CancellationToken $originalCancellation
|
||||
* @param CancellationToken $readingCancellation
|
||||
*
|
||||
* @param Stream $stream
|
||||
*
|
||||
* @return \Generator
|
||||
* @throws CancelledException
|
||||
* @throws HttpException
|
||||
* @throws ParseException
|
||||
* @throws SocketException
|
||||
*/
|
||||
private function readResponse(Request $request, CancellationToken $originalCancellation, CancellationToken $readingCancellation, Stream $stream) : \Generator
|
||||
{
|
||||
$bodyEmitter = new Emitter();
|
||||
$backpressure = new Success();
|
||||
$bodyCallback = static function ($data) use($bodyEmitter, &$backpressure) : void {
|
||||
$backpressure = $bodyEmitter->emit($data);
|
||||
};
|
||||
$trailersDeferred = new Deferred();
|
||||
$trailers = [];
|
||||
$trailersCallback = static function (array $headers) use(&$trailers) : void {
|
||||
$trailers = $headers;
|
||||
};
|
||||
$parser = new Http1Parser($request, $bodyCallback, $trailersCallback);
|
||||
$start = getCurrentTime();
|
||||
$timeout = $request->getInactivityTimeout();
|
||||
try {
|
||||
if ($this->socket === null) {
|
||||
throw new SocketException('Socket closed prior to response completion');
|
||||
}
|
||||
while (null !== ($chunk = (yield $timeout > 0 ? Promise\timeout($this->idleRead ?: $this->socket->read(), $timeout) : ($this->idleRead ?: $this->socket->read())))) {
|
||||
$this->idleRead = null;
|
||||
parseChunk:
|
||||
$response = $parser->parse($chunk);
|
||||
if ($response === null) {
|
||||
if ($this->socket === null) {
|
||||
throw new SocketException('Socket closed prior to response completion');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$this->lastUsedAt = getCurrentTime();
|
||||
$status = $response->getStatus();
|
||||
if ($status === Http\Status::SWITCHING_PROTOCOLS) {
|
||||
$connection = Http\createFieldValueComponentMap(Http\parseFieldValueComponents($response, 'connection'));
|
||||
if (!isset($connection['upgrade'])) {
|
||||
throw new HttpException('Switching protocols response missing "Connection: upgrade" header');
|
||||
}
|
||||
if (!$response->hasHeader('upgrade')) {
|
||||
throw new HttpException('Switching protocols response missing "Upgrade" header');
|
||||
}
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->completeReceivingResponse($request, $stream));
|
||||
}
|
||||
$trailersDeferred->resolve($trailers);
|
||||
return $this->handleUpgradeResponse($request, $response, $parser->getBuffer());
|
||||
}
|
||||
if ($status < 200) {
|
||||
// 1XX responses (excluding 101, handled above)
|
||||
$onInformationalResponse = $request->getInformationalResponseHandler();
|
||||
if ($onInformationalResponse !== null) {
|
||||
(yield call($onInformationalResponse, $response));
|
||||
}
|
||||
$chunk = $parser->getBuffer();
|
||||
$parser = new Http1Parser($request, $bodyCallback, $trailersCallback);
|
||||
goto parseChunk;
|
||||
}
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->startReceivingResponse($request, $stream));
|
||||
}
|
||||
if ($status >= 200 && $status < 300 && $request->getMethod() === 'CONNECT') {
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->completeReceivingResponse($request, $stream));
|
||||
}
|
||||
$trailersDeferred->resolve($trailers);
|
||||
return $this->handleUpgradeResponse($request, $response, $parser->getBuffer());
|
||||
}
|
||||
$bodyCancellationSource = new CancellationTokenSource();
|
||||
$bodyCancellationToken = new CombinedCancellationToken($readingCancellation, $bodyCancellationSource->getToken());
|
||||
$response->setTrailers($trailersDeferred->promise());
|
||||
$response->setBody(new ResponseBodyStream(new IteratorStream($bodyEmitter->iterate()), $bodyCancellationSource));
|
||||
// Read body async
|
||||
asyncCall(function () use($parser, $request, $response, $bodyEmitter, $trailersDeferred, $originalCancellation, $readingCancellation, $bodyCancellationToken, $stream, $timeout, &$backpressure, &$trailers) {
|
||||
$id = $bodyCancellationToken->subscribe([$this, 'close']);
|
||||
try {
|
||||
// Required, otherwise responses without body hang
|
||||
if (!$parser->isComplete()) {
|
||||
// Directly parse again in case we already have the full body but aborted parsing
|
||||
// to resolve promise with headers.
|
||||
$chunk = null;
|
||||
try {
|
||||
/** @psalm-suppress PossiblyNullReference */
|
||||
do {
|
||||
/** @noinspection CallableParameterUseCaseInTypeContextInspection */
|
||||
$parser->parse($chunk);
|
||||
/**
|
||||
* @noinspection NotOptimalIfConditionsInspection
|
||||
* @psalm-suppress TypeDoesNotContainType
|
||||
*/
|
||||
if ($parser->isComplete()) {
|
||||
break;
|
||||
}
|
||||
if (!$backpressure instanceof Success) {
|
||||
(yield $this->withCancellation($backpressure, $bodyCancellationToken));
|
||||
}
|
||||
/** @psalm-suppress TypeDoesNotContainNull */
|
||||
if ($this->socket === null) {
|
||||
throw new SocketException('Socket closed prior to response completion');
|
||||
}
|
||||
} while (null !== ($chunk = (yield $timeout > 0 ? Promise\timeout($this->socket->read(), $timeout) : $this->socket->read())));
|
||||
} catch (PromiseTimeoutException $e) {
|
||||
$this->close();
|
||||
throw new TimeoutException('Inactivity timeout exceeded, more than ' . $timeout . ' ms elapsed from last data received', 0, $e);
|
||||
}
|
||||
$originalCancellation->throwIfRequested();
|
||||
if ($readingCancellation->isRequested()) {
|
||||
throw new TimeoutException('Allowed transfer timeout exceeded, took longer than ' . $request->getTransferTimeout() . ' ms');
|
||||
}
|
||||
$bodyCancellationToken->throwIfRequested();
|
||||
// Ignore check if neither content-length nor chunked encoding are given.
|
||||
/** @psalm-suppress RedundantCondition */
|
||||
if (!$parser->isComplete() && $parser->getState() !== Http1Parser::BODY_IDENTITY_EOF) {
|
||||
throw new SocketException('Socket disconnected prior to response completion');
|
||||
}
|
||||
}
|
||||
$timeout = $this->determineKeepAliveTimeout($response);
|
||||
if ($timeout > 0 && $parser->getState() !== Http1Parser::BODY_IDENTITY_EOF) {
|
||||
$this->timeoutWatcher = Loop::delay($timeout * 1000, [$this, 'close']);
|
||||
Loop::unreference($this->timeoutWatcher);
|
||||
$this->watchIdleConnection();
|
||||
} else {
|
||||
$this->close();
|
||||
}
|
||||
$this->busy = \false;
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->completeReceivingResponse($request, $stream));
|
||||
}
|
||||
$bodyEmitter->complete();
|
||||
$trailersDeferred->resolve($trailers);
|
||||
} catch (\Throwable $e) {
|
||||
$this->close();
|
||||
try {
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->abort($request, $e));
|
||||
}
|
||||
} finally {
|
||||
$bodyEmitter->fail($e);
|
||||
$trailersDeferred->fail($e);
|
||||
}
|
||||
} finally {
|
||||
$bodyCancellationToken->unsubscribe($id);
|
||||
}
|
||||
});
|
||||
return $response;
|
||||
}
|
||||
$originalCancellation->throwIfRequested();
|
||||
throw new SocketException(\sprintf("Receiving the response headers for '%s' failed, because the socket to '%s' @ '%s' closed early with %d bytes received within %d milliseconds", (string) $request->getUri()->withUserInfo(''), $request->getUri()->withUserInfo('')->getAuthority(), $this->socket === null ? '???' : (string) $this->socket->getRemoteAddress(), \strlen($parser->getBuffer()), getCurrentTime() - $start));
|
||||
} catch (HttpException $e) {
|
||||
$this->close();
|
||||
throw $e;
|
||||
} catch (PromiseTimeoutException $e) {
|
||||
$this->close();
|
||||
throw new TimeoutException('Inactivity timeout exceeded, more than ' . $timeout . ' ms elapsed from last data received', 0, $e);
|
||||
} catch (\Throwable $e) {
|
||||
$this->close();
|
||||
throw new SocketException('Receiving the response headers failed: ' . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
private function handleUpgradeResponse(Request $request, Response $response, string $buffer) : Response
|
||||
{
|
||||
if ($this->socket === null) {
|
||||
throw new SocketException('Socket closed while upgrading');
|
||||
}
|
||||
$socket = new UpgradedSocket($this->socket, $buffer);
|
||||
$this->free();
|
||||
// Mark this connection as unusable without closing socket.
|
||||
if (($onUpgrade = $request->getUpgradeHandler()) === null) {
|
||||
$socket->close();
|
||||
throw new HttpException('CONNECT or upgrade request made without upgrade handler callback');
|
||||
}
|
||||
asyncCall(static function () use($onUpgrade, $socket, $request, $response) : \Generator {
|
||||
try {
|
||||
(yield call($onUpgrade, $socket, $request, $response));
|
||||
} catch (\Throwable $exception) {
|
||||
$socket->close();
|
||||
throw new HttpException('Upgrade handler threw an exception', 0, $exception);
|
||||
}
|
||||
});
|
||||
return $response;
|
||||
}
|
||||
/**
|
||||
* @return int Approximate number of milliseconds remaining until the connection is closed.
|
||||
*/
|
||||
private function getRemainingTime() : int
|
||||
{
|
||||
$timestamp = $this->lastUsedAt + ($this->explicitTimeout ? $this->priorTimeout * 1000 : $this->timeoutGracePeriod);
|
||||
return \max(0, $timestamp - getCurrentTime());
|
||||
}
|
||||
private function withCancellation(Promise $promise, CancellationToken $cancellationToken) : Promise
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
$newPromise = $deferred->promise();
|
||||
$promise->onResolve(static function ($error, $value) use(&$deferred) : void {
|
||||
if ($deferred) {
|
||||
$temp = $deferred;
|
||||
$deferred = null;
|
||||
if ($error) {
|
||||
$temp->fail($error);
|
||||
} else {
|
||||
$temp->resolve($value);
|
||||
}
|
||||
}
|
||||
});
|
||||
$cancellationSubscription = $cancellationToken->subscribe(static function ($e) use(&$deferred) : void {
|
||||
if ($deferred) {
|
||||
$temp = $deferred;
|
||||
$deferred = null;
|
||||
$temp->fail($e);
|
||||
}
|
||||
});
|
||||
$newPromise->onResolve(static function () use($cancellationToken, $cancellationSubscription) : void {
|
||||
$cancellationToken->unsubscribe($cancellationSubscription);
|
||||
});
|
||||
return $newPromise;
|
||||
}
|
||||
private function determineKeepAliveTimeout(Response $response) : int
|
||||
{
|
||||
$request = $response->getRequest();
|
||||
$requestConnHeader = $request->getHeader('connection') ?? '';
|
||||
$responseConnHeader = $response->getHeader('connection') ?? '';
|
||||
if (!\strcasecmp($requestConnHeader, 'close')) {
|
||||
return 0;
|
||||
}
|
||||
if ($response->getProtocolVersion() === '1.0') {
|
||||
return 0;
|
||||
}
|
||||
if (!\strcasecmp($responseConnHeader, 'close')) {
|
||||
return 0;
|
||||
}
|
||||
$params = Http\createFieldValueComponentMap(Http\parseFieldValueComponents($response, 'keep-alive'));
|
||||
$timeout = (int) ($params['timeout'] ?? $this->priorTimeout);
|
||||
if (isset($params['timeout'])) {
|
||||
$this->explicitTimeout = \true;
|
||||
}
|
||||
return $this->priorTimeout = \min(\max(0, $timeout), self::MAX_KEEP_ALIVE_TIMEOUT);
|
||||
}
|
||||
private function determineProtocolVersion(Request $request) : string
|
||||
{
|
||||
$protocolVersions = $request->getProtocolVersions();
|
||||
if (\in_array("1.1", $protocolVersions, \true)) {
|
||||
return "1.1";
|
||||
}
|
||||
if (\in_array("1.0", $protocolVersions, \true)) {
|
||||
return "1.0";
|
||||
}
|
||||
throw new InvalidRequestException($request, "None of the requested protocol versions is supported: " . \implode(", ", $protocolVersions));
|
||||
}
|
||||
private function writeRequest(Request $request, string $protocolVersion, CancellationToken $cancellation) : \Generator
|
||||
{
|
||||
try {
|
||||
$rawHeaders = $this->generateRawHeader($request, $protocolVersion);
|
||||
if ($this->socket === null) {
|
||||
throw new UnprocessedRequestException(new SocketException('Socket closed before request started'));
|
||||
}
|
||||
(yield $this->socket->write($rawHeaders));
|
||||
if ($request->getMethod() === 'CONNECT') {
|
||||
return;
|
||||
}
|
||||
$body = $request->getBody()->createBodyStream();
|
||||
$chunking = $request->getHeader("transfer-encoding") === "chunked";
|
||||
$remainingBytes = $request->getHeader("content-length");
|
||||
if ($remainingBytes !== null) {
|
||||
$remainingBytes = (int) $remainingBytes;
|
||||
}
|
||||
if ($chunking && $protocolVersion === "1.0") {
|
||||
throw new InvalidRequestException($request, "Can't send chunked bodies over HTTP/1.0");
|
||||
}
|
||||
// We always buffer the last chunk to make sure we don't write $contentLength bytes if the body is too long.
|
||||
$buffer = "";
|
||||
while (null !== ($chunk = (yield $body->read()))) {
|
||||
$cancellation->throwIfRequested();
|
||||
if ($chunk === "") {
|
||||
continue;
|
||||
}
|
||||
if ($chunking) {
|
||||
$chunk = \dechex(\strlen($chunk)) . "\r\n" . $chunk . "\r\n";
|
||||
} elseif ($remainingBytes !== null) {
|
||||
$remainingBytes -= \strlen($chunk);
|
||||
if ($remainingBytes < 0) {
|
||||
throw new InvalidRequestException($request, "Body contained more bytes than specified in Content-Length, aborting request");
|
||||
}
|
||||
}
|
||||
(yield $this->socket->write($buffer));
|
||||
$buffer = $chunk;
|
||||
}
|
||||
$cancellation->throwIfRequested();
|
||||
// Flush last buffered chunk.
|
||||
(yield $this->socket->write($buffer));
|
||||
if ($chunking) {
|
||||
(yield $this->socket->write("0\r\n\r\n"));
|
||||
} elseif ($remainingBytes !== null && $remainingBytes > 0) {
|
||||
throw new InvalidRequestException($request, "Body contained fewer bytes than specified in Content-Length, aborting request");
|
||||
}
|
||||
} catch (StreamException $exception) {
|
||||
throw new SocketException('Socket disconnected prior to response completion');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param string $protocolVersion
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws HttpException
|
||||
*/
|
||||
private function generateRawHeader(Request $request, string $protocolVersion) : string
|
||||
{
|
||||
$uri = $request->getUri();
|
||||
$requestUri = normalizeRequestPathWithQuery($request);
|
||||
$method = $request->getMethod();
|
||||
if ($method === 'CONNECT') {
|
||||
$defaultPort = $uri->getScheme() === 'https' ? 443 : 80;
|
||||
$requestUri = $uri->getHost() . ':' . ($uri->getPort() ?? $defaultPort);
|
||||
}
|
||||
$header = $method . ' ' . $requestUri . ' HTTP/' . $protocolVersion . "\r\n";
|
||||
try {
|
||||
$header .= Rfc7230::formatRawHeaders($request->getRawHeaders());
|
||||
} catch (InvalidHeaderException $e) {
|
||||
throw new HttpException($e->getMessage());
|
||||
}
|
||||
return $header . "\r\n";
|
||||
}
|
||||
private function watchIdleConnection() : void
|
||||
{
|
||||
if ($this->socket === null || $this->socket->isClosed()) {
|
||||
return;
|
||||
}
|
||||
$this->socket->unreference();
|
||||
$this->idleRead = $this->socket->read();
|
||||
$this->idleRead->onResolve(function ($error, $chunk) {
|
||||
if ($error || $chunk === null) {
|
||||
$this->close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
78
dependencies/amphp/http-client/src/Connection/Http2Connection.php
vendored
Normal file
78
dependencies/amphp/http-client/src/Connection/Http2Connection.php
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\Internal\Http2ConnectionProcessor;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\EncryptableSocket;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\SocketAddress;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\TlsInfo;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
final class Http2Connection implements Connection
|
||||
{
|
||||
use ForbidSerialization;
|
||||
use ForbidCloning;
|
||||
private const PROTOCOL_VERSIONS = ['2'];
|
||||
/** @var EncryptableSocket */
|
||||
private $socket;
|
||||
/** @var Http2ConnectionProcessor */
|
||||
private $processor;
|
||||
/** @var int */
|
||||
private $requestCount = 0;
|
||||
public function __construct(EncryptableSocket $socket)
|
||||
{
|
||||
$this->socket = $socket;
|
||||
$this->processor = new Http2ConnectionProcessor($socket);
|
||||
}
|
||||
public function getProtocolVersions() : array
|
||||
{
|
||||
return self::PROTOCOL_VERSIONS;
|
||||
}
|
||||
public function initialize() : Promise
|
||||
{
|
||||
return $this->processor->initialize();
|
||||
}
|
||||
public function getStream(Request $request) : Promise
|
||||
{
|
||||
if (!$this->processor->isInitialized()) {
|
||||
throw new \Error('The promise returned from ' . __CLASS__ . '::initialize() must resolve before using the connection');
|
||||
}
|
||||
return call(function () {
|
||||
if ($this->processor->isClosed() || $this->processor->getRemainingStreams() <= 0) {
|
||||
return null;
|
||||
}
|
||||
$this->processor->reserveStream();
|
||||
return HttpStream::fromConnection($this, \Closure::fromCallable([$this, 'request']), \Closure::fromCallable([$this->processor, 'unreserveStream']));
|
||||
});
|
||||
}
|
||||
public function onClose(callable $onClose) : void
|
||||
{
|
||||
$this->processor->onClose($onClose);
|
||||
}
|
||||
public function close() : Promise
|
||||
{
|
||||
return $this->processor->close();
|
||||
}
|
||||
public function getLocalAddress() : SocketAddress
|
||||
{
|
||||
return $this->socket->getLocalAddress();
|
||||
}
|
||||
public function getRemoteAddress() : SocketAddress
|
||||
{
|
||||
return $this->socket->getRemoteAddress();
|
||||
}
|
||||
public function getTlsInfo() : ?TlsInfo
|
||||
{
|
||||
return $this->socket->getTlsInfo();
|
||||
}
|
||||
private function request(Request $request, CancellationToken $token, Stream $applicationStream) : Promise
|
||||
{
|
||||
$this->requestCount++;
|
||||
return $this->processor->request($request, $token, $applicationStream);
|
||||
}
|
||||
}
|
15
dependencies/amphp/http-client/src/Connection/Http2ConnectionException.php
vendored
Normal file
15
dependencies/amphp/http-client/src/Connection/Http2ConnectionException.php
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\HttpException;
|
||||
/**
|
||||
* @deprecated Exception moved to amphp/http. Catch the base exception class (HttpException) instead.
|
||||
*/
|
||||
final class Http2ConnectionException extends HttpException
|
||||
{
|
||||
public function __construct(string $message, int $code, ?\Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
22
dependencies/amphp/http-client/src/Connection/Http2StreamException.php
vendored
Normal file
22
dependencies/amphp/http-client/src/Connection/Http2StreamException.php
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\HttpException;
|
||||
/**
|
||||
* @deprecated Exception moved to amphp/http. Catch the base exception class (HttpException) instead.
|
||||
*/
|
||||
final class Http2StreamException extends HttpException
|
||||
{
|
||||
/** @var int */
|
||||
private $streamId;
|
||||
public function __construct(string $message, int $streamId, int $code, ?\Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->streamId = $streamId;
|
||||
}
|
||||
public function getStreamId() : int
|
||||
{
|
||||
return $this->streamId;
|
||||
}
|
||||
}
|
74
dependencies/amphp/http-client/src/Connection/HttpStream.php
vendored
Normal file
74
dependencies/amphp/http-client/src/Connection/HttpStream.php
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\SocketAddress;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\TlsInfo;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
final class HttpStream implements Stream
|
||||
{
|
||||
use ForbidSerialization;
|
||||
use ForbidCloning;
|
||||
public static function fromConnection(Connection $connection, callable $requestCallback, callable $releaseCallback) : self
|
||||
{
|
||||
return new self($connection->getLocalAddress(), $connection->getRemoteAddress(), $connection->getTlsInfo(), $requestCallback, $releaseCallback);
|
||||
}
|
||||
public static function fromStream(Stream $stream, callable $requestCallback, callable $releaseCallback) : self
|
||||
{
|
||||
return new self($stream->getLocalAddress(), $stream->getRemoteAddress(), $stream->getTlsInfo(), $requestCallback, $releaseCallback);
|
||||
}
|
||||
/** @var SocketAddress */
|
||||
private $localAddress;
|
||||
/** @var SocketAddress */
|
||||
private $remoteAddress;
|
||||
/** @var TlsInfo|null */
|
||||
private $tlsInfo;
|
||||
/** @var callable */
|
||||
private $requestCallback;
|
||||
/** @var callable|null */
|
||||
private $releaseCallback;
|
||||
private function __construct(SocketAddress $localAddress, SocketAddress $remoteAddress, ?TlsInfo $tlsInfo, callable $requestCallback, callable $releaseCallback)
|
||||
{
|
||||
$this->localAddress = $localAddress;
|
||||
$this->remoteAddress = $remoteAddress;
|
||||
$this->tlsInfo = $tlsInfo;
|
||||
$this->requestCallback = $requestCallback;
|
||||
$this->releaseCallback = $releaseCallback;
|
||||
}
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->releaseCallback !== null) {
|
||||
($this->releaseCallback)();
|
||||
}
|
||||
}
|
||||
public function request(Request $request, CancellationToken $cancellation) : Promise
|
||||
{
|
||||
if ($this->releaseCallback === null) {
|
||||
throw new \Error('A stream may only be used for a single request');
|
||||
}
|
||||
$this->releaseCallback = null;
|
||||
return call(function () use($request, $cancellation) {
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->startRequest($request));
|
||||
}
|
||||
return call($this->requestCallback, $request, $cancellation, $this);
|
||||
});
|
||||
}
|
||||
public function getLocalAddress() : SocketAddress
|
||||
{
|
||||
return $this->localAddress;
|
||||
}
|
||||
public function getRemoteAddress() : SocketAddress
|
||||
{
|
||||
return $this->remoteAddress;
|
||||
}
|
||||
public function getTlsInfo() : ?TlsInfo
|
||||
{
|
||||
return $this->tlsInfo;
|
||||
}
|
||||
}
|
53
dependencies/amphp/http-client/src/Connection/InterceptedStream.php
vendored
Normal file
53
dependencies/amphp/http-client/src/Connection/InterceptedStream.php
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\NetworkInterceptor;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\SocketAddress;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\TlsInfo;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
final class InterceptedStream implements Stream
|
||||
{
|
||||
use ForbidCloning;
|
||||
use ForbidSerialization;
|
||||
/** @var Stream */
|
||||
private $stream;
|
||||
/** @var NetworkInterceptor|null */
|
||||
private $interceptor;
|
||||
public function __construct(Stream $stream, NetworkInterceptor $interceptor)
|
||||
{
|
||||
$this->stream = $stream;
|
||||
$this->interceptor = $interceptor;
|
||||
}
|
||||
public function request(Request $request, CancellationToken $cancellation) : Promise
|
||||
{
|
||||
if (!$this->interceptor) {
|
||||
throw new \Error(__METHOD__ . ' may only be invoked once per instance. ' . 'If you need to implement retries or otherwise issue multiple requests, register an ApplicationInterceptor to do so.');
|
||||
}
|
||||
$interceptor = $this->interceptor;
|
||||
$this->interceptor = null;
|
||||
return call(function () use($interceptor, $request, $cancellation) {
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->startRequest($request));
|
||||
}
|
||||
return $interceptor->requestViaNetwork($request, $cancellation, $this->stream);
|
||||
});
|
||||
}
|
||||
public function getLocalAddress() : SocketAddress
|
||||
{
|
||||
return $this->stream->getLocalAddress();
|
||||
}
|
||||
public function getRemoteAddress() : SocketAddress
|
||||
{
|
||||
return $this->stream->getRemoteAddress();
|
||||
}
|
||||
public function getTlsInfo() : ?TlsInfo
|
||||
{
|
||||
return $this->stream->getTlsInfo();
|
||||
}
|
||||
}
|
344
dependencies/amphp/http-client/src/Connection/Internal/Http1Parser.php
vendored
Normal file
344
dependencies/amphp/http-client/src/Connection/Internal/Http1Parser.php
vendored
Normal file
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection\Internal;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\InMemoryStream;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\ParseException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\InvalidHeaderException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Rfc7230;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Status;
|
||||
/** @internal */
|
||||
final class Http1Parser
|
||||
{
|
||||
use ForbidSerialization;
|
||||
use ForbidCloning;
|
||||
private const STATUS_LINE_PATTERN = "#^\n HTTP/(?P<protocol>\\d+\\.\\d+)[ \t]+\n (?P<status>[1-9]\\d\\d)[ \t]*\n (?P<reason>[^\x01-\x08\x10-\x19]*)\n \$#ix";
|
||||
public const AWAITING_HEADERS = 0;
|
||||
public const BODY_IDENTITY = 1;
|
||||
public const BODY_IDENTITY_EOF = 2;
|
||||
public const BODY_CHUNKS = 3;
|
||||
public const TRAILERS_START = 4;
|
||||
public const TRAILERS = 5;
|
||||
/** @var int */
|
||||
private $state = self::AWAITING_HEADERS;
|
||||
/** @var string */
|
||||
private $buffer = '';
|
||||
/** @var string|null */
|
||||
private $protocol;
|
||||
/** @var int|null */
|
||||
private $statusCode;
|
||||
/** @var string|null */
|
||||
private $statusReason;
|
||||
/** @var string[][] */
|
||||
private $headers = [];
|
||||
/** @var int|null */
|
||||
private $remainingBodyBytes;
|
||||
/** @var int */
|
||||
private $bodyBytesConsumed = 0;
|
||||
/** @var bool */
|
||||
private $chunkedEncoding = \false;
|
||||
/** @var int|null */
|
||||
private $chunkLengthRemaining;
|
||||
/** @var bool */
|
||||
private $complete = \false;
|
||||
/** @var Request */
|
||||
private $request;
|
||||
/** @var int */
|
||||
private $maxHeaderBytes;
|
||||
/** @var int */
|
||||
private $maxBodyBytes;
|
||||
/** @var callable */
|
||||
private $bodyDataCallback;
|
||||
/** @var callable */
|
||||
private $trailersCallback;
|
||||
public function __construct(Request $request, callable $bodyDataCallback, callable $trailersCallback)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->bodyDataCallback = $bodyDataCallback;
|
||||
$this->trailersCallback = $trailersCallback;
|
||||
$this->maxHeaderBytes = $request->getHeaderSizeLimit();
|
||||
$this->maxBodyBytes = $request->getBodySizeLimit();
|
||||
}
|
||||
public function getBuffer() : string
|
||||
{
|
||||
return $this->buffer;
|
||||
}
|
||||
public function getState() : int
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
public function buffer(string $data) : void
|
||||
{
|
||||
$this->buffer .= $data;
|
||||
}
|
||||
/**
|
||||
* @param string|null $data
|
||||
*
|
||||
* @return Response|null
|
||||
*
|
||||
* @throws ParseException
|
||||
*/
|
||||
public function parse(string $data = null) : ?Response
|
||||
{
|
||||
if ($data !== null) {
|
||||
$this->buffer .= $data;
|
||||
}
|
||||
if ($this->buffer === '') {
|
||||
return null;
|
||||
}
|
||||
if ($this->complete) {
|
||||
throw new ParseException('Can\'t continue parsing, response is already complete', Status::BAD_REQUEST);
|
||||
}
|
||||
switch ($this->state) {
|
||||
case self::AWAITING_HEADERS:
|
||||
goto headers;
|
||||
case self::BODY_IDENTITY:
|
||||
goto body_identity;
|
||||
case self::BODY_IDENTITY_EOF:
|
||||
goto body_identity_eof;
|
||||
case self::BODY_CHUNKS:
|
||||
goto body_chunks;
|
||||
case self::TRAILERS_START:
|
||||
goto trailers_start;
|
||||
case self::TRAILERS:
|
||||
goto trailers;
|
||||
}
|
||||
headers:
|
||||
$startLineAndHeaders = $this->shiftHeadersFromBuffer();
|
||||
if ($startLineAndHeaders === null) {
|
||||
return null;
|
||||
}
|
||||
$startLineEndPos = \strpos($startLineAndHeaders, "\r\n");
|
||||
\assert($startLineEndPos !== \false);
|
||||
$startLine = \substr($startLineAndHeaders, 0, $startLineEndPos);
|
||||
$rawHeaders = \substr($startLineAndHeaders, $startLineEndPos + 2);
|
||||
if (\preg_match(self::STATUS_LINE_PATTERN, $startLine, $match)) {
|
||||
$this->protocol = $match['protocol'];
|
||||
$this->statusCode = (int) $match['status'];
|
||||
$this->statusReason = \trim($match['reason']);
|
||||
} else {
|
||||
throw new ParseException('Invalid status line: ' . $startLine, Status::BAD_REQUEST);
|
||||
}
|
||||
if ($rawHeaders !== '') {
|
||||
$this->headers = $this->parseRawHeaders($rawHeaders);
|
||||
} else {
|
||||
$this->headers = [];
|
||||
}
|
||||
$requestMethod = $this->request->getMethod();
|
||||
$skipBody = $this->statusCode < Status::OK || $this->statusCode === Status::NOT_MODIFIED || $this->statusCode === Status::NO_CONTENT || $requestMethod === 'HEAD' || $requestMethod === 'CONNECT';
|
||||
if ($skipBody) {
|
||||
$this->complete = \true;
|
||||
} elseif ($this->chunkedEncoding) {
|
||||
$this->state = self::BODY_CHUNKS;
|
||||
} elseif ($this->remainingBodyBytes === null) {
|
||||
$this->state = self::BODY_IDENTITY_EOF;
|
||||
} elseif ($this->remainingBodyBytes > 0) {
|
||||
$this->state = self::BODY_IDENTITY;
|
||||
} else {
|
||||
$this->complete = \true;
|
||||
}
|
||||
$response = new Response($this->protocol, $this->statusCode, $this->statusReason, [], new InMemoryStream(), $this->request);
|
||||
foreach ($this->headers as [$key, $value]) {
|
||||
$response->addHeader($key, $value);
|
||||
}
|
||||
return $response;
|
||||
body_identity:
|
||||
$bufferDataSize = \strlen($this->buffer);
|
||||
if ($bufferDataSize <= $this->remainingBodyBytes) {
|
||||
$chunk = $this->buffer;
|
||||
$this->buffer = '';
|
||||
$this->remainingBodyBytes -= $bufferDataSize;
|
||||
$this->addToBody($chunk);
|
||||
if ($this->remainingBodyBytes === 0) {
|
||||
$this->complete = \true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
$bodyData = \substr($this->buffer, 0, $this->remainingBodyBytes);
|
||||
$this->addToBody($bodyData);
|
||||
$this->buffer = \substr($this->buffer, $this->remainingBodyBytes);
|
||||
$this->remainingBodyBytes = 0;
|
||||
goto complete;
|
||||
body_identity_eof:
|
||||
$this->addToBody($this->buffer);
|
||||
$this->buffer = '';
|
||||
return null;
|
||||
body_chunks:
|
||||
if ($this->parseChunkedBody()) {
|
||||
$this->state = self::TRAILERS_START;
|
||||
goto trailers_start;
|
||||
}
|
||||
return null;
|
||||
trailers_start:
|
||||
$firstTwoBytes = \substr($this->buffer, 0, 2);
|
||||
if ($firstTwoBytes === "" || $firstTwoBytes === "\r") {
|
||||
return null;
|
||||
}
|
||||
if ($firstTwoBytes === "\r\n") {
|
||||
$this->buffer = \substr($this->buffer, 2);
|
||||
goto complete;
|
||||
}
|
||||
$this->state = self::TRAILERS;
|
||||
goto trailers;
|
||||
trailers:
|
||||
$trailers = $this->shiftHeadersFromBuffer();
|
||||
if ($trailers === null) {
|
||||
return null;
|
||||
}
|
||||
$this->parseTrailers($trailers);
|
||||
goto complete;
|
||||
complete:
|
||||
$this->complete = \true;
|
||||
return null;
|
||||
}
|
||||
public function isComplete() : bool
|
||||
{
|
||||
return $this->complete;
|
||||
}
|
||||
/**
|
||||
* @return string|null
|
||||
*
|
||||
* @throws ParseException
|
||||
*/
|
||||
private function shiftHeadersFromBuffer() : ?string
|
||||
{
|
||||
$this->buffer = \ltrim($this->buffer, "\r\n");
|
||||
if ($headersSize = \strpos($this->buffer, "\r\n\r\n")) {
|
||||
$headers = \substr($this->buffer, 0, $headersSize + 2);
|
||||
$this->buffer = \substr($this->buffer, $headersSize + 4);
|
||||
} else {
|
||||
$headersSize = \strlen($this->buffer);
|
||||
$headers = null;
|
||||
}
|
||||
if ($this->maxHeaderBytes > 0 && $headersSize > $this->maxHeaderBytes) {
|
||||
throw new ParseException("Configured header size exceeded: {$headersSize} bytes received, while the configured limit is {$this->maxHeaderBytes} bytes", Status::REQUEST_HEADER_FIELDS_TOO_LARGE);
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
/**
|
||||
* @param string $rawHeaders
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws ParseException
|
||||
*/
|
||||
private function parseRawHeaders(string $rawHeaders) : array
|
||||
{
|
||||
// Legacy support for folded headers
|
||||
if (\strpos($rawHeaders, "\r\n ") || \strpos($rawHeaders, "\r\n\t")) {
|
||||
$rawHeaders = \preg_replace("/\r\n[ \t]++/", ' ', $rawHeaders);
|
||||
}
|
||||
try {
|
||||
$headers = Rfc7230::parseRawHeaders($rawHeaders);
|
||||
$headerMap = [];
|
||||
foreach ($headers as [$key, $value]) {
|
||||
$headerMap[\strtolower($key)][] = $value;
|
||||
}
|
||||
} catch (InvalidHeaderException $e) {
|
||||
throw new ParseException('Invalid headers', Status::BAD_REQUEST, $e);
|
||||
}
|
||||
if (isset($headerMap['transfer-encoding'])) {
|
||||
$transferEncodings = \explode(',', \strtolower(\implode(',', $headerMap['transfer-encoding'])));
|
||||
$transferEncodings = \array_map('trim', $transferEncodings);
|
||||
$this->chunkedEncoding = \in_array('chunked', $transferEncodings, \true);
|
||||
} elseif (isset($headerMap['content-length'])) {
|
||||
if (\count($headerMap['content-length']) > 1) {
|
||||
throw new ParseException('Can\'t determine body length, because multiple content-length headers present in the response', Status::BAD_REQUEST);
|
||||
}
|
||||
$contentLength = $headerMap['content-length'][0];
|
||||
if (!\preg_match('/^(0|[1-9][0-9]*)$/', $contentLength)) {
|
||||
throw new ParseException('Can\'t determine body length, because the content-length header value is invalid', Status::BAD_REQUEST);
|
||||
}
|
||||
$this->remainingBodyBytes = (int) $contentLength;
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
/**
|
||||
* Decodes a chunked response body.
|
||||
*
|
||||
* @return bool Returns {@code true} if the body is complete, otherwise {@code false}.
|
||||
*
|
||||
* @throws ParseException
|
||||
*/
|
||||
private function parseChunkedBody() : bool
|
||||
{
|
||||
if ($this->chunkLengthRemaining !== null) {
|
||||
goto decode_chunk;
|
||||
}
|
||||
determine_chunk_size:
|
||||
if (\false === ($lineEndPos = \strpos($this->buffer, "\r\n"))) {
|
||||
return \false;
|
||||
}
|
||||
if ($lineEndPos === 0) {
|
||||
throw new ParseException('Invalid line; hexadecimal chunk size expected', Status::BAD_REQUEST);
|
||||
}
|
||||
$line = \substr($this->buffer, 0, $lineEndPos);
|
||||
$hex = \strtolower(\trim(\ltrim($line, '0'))) ?: '0';
|
||||
$dec = \hexdec($hex);
|
||||
if ($hex !== \dechex($dec)) {
|
||||
throw new ParseException('Invalid hexadecimal chunk size', Status::BAD_REQUEST);
|
||||
}
|
||||
$this->chunkLengthRemaining = $dec;
|
||||
$this->buffer = \substr($this->buffer, $lineEndPos + 2);
|
||||
if ($this->chunkLengthRemaining === 0) {
|
||||
return \true;
|
||||
}
|
||||
decode_chunk:
|
||||
$bufferLength = \strlen($this->buffer);
|
||||
// These first two (extreme) edge cases prevent errors where the packet boundary ends after
|
||||
// the \r and before the \n at the end of a chunk.
|
||||
if ($bufferLength === $this->chunkLengthRemaining || $bufferLength === $this->chunkLengthRemaining + 1) {
|
||||
return \false;
|
||||
}
|
||||
if ($bufferLength >= $this->chunkLengthRemaining + 2) {
|
||||
$chunk = \substr($this->buffer, 0, $this->chunkLengthRemaining);
|
||||
$this->buffer = \substr($this->buffer, $this->chunkLengthRemaining + 2);
|
||||
$this->chunkLengthRemaining = null;
|
||||
$this->addToBody($chunk);
|
||||
goto determine_chunk_size;
|
||||
}
|
||||
/** @noinspection SuspiciousAssignmentsInspection */
|
||||
$chunk = $this->buffer;
|
||||
$this->buffer = '';
|
||||
$this->chunkLengthRemaining -= $bufferLength;
|
||||
$this->addToBody($chunk);
|
||||
return \false;
|
||||
}
|
||||
/**
|
||||
* @param string $trailers
|
||||
*
|
||||
* @throws ParseException
|
||||
*/
|
||||
private function parseTrailers(string $trailers) : void
|
||||
{
|
||||
try {
|
||||
$trailers = Rfc7230::parseHeaders($trailers);
|
||||
} catch (InvalidHeaderException $e) {
|
||||
throw new ParseException('Invalid trailers', Status::BAD_REQUEST, $e);
|
||||
}
|
||||
($this->trailersCallback)($trailers);
|
||||
}
|
||||
/**
|
||||
* @param string $data
|
||||
*
|
||||
* @throws ParseException
|
||||
*/
|
||||
private function addToBody(string $data) : void
|
||||
{
|
||||
$length = \strlen($data);
|
||||
if (!$length) {
|
||||
return;
|
||||
}
|
||||
$this->bodyBytesConsumed += $length;
|
||||
if ($this->maxBodyBytes > 0 && $this->bodyBytesConsumed > $this->maxBodyBytes) {
|
||||
throw new ParseException("Configured body size exceeded: {$this->bodyBytesConsumed} bytes received, while the configured limit is {$this->maxBodyBytes} bytes", Status::PAYLOAD_TOO_LARGE);
|
||||
}
|
||||
if ($this->bodyDataCallback) {
|
||||
($this->bodyDataCallback)($data);
|
||||
}
|
||||
}
|
||||
}
|
1143
dependencies/amphp/http-client/src/Connection/Internal/Http2ConnectionProcessor.php
vendored
Normal file
1143
dependencies/amphp/http-client/src/Connection/Internal/Http2ConnectionProcessor.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
107
dependencies/amphp/http-client/src/Connection/Internal/Http2Stream.php
vendored
Normal file
107
dependencies/amphp/http-client/src/Connection/Internal/Http2Stream.php
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection\Internal;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Deferred;
|
||||
use WP_Ultimo\Dependencies\Amp\Emitter;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\Stream;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
use WP_Ultimo\Dependencies\Amp\Loop;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Struct;
|
||||
/**
|
||||
* Used in Http2Connection.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Http2Stream
|
||||
{
|
||||
use Struct;
|
||||
use ForbidSerialization;
|
||||
use ForbidCloning;
|
||||
/** @var int */
|
||||
public $id;
|
||||
/** @var Request */
|
||||
public $request;
|
||||
/** @var Response|null */
|
||||
public $response;
|
||||
/** @var Deferred|null */
|
||||
public $pendingResponse;
|
||||
/** @var Promise|null */
|
||||
public $preResponseResolution;
|
||||
/** @var bool */
|
||||
public $responsePending = \true;
|
||||
/** @var Emitter|null */
|
||||
public $body;
|
||||
/** @var Deferred|null */
|
||||
public $trailers;
|
||||
/** @var CancellationToken */
|
||||
public $originalCancellation;
|
||||
/** @var CancellationToken */
|
||||
public $cancellationToken;
|
||||
/** @var int Bytes received on the stream. */
|
||||
public $received = 0;
|
||||
/** @var int */
|
||||
public $serverWindow;
|
||||
/** @var int */
|
||||
public $clientWindow;
|
||||
/** @var int */
|
||||
public $bufferSize;
|
||||
/** @var string */
|
||||
public $requestBodyBuffer = '';
|
||||
/** @var bool */
|
||||
public $requestBodyComplete = \false;
|
||||
/** @var Deferred */
|
||||
public $requestBodyCompletion;
|
||||
/** @var int Integer between 1 and 256 */
|
||||
public $weight = 16;
|
||||
/** @var int */
|
||||
public $dependency = 0;
|
||||
/** @var int|null */
|
||||
public $expectedLength;
|
||||
/** @var Stream */
|
||||
public $stream;
|
||||
/** @var Deferred|null */
|
||||
public $windowSizeIncrease;
|
||||
/** @var string|null */
|
||||
private $watcher;
|
||||
public function __construct(int $id, Request $request, Stream $stream, CancellationToken $cancellationToken, CancellationToken $originalCancellation, ?string $watcher, int $serverSize, int $clientSize)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->request = $request;
|
||||
$this->stream = $stream;
|
||||
$this->cancellationToken = $cancellationToken;
|
||||
$this->originalCancellation = $originalCancellation;
|
||||
$this->watcher = $watcher;
|
||||
$this->serverWindow = $serverSize;
|
||||
$this->clientWindow = $clientSize;
|
||||
$this->pendingResponse = new Deferred();
|
||||
$this->requestBodyCompletion = new Deferred();
|
||||
$this->bufferSize = 0;
|
||||
}
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->watcher !== null) {
|
||||
Loop::cancel($this->watcher);
|
||||
}
|
||||
}
|
||||
public function disableInactivityWatcher() : void
|
||||
{
|
||||
if ($this->watcher === null) {
|
||||
return;
|
||||
}
|
||||
Loop::disable($this->watcher);
|
||||
}
|
||||
public function enableInactivityWatcher() : void
|
||||
{
|
||||
if ($this->watcher === null) {
|
||||
return;
|
||||
}
|
||||
Loop::disable($this->watcher);
|
||||
Loop::enable($this->watcher);
|
||||
}
|
||||
}
|
71
dependencies/amphp/http-client/src/Connection/Internal/RequestNormalizer.php
vendored
Normal file
71
dependencies/amphp/http-client/src/Connection/Internal/RequestNormalizer.php
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection\Internal;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\RequestBody;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
/** @internal */
|
||||
final class RequestNormalizer
|
||||
{
|
||||
public static function normalizeRequest(Request $request) : Promise
|
||||
{
|
||||
return call(static function () use($request) {
|
||||
/** @var array $headers */
|
||||
$headers = (yield $request->getBody()->getHeaders());
|
||||
foreach ($headers as $name => $header) {
|
||||
if (!$request->hasHeader($name)) {
|
||||
$request->setHeaders([$name => $header]);
|
||||
}
|
||||
}
|
||||
yield from self::normalizeRequestBodyHeaders($request);
|
||||
// Always normalize this as last item, because we need to strip sensitive headers
|
||||
self::normalizeTraceRequest($request);
|
||||
return $request;
|
||||
});
|
||||
}
|
||||
private static function normalizeRequestBodyHeaders(Request $request) : \Generator
|
||||
{
|
||||
if (!$request->hasHeader('host')) {
|
||||
// Though servers are supposed to be able to handle standard port names on the end of the
|
||||
// Host header some fail to do this correctly. Thankfully PSR-7 recommends to strip the port
|
||||
// if it is the standard port for the given scheme.
|
||||
$request->setHeader('host', $request->getUri()->withUserInfo('')->getAuthority());
|
||||
}
|
||||
if ($request->hasHeader("transfer-encoding")) {
|
||||
$request->removeHeader("content-length");
|
||||
return;
|
||||
}
|
||||
if ($request->hasHeader("content-length")) {
|
||||
return;
|
||||
}
|
||||
/** @var RequestBody $body */
|
||||
$body = $request->getBody();
|
||||
$bodyLength = (yield $body->getBodyLength());
|
||||
if ($bodyLength === 0) {
|
||||
if (\in_array($request->getMethod(), ['HEAD', 'GET', 'CONNECT'], \true)) {
|
||||
$request->removeHeader('content-length');
|
||||
} else {
|
||||
$request->setHeader('content-length', '0');
|
||||
}
|
||||
$request->removeHeader('transfer-encoding');
|
||||
} elseif ($bodyLength > 0) {
|
||||
$request->setHeader("content-length", $bodyLength);
|
||||
$request->removeHeader("transfer-encoding");
|
||||
} else {
|
||||
$request->setHeader("transfer-encoding", "chunked");
|
||||
}
|
||||
}
|
||||
private static function normalizeTraceRequest(Request $request) : void
|
||||
{
|
||||
$method = $request->getMethod();
|
||||
if ($method !== 'TRACE') {
|
||||
return;
|
||||
}
|
||||
// https://tools.ietf.org/html/rfc7231#section-4.3.8
|
||||
$request->setBody(null);
|
||||
// Remove all body and sensitive headers
|
||||
$request->setHeaders(["transfer-encoding" => [], "content-length" => [], "authorization" => [], "proxy-authorization" => [], "cookie" => []]);
|
||||
}
|
||||
}
|
6
dependencies/amphp/http-client/src/Connection/LimitedConnectionPool.php
vendored
Normal file
6
dependencies/amphp/http-client/src/Connection/LimitedConnectionPool.php
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
// Alias for backward compatibility.
|
||||
\class_alias(StreamLimitingPool::class, LimitedConnectionPool::class);
|
37
dependencies/amphp/http-client/src/Connection/Stream.php
vendored
Normal file
37
dependencies/amphp/http-client/src/Connection/Stream.php
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\DelegateHttpClient;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\EventListener;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\SocketAddress;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\TlsInfo;
|
||||
interface Stream extends DelegateHttpClient
|
||||
{
|
||||
/**
|
||||
* Executes the request.
|
||||
*
|
||||
* This method may only be invoked once per instance.
|
||||
*
|
||||
* The stream must call {@see EventListener::startSendingRequest()},
|
||||
* {@see EventListener::completeSendingRequest()}, {@see EventListener::startReceivingResponse()}, and
|
||||
* {@see EventListener::completeReceivingResponse()} event listener methods on all event listeners registered on
|
||||
* the given request in the order defined by {@see Request::getEventListeners()}. Before calling the next listener,
|
||||
* the promise returned from the previous one must resolve successfully.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param CancellationToken $cancellation
|
||||
*
|
||||
* @return Promise<Response>
|
||||
*
|
||||
* @throws \Error Thrown if this method is called more than once.
|
||||
*/
|
||||
public function request(Request $request, CancellationToken $cancellation) : Promise;
|
||||
public function getLocalAddress() : SocketAddress;
|
||||
public function getRemoteAddress() : SocketAddress;
|
||||
public function getTlsInfo() : ?TlsInfo;
|
||||
}
|
72
dependencies/amphp/http-client/src/Connection/StreamLimitingPool.php
vendored
Normal file
72
dependencies/amphp/http-client/src/Connection/StreamLimitingPool.php
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Sync\KeyedSemaphore;
|
||||
use WP_Ultimo\Dependencies\Amp\Sync\Lock;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
use function WP_Ultimo\Dependencies\Amp\coroutine;
|
||||
final class StreamLimitingPool implements ConnectionPool
|
||||
{
|
||||
use ForbidCloning;
|
||||
use ForbidSerialization;
|
||||
public static function byHost(ConnectionPool $delegate, KeyedSemaphore $semaphore) : self
|
||||
{
|
||||
return new self($delegate, $semaphore, static function (Request $request) {
|
||||
return $request->getUri()->getHost();
|
||||
});
|
||||
}
|
||||
public static function byStaticKey(ConnectionPool $delegate, KeyedSemaphore $semaphore, string $key = '') : self
|
||||
{
|
||||
return new self($delegate, $semaphore, static function () use($key) {
|
||||
return $key;
|
||||
});
|
||||
}
|
||||
public static function byCustomKey(ConnectionPool $delegate, KeyedSemaphore $semaphore, callable $requestToKeyMapper) : self
|
||||
{
|
||||
return new self($delegate, $semaphore, $requestToKeyMapper);
|
||||
}
|
||||
/** @var ConnectionPool */
|
||||
private $delegate;
|
||||
/** @var KeyedSemaphore */
|
||||
private $semaphore;
|
||||
/** @var callable */
|
||||
private $requestToKeyMapper;
|
||||
private function __construct(ConnectionPool $delegate, KeyedSemaphore $semaphore, callable $requestToKeyMapper)
|
||||
{
|
||||
$this->delegate = $delegate;
|
||||
$this->semaphore = $semaphore;
|
||||
$this->requestToKeyMapper = $requestToKeyMapper;
|
||||
}
|
||||
public function getStream(Request $request, CancellationToken $cancellation) : Promise
|
||||
{
|
||||
return call(function () use($request, $cancellation) {
|
||||
/** @var Lock $lock */
|
||||
$lock = (yield $this->semaphore->acquire(($this->requestToKeyMapper)($request)));
|
||||
/** @var Stream $stream */
|
||||
$stream = (yield $this->delegate->getStream($request, $cancellation));
|
||||
return HttpStream::fromStream($stream, coroutine(static function (Request $request, CancellationToken $cancellationToken) use($stream, $lock) {
|
||||
try {
|
||||
/** @var Response $response */
|
||||
$response = (yield $stream->request($request, $cancellationToken));
|
||||
// await response being completely received
|
||||
$response->getTrailers()->onResolve(static function () use($lock) {
|
||||
$lock->release();
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
$lock->release();
|
||||
throw $e;
|
||||
}
|
||||
return $response;
|
||||
}), static function () use($lock) {
|
||||
$lock->release();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
38
dependencies/amphp/http-client/src/Connection/UnlimitedConnectionPool.php
vendored
Normal file
38
dependencies/amphp/http-client/src/Connection/UnlimitedConnectionPool.php
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
final class UnlimitedConnectionPool implements ConnectionPool
|
||||
{
|
||||
use ForbidSerialization;
|
||||
/** @var ConnectionLimitingPool */
|
||||
private $pool;
|
||||
public function __construct(?ConnectionFactory $connectionFactory = null)
|
||||
{
|
||||
$this->pool = ConnectionLimitingPool::byAuthority(\PHP_INT_MAX, $connectionFactory);
|
||||
}
|
||||
public function __clone()
|
||||
{
|
||||
$this->pool = clone $this->pool;
|
||||
}
|
||||
public function getTotalConnectionAttempts() : int
|
||||
{
|
||||
return $this->pool->getTotalConnectionAttempts();
|
||||
}
|
||||
public function getTotalStreamRequests() : int
|
||||
{
|
||||
return $this->pool->getTotalStreamRequests();
|
||||
}
|
||||
public function getOpenConnectionCount() : int
|
||||
{
|
||||
return $this->pool->getOpenConnectionCount();
|
||||
}
|
||||
public function getStream(Request $request, CancellationToken $cancellation) : Promise
|
||||
{
|
||||
return $this->pool->getStream($request, $cancellation);
|
||||
}
|
||||
}
|
12
dependencies/amphp/http-client/src/Connection/UnprocessedRequestException.php
vendored
Normal file
12
dependencies/amphp/http-client/src/Connection/UnprocessedRequestException.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\HttpException;
|
||||
final class UnprocessedRequestException extends HttpException
|
||||
{
|
||||
public function __construct(HttpException $previous)
|
||||
{
|
||||
parent::__construct("The request was not processed and can be safely retried", 0, $previous);
|
||||
}
|
||||
}
|
91
dependencies/amphp/http-client/src/Connection/UpgradedSocket.php
vendored
Normal file
91
dependencies/amphp/http-client/src/Connection/UpgradedSocket.php
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Connection;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\EncryptableSocket;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\SocketAddress;
|
||||
use WP_Ultimo\Dependencies\Amp\Socket\TlsInfo;
|
||||
use WP_Ultimo\Dependencies\Amp\Success;
|
||||
final class UpgradedSocket implements EncryptableSocket
|
||||
{
|
||||
use ForbidCloning;
|
||||
use ForbidSerialization;
|
||||
/** @var EncryptableSocket */
|
||||
private $socket;
|
||||
/** @var string|null */
|
||||
private $buffer;
|
||||
/**
|
||||
* @param EncryptableSocket $socket
|
||||
* @param string $buffer Remaining buffer previously read from the socket.
|
||||
*/
|
||||
public function __construct(EncryptableSocket $socket, string $buffer)
|
||||
{
|
||||
$this->socket = $socket;
|
||||
$this->buffer = $buffer !== '' ? $buffer : null;
|
||||
}
|
||||
public function read() : Promise
|
||||
{
|
||||
if ($this->buffer !== null) {
|
||||
$buffer = $this->buffer;
|
||||
$this->buffer = null;
|
||||
return new Success($buffer);
|
||||
}
|
||||
return $this->socket->read();
|
||||
}
|
||||
public function close() : void
|
||||
{
|
||||
$this->socket->close();
|
||||
}
|
||||
public function __destruct()
|
||||
{
|
||||
$this->close();
|
||||
}
|
||||
public function write(string $data) : Promise
|
||||
{
|
||||
return $this->socket->write($data);
|
||||
}
|
||||
public function end(string $finalData = "") : Promise
|
||||
{
|
||||
return $this->socket->end($finalData);
|
||||
}
|
||||
public function reference() : void
|
||||
{
|
||||
$this->socket->reference();
|
||||
}
|
||||
public function unreference() : void
|
||||
{
|
||||
$this->socket->unreference();
|
||||
}
|
||||
public function isClosed() : bool
|
||||
{
|
||||
return $this->socket->isClosed();
|
||||
}
|
||||
public function getLocalAddress() : SocketAddress
|
||||
{
|
||||
return $this->socket->getLocalAddress();
|
||||
}
|
||||
public function getRemoteAddress() : SocketAddress
|
||||
{
|
||||
return $this->socket->getRemoteAddress();
|
||||
}
|
||||
public function setupTls(?CancellationToken $cancellationToken = null) : Promise
|
||||
{
|
||||
return $this->socket->setupTls($cancellationToken);
|
||||
}
|
||||
public function shutdownTls(?CancellationToken $cancellationToken = null) : Promise
|
||||
{
|
||||
return $this->socket->shutdownTls();
|
||||
}
|
||||
public function getTlsState() : int
|
||||
{
|
||||
return $this->socket->getTlsState();
|
||||
}
|
||||
public function getTlsInfo() : ?TlsInfo
|
||||
{
|
||||
return $this->socket->getTlsInfo();
|
||||
}
|
||||
}
|
31
dependencies/amphp/http-client/src/DelegateHttpClient.php
vendored
Normal file
31
dependencies/amphp/http-client/src/DelegateHttpClient.php
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/**
|
||||
* Base HTTP client interface for use in {@see ApplicationInterceptor}.
|
||||
*
|
||||
* Applications and implementations should depend on {@see HttpClient} instead. The intent of this interface is to
|
||||
* allow static analysis tools to find interceptors that forget to pass the cancellation token down. This situation is
|
||||
* created because of the cancellation token being optional.
|
||||
*
|
||||
* Before executing or delegating the request, any client implementation must call {@see EventListener::startRequest()}
|
||||
* on all event listeners registered on the given request in the order defined by {@see Request::getEventListeners()}.
|
||||
* Before calling the next listener, the promise returned from the previous one must resolve successfully.
|
||||
*
|
||||
* @see HttpClient
|
||||
*/
|
||||
interface DelegateHttpClient
|
||||
{
|
||||
/**
|
||||
* Request a specific resource from an HTTP server.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param CancellationToken $cancellation
|
||||
*
|
||||
* @return Promise<Response>
|
||||
*/
|
||||
public function request(Request $request, CancellationToken $cancellation) : Promise;
|
||||
}
|
117
dependencies/amphp/http-client/src/EventListener.php
vendored
Normal file
117
dependencies/amphp/http-client/src/EventListener.php
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\ConnectionPool;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\Stream;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/**
|
||||
* Allows listening to more fine granular events than interceptors are able to achieve.
|
||||
*
|
||||
* All event listener methods might be called multiple times for a single request. The implementing listener is
|
||||
* responsible to detect another call, e.g. via attributes in the request.
|
||||
*/
|
||||
interface EventListener
|
||||
{
|
||||
/**
|
||||
* Called at the very beginning of {@see DelegateHttpClient::request()}.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Promise Should resolve successfully, otherwise aborts the request.
|
||||
*/
|
||||
public function startRequest(Request $request) : Promise;
|
||||
/**
|
||||
* Optionally called by {@see ConnectionPool::getStream()} before DNS resolution is started.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Promise Should resolve successfully, otherwise aborts the request.
|
||||
*/
|
||||
public function startDnsResolution(Request $request) : Promise;
|
||||
/**
|
||||
* Optionally called by {@see ConnectionPool::getStream()} after DNS resolution is completed.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Promise Should resolve successfully, otherwise aborts the request.
|
||||
*/
|
||||
public function completeDnsResolution(Request $request) : Promise;
|
||||
/**
|
||||
* Called by {@see ConnectionPool::getStream()} before a new connection is initiated.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Promise Should resolve successfully, otherwise aborts the request.
|
||||
*/
|
||||
public function startConnectionCreation(Request $request) : Promise;
|
||||
/**
|
||||
* Called by {@see ConnectionPool::getStream()} after a new connection is established and TLS negotiated.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Promise Should resolve successfully, otherwise aborts the request.
|
||||
*/
|
||||
public function completeConnectionCreation(Request $request) : Promise;
|
||||
/**
|
||||
* Called by {@see ConnectionPool::getStream()} before TLS negotiation is started (only if HTTPS is used).
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Promise Should resolve successfully, otherwise aborts the request.
|
||||
*/
|
||||
public function startTlsNegotiation(Request $request) : Promise;
|
||||
/**
|
||||
* Called by {@see ConnectionPool::getStream()} after TLS negotiation is successful (only if HTTPS is used).
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Promise Should resolve successfully, otherwise aborts the request.
|
||||
*/
|
||||
public function completeTlsNegotiation(Request $request) : Promise;
|
||||
/**
|
||||
* Called by {@see Stream::request()} before the request is sent.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Stream $stream
|
||||
*
|
||||
* @return Promise Should resolve successfully, otherwise aborts the request.
|
||||
*/
|
||||
public function startSendingRequest(Request $request, Stream $stream) : Promise;
|
||||
/**
|
||||
* Called by {@see Stream::request()} after the request is sent.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Stream $stream
|
||||
*
|
||||
* @return Promise Should resolve successfully, otherwise aborts the request.
|
||||
*/
|
||||
public function completeSendingRequest(Request $request, Stream $stream) : Promise;
|
||||
/**
|
||||
* Called by {@see Stream::request()} after the first response byte is received.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Stream $stream
|
||||
*
|
||||
* @return Promise Should resolve successfully, otherwise aborts the request.
|
||||
*/
|
||||
public function startReceivingResponse(Request $request, Stream $stream) : Promise;
|
||||
/**
|
||||
* Called by {@see Stream::request()} after the request is complete.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Stream $stream
|
||||
*
|
||||
* @return Promise Should resolve successfully, otherwise aborts the request.
|
||||
*/
|
||||
public function completeReceivingResponse(Request $request, Stream $stream) : Promise;
|
||||
/**
|
||||
* Called if the request is aborted.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param \Throwable $cause
|
||||
*
|
||||
* @return Promise Should resolve successfully.
|
||||
*/
|
||||
public function abort(Request $request, \Throwable $cause) : Promise;
|
||||
}
|
81
dependencies/amphp/http-client/src/EventListener/RecordHarAttributes.php
vendored
Normal file
81
dependencies/amphp/http-client/src/EventListener/RecordHarAttributes.php
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\EventListener;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\Stream;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\EventListener;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\HarAttributes;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Success;
|
||||
use function WP_Ultimo\Dependencies\Amp\getCurrentTime;
|
||||
final class RecordHarAttributes implements EventListener
|
||||
{
|
||||
public function startRequest(Request $request) : Promise
|
||||
{
|
||||
if (!$request->hasAttribute(HarAttributes::STARTED_DATE_TIME)) {
|
||||
$request->setAttribute(HarAttributes::STARTED_DATE_TIME, new \DateTimeImmutable());
|
||||
}
|
||||
return $this->addTiming(HarAttributes::TIME_START, $request);
|
||||
}
|
||||
public function startDnsResolution(Request $request) : Promise
|
||||
{
|
||||
return new Success();
|
||||
// not implemented
|
||||
}
|
||||
public function startConnectionCreation(Request $request) : Promise
|
||||
{
|
||||
return $this->addTiming(HarAttributes::TIME_CONNECT, $request);
|
||||
}
|
||||
public function startTlsNegotiation(Request $request) : Promise
|
||||
{
|
||||
return $this->addTiming(HarAttributes::TIME_SSL, $request);
|
||||
}
|
||||
public function startSendingRequest(Request $request, Stream $stream) : Promise
|
||||
{
|
||||
$host = $stream->getRemoteAddress()->getHost();
|
||||
if (\strrpos($host, ':')) {
|
||||
$host = '[' . $host . ']';
|
||||
}
|
||||
$request->setAttribute(HarAttributes::SERVER_IP_ADDRESS, $host);
|
||||
return $this->addTiming(HarAttributes::TIME_SEND, $request);
|
||||
}
|
||||
public function completeSendingRequest(Request $request, Stream $stream) : Promise
|
||||
{
|
||||
return $this->addTiming(HarAttributes::TIME_WAIT, $request);
|
||||
}
|
||||
public function startReceivingResponse(Request $request, Stream $stream) : Promise
|
||||
{
|
||||
return $this->addTiming(HarAttributes::TIME_RECEIVE, $request);
|
||||
}
|
||||
public function completeReceivingResponse(Request $request, Stream $stream) : Promise
|
||||
{
|
||||
return $this->addTiming(HarAttributes::TIME_COMPLETE, $request);
|
||||
}
|
||||
public function completeDnsResolution(Request $request) : Promise
|
||||
{
|
||||
return new Success();
|
||||
// not implemented
|
||||
}
|
||||
public function completeConnectionCreation(Request $request) : Promise
|
||||
{
|
||||
return new Success();
|
||||
// not implemented
|
||||
}
|
||||
public function completeTlsNegotiation(Request $request) : Promise
|
||||
{
|
||||
return new Success();
|
||||
// not implemented
|
||||
}
|
||||
private function addTiming(string $key, Request $request) : Promise
|
||||
{
|
||||
if (!$request->hasAttribute($key)) {
|
||||
$request->setAttribute($key, getCurrentTime());
|
||||
}
|
||||
return new Success();
|
||||
}
|
||||
public function abort(Request $request, \Throwable $cause) : Promise
|
||||
{
|
||||
return new Success();
|
||||
}
|
||||
}
|
31
dependencies/amphp/http-client/src/HttpClient.php
vendored
Normal file
31
dependencies/amphp/http-client/src/HttpClient.php
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\NullCancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/**
|
||||
* Convenient HTTP client for use in applications and libraries, providing a default for the cancellation token and
|
||||
* automatically cloning the passed request, so future application requests can re-use the same object again.
|
||||
*/
|
||||
final class HttpClient implements DelegateHttpClient
|
||||
{
|
||||
private $httpClient;
|
||||
public function __construct(DelegateHttpClient $httpClient)
|
||||
{
|
||||
$this->httpClient = $httpClient;
|
||||
}
|
||||
/**
|
||||
* Request a specific resource from an HTTP server.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param CancellationToken $cancellation
|
||||
*
|
||||
* @return Promise<Response>
|
||||
*/
|
||||
public function request(Request $request, ?CancellationToken $cancellation = null) : Promise
|
||||
{
|
||||
return $this->httpClient->request(clone $request, $cancellation ?? new NullCancellationToken());
|
||||
}
|
||||
}
|
204
dependencies/amphp/http-client/src/HttpClientBuilder.php
vendored
Normal file
204
dependencies/amphp/http-client/src/HttpClientBuilder.php
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\ConnectionPool;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\UnlimitedConnectionPool;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor\DecompressResponse;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor\FollowRedirects;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor\ForbidUriUserInfo;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor\RetryRequests;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor\SetRequestHeaderIfUnset;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
/**
|
||||
* Allows building an HttpClient instance.
|
||||
*
|
||||
* The builder is the recommended way to build an HttpClient instance.
|
||||
*/
|
||||
final class HttpClientBuilder
|
||||
{
|
||||
use ForbidCloning;
|
||||
use ForbidSerialization;
|
||||
public static function buildDefault() : HttpClient
|
||||
{
|
||||
return (new self())->build();
|
||||
}
|
||||
/** @var ForbidUriUserInfo|null */
|
||||
private $forbidUriUserInfo;
|
||||
/** @var RetryRequests|null */
|
||||
private $retryInterceptor;
|
||||
/** @var FollowRedirects|null */
|
||||
private $followRedirectsInterceptor;
|
||||
/** @var SetRequestHeaderIfUnset|null */
|
||||
private $defaultUserAgentInterceptor;
|
||||
/** @var SetRequestHeaderIfUnset|null */
|
||||
private $defaultAcceptInterceptor;
|
||||
/** @var NetworkInterceptor|null */
|
||||
private $defaultCompressionHandler;
|
||||
/** @var ApplicationInterceptor[] */
|
||||
private $applicationInterceptors = [];
|
||||
/** @var NetworkInterceptor[] */
|
||||
private $networkInterceptors = [];
|
||||
/** @var ConnectionPool */
|
||||
private $pool;
|
||||
public function __construct()
|
||||
{
|
||||
$this->pool = new UnlimitedConnectionPool();
|
||||
$this->forbidUriUserInfo = new ForbidUriUserInfo();
|
||||
$this->followRedirectsInterceptor = new FollowRedirects(10);
|
||||
$this->retryInterceptor = new RetryRequests(2);
|
||||
$this->defaultAcceptInterceptor = new SetRequestHeaderIfUnset('accept', '*/*');
|
||||
$this->defaultUserAgentInterceptor = new SetRequestHeaderIfUnset('user-agent', 'amphp/http-client @ v4.x');
|
||||
$this->defaultCompressionHandler = new DecompressResponse();
|
||||
}
|
||||
public function build() : HttpClient
|
||||
{
|
||||
/** @var PooledHttpClient $client */
|
||||
$client = new PooledHttpClient($this->pool);
|
||||
foreach ($this->networkInterceptors as $interceptor) {
|
||||
$client = $client->intercept($interceptor);
|
||||
}
|
||||
if ($this->defaultAcceptInterceptor) {
|
||||
$client = $client->intercept($this->defaultAcceptInterceptor);
|
||||
}
|
||||
if ($this->defaultUserAgentInterceptor) {
|
||||
$client = $client->intercept($this->defaultUserAgentInterceptor);
|
||||
}
|
||||
if ($this->defaultCompressionHandler) {
|
||||
$client = $client->intercept($this->defaultCompressionHandler);
|
||||
}
|
||||
$applicationInterceptors = $this->applicationInterceptors;
|
||||
if ($this->followRedirectsInterceptor) {
|
||||
\array_unshift($applicationInterceptors, $this->followRedirectsInterceptor);
|
||||
}
|
||||
if ($this->forbidUriUserInfo) {
|
||||
\array_unshift($applicationInterceptors, $this->forbidUriUserInfo);
|
||||
}
|
||||
if ($this->retryInterceptor) {
|
||||
$applicationInterceptors[] = $this->retryInterceptor;
|
||||
}
|
||||
foreach (\array_reverse($applicationInterceptors) as $applicationInterceptor) {
|
||||
$client = new InterceptedHttpClient($client, $applicationInterceptor);
|
||||
}
|
||||
return new HttpClient($client);
|
||||
}
|
||||
/**
|
||||
* @param ConnectionPool $pool Connection pool to use.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function usingPool(ConnectionPool $pool) : self
|
||||
{
|
||||
$builder = clone $this;
|
||||
$builder->pool = $pool;
|
||||
return $builder;
|
||||
}
|
||||
/**
|
||||
* @param ApplicationInterceptor $interceptor This interceptor gets added to the interceptor queue, so interceptors
|
||||
* are executed in the order given to this method.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function intercept(ApplicationInterceptor $interceptor) : self
|
||||
{
|
||||
if ($this->followRedirectsInterceptor !== null && $interceptor instanceof FollowRedirects) {
|
||||
throw new \Error('Disable automatic redirect following or use HttpClientBuilder::followRedirects() to customize redirects');
|
||||
}
|
||||
if ($this->retryInterceptor !== null && $interceptor instanceof RetryRequests) {
|
||||
throw new \Error('Disable automatic retries or use HttpClientBuilder::retry() to customize retries');
|
||||
}
|
||||
$builder = clone $this;
|
||||
$builder->applicationInterceptors[] = $interceptor;
|
||||
return $builder;
|
||||
}
|
||||
/**
|
||||
* @param NetworkInterceptor $interceptor This interceptor gets added to the interceptor queue, so interceptors
|
||||
* are executed in the order given to this method.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function interceptNetwork(NetworkInterceptor $interceptor) : self
|
||||
{
|
||||
$builder = clone $this;
|
||||
$builder->networkInterceptors[] = $interceptor;
|
||||
return $builder;
|
||||
}
|
||||
/**
|
||||
* @param int $retryLimit Maximum number of times a request may be retried. Only certain requests will be retried
|
||||
* automatically (GET, HEAD, PUT, and DELETE requests are automatically retried, or any
|
||||
* request that was indicated as unprocessed by the connection).
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function retry(int $retryLimit) : self
|
||||
{
|
||||
$builder = clone $this;
|
||||
if ($retryLimit <= 0) {
|
||||
$builder->retryInterceptor = null;
|
||||
} else {
|
||||
$builder->retryInterceptor = new RetryRequests($retryLimit);
|
||||
}
|
||||
return $builder;
|
||||
}
|
||||
/**
|
||||
* @param int $limit Maximum number of redirects to follow. The client will automatically request the URI supplied
|
||||
* by a redirect response (3xx status codes) and returns that response instead.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function followRedirects(int $limit = 10) : self
|
||||
{
|
||||
$builder = clone $this;
|
||||
if ($limit <= 0) {
|
||||
$builder->followRedirectsInterceptor = null;
|
||||
} else {
|
||||
$builder->followRedirectsInterceptor = new FollowRedirects($limit);
|
||||
}
|
||||
return $builder;
|
||||
}
|
||||
/**
|
||||
* Removes the default restriction of user:password in request URIs.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function allowDeprecatedUriUserInfo() : self
|
||||
{
|
||||
$builder = clone $this;
|
||||
$builder->forbidUriUserInfo = null;
|
||||
return $builder;
|
||||
}
|
||||
/**
|
||||
* Doesn't automatically set an 'accept' header.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function skipDefaultAcceptHeader() : self
|
||||
{
|
||||
$builder = clone $this;
|
||||
$builder->defaultAcceptInterceptor = null;
|
||||
return $builder;
|
||||
}
|
||||
/**
|
||||
* Doesn't automatically set a 'user-agent' header.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function skipDefaultUserAgent() : self
|
||||
{
|
||||
$builder = clone $this;
|
||||
$builder->defaultUserAgentInterceptor = null;
|
||||
return $builder;
|
||||
}
|
||||
/**
|
||||
* Doesn't automatically set an 'accept-encoding' header and decompress the response.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function skipAutomaticCompression() : self
|
||||
{
|
||||
$builder = clone $this;
|
||||
$builder->defaultCompressionHandler = null;
|
||||
return $builder;
|
||||
}
|
||||
}
|
7
dependencies/amphp/http-client/src/HttpException.php
vendored
Normal file
7
dependencies/amphp/http-client/src/HttpException.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
class HttpException extends \Exception
|
||||
{
|
||||
}
|
32
dependencies/amphp/http-client/src/InterceptedHttpClient.php
vendored
Normal file
32
dependencies/amphp/http-client/src/InterceptedHttpClient.php
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
final class InterceptedHttpClient implements DelegateHttpClient
|
||||
{
|
||||
use ForbidCloning;
|
||||
use ForbidSerialization;
|
||||
/** @var DelegateHttpClient */
|
||||
private $httpClient;
|
||||
/** @var ApplicationInterceptor */
|
||||
private $interceptor;
|
||||
public function __construct(DelegateHttpClient $httpClient, ApplicationInterceptor $interceptor)
|
||||
{
|
||||
$this->httpClient = $httpClient;
|
||||
$this->interceptor = $interceptor;
|
||||
}
|
||||
public function request(Request $request, CancellationToken $cancellation) : Promise
|
||||
{
|
||||
return call(function () use($request, $cancellation) {
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->startRequest($request));
|
||||
}
|
||||
return $this->interceptor->request($request, $cancellation, $this->httpClient);
|
||||
});
|
||||
}
|
||||
}
|
15
dependencies/amphp/http-client/src/Interceptor/AddRequestHeader.php
vendored
Normal file
15
dependencies/amphp/http-client/src/Interceptor/AddRequestHeader.php
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
final class AddRequestHeader extends ModifyRequest
|
||||
{
|
||||
public function __construct(string $headerName, string ...$headerValues)
|
||||
{
|
||||
parent::__construct(static function (Request $request) use($headerName, $headerValues) {
|
||||
$request->addHeader($headerName, $headerValues);
|
||||
return $request;
|
||||
});
|
||||
}
|
||||
}
|
15
dependencies/amphp/http-client/src/Interceptor/AddResponseHeader.php
vendored
Normal file
15
dependencies/amphp/http-client/src/Interceptor/AddResponseHeader.php
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
final class AddResponseHeader extends ModifyResponse
|
||||
{
|
||||
public function __construct(string $headerName, string ...$headerValues)
|
||||
{
|
||||
parent::__construct(static function (Response $response) use($headerName, $headerValues) {
|
||||
$response->addHeader($headerName, $headerValues);
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
}
|
76
dependencies/amphp/http-client/src/Interceptor/DecompressResponse.php
vendored
Normal file
76
dependencies/amphp/http-client/src/Interceptor/DecompressResponse.php
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\ZlibInputStream;
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\Stream;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\SizeLimitingInputStream;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\NetworkInterceptor;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
final class DecompressResponse implements NetworkInterceptor
|
||||
{
|
||||
use ForbidCloning;
|
||||
use ForbidSerialization;
|
||||
/** @var bool */
|
||||
private $hasZlib;
|
||||
public function __construct()
|
||||
{
|
||||
$this->hasZlib = \extension_loaded('zlib');
|
||||
}
|
||||
public function requestViaNetwork(Request $request, CancellationToken $cancellation, Stream $stream) : Promise
|
||||
{
|
||||
// If a header is manually set, we won't interfere
|
||||
if ($request->hasHeader('accept-encoding')) {
|
||||
return $stream->request($request, $cancellation);
|
||||
}
|
||||
return call(function () use($request, $cancellation, $stream) {
|
||||
$this->addAcceptEncodingHeader($request);
|
||||
$request->interceptPush(function (Response $response) {
|
||||
return $this->decompressResponse($response);
|
||||
});
|
||||
return $this->decompressResponse((yield $stream->request($request, $cancellation)));
|
||||
});
|
||||
}
|
||||
private function addAcceptEncodingHeader(Request $request) : void
|
||||
{
|
||||
if ($this->hasZlib) {
|
||||
$request->setHeader('Accept-Encoding', 'gzip, deflate, identity');
|
||||
}
|
||||
}
|
||||
private function decompressResponse(Response $response) : Response
|
||||
{
|
||||
if ($encoding = $this->determineCompressionEncoding($response)) {
|
||||
$sizeLimit = $response->getRequest()->getBodySizeLimit();
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$decompressedBody = new ZlibInputStream($response->getBody(), $encoding);
|
||||
$response->setBody(new SizeLimitingInputStream($decompressedBody, $sizeLimit));
|
||||
$response->removeHeader('content-encoding');
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
private function determineCompressionEncoding(Response $response) : int
|
||||
{
|
||||
if (!$this->hasZlib) {
|
||||
return 0;
|
||||
}
|
||||
if (!$response->hasHeader("content-encoding")) {
|
||||
return 0;
|
||||
}
|
||||
$contentEncoding = $response->getHeader("content-encoding");
|
||||
\assert($contentEncoding !== null);
|
||||
$contentEncodingHeader = \trim($contentEncoding);
|
||||
if (\strcasecmp($contentEncodingHeader, 'gzip') === 0) {
|
||||
return \ZLIB_ENCODING_GZIP;
|
||||
}
|
||||
if (\strcasecmp($contentEncodingHeader, 'deflate') === 0) {
|
||||
return \ZLIB_ENCODING_DEFLATE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
245
dependencies/amphp/http-client/src/Interceptor/FollowRedirects.php
vendored
Normal file
245
dependencies/amphp/http-client/src/Interceptor/FollowRedirects.php
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\StreamException;
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\ApplicationInterceptor;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\DelegateHttpClient;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\HttpException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\League\Uri;
|
||||
use WP_Ultimo\Dependencies\Psr\Http\Message\UriInterface as PsrUri;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
final class FollowRedirects implements ApplicationInterceptor
|
||||
{
|
||||
use ForbidCloning;
|
||||
use ForbidSerialization;
|
||||
/**
|
||||
* Resolves the given path in $locationUri using $baseUri as a base URI. For example, a base URI of
|
||||
* http://example.com/example/path and a location path of 'to/resolve' will return a URI of
|
||||
* http://example.com/example/to/resolve.
|
||||
*
|
||||
* @param PsrUri $baseUri
|
||||
* @param PsrUri $locationUri
|
||||
*
|
||||
* @return PsrUri
|
||||
*/
|
||||
public static function resolve(PsrUri $baseUri, PsrUri $locationUri) : PsrUri
|
||||
{
|
||||
if ((string) $locationUri === '') {
|
||||
return $baseUri;
|
||||
}
|
||||
if ($locationUri->getScheme() !== '' || $locationUri->getHost() !== '') {
|
||||
$resultUri = $locationUri->withPath(self::removeDotSegments($locationUri->getPath()));
|
||||
if ($locationUri->getScheme() === '') {
|
||||
$resultUri = $resultUri->withScheme($baseUri->getScheme());
|
||||
}
|
||||
return $resultUri;
|
||||
}
|
||||
$baseUri = $baseUri->withQuery($locationUri->getQuery());
|
||||
$baseUri = $baseUri->withFragment($locationUri->getFragment());
|
||||
if ($locationUri->getPath() !== '' && \substr($locationUri->getPath(), 0, 1) === "/") {
|
||||
$baseUri = $baseUri->withPath(self::removeDotSegments($locationUri->getPath()));
|
||||
} else {
|
||||
$baseUri = $baseUri->withPath(self::mergePaths($baseUri->getPath(), $locationUri->getPath()));
|
||||
}
|
||||
return $baseUri;
|
||||
}
|
||||
/**
|
||||
* @param string $input
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @link http://www.apps.ietf.org/rfc/rfc3986.html#sec-5.2.4
|
||||
*/
|
||||
private static function removeDotSegments(string $input) : string
|
||||
{
|
||||
$output = '';
|
||||
$patternA = ',^(\\.\\.?/),';
|
||||
$patternB1 = ',^(/\\./),';
|
||||
$patternB2 = ',^(/\\.)$,';
|
||||
$patternC = ',^(/\\.\\./|/\\.\\.),';
|
||||
// $patternD = ',^(\.\.?)$,';
|
||||
$patternE = ',(/*[^/]*),';
|
||||
while ($input !== '') {
|
||||
if (\preg_match($patternA, $input)) {
|
||||
$input = \preg_replace($patternA, '', $input);
|
||||
} elseif (\preg_match($patternB1, $input, $match) || \preg_match($patternB2, $input, $match)) {
|
||||
$input = \preg_replace(",^" . $match[1] . ",", '/', $input);
|
||||
} elseif (\preg_match($patternC, $input, $match)) {
|
||||
$input = \preg_replace(',^' . \preg_quote($match[1], ',') . ',', '/', $input);
|
||||
$output = \preg_replace(',/([^/]+)$,', '', $output);
|
||||
} elseif ($input === '.' || $input === '..') {
|
||||
// pattern D
|
||||
$input = '';
|
||||
} elseif (\preg_match($patternE, $input, $match)) {
|
||||
$initialSegment = $match[1];
|
||||
$input = \preg_replace(',^' . \preg_quote($initialSegment, ',') . ',', '', $input, 1);
|
||||
$output .= $initialSegment;
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
/**
|
||||
* @param string $basePath
|
||||
* @param string $pathToMerge
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc3986#section-5.2.3
|
||||
*/
|
||||
private static function mergePaths(string $basePath, string $pathToMerge) : string
|
||||
{
|
||||
if ($pathToMerge === '') {
|
||||
return self::removeDotSegments($basePath);
|
||||
}
|
||||
if ($basePath === '') {
|
||||
return self::removeDotSegments('/' . $pathToMerge);
|
||||
}
|
||||
$parts = \explode('/', $basePath);
|
||||
\array_pop($parts);
|
||||
$parts[] = $pathToMerge;
|
||||
return self::removeDotSegments(\implode('/', $parts));
|
||||
}
|
||||
/** @var int */
|
||||
private $maxRedirects;
|
||||
/** @var bool */
|
||||
private $autoReferrer;
|
||||
public function __construct(int $limit, bool $autoReferrer = \true)
|
||||
{
|
||||
if ($limit < 1) {
|
||||
/** @noinspection PhpUndefinedClassInspection */
|
||||
throw new \Error("Invalid redirection limit: " . $limit);
|
||||
}
|
||||
$this->maxRedirects = $limit;
|
||||
$this->autoReferrer = $autoReferrer;
|
||||
}
|
||||
public function request(Request $request, CancellationToken $cancellation, DelegateHttpClient $httpClient) : Promise
|
||||
{
|
||||
// Don't follow redirects on pushes, just store the redirect in cache (if an interceptor is configured)
|
||||
return call(function () use($request, $cancellation, $httpClient) {
|
||||
/** @var Response $response */
|
||||
$response = (yield $httpClient->request(clone $request, $cancellation));
|
||||
$response = (yield from $this->followRedirects($request, $response, $httpClient, $cancellation));
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
private function followRedirects(Request $request, Response $response, DelegateHttpClient $client, CancellationToken $cancellationToken) : \Generator
|
||||
{
|
||||
$previousResponse = null;
|
||||
$maxRedirects = $this->maxRedirects;
|
||||
$requestNr = 2;
|
||||
do {
|
||||
$request = (yield from $this->createRedirectRequest($request, $response));
|
||||
if ($request === null) {
|
||||
return $response;
|
||||
}
|
||||
/** @var Response $redirectResponse */
|
||||
$redirectResponse = (yield $client->request(clone $request, $cancellationToken));
|
||||
$redirectResponse->setPreviousResponse($response);
|
||||
$response = $redirectResponse;
|
||||
} while (++$requestNr <= $maxRedirects + 1);
|
||||
if ($this->getRedirectUri($response) !== null) {
|
||||
throw new TooManyRedirectsException($response);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
private function createRedirectRequest(Request $originalRequest, Response $response) : \Generator
|
||||
{
|
||||
$redirectUri = $this->getRedirectUri($response);
|
||||
if ($redirectUri === null) {
|
||||
return null;
|
||||
}
|
||||
$originalUri = $response->getRequest()->getUri();
|
||||
$isSameHost = $redirectUri->getAuthority() === $originalUri->getAuthority();
|
||||
$request = clone $originalRequest;
|
||||
$request->setMethod('GET');
|
||||
$request->setUri($redirectUri);
|
||||
$request->removeHeader('transfer-encoding');
|
||||
$request->removeHeader('content-length');
|
||||
$request->removeHeader('content-type');
|
||||
$request->removeAttributes();
|
||||
$request->setBody(null);
|
||||
if (!$isSameHost) {
|
||||
// Remove for security reasons, any interceptor headers will be added again,
|
||||
// but application headers will be discarded.
|
||||
foreach ($request->getRawHeaders() as [$field]) {
|
||||
$request->removeHeader($field);
|
||||
}
|
||||
}
|
||||
if ($this->autoReferrer) {
|
||||
$this->assignRedirectRefererHeader($request, $originalUri, $redirectUri);
|
||||
}
|
||||
yield from $this->discardResponseBody($response);
|
||||
return $request;
|
||||
}
|
||||
/**
|
||||
* Clients must not add a Referer header when leaving an unencrypted resource and redirecting to an encrypted
|
||||
* resource.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param PsrUri $referrerUri
|
||||
* @param PsrUri $followUri
|
||||
*
|
||||
* @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3
|
||||
*/
|
||||
private function assignRedirectRefererHeader(Request $request, PsrUri $referrerUri, PsrUri $followUri) : void
|
||||
{
|
||||
$referrerIsEncrypted = $referrerUri->getScheme() === 'https';
|
||||
$destinationIsEncrypted = $followUri->getScheme() === 'https';
|
||||
if (!$referrerIsEncrypted || $destinationIsEncrypted) {
|
||||
$request->setHeader('Referer', (string) $referrerUri->withUserInfo('')->withFragment(''));
|
||||
} else {
|
||||
$request->removeHeader('Referer');
|
||||
}
|
||||
}
|
||||
private function getRedirectUri(Response $response) : ?PsrUri
|
||||
{
|
||||
if (\count($response->getHeaderArray('location')) !== 1) {
|
||||
return null;
|
||||
}
|
||||
$status = $response->getStatus();
|
||||
$request = $response->getRequest();
|
||||
$method = $request->getMethod();
|
||||
if ($method !== 'GET' && \in_array($status, [307, 308], \true)) {
|
||||
return null;
|
||||
}
|
||||
// We don't automatically follow:
|
||||
// - 300 (Multiple Choices)
|
||||
// - 304 (Not Modified)
|
||||
// - 305 (Use Proxy)
|
||||
if (!\in_array($status, [301, 302, 303, 307, 308], \true)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
$location = $response->getHeader('location');
|
||||
\assert($location !== null);
|
||||
$locationUri = Uri\Http::createFromString($location);
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
return self::resolve($request->getUri(), $locationUri);
|
||||
}
|
||||
private function discardResponseBody(Response $response) : \Generator
|
||||
{
|
||||
// Discard response body of redirect responses
|
||||
$body = $response->getBody();
|
||||
try {
|
||||
/** @noinspection PhpStatementHasEmptyBodyInspection */
|
||||
/** @noinspection LoopWhichDoesNotLoopInspection */
|
||||
/** @noinspection MissingOrEmptyGroupStatementInspection */
|
||||
while (null !== (yield $body->read())) {
|
||||
// discard
|
||||
}
|
||||
} catch (HttpException|StreamException $e) {
|
||||
// ignore streaming errors on previous responses
|
||||
} finally {
|
||||
unset($body);
|
||||
}
|
||||
}
|
||||
}
|
17
dependencies/amphp/http-client/src/Interceptor/ForbidUriUserInfo.php
vendored
Normal file
17
dependencies/amphp/http-client/src/Interceptor/ForbidUriUserInfo.php
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\InvalidRequestException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
final class ForbidUriUserInfo extends ModifyRequest
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(static function (Request $request) {
|
||||
if ($request->getUri()->getUserInfo() !== '') {
|
||||
throw new InvalidRequestException($request, 'The user information (username:password) component of URIs has been deprecated ' . '(see https://tools.ietf.org/html/rfc3986#section-3.2.1 and https://tools.ietf.org/html/rfc7230#section-2.7.1); ' . 'Instead, set an "Authorization" header containing "Basic " . \\base64_encode("username:password"). ' . 'If you used HttpClientBuilder, you can use HttpClientBuilder::allowDeprecatedUriUserInfo() to disable this protection. ' . 'Doing so is strongly discouraged and you need to be aware of any interceptor using UriInterface::__toString(), which might expose the password in headers or logs.');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
148
dependencies/amphp/http-client/src/Interceptor/LogHttpArchive.php
vendored
Normal file
148
dependencies/amphp/http-client/src/Interceptor/LogHttpArchive.php
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\File;
|
||||
use WP_Ultimo\Dependencies\Amp\File\Driver;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\ApplicationInterceptor;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\DelegateHttpClient;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\EventListener;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\EventListener\RecordHarAttributes;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\HttpException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\HarAttributes;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Message;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Sync\LocalMutex;
|
||||
use WP_Ultimo\Dependencies\Amp\Sync\Lock;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
use function WP_Ultimo\Dependencies\Amp\Promise\rethrow;
|
||||
final class LogHttpArchive implements ApplicationInterceptor
|
||||
{
|
||||
use ForbidCloning;
|
||||
use ForbidSerialization;
|
||||
private static function getTime(Request $request, string $start, string ...$ends) : int
|
||||
{
|
||||
if (!$request->hasAttribute($start)) {
|
||||
return -1;
|
||||
}
|
||||
foreach ($ends as $end) {
|
||||
if ($request->hasAttribute($end)) {
|
||||
return $request->getAttribute($end) - $request->getAttribute($start);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
private static function formatHeaders(Message $message) : array
|
||||
{
|
||||
$headers = [];
|
||||
foreach ($message->getHeaders() as $field => $values) {
|
||||
foreach ($values as $value) {
|
||||
$headers[] = ['name' => $field, 'value' => $value];
|
||||
}
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
private static function formatEntry(Response $response) : array
|
||||
{
|
||||
$request = $response->getRequest();
|
||||
$data = ['startedDateTime' => $request->getAttribute(HarAttributes::STARTED_DATE_TIME)->format(\DateTimeInterface::RFC3339_EXTENDED), 'time' => self::getTime($request, HarAttributes::TIME_START, HarAttributes::TIME_COMPLETE), 'request' => ['method' => $request->getMethod(), 'url' => (string) $request->getUri()->withUserInfo(''), 'httpVersion' => 'http/' . $request->getProtocolVersions()[0], 'headers' => self::formatHeaders($request), 'queryString' => [], 'cookies' => [], 'headersSize' => -1, 'bodySize' => -1], 'response' => ['status' => $response->getStatus(), 'statusText' => $response->getReason(), 'httpVersion' => 'http/' . $response->getProtocolVersion(), 'headers' => self::formatHeaders($response), 'cookies' => [], 'redirectURL' => $response->getHeader('location') ?? '', 'headersSize' => -1, 'bodySize' => -1, 'content' => ['size' => (int) ($response->getHeader('content-length') ?? '-1'), 'mimeType' => $response->getHeader('content-type') ?? '']], 'cache' => [], 'timings' => ['blocked' => self::getTime($request, HarAttributes::TIME_START, HarAttributes::TIME_CONNECT, HarAttributes::TIME_SEND), 'dns' => -1, 'connect' => self::getTime($request, HarAttributes::TIME_CONNECT, HarAttributes::TIME_SEND), 'ssl' => self::getTime($request, HarAttributes::TIME_SSL, HarAttributes::TIME_SEND), 'send' => self::getTime($request, HarAttributes::TIME_SEND, HarAttributes::TIME_WAIT), 'wait' => self::getTime($request, HarAttributes::TIME_WAIT, HarAttributes::TIME_RECEIVE), 'receive' => self::getTime($request, HarAttributes::TIME_RECEIVE, HarAttributes::TIME_COMPLETE)]];
|
||||
if ($request->hasAttribute(HarAttributes::SERVER_IP_ADDRESS)) {
|
||||
$data['serverIPAddress'] = $request->getAttribute(HarAttributes::SERVER_IP_ADDRESS);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
/** @var LocalMutex */
|
||||
private $fileMutex;
|
||||
/** @var File\File|null */
|
||||
private $fileHandle;
|
||||
/** @var string */
|
||||
private $filePath;
|
||||
/** @var \Throwable|null */
|
||||
private $error;
|
||||
/** @var EventListener */
|
||||
private $eventListener;
|
||||
public function __construct(string $filePath)
|
||||
{
|
||||
$this->filePath = $filePath;
|
||||
$this->fileMutex = new LocalMutex();
|
||||
$this->eventListener = new RecordHarAttributes();
|
||||
if (!\interface_exists(Driver::class)) {
|
||||
throw new \Error(__CLASS__ . ' requires amphp/file to be installed');
|
||||
}
|
||||
}
|
||||
public function request(Request $request, CancellationToken $cancellation, DelegateHttpClient $httpClient) : Promise
|
||||
{
|
||||
return call(function () use($request, $cancellation, $httpClient) {
|
||||
if ($this->error) {
|
||||
throw $this->error;
|
||||
}
|
||||
$this->ensureEventListenerIsRegistered($request);
|
||||
/** @var Response $response */
|
||||
$response = (yield $httpClient->request($request, $cancellation));
|
||||
rethrow($this->writeLog($response));
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
public function reset() : Promise
|
||||
{
|
||||
return $this->rotate($this->filePath);
|
||||
}
|
||||
public function rotate(string $filePath) : Promise
|
||||
{
|
||||
return call(function () use($filePath) {
|
||||
/** @var Lock $lock */
|
||||
$lock = (yield $this->fileMutex->acquire());
|
||||
// Will automatically reopen and reset the file
|
||||
$this->fileHandle = null;
|
||||
$this->filePath = $filePath;
|
||||
$this->error = null;
|
||||
$lock->release();
|
||||
});
|
||||
}
|
||||
private function writeLog(Response $response) : Promise
|
||||
{
|
||||
return call(function () use($response) {
|
||||
try {
|
||||
(yield $response->getTrailers());
|
||||
} catch (\Throwable $e) {
|
||||
// ignore, still log the remaining response times
|
||||
}
|
||||
try {
|
||||
/** @var Lock $lock */
|
||||
$lock = (yield $this->fileMutex->acquire());
|
||||
$firstEntry = $this->fileHandle === null;
|
||||
if ($firstEntry) {
|
||||
$this->fileHandle = (yield \function_exists('WP_Ultimo\\Dependencies\\Amp\\File\\openFile') ? File\openFile($this->filePath, 'w') : File\open($this->filePath, 'w'));
|
||||
$header = '{"log":{"version":"1.2","creator":{"name":"amphp/http-client","version":"4.x"},"pages":[],"entries":[';
|
||||
(yield $this->fileHandle->write($header));
|
||||
} else {
|
||||
\assert($this->fileHandle !== null);
|
||||
(yield $this->fileHandle->seek(-3, \SEEK_CUR));
|
||||
}
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
$json = \json_encode(self::formatEntry($response));
|
||||
(yield $this->fileHandle->write(($firstEntry ? '' : ',') . $json . ']}}'));
|
||||
$lock->release();
|
||||
} catch (HttpException $e) {
|
||||
$this->error = $e;
|
||||
} catch (\Throwable $e) {
|
||||
$this->error = new HttpException('Writing HTTP archive log failed', 0, $e);
|
||||
}
|
||||
});
|
||||
}
|
||||
private function ensureEventListenerIsRegistered(Request $request) : void
|
||||
{
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
if ($eventListener instanceof RecordHarAttributes) {
|
||||
return;
|
||||
// user added it manually
|
||||
}
|
||||
}
|
||||
$request->addEventListener($this->eventListener);
|
||||
}
|
||||
}
|
80
dependencies/amphp/http-client/src/Interceptor/MatchOrigin.php
vendored
Normal file
80
dependencies/amphp/http-client/src/Interceptor/MatchOrigin.php
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\ApplicationInterceptor;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\DelegateHttpClient;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\HttpException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\League\Uri\Http;
|
||||
use WP_Ultimo\Dependencies\Psr\Http\Message\UriInterface;
|
||||
final class MatchOrigin implements ApplicationInterceptor
|
||||
{
|
||||
use ForbidCloning;
|
||||
use ForbidSerialization;
|
||||
/** @var ApplicationInterceptor[] */
|
||||
private $originMap = [];
|
||||
/** @var ApplicationInterceptor|null */
|
||||
private $default;
|
||||
/**
|
||||
* @param ApplicationInterceptor[] $originMap
|
||||
* @param ApplicationInterceptor $default
|
||||
*
|
||||
* @throws HttpException
|
||||
*/
|
||||
public function __construct(array $originMap, ?ApplicationInterceptor $default = null)
|
||||
{
|
||||
foreach ($originMap as $origin => $interceptor) {
|
||||
if (!$interceptor instanceof ApplicationInterceptor) {
|
||||
$type = \is_object($interceptor) ? \get_class($interceptor) : \gettype($interceptor);
|
||||
throw new HttpException('Origin map must be a map from origin to ApplicationInterceptor, got ' . $type);
|
||||
}
|
||||
$this->originMap[$this->checkOrigin($origin)] = $interceptor;
|
||||
}
|
||||
$this->default = $default;
|
||||
}
|
||||
public function request(Request $request, CancellationToken $cancellation, DelegateHttpClient $httpClient) : Promise
|
||||
{
|
||||
$interceptor = $this->originMap[$this->normalizeOrigin($request->getUri())] ?? $this->default;
|
||||
if (!$interceptor) {
|
||||
return $httpClient->request($request, $cancellation);
|
||||
}
|
||||
return $interceptor->request($request, $cancellation, $httpClient);
|
||||
}
|
||||
private function checkOrigin(string $origin) : string
|
||||
{
|
||||
try {
|
||||
$originUri = Http::createFromString($origin);
|
||||
} catch (\Exception $e) {
|
||||
throw new HttpException("Invalid origin provided: parsing failed: " . $origin);
|
||||
}
|
||||
if (!\in_array($originUri->getScheme(), ['http', 'https'], \true)) {
|
||||
throw new HttpException('Invalid origin with unsupported scheme: ' . $origin);
|
||||
}
|
||||
if ($originUri->getHost() === '') {
|
||||
throw new HttpException('Invalid origin without host: ' . $origin);
|
||||
}
|
||||
if ($originUri->getUserInfo() !== '') {
|
||||
throw new HttpException('Invalid origin with user info, which must not be present: ' . $origin);
|
||||
}
|
||||
if (!\in_array($originUri->getPath(), ['', '/'], \true)) {
|
||||
throw new HttpException('Invalid origin with path, which must not be present: ' . $origin);
|
||||
}
|
||||
if ($originUri->getQuery() !== '') {
|
||||
throw new HttpException('Invalid origin with query, which must not be present: ' . $origin);
|
||||
}
|
||||
if ($originUri->getFragment() !== '') {
|
||||
throw new HttpException('Invalid origin with fragment, which must not be present: ' . $origin);
|
||||
}
|
||||
return $this->normalizeOrigin($originUri);
|
||||
}
|
||||
private function normalizeOrigin(UriInterface $uri) : string
|
||||
{
|
||||
$defaultPort = $uri->getScheme() === 'https' ? 443 : 80;
|
||||
return $uri->getScheme() . '://' . $uri->getHost() . ':' . ($uri->getPort() ?? $defaultPort);
|
||||
}
|
||||
}
|
52
dependencies/amphp/http-client/src/Interceptor/ModifyRequest.php
vendored
Normal file
52
dependencies/amphp/http-client/src/Interceptor/ModifyRequest.php
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\ApplicationInterceptor;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\Stream;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\DelegateHttpClient;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\NetworkInterceptor;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
class ModifyRequest implements NetworkInterceptor, ApplicationInterceptor
|
||||
{
|
||||
use ForbidCloning;
|
||||
use ForbidSerialization;
|
||||
/** @var callable(Request):(\Generator<mixed, mixed, mixed, Promise<Request|null>|Request|null>|Promise<Request|null>|Request|null) */
|
||||
private $mapper;
|
||||
/**
|
||||
* @psalm-param callable(Request):(\Generator<mixed, mixed, mixed, Promise<Request|null>|Request|null>|Promise<Request|null>|Request|null) $mapper
|
||||
*/
|
||||
public function __construct(callable $mapper)
|
||||
{
|
||||
$this->mapper = $mapper;
|
||||
}
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param CancellationToken $cancellation
|
||||
* @param Stream $stream
|
||||
*
|
||||
* @return Promise<Response>
|
||||
*/
|
||||
public final function requestViaNetwork(Request $request, CancellationToken $cancellation, Stream $stream) : Promise
|
||||
{
|
||||
return call(function () use($request, $cancellation, $stream) {
|
||||
$mappedRequest = (yield call($this->mapper, $request));
|
||||
\assert($mappedRequest instanceof Request || $mappedRequest === null);
|
||||
return (yield $stream->request($mappedRequest ?? $request, $cancellation));
|
||||
});
|
||||
}
|
||||
public function request(Request $request, CancellationToken $cancellation, DelegateHttpClient $httpClient) : Promise
|
||||
{
|
||||
return call(function () use($request, $cancellation, $httpClient) {
|
||||
$mappedRequest = (yield call($this->mapper, $request));
|
||||
\assert($mappedRequest instanceof Request || $mappedRequest === null);
|
||||
return $httpClient->request($mappedRequest ?? $request, $cancellation);
|
||||
});
|
||||
}
|
||||
}
|
48
dependencies/amphp/http-client/src/Interceptor/ModifyResponse.php
vendored
Normal file
48
dependencies/amphp/http-client/src/Interceptor/ModifyResponse.php
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\ApplicationInterceptor;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\Stream;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\DelegateHttpClient;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\NetworkInterceptor;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
class ModifyResponse implements NetworkInterceptor, ApplicationInterceptor
|
||||
{
|
||||
use ForbidCloning;
|
||||
use ForbidSerialization;
|
||||
/** @var callable(Response):(\Generator<mixed, mixed, mixed, Response|null>|Promise<Response>|Response|null) */
|
||||
private $mapper;
|
||||
/**
|
||||
* @psalm-param callable(Response):(\Generator<mixed, mixed, mixed, Response|null>|Promise<Response>|Response|null) $mapper
|
||||
*/
|
||||
public function __construct(callable $mapper)
|
||||
{
|
||||
$this->mapper = $mapper;
|
||||
}
|
||||
public final function requestViaNetwork(Request $request, CancellationToken $cancellation, Stream $stream) : Promise
|
||||
{
|
||||
return call(function () use($request, $cancellation, $stream) {
|
||||
$response = (yield $stream->request($request, $cancellation));
|
||||
$mappedResponse = (yield call($this->mapper, $response));
|
||||
\assert($mappedResponse instanceof Response || $mappedResponse === null);
|
||||
return $mappedResponse ?? $response;
|
||||
});
|
||||
}
|
||||
public function request(Request $request, CancellationToken $cancellation, DelegateHttpClient $httpClient) : Promise
|
||||
{
|
||||
return call(function () use($request, $cancellation, $httpClient) {
|
||||
$request->interceptPush($this->mapper);
|
||||
$response = (yield $httpClient->request($request, $cancellation));
|
||||
$mappedResponse = (yield call($this->mapper, $response));
|
||||
\assert($mappedResponse instanceof Response || $mappedResponse === null);
|
||||
return $mappedResponse ?? $response;
|
||||
});
|
||||
}
|
||||
}
|
15
dependencies/amphp/http-client/src/Interceptor/RemoveRequestHeader.php
vendored
Normal file
15
dependencies/amphp/http-client/src/Interceptor/RemoveRequestHeader.php
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
final class RemoveRequestHeader extends ModifyRequest
|
||||
{
|
||||
public function __construct(string $headerName)
|
||||
{
|
||||
parent::__construct(static function (Request $request) use($headerName) {
|
||||
$request->removeHeader($headerName);
|
||||
return $request;
|
||||
});
|
||||
}
|
||||
}
|
15
dependencies/amphp/http-client/src/Interceptor/RemoveResponseHeader.php
vendored
Normal file
15
dependencies/amphp/http-client/src/Interceptor/RemoveResponseHeader.php
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
final class RemoveResponseHeader extends ModifyResponse
|
||||
{
|
||||
public function __construct(string $headerName)
|
||||
{
|
||||
parent::__construct(static function (Response $response) use($headerName) {
|
||||
$response->removeHeader($headerName);
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
}
|
45
dependencies/amphp/http-client/src/Interceptor/RetryRequests.php
vendored
Normal file
45
dependencies/amphp/http-client/src/Interceptor/RetryRequests.php
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\ApplicationInterceptor;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\Http2ConnectionException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\UnprocessedRequestException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\DelegateHttpClient;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\SocketException;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
final class RetryRequests implements ApplicationInterceptor
|
||||
{
|
||||
use ForbidCloning;
|
||||
use ForbidSerialization;
|
||||
/** @var int */
|
||||
private $retryLimit;
|
||||
public function __construct(int $retryLimit)
|
||||
{
|
||||
$this->retryLimit = $retryLimit;
|
||||
}
|
||||
public function request(Request $request, CancellationToken $cancellation, DelegateHttpClient $httpClient) : Promise
|
||||
{
|
||||
return call(function () use($request, $cancellation, $httpClient) {
|
||||
$attempt = 1;
|
||||
do {
|
||||
try {
|
||||
return (yield $httpClient->request(clone $request, $cancellation));
|
||||
} catch (UnprocessedRequestException $exception) {
|
||||
// Request was deemed retryable by connection, so carry on.
|
||||
} catch (SocketException|Http2ConnectionException $exception) {
|
||||
if (!$request->isIdempotent()) {
|
||||
throw $exception;
|
||||
}
|
||||
// Request can safely be retried.
|
||||
}
|
||||
} while ($attempt++ <= $this->retryLimit);
|
||||
throw $exception;
|
||||
});
|
||||
}
|
||||
}
|
16
dependencies/amphp/http-client/src/Interceptor/SetRequestHeader.php
vendored
Normal file
16
dependencies/amphp/http-client/src/Interceptor/SetRequestHeader.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
final class SetRequestHeader extends ModifyRequest
|
||||
{
|
||||
public function __construct(string $headerName, string $headerValue, string ...$headerValues)
|
||||
{
|
||||
\array_unshift($headerValues, $headerValue);
|
||||
parent::__construct(static function (Request $request) use($headerName, $headerValues) {
|
||||
$request->setHeader($headerName, $headerValues);
|
||||
return $request;
|
||||
});
|
||||
}
|
||||
}
|
18
dependencies/amphp/http-client/src/Interceptor/SetRequestHeaderIfUnset.php
vendored
Normal file
18
dependencies/amphp/http-client/src/Interceptor/SetRequestHeaderIfUnset.php
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
final class SetRequestHeaderIfUnset extends ModifyRequest
|
||||
{
|
||||
public function __construct(string $headerName, string $headerValue, string ...$headerValues)
|
||||
{
|
||||
\array_unshift($headerValues, $headerValue);
|
||||
parent::__construct(static function (Request $request) use($headerName, $headerValues) {
|
||||
if (!$request->hasHeader($headerName)) {
|
||||
$request->setHeader($headerName, $headerValues);
|
||||
}
|
||||
return $request;
|
||||
});
|
||||
}
|
||||
}
|
17
dependencies/amphp/http-client/src/Interceptor/SetRequestTimeout.php
vendored
Normal file
17
dependencies/amphp/http-client/src/Interceptor/SetRequestTimeout.php
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
final class SetRequestTimeout extends ModifyRequest
|
||||
{
|
||||
public function __construct(int $tcpConnectTimeout = 10000, int $tlsHandshakeTimeout = 10000, int $transferTimeout = 10000)
|
||||
{
|
||||
parent::__construct(static function (Request $request) use($tcpConnectTimeout, $tlsHandshakeTimeout, $transferTimeout) {
|
||||
$request->setTcpConnectTimeout($tcpConnectTimeout);
|
||||
$request->setTlsHandshakeTimeout($tlsHandshakeTimeout);
|
||||
$request->setTransferTimeout($transferTimeout);
|
||||
return $request;
|
||||
});
|
||||
}
|
||||
}
|
16
dependencies/amphp/http-client/src/Interceptor/SetResponseHeader.php
vendored
Normal file
16
dependencies/amphp/http-client/src/Interceptor/SetResponseHeader.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
final class SetResponseHeader extends ModifyResponse
|
||||
{
|
||||
public function __construct(string $headerName, string $headerValue, string ...$headerValues)
|
||||
{
|
||||
\array_unshift($headerValues, $headerValue);
|
||||
parent::__construct(static function (Response $response) use($headerName, $headerValues) {
|
||||
$response->setHeader($headerName, $headerValues);
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
}
|
18
dependencies/amphp/http-client/src/Interceptor/SetResponseHeaderIfUnset.php
vendored
Normal file
18
dependencies/amphp/http-client/src/Interceptor/SetResponseHeaderIfUnset.php
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
final class SetResponseHeaderIfUnset extends ModifyResponse
|
||||
{
|
||||
public function __construct(string $headerName, string $headerValue, string ...$headerValues)
|
||||
{
|
||||
\array_unshift($headerValues, $headerValue);
|
||||
parent::__construct(static function (Response $response) use($headerName, $headerValues) {
|
||||
if (!$response->hasHeader($headerName)) {
|
||||
$response->setHeader($headerName, $headerValues);
|
||||
}
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
}
|
20
dependencies/amphp/http-client/src/Interceptor/TooManyRedirectsException.php
vendored
Normal file
20
dependencies/amphp/http-client/src/Interceptor/TooManyRedirectsException.php
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Interceptor;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\HttpException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Response;
|
||||
class TooManyRedirectsException extends HttpException
|
||||
{
|
||||
/** @var Response */
|
||||
private $response;
|
||||
public function __construct(Response $response)
|
||||
{
|
||||
parent::__construct("There were too many redirects");
|
||||
$this->response = $response;
|
||||
}
|
||||
public function getResponse() : Response
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
}
|
13
dependencies/amphp/http-client/src/Internal/ForbidCloning.php
vendored
Normal file
13
dependencies/amphp/http-client/src/Internal/ForbidCloning.php
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Internal;
|
||||
|
||||
/** @internal */
|
||||
trait ForbidCloning
|
||||
{
|
||||
protected final function __clone()
|
||||
{
|
||||
// clone is automatically denied to all external calls
|
||||
// final protected instead of private to also force denial for all children
|
||||
}
|
||||
}
|
12
dependencies/amphp/http-client/src/Internal/ForbidSerialization.php
vendored
Normal file
12
dependencies/amphp/http-client/src/Internal/ForbidSerialization.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Internal;
|
||||
|
||||
/** @internal */
|
||||
trait ForbidSerialization
|
||||
{
|
||||
public final function __sleep()
|
||||
{
|
||||
throw new \Error(__CLASS__ . ' does not support serialization');
|
||||
}
|
||||
}
|
19
dependencies/amphp/http-client/src/Internal/HarAttributes.php
vendored
Normal file
19
dependencies/amphp/http-client/src/Internal/HarAttributes.php
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Internal;
|
||||
|
||||
/** @internal */
|
||||
final class HarAttributes
|
||||
{
|
||||
use ForbidCloning;
|
||||
use ForbidSerialization;
|
||||
public const STARTED_DATE_TIME = 'amp.http.client.har.startedDateTime';
|
||||
public const SERVER_IP_ADDRESS = 'amp.http.client.har.serverIPAddress';
|
||||
public const TIME_START = 'amp.http.client.har.timings.start';
|
||||
public const TIME_SSL = 'amp.http.client.har.timings.ssl';
|
||||
public const TIME_CONNECT = 'amp.http.client.har.timings.connect';
|
||||
public const TIME_SEND = 'amp.http.client.har.timings.send';
|
||||
public const TIME_WAIT = 'amp.http.client.har.timings.wait';
|
||||
public const TIME_RECEIVE = 'amp.http.client.har.timings.receive';
|
||||
public const TIME_COMPLETE = 'amp.http.client.har.timings.complete';
|
||||
}
|
40
dependencies/amphp/http-client/src/Internal/ResponseBodyStream.php
vendored
Normal file
40
dependencies/amphp/http-client/src/Internal/ResponseBodyStream.php
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Internal;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\InputStream;
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationTokenSource;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/** @internal */
|
||||
final class ResponseBodyStream implements InputStream
|
||||
{
|
||||
use ForbidSerialization;
|
||||
use ForbidCloning;
|
||||
/** @var InputStream */
|
||||
private $body;
|
||||
/** @var CancellationTokenSource */
|
||||
private $bodyCancellation;
|
||||
/** @var bool */
|
||||
private $successfulEnd = \false;
|
||||
public function __construct(InputStream $body, CancellationTokenSource $bodyCancellation)
|
||||
{
|
||||
$this->body = $body;
|
||||
$this->bodyCancellation = $bodyCancellation;
|
||||
}
|
||||
public function read() : Promise
|
||||
{
|
||||
$promise = $this->body->read();
|
||||
$promise->onResolve(function ($error, $value) {
|
||||
if ($value === null && $error === null) {
|
||||
$this->successfulEnd = \true;
|
||||
}
|
||||
});
|
||||
return $promise;
|
||||
}
|
||||
public function __destruct()
|
||||
{
|
||||
if (!$this->successfulEnd) {
|
||||
$this->bodyCancellation->cancel();
|
||||
}
|
||||
}
|
||||
}
|
46
dependencies/amphp/http-client/src/Internal/SizeLimitingInputStream.php
vendored
Normal file
46
dependencies/amphp/http-client/src/Internal/SizeLimitingInputStream.php
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Internal;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\InputStream;
|
||||
use WP_Ultimo\Dependencies\Amp\Failure;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\ParseException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Status;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/** @internal */
|
||||
final class SizeLimitingInputStream implements InputStream
|
||||
{
|
||||
use ForbidSerialization;
|
||||
use ForbidCloning;
|
||||
/** @var InputStream|null */
|
||||
private $source;
|
||||
/** @var int */
|
||||
private $bytesRead = 0;
|
||||
/** @var int */
|
||||
private $sizeLimit;
|
||||
/** @var \Throwable|null */
|
||||
private $exception;
|
||||
public function __construct(InputStream $source, int $sizeLimit)
|
||||
{
|
||||
$this->source = $source;
|
||||
$this->sizeLimit = $sizeLimit;
|
||||
}
|
||||
public function read() : Promise
|
||||
{
|
||||
if ($this->exception) {
|
||||
return new Failure($this->exception);
|
||||
}
|
||||
\assert($this->source !== null);
|
||||
$promise = $this->source->read();
|
||||
$promise->onResolve(function ($error, $value) {
|
||||
if ($value !== null) {
|
||||
$this->bytesRead += \strlen($value);
|
||||
if ($this->bytesRead > $this->sizeLimit) {
|
||||
$this->exception = new ParseException("Configured body size exceeded: {$this->bytesRead} bytes received, while the configured limit is {$this->sizeLimit} bytes", Status::PAYLOAD_TOO_LARGE);
|
||||
$this->source = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
return $promise;
|
||||
}
|
||||
}
|
26
dependencies/amphp/http-client/src/Internal/functions.php
vendored
Normal file
26
dependencies/amphp/http-client/src/Internal/functions.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client\Internal;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\InvalidRequestException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Request;
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @return string
|
||||
* @throws InvalidRequestException
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function normalizeRequestPathWithQuery(Request $request) : string
|
||||
{
|
||||
$path = $request->getUri()->getPath();
|
||||
$query = $request->getUri()->getQuery();
|
||||
if ($path === '') {
|
||||
return '/' . ($query !== '' ? '?' . $query : '');
|
||||
}
|
||||
if ($path[0] !== '/') {
|
||||
throw new InvalidRequestException($request, 'Relative path (' . $path . ') is not allowed in the request URI');
|
||||
}
|
||||
return $path . ($query !== '' ? '?' . $query : '');
|
||||
}
|
18
dependencies/amphp/http-client/src/InvalidRequestException.php
vendored
Normal file
18
dependencies/amphp/http-client/src/InvalidRequestException.php
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
final class InvalidRequestException extends HttpException
|
||||
{
|
||||
/** @var Request */
|
||||
private $request;
|
||||
public function __construct(Request $request, string $message, int $code = 0, \Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->request = $request;
|
||||
}
|
||||
public function getRequest() : Request
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
}
|
11
dependencies/amphp/http-client/src/MissingAttributeError.php
vendored
Normal file
11
dependencies/amphp/http-client/src/MissingAttributeError.php
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
final class MissingAttributeError extends \Error
|
||||
{
|
||||
public function __construct(string $message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
30
dependencies/amphp/http-client/src/NetworkInterceptor.php
vendored
Normal file
30
dependencies/amphp/http-client/src/NetworkInterceptor.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\Stream;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/**
|
||||
* Allows intercepting an HTTP request after the connection to the remote server has been established.
|
||||
*/
|
||||
interface NetworkInterceptor
|
||||
{
|
||||
/**
|
||||
* Intercepts an HTTP request after the connection to the remote server has been established.
|
||||
*
|
||||
* The implementation might modify the request and/or modify the response after the promise returned from
|
||||
* `$stream->request(...)` resolved.
|
||||
*
|
||||
* A NetworkInterceptor SHOULD NOT short-circuit and SHOULD delegate to the `$stream` passed as third argument
|
||||
* exactly once. The only exception to this is throwing an exception, e.g. because the TLS settings used are
|
||||
* unacceptable. If you need short circuits, use an {@see ApplicationInterceptor} instead.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param CancellationToken $cancellation
|
||||
* @param Stream $stream
|
||||
*
|
||||
* @return Promise<Response>
|
||||
*/
|
||||
public function requestViaNetwork(Request $request, CancellationToken $cancellation, Stream $stream) : Promise;
|
||||
}
|
16
dependencies/amphp/http-client/src/ParseException.php
vendored
Normal file
16
dependencies/amphp/http-client/src/ParseException.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
final class ParseException extends HttpException
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @param \Throwable|null $previousException
|
||||
*/
|
||||
public function __construct(string $message, int $code, \Throwable $previousException = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previousException);
|
||||
}
|
||||
}
|
60
dependencies/amphp/http-client/src/PooledHttpClient.php
vendored
Normal file
60
dependencies/amphp/http-client/src/PooledHttpClient.php
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancellationToken;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\ConnectionPool;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\InterceptedStream;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\Stream;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Connection\UnlimitedConnectionPool;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
final class PooledHttpClient implements DelegateHttpClient
|
||||
{
|
||||
use ForbidCloning;
|
||||
use ForbidSerialization;
|
||||
/** @var ConnectionPool */
|
||||
private $connectionPool;
|
||||
/** @var NetworkInterceptor[] */
|
||||
private $networkInterceptors = [];
|
||||
public function __construct(?ConnectionPool $connectionPool = null)
|
||||
{
|
||||
$this->connectionPool = $connectionPool ?? new UnlimitedConnectionPool();
|
||||
}
|
||||
public function request(Request $request, CancellationToken $cancellation) : Promise
|
||||
{
|
||||
return call(function () use($request, $cancellation) {
|
||||
foreach ($request->getEventListeners() as $eventListener) {
|
||||
(yield $eventListener->startRequest($request));
|
||||
}
|
||||
$stream = (yield $this->connectionPool->getStream($request, $cancellation));
|
||||
\assert($stream instanceof Stream);
|
||||
foreach (\array_reverse($this->networkInterceptors) as $interceptor) {
|
||||
$stream = new InterceptedStream($stream, $interceptor);
|
||||
}
|
||||
return (yield $stream->request($request, $cancellation));
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Adds a network interceptor.
|
||||
*
|
||||
* Network interceptors are only invoked if the request requires network access, i.e. there's no short-circuit by
|
||||
* an application interceptor, e.g. a cache.
|
||||
*
|
||||
* Whether the given network interceptor will be respected for currently running requests is undefined.
|
||||
*
|
||||
* Any new requests have to take the new interceptor into account.
|
||||
*
|
||||
* @param NetworkInterceptor $networkInterceptor
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function intercept(NetworkInterceptor $networkInterceptor) : self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->networkInterceptors[] = $networkInterceptor;
|
||||
return $clone;
|
||||
}
|
||||
}
|
468
dependencies/amphp/http-client/src/Request.php
vendored
Normal file
468
dependencies/amphp/http-client/src/Request.php
vendored
Normal file
@@ -0,0 +1,468 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Body\StringBody;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Message;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\League\Uri;
|
||||
use WP_Ultimo\Dependencies\Psr\Http\Message\UriInterface;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
/**
|
||||
* An HTTP request.
|
||||
*/
|
||||
final class Request extends Message
|
||||
{
|
||||
use ForbidSerialization;
|
||||
public const DEFAULT_HEADER_SIZE_LIMIT = 2 * 8192;
|
||||
public const DEFAULT_BODY_SIZE_LIMIT = 10485760;
|
||||
/**
|
||||
* @template TValue
|
||||
*
|
||||
* @param mixed $value
|
||||
* @psalm-param TValue $value
|
||||
*
|
||||
* @return mixed
|
||||
* @psalm-return TValue
|
||||
*/
|
||||
private static function clone($value)
|
||||
{
|
||||
if ($value === null || \is_scalar($value)) {
|
||||
return $value;
|
||||
}
|
||||
// force deep cloning
|
||||
return \unserialize(\serialize($value), ['allowed_classes' => \true]);
|
||||
}
|
||||
/** @var string[] */
|
||||
private $protocolVersions = ['1.1', '2'];
|
||||
/** @var string */
|
||||
private $method;
|
||||
/** @var UriInterface */
|
||||
private $uri;
|
||||
/** @var RequestBody */
|
||||
private $body;
|
||||
/** @var int */
|
||||
private $tcpConnectTimeout = 10000;
|
||||
/** @var int */
|
||||
private $tlsHandshakeTimeout = 10000;
|
||||
/** @var int */
|
||||
private $transferTimeout = 10000;
|
||||
/** @var int */
|
||||
private $inactivityTimeout = 10000;
|
||||
/** @var int */
|
||||
private $bodySizeLimit = self::DEFAULT_BODY_SIZE_LIMIT;
|
||||
/** @var int */
|
||||
private $headerSizeLimit = self::DEFAULT_HEADER_SIZE_LIMIT;
|
||||
/** @var callable|null */
|
||||
private $onPush;
|
||||
/** @var callable|null */
|
||||
private $onUpgrade;
|
||||
/** @var callable|null */
|
||||
private $onInformationalResponse;
|
||||
/** @var mixed[] */
|
||||
private $attributes = [];
|
||||
/** @var EventListener[] */
|
||||
private $eventListeners = [];
|
||||
/**
|
||||
* Request constructor.
|
||||
*
|
||||
* @param string|UriInterface $uri
|
||||
* @param string $method
|
||||
* @param string $body
|
||||
*/
|
||||
public function __construct($uri, string $method = "GET", ?string $body = null)
|
||||
{
|
||||
$this->setUri($uri);
|
||||
$this->setMethod($method);
|
||||
$this->setBody($body);
|
||||
}
|
||||
public function addEventListener(EventListener $eventListener) : void
|
||||
{
|
||||
$this->eventListeners[] = $eventListener;
|
||||
}
|
||||
/**
|
||||
* @return EventListener[]
|
||||
*/
|
||||
public function getEventListeners() : array
|
||||
{
|
||||
return $this->eventListeners;
|
||||
}
|
||||
/**
|
||||
* Retrieve the requests's acceptable HTTP protocol versions.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getProtocolVersions() : array
|
||||
{
|
||||
return $this->protocolVersions;
|
||||
}
|
||||
/**
|
||||
* Assign the requests's acceptable HTTP protocol versions.
|
||||
*
|
||||
* The HTTP client might choose any of these.
|
||||
*
|
||||
* @param string[] $versions
|
||||
*/
|
||||
public function setProtocolVersions(array $versions) : void
|
||||
{
|
||||
$versions = \array_unique($versions);
|
||||
if (empty($versions)) {
|
||||
/** @noinspection PhpUndefinedClassInspection */
|
||||
throw new \Error("Empty array of protocol versions provided, must not be empty.");
|
||||
}
|
||||
foreach ($versions as $version) {
|
||||
if (!\in_array($version, ["1.0", "1.1", "2"], \true)) {
|
||||
/** @noinspection PhpUndefinedClassInspection */
|
||||
throw new \Error("Invalid HTTP protocol version: " . $version);
|
||||
}
|
||||
}
|
||||
$this->protocolVersions = $versions;
|
||||
}
|
||||
/**
|
||||
* Retrieve the request's HTTP method verb.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMethod() : string
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
/**
|
||||
* Specify the request's HTTP method verb.
|
||||
*
|
||||
* @param string $method
|
||||
*/
|
||||
public function setMethod(string $method) : void
|
||||
{
|
||||
$this->method = $method;
|
||||
}
|
||||
/**
|
||||
* Retrieve the request's URI.
|
||||
*
|
||||
* @return UriInterface
|
||||
*/
|
||||
public function getUri() : UriInterface
|
||||
{
|
||||
return $this->uri;
|
||||
}
|
||||
/**
|
||||
* Specify the request's HTTP URI.
|
||||
*
|
||||
* @param string|UriInterface $uri
|
||||
*/
|
||||
public function setUri($uri) : void
|
||||
{
|
||||
$this->uri = $uri instanceof UriInterface ? $uri : $this->createUriFromString($uri);
|
||||
}
|
||||
/**
|
||||
* Assign a value for the specified header field by replacing any existing values for that field.
|
||||
*
|
||||
* @param string $name Header name.
|
||||
* @param string|string[] $value Header value.
|
||||
*/
|
||||
public function setHeader(string $name, $value) : void
|
||||
{
|
||||
if (($name[0] ?? ":") === ":") {
|
||||
throw new \Error("Header name cannot be empty or start with a colon (:)");
|
||||
}
|
||||
parent::setHeader($name, $value);
|
||||
}
|
||||
/**
|
||||
* Assign a value for the specified header field by adding an additional header line.
|
||||
*
|
||||
* @param string $name Header name.
|
||||
* @param string|string[] $value Header value.
|
||||
*/
|
||||
public function addHeader(string $name, $value) : void
|
||||
{
|
||||
if (($name[0] ?? ":") === ":") {
|
||||
throw new \Error("Header name cannot be empty or start with a colon (:)");
|
||||
}
|
||||
parent::addHeader($name, $value);
|
||||
}
|
||||
public function setHeaders(array $headers) : void
|
||||
{
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
parent::setHeaders($headers);
|
||||
}
|
||||
/**
|
||||
* Remove the specified header field from the message.
|
||||
*
|
||||
* @param string $name Header name.
|
||||
*/
|
||||
public function removeHeader(string $name) : void
|
||||
{
|
||||
parent::removeHeader($name);
|
||||
}
|
||||
/**
|
||||
* Retrieve the message entity body.
|
||||
*/
|
||||
public function getBody() : RequestBody
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
/**
|
||||
* Assign the message entity body.
|
||||
*
|
||||
* @param mixed $body
|
||||
*/
|
||||
public function setBody($body) : void
|
||||
{
|
||||
if ($body === null) {
|
||||
$this->body = new StringBody("");
|
||||
} elseif (\is_string($body)) {
|
||||
$this->body = new StringBody($body);
|
||||
} elseif (\is_scalar($body)) {
|
||||
$this->body = new StringBody(\var_export($body, \true));
|
||||
} elseif ($body instanceof RequestBody) {
|
||||
$this->body = $body;
|
||||
} else {
|
||||
/** @noinspection PhpUndefinedClassInspection */
|
||||
throw new \TypeError("Invalid body type: " . \gettype($body));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Registers a callback to the request that is invoked when the server pushes an additional resource.
|
||||
* The callback is given two parameters: the Request generated from the pushed resource, and a promise for the
|
||||
* Response containing the pushed resource. An HttpException, StreamException, or CancelledException can be thrown
|
||||
* to refuse the push. If no callback is registered, pushes are automatically rejected.
|
||||
*
|
||||
* Interceptors can mostly use {@code interceptPush} instead.
|
||||
*
|
||||
* Example:
|
||||
* function (Request $request, Promise $response): \Generator {
|
||||
* $uri = $request->getUri(); // URI of pushed resource.
|
||||
* $response = yield $promise; // Wait for resource to arrive.
|
||||
* // Use Response object from resolved promise.
|
||||
* }
|
||||
*
|
||||
* @param callable|null $onPush
|
||||
*/
|
||||
public function setPushHandler(?callable $onPush) : void
|
||||
{
|
||||
$this->onPush = $onPush;
|
||||
}
|
||||
/**
|
||||
* Allows interceptors to modify also pushed responses.
|
||||
*
|
||||
* If no push callable has been set by the application, the interceptor won't be invoked. If you want to enable
|
||||
* push in an interceptor without the application setting a push handler, you need to use {@code setPushHandler}.
|
||||
*
|
||||
* @param callable $interceptor Receives the response and might modify it or return a new instance.
|
||||
*/
|
||||
public function interceptPush(callable $interceptor) : void
|
||||
{
|
||||
if ($this->onPush === null) {
|
||||
return;
|
||||
}
|
||||
$onPush = $this->onPush;
|
||||
/** @psalm-suppress MissingClosureReturnType */
|
||||
$this->onPush = static function (Request $request, Promise $response) use($onPush, $interceptor) {
|
||||
$response = call(static function () use($response, $interceptor) : \Generator {
|
||||
return (yield call($interceptor, (yield $response))) ?? $response;
|
||||
});
|
||||
return $onPush($request, $response);
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @return callable|null
|
||||
*/
|
||||
public function getPushHandler() : ?callable
|
||||
{
|
||||
return $this->onPush;
|
||||
}
|
||||
/**
|
||||
* Registers a callback invoked if a 101 response is returned to the request.
|
||||
*
|
||||
* @param callable|null $onUpgrade
|
||||
*/
|
||||
public function setUpgradeHandler(?callable $onUpgrade) : void
|
||||
{
|
||||
$this->onUpgrade = $onUpgrade;
|
||||
}
|
||||
/**
|
||||
* @return callable|null
|
||||
*/
|
||||
public function getUpgradeHandler() : ?callable
|
||||
{
|
||||
return $this->onUpgrade;
|
||||
}
|
||||
/**
|
||||
* Registers a callback invoked when a 1xx response is returned to the request (other than a 101).
|
||||
*
|
||||
* @param callable|null $onInformationalResponse
|
||||
*/
|
||||
public function setInformationalResponseHandler(?callable $onInformationalResponse) : void
|
||||
{
|
||||
$this->onInformationalResponse = $onInformationalResponse;
|
||||
}
|
||||
/**
|
||||
* @return callable|null
|
||||
*/
|
||||
public function getInformationalResponseHandler() : ?callable
|
||||
{
|
||||
return $this->onInformationalResponse;
|
||||
}
|
||||
/**
|
||||
* @return int Timeout in milliseconds for the TCP connection.
|
||||
*/
|
||||
public function getTcpConnectTimeout() : int
|
||||
{
|
||||
return $this->tcpConnectTimeout;
|
||||
}
|
||||
public function setTcpConnectTimeout(int $tcpConnectTimeout) : void
|
||||
{
|
||||
$this->tcpConnectTimeout = $tcpConnectTimeout;
|
||||
}
|
||||
/**
|
||||
* @return int Timeout in milliseconds for the TLS handshake.
|
||||
*/
|
||||
public function getTlsHandshakeTimeout() : int
|
||||
{
|
||||
return $this->tlsHandshakeTimeout;
|
||||
}
|
||||
public function setTlsHandshakeTimeout(int $tlsHandshakeTimeout) : void
|
||||
{
|
||||
$this->tlsHandshakeTimeout = $tlsHandshakeTimeout;
|
||||
}
|
||||
/**
|
||||
* @return int Timeout in milliseconds for the HTTP transfer (not counting TCP connect and TLS handshake)
|
||||
*/
|
||||
public function getTransferTimeout() : int
|
||||
{
|
||||
return $this->transferTimeout;
|
||||
}
|
||||
public function setTransferTimeout(int $transferTimeout) : void
|
||||
{
|
||||
$this->transferTimeout = $transferTimeout;
|
||||
}
|
||||
/**
|
||||
* @return int Timeout in milliseconds since the last data was received before the request fails due to inactivity.
|
||||
*/
|
||||
public function getInactivityTimeout() : int
|
||||
{
|
||||
return $this->inactivityTimeout;
|
||||
}
|
||||
public function setInactivityTimeout(int $inactivityTimeout) : void
|
||||
{
|
||||
$this->inactivityTimeout = $inactivityTimeout;
|
||||
}
|
||||
public function getHeaderSizeLimit() : int
|
||||
{
|
||||
return $this->headerSizeLimit;
|
||||
}
|
||||
public function setHeaderSizeLimit(int $headerSizeLimit) : void
|
||||
{
|
||||
$this->headerSizeLimit = $headerSizeLimit;
|
||||
}
|
||||
public function getBodySizeLimit() : int
|
||||
{
|
||||
return $this->bodySizeLimit;
|
||||
}
|
||||
public function setBodySizeLimit(int $bodySizeLimit) : void
|
||||
{
|
||||
$this->bodySizeLimit = $bodySizeLimit;
|
||||
}
|
||||
/**
|
||||
* Note: This method returns a deep clone of the request's attributes, so you can't modify the request attributes
|
||||
* by modifying the returned value in any way.
|
||||
*
|
||||
* @return mixed[] An array of all request attributes in the request's local storage, indexed by name.
|
||||
*/
|
||||
public function getAttributes() : array
|
||||
{
|
||||
return self::clone($this->attributes);
|
||||
}
|
||||
/**
|
||||
* Check whether a variable with the given name exists in the request's local storage.
|
||||
*
|
||||
* Each request has its own local storage to which applications and interceptors may read and write data.
|
||||
* Other interceptors which are aware of this data can then access it without the server being tightly coupled to
|
||||
* specific implementations.
|
||||
*
|
||||
* @param string $name Name of the attribute, should be namespaced with a vendor and package namespace like classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAttribute(string $name) : bool
|
||||
{
|
||||
return \array_key_exists($name, $this->attributes);
|
||||
}
|
||||
/**
|
||||
* Retrieve a variable from the request's local storage.
|
||||
*
|
||||
* Each request has its own local storage to which applications and interceptors may read and write data.
|
||||
* Other interceptors which are aware of this data can then access it without the server being tightly coupled to
|
||||
* specific implementations.
|
||||
*
|
||||
* Note: This method returns a deep clone of the request's attribute, so you can't modify the request attribute
|
||||
* by modifying the returned value in any way.
|
||||
*
|
||||
* @param string $name Name of the attribute, should be namespaced with a vendor and package namespace like classes.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws MissingAttributeError If an attribute with the given name does not exist.
|
||||
*/
|
||||
public function getAttribute(string $name)
|
||||
{
|
||||
if (!$this->hasAttribute($name)) {
|
||||
throw new MissingAttributeError("The requested attribute '{$name}' does not exist");
|
||||
}
|
||||
return self::clone($this->attributes[$name]);
|
||||
}
|
||||
/**
|
||||
* Assign a variable to the request's local storage.
|
||||
*
|
||||
* Each request has its own local storage to which applications and interceptors may read and write data.
|
||||
* Other interceptors which are aware of this data can then access it without the server being tightly coupled to
|
||||
* specific implementations.
|
||||
*
|
||||
* Note: This method performs a deep clone of the value via serialization, so you can't modify the given value
|
||||
* after setting it.
|
||||
*
|
||||
* **Example**
|
||||
*
|
||||
* ```php
|
||||
* $request->setAttribute(Timing::class, $stopWatch);
|
||||
* ```
|
||||
*
|
||||
* @param string $name Name of the attribute, should be namespaced with a vendor and package namespace like classes.
|
||||
* @param mixed $value Value of the attribute, might be any serializable value.
|
||||
*/
|
||||
public function setAttribute(string $name, $value) : void
|
||||
{
|
||||
$this->attributes[$name] = self::clone($value);
|
||||
}
|
||||
/**
|
||||
* Remove an attribute from the request's local storage.
|
||||
*
|
||||
* @param string $name Name of the attribute, should be namespaced with a vendor and package namespace like classes.
|
||||
*
|
||||
* @throws MissingAttributeError If an attribute with the given name does not exist.
|
||||
*/
|
||||
public function removeAttribute(string $name) : void
|
||||
{
|
||||
if (!$this->hasAttribute($name)) {
|
||||
throw new MissingAttributeError("The requested attribute '{$name}' does not exist");
|
||||
}
|
||||
unset($this->attributes[$name]);
|
||||
}
|
||||
/**
|
||||
* Remove all attributes from the request's local storage.
|
||||
*/
|
||||
public function removeAttributes() : void
|
||||
{
|
||||
$this->attributes = [];
|
||||
}
|
||||
public function isIdempotent() : bool
|
||||
{
|
||||
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
|
||||
return \in_array($this->method, ['GET', 'HEAD', 'PUT', 'DELETE'], \true);
|
||||
}
|
||||
private function createUriFromString(string $uri) : UriInterface
|
||||
{
|
||||
return Uri\Http::createFromString($uri);
|
||||
}
|
||||
}
|
34
dependencies/amphp/http-client/src/RequestBody.php
vendored
Normal file
34
dependencies/amphp/http-client/src/RequestBody.php
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\InputStream;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/**
|
||||
* An interface for generating HTTP message bodies + headers.
|
||||
*/
|
||||
interface RequestBody
|
||||
{
|
||||
/**
|
||||
* Retrieve a key-value array of headers to add to the outbound request.
|
||||
*
|
||||
* The resolved promise value must be a key-value array mapping header fields to values.
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function getHeaders() : Promise;
|
||||
/**
|
||||
* Create the HTTP message body to be sent.
|
||||
*
|
||||
* Further calls MUST return a new stream to make it possible to resend bodies on redirects.
|
||||
*
|
||||
* @return InputStream
|
||||
*/
|
||||
public function createBodyStream() : InputStream;
|
||||
/**
|
||||
* Retrieve the HTTP message body length. If not available, return -1.
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function getBodyLength() : Promise;
|
||||
}
|
227
dependencies/amphp/http-client/src/Response.php
vendored
Normal file
227
dependencies/amphp/http-client/src/Response.php
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\InMemoryStream;
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\InputStream;
|
||||
use WP_Ultimo\Dependencies\Amp\ByteStream\Payload;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidCloning;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Message;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Status;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Success;
|
||||
/**
|
||||
* An HTTP response.
|
||||
*/
|
||||
final class Response extends Message
|
||||
{
|
||||
use ForbidSerialization;
|
||||
use ForbidCloning;
|
||||
/** @var string */
|
||||
private $protocolVersion;
|
||||
/** @var int */
|
||||
private $status;
|
||||
/** @var string */
|
||||
private $reason;
|
||||
/** @var Request */
|
||||
private $request;
|
||||
/** @var Payload */
|
||||
private $body;
|
||||
/** @var Promise<Trailers> */
|
||||
private $trailers;
|
||||
/** @var Response|null */
|
||||
private $previousResponse;
|
||||
public function __construct(string $protocolVersion, int $status, ?string $reason, array $headers, InputStream $body, Request $request, ?Promise $trailerPromise = null, ?Response $previousResponse = null)
|
||||
{
|
||||
$this->setProtocolVersion($protocolVersion);
|
||||
$this->setStatus($status, $reason);
|
||||
$this->setHeaders($headers);
|
||||
$this->setBody($body);
|
||||
$this->request = $request;
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$this->trailers = $trailerPromise ?? new Success(new Trailers([]));
|
||||
$this->previousResponse = $previousResponse;
|
||||
}
|
||||
/**
|
||||
* Retrieve the requests's HTTP protocol version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getProtocolVersion() : string
|
||||
{
|
||||
return $this->protocolVersion;
|
||||
}
|
||||
public function setProtocolVersion(string $protocolVersion) : void
|
||||
{
|
||||
if (!\in_array($protocolVersion, ["1.0", "1.1", "2"], \true)) {
|
||||
/** @noinspection PhpUndefinedClassInspection */
|
||||
throw new \Error("Invalid HTTP protocol version: " . $protocolVersion);
|
||||
}
|
||||
$this->protocolVersion = $protocolVersion;
|
||||
}
|
||||
/**
|
||||
* Retrieve the response's three-digit HTTP status code.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStatus() : int
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
public function setStatus(int $status, ?string $reason = null) : void
|
||||
{
|
||||
$this->status = $status;
|
||||
$this->reason = $reason ?? Status::getReason($status);
|
||||
}
|
||||
/**
|
||||
* Retrieve the response's (possibly empty) reason phrase.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReason() : string
|
||||
{
|
||||
return $this->reason;
|
||||
}
|
||||
/**
|
||||
* Retrieve the Request instance that resulted in this Response instance.
|
||||
*
|
||||
* @return Request
|
||||
*/
|
||||
public function getRequest() : Request
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
public function setRequest(Request $request) : void
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
/**
|
||||
* Retrieve the original Request instance associated with this Response instance.
|
||||
*
|
||||
* A given Response may be the result of one or more redirects. This method is a shortcut to
|
||||
* access information from the original Request that led to this response.
|
||||
*
|
||||
* @return Request
|
||||
*/
|
||||
public function getOriginalRequest() : Request
|
||||
{
|
||||
if (empty($this->previousResponse)) {
|
||||
return $this->request;
|
||||
}
|
||||
return $this->previousResponse->getOriginalRequest();
|
||||
}
|
||||
/**
|
||||
* Retrieve the original Response instance associated with this Response instance.
|
||||
*
|
||||
* A given Response may be the result of one or more redirects. This method is a shortcut to
|
||||
* access information from the original Response that led to this response.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getOriginalResponse() : Response
|
||||
{
|
||||
if (empty($this->previousResponse)) {
|
||||
return $this;
|
||||
}
|
||||
return $this->previousResponse->getOriginalResponse();
|
||||
}
|
||||
/**
|
||||
* If this Response is the result of a redirect traverse up the redirect history.
|
||||
*
|
||||
* @return Response|null
|
||||
*/
|
||||
public function getPreviousResponse() : ?Response
|
||||
{
|
||||
return $this->previousResponse;
|
||||
}
|
||||
public function setPreviousResponse(?Response $previousResponse) : void
|
||||
{
|
||||
$this->previousResponse = $previousResponse;
|
||||
}
|
||||
/**
|
||||
* Assign a value for the specified header field by replacing any existing values for that field.
|
||||
*
|
||||
* @param string $name Header name.
|
||||
* @param string|string[] $value Header value.
|
||||
*/
|
||||
public function setHeader(string $name, $value) : void
|
||||
{
|
||||
if (($name[0] ?? ":") === ":") {
|
||||
throw new \Error("Header name cannot be empty or start with a colon (:)");
|
||||
}
|
||||
parent::setHeader($name, $value);
|
||||
}
|
||||
/**
|
||||
* Assign a value for the specified header field by adding an additional header line.
|
||||
*
|
||||
* @param string $name Header name.
|
||||
* @param string|string[] $value Header value.
|
||||
*/
|
||||
public function addHeader(string $name, $value) : void
|
||||
{
|
||||
if (($name[0] ?? ":") === ":") {
|
||||
throw new \Error("Header name cannot be empty or start with a colon (:)");
|
||||
}
|
||||
parent::addHeader($name, $value);
|
||||
}
|
||||
public function setHeaders(array $headers) : void
|
||||
{
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
parent::setHeaders($headers);
|
||||
}
|
||||
/**
|
||||
* Remove the specified header field from the message.
|
||||
*
|
||||
* @param string $name Header name.
|
||||
*/
|
||||
public function removeHeader(string $name) : void
|
||||
{
|
||||
parent::removeHeader($name);
|
||||
}
|
||||
/**
|
||||
* Retrieve the response body.
|
||||
*
|
||||
* Note: If you stream a Message, you can't consume the payload twice.
|
||||
*
|
||||
* @return Payload
|
||||
*/
|
||||
public function getBody() : Payload
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
/**
|
||||
* @param Payload|InputStream|string|int|float|bool $body
|
||||
*/
|
||||
public function setBody($body) : void
|
||||
{
|
||||
if ($body instanceof Payload) {
|
||||
$this->body = $body;
|
||||
} elseif ($body === null) {
|
||||
$this->body = new Payload(new InMemoryStream());
|
||||
} elseif (\is_string($body)) {
|
||||
$this->body = new Payload(new InMemoryStream($body));
|
||||
} elseif (\is_scalar($body)) {
|
||||
$this->body = new Payload(new InMemoryStream(\var_export($body, \true)));
|
||||
} elseif ($body instanceof InputStream) {
|
||||
$this->body = new Payload($body);
|
||||
} else {
|
||||
/** @noinspection PhpUndefinedClassInspection */
|
||||
throw new \TypeError("Invalid body type: " . \gettype($body));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @return Promise<Trailers>
|
||||
*/
|
||||
public function getTrailers() : Promise
|
||||
{
|
||||
return $this->trailers;
|
||||
}
|
||||
/**
|
||||
* @param Promise<Trailers> $promise
|
||||
*/
|
||||
public function setTrailers(Promise $promise) : void
|
||||
{
|
||||
$this->trailers = $promise;
|
||||
}
|
||||
}
|
7
dependencies/amphp/http-client/src/SocketException.php
vendored
Normal file
7
dependencies/amphp/http-client/src/SocketException.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
final class SocketException extends HttpException
|
||||
{
|
||||
}
|
7
dependencies/amphp/http-client/src/TimeoutException.php
vendored
Normal file
7
dependencies/amphp/http-client/src/TimeoutException.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
final class TimeoutException extends HttpException
|
||||
{
|
||||
}
|
27
dependencies/amphp/http-client/src/Trailers.php
vendored
Normal file
27
dependencies/amphp/http-client/src/Trailers.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Http\Client;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Client\Internal\ForbidSerialization;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\InvalidHeaderException;
|
||||
use WP_Ultimo\Dependencies\Amp\Http\Message;
|
||||
final class Trailers extends Message
|
||||
{
|
||||
use ForbidSerialization;
|
||||
/** @see https://tools.ietf.org/html/rfc7230#section-4.1.2 */
|
||||
public const DISALLOWED_TRAILERS = ["authorization" => \true, "content-encoding" => \true, "content-length" => \true, "content-range" => \true, "content-type" => \true, "cookie" => \true, "expect" => \true, "host" => \true, "pragma" => \true, "proxy-authenticate" => \true, "proxy-authorization" => \true, "range" => \true, "te" => \true, "trailer" => \true, "transfer-encoding" => \true, "www-authenticate" => \true];
|
||||
/**
|
||||
* @param string[]|string[][] $headers
|
||||
*
|
||||
* @throws InvalidHeaderException Thrown if a disallowed field is in the header values.
|
||||
*/
|
||||
public function __construct(array $headers)
|
||||
{
|
||||
if (!empty($headers)) {
|
||||
$this->setHeaders($headers);
|
||||
}
|
||||
if (\array_intersect_key($this->getHeaders(), self::DISALLOWED_TRAILERS)) {
|
||||
throw new InvalidHeaderException('Disallowed field in trailers');
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user