updated plugin WP-WebAuthn version 1.3.4

This commit is contained in:
KawaiiPunk 2024-10-09 12:44:38 +00:00 committed by Gitium
parent f970470c59
commit e73c3de31d
56 changed files with 1040 additions and 1173 deletions

View File

@ -1 +1 @@
!function(e){function t(a){if(n[a])return n[a].exports;var o=n[a]={i:a,l:!1,exports:{}};return e[a].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};t.m=e,t.c=n,t.d=function(e,n,a){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:a})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=0)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});n(1)},function(e,t,n){"use strict";var a=n(2),o=(n.n(a),wp.i18n.__),r=wp.blocks.registerBlockType;r("wp-webauthn/login",{title:o("WebAuthn Login Form","wp-webauthn"),icon:"admin-network",category:"widgets",keywords:["WebAuthn",o("Login Form","wp-webauthn")],attributes:{traditional:{type:"boolean",default:!0},username:{type:"string"},autoHide:{type:"boolean",default:!0},to:{type:"string"}},edit:function(e){var t=e.attributes,n=e.setAttributes,r=e.className;return wp.element.createElement("div",{className:r,style:{padding:"20px",boxSizing:"border-box",backgroundColor:"#F4F4F4",borderRadius:"3px"}},wp.element.createElement("span",{style:{fontSize:"15px",marginBottom:"20px",opacity:".5"}},o("WebAuthn Login Form","wp-webauthn")),wp.element.createElement(a.TextControl,{label:o("Default username","wp-webauthn"),value:t.username,onChange:function(e){n({username:e})}}),wp.element.createElement(a.TextControl,{label:o("Redirect to","wp-webauthn"),value:t.to,onChange:function(e){n({to:e})}}),wp.element.createElement(a.CheckboxControl,{label:o("Show password form as well","wp-webauthn"),checked:t.traditional,onChange:function(e){n({traditional:e})}}),wp.element.createElement(a.CheckboxControl,{label:o("Hide for logged-in users","wp-webauthn"),checked:t.autoHide,onChange:function(e){n({autoHide:e})}}))},save:function(e){var t=e.attributes;return'[wwa_login_form traditional="'+t.traditional+'" username="'+t.username+'" auto_hide="'+t.autoHide+'" to="'+t.to+'"]'}}),r("wp-webauthn/register",{title:o("WebAuthn Register Form","wp-webauthn"),icon:"plus-alt",category:"widgets",keywords:["WebAuthn",o("Register Form","wp-webauthn")],attributes:{display:{type:"boolean",default:!0}},edit:function(e){var t=e.attributes,n=e.setAttributes,r=e.className;return wp.element.createElement("div",{className:r,style:{padding:"20px",boxSizing:"border-box",backgroundColor:"#F4F4F4",borderRadius:"3px"}},wp.element.createElement("span",{style:{fontSize:"15px",marginBottom:"20px",opacity:".5"}},o("WebAuthn Register Form","wp-webauthn")),wp.element.createElement("div",{className:r,style:{height:"150px",display:"flex",justifyContent:"center",alignItems:"center"}},wp.element.createElement(a.CheckboxControl,{label:o("Show a message for users who doesn't logged-in","wp-webauthn"),checked:t.display,onChange:function(e){n({display:e})}})))},save:function(e){return'[wwa_register_form display="'+e.attributes.display+'"]'}}),r("wp-webauthn/verify",{title:o("WebAuthn Verify Buttons","wp-webauthn"),icon:"sos",category:"widgets",keywords:["WebAuthn",o("Verify Buttons","wp-webauthn")],attributes:{display:{type:"boolean",default:!0}},edit:function(e){var t=e.attributes,n=e.setAttributes,r=e.className;return wp.element.createElement("div",{className:r,style:{padding:"20px",boxSizing:"border-box",backgroundColor:"#F4F4F4",borderRadius:"3px"}},wp.element.createElement("span",{style:{fontSize:"15px",marginBottom:"20px",opacity:".5"}},o("WebAuthn Verify Buttons","wp-webauthn")),wp.element.createElement("div",{className:r,style:{height:"50px",display:"flex",justifyContent:"center",alignItems:"center"}},wp.element.createElement(a.CheckboxControl,{label:o("Show a message for users who doesn't logged-in","wp-webauthn"),checked:t.display,onChange:function(e){n({display:e})}})))},save:function(e){return'[wwa_verify_button display="'+e.attributes.display+'"]'}}),r("wp-webauthn/list",{title:o("WebAuthn Authenticator List","wp-webauthn"),icon:"menu",category:"widgets",keywords:["WebAuthn",o("Authenticator List","wp-webauthn")],attributes:{display:{type:"boolean",default:!0}},edit:function(e){var t=e.attributes,n=e.setAttributes,r=e.className;return wp.element.createElement("div",{className:r,style:{padding:"20px",boxSizing:"border-box",backgroundColor:"#F4F4F4",borderRadius:"3px"}},wp.element.createElement("span",{style:{fontSize:"15px",marginBottom:"20px",opacity:".5"}},o("WebAuthn Authenticator List","wp-webauthn")),wp.element.createElement("div",{className:r,style:{height:"150px",display:"flex",justifyContent:"center",alignItems:"center"}},wp.element.createElement(a.CheckboxControl,{label:o("Show a message for users who doesn't logged-in","wp-webauthn"),checked:t.display,onChange:function(e){n({display:e})}})))},save:function(e){return'[wwa_list display="'+e.attributes.display+'"]'}})},function(e,t){e.exports=wp.components}]); !function(e){function t(a){if(n[a])return n[a].exports;var o=n[a]={i:a,l:!1,exports:{}};return e[a].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};t.m=e,t.c=n,t.d=function(e,n,a){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:a})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=0)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});n(1)},function(e,t,n){"use strict";var a=n(2),__=(n.n(a),wp.i18n.__),o=wp.blocks.registerBlockType;o("wp-webauthn/login",{title:__("WebAuthn Login Form","wp-webauthn"),icon:"admin-network",category:"widgets",keywords:["WebAuthn",__("Login Form","wp-webauthn")],attributes:{traditional:{type:"boolean",default:!0},username:{type:"string",default:""},autoHide:{type:"boolean",default:!0},to:{type:"string",default:""}},edit:function(e){var t=e.attributes,n=e.setAttributes,o=e.className;return wp.element.createElement("div",{className:o,style:{padding:"20px",boxSizing:"border-box",backgroundColor:"#F4F4F4",borderRadius:"3px"}},wp.element.createElement("span",{style:{fontSize:"15px",marginBottom:"20px",opacity:".5"}},__("WebAuthn Login Form","wp-webauthn")),wp.element.createElement(a.TextControl,{label:__("Default username","wp-webauthn"),value:t.username,onChange:function(e){n({username:e})}}),wp.element.createElement(a.TextControl,{label:__("Redirect to","wp-webauthn"),value:t.to,onChange:function(e){n({to:e})}}),wp.element.createElement(a.CheckboxControl,{label:__("Show password form as well","wp-webauthn"),checked:t.traditional,onChange:function(e){n({traditional:e})}}),wp.element.createElement(a.CheckboxControl,{label:__("Hide for logged-in users","wp-webauthn"),checked:t.autoHide,onChange:function(e){n({autoHide:e})}}))},save:function(e){var t=e.attributes;return'[wwa_login_form traditional="'+t.traditional+'" auto_hide="'+t.autoHide+'"'+(t.username?' username="'+t.username+'"':"")+(t.to?' to="'+t.to+'"':"")+"]"}}),o("wp-webauthn/register",{title:__("WebAuthn Register Form","wp-webauthn"),icon:"plus-alt",category:"widgets",keywords:["WebAuthn",__("Register Form","wp-webauthn")],attributes:{display:{type:"boolean",default:!0}},edit:function(e){var t=e.attributes,n=e.setAttributes,o=e.className;return wp.element.createElement("div",{className:o,style:{padding:"20px",boxSizing:"border-box",backgroundColor:"#F4F4F4",borderRadius:"3px"}},wp.element.createElement("span",{style:{fontSize:"15px",marginBottom:"20px",opacity:".5"}},__("WebAuthn Register Form","wp-webauthn")),wp.element.createElement("div",{className:o,style:{height:"150px",display:"flex",justifyContent:"center",alignItems:"center"}},wp.element.createElement(a.CheckboxControl,{label:__("Show a message for users who doesn't logged-in","wp-webauthn"),checked:t.display,onChange:function(e){n({display:e})}})))},save:function(e){return'[wwa_register_form display="'+e.attributes.display+'"]'}}),o("wp-webauthn/verify",{title:__("WebAuthn Verify Buttons","wp-webauthn"),icon:"sos",category:"widgets",keywords:["WebAuthn",__("Verify Buttons","wp-webauthn")],attributes:{display:{type:"boolean",default:!0}},edit:function(e){var t=e.attributes,n=e.setAttributes,o=e.className;return wp.element.createElement("div",{className:o,style:{padding:"20px",boxSizing:"border-box",backgroundColor:"#F4F4F4",borderRadius:"3px"}},wp.element.createElement("span",{style:{fontSize:"15px",marginBottom:"20px",opacity:".5"}},__("WebAuthn Verify Buttons","wp-webauthn")),wp.element.createElement("div",{className:o,style:{height:"50px",display:"flex",justifyContent:"center",alignItems:"center"}},wp.element.createElement(a.CheckboxControl,{label:__("Show a message for users who doesn't logged-in","wp-webauthn"),checked:t.display,onChange:function(e){n({display:e})}})))},save:function(e){return'[wwa_verify_button display="'+e.attributes.display+'"]'}}),o("wp-webauthn/list",{title:__("WebAuthn Authenticator List","wp-webauthn"),icon:"menu",category:"widgets",keywords:["WebAuthn",__("Authenticator List","wp-webauthn")],attributes:{display:{type:"boolean",default:!0}},edit:function(e){var t=e.attributes,n=e.setAttributes,o=e.className;return wp.element.createElement("div",{className:o,style:{padding:"20px",boxSizing:"border-box",backgroundColor:"#F4F4F4",borderRadius:"3px"}},wp.element.createElement("span",{style:{fontSize:"15px",marginBottom:"20px",opacity:".5"}},__("WebAuthn Authenticator List","wp-webauthn")),wp.element.createElement("div",{className:o,style:{height:"150px",display:"flex",justifyContent:"center",alignItems:"center"}},wp.element.createElement(a.CheckboxControl,{label:__("Show a message for users who doesn't logged-in","wp-webauthn"),checked:t.display,onChange:function(e){n({display:e})}})))},save:function(e){return'[wwa_list display="'+e.attributes.display+'"]'}})},function(e,t){e.exports=wp.components}]);

View File

@ -15,7 +15,7 @@ const wwa_ajax = function () {
xmlHttpReq.send(); xmlHttpReq.send();
xmlHttpReq.onreadystatechange = () => { xmlHttpReq.onreadystatechange = () => {
if (xmlHttpReq.readyState === 4 && xmlHttpReq.status === 200) { if (xmlHttpReq.readyState === 4 && xmlHttpReq.status === 200) {
callback(xmlHttpReq.responseText, true); callback(xmlHttpReq.responseText.trim(), true);
} else if (xmlHttpReq.readyState === 4) { } else if (xmlHttpReq.readyState === 4) {
callback('Network Error.', false); callback('Network Error.', false);
} }
@ -33,7 +33,7 @@ const wwa_ajax = function () {
xmlHttpReq.send(data); xmlHttpReq.send(data);
xmlHttpReq.onreadystatechange = () => { xmlHttpReq.onreadystatechange = () => {
if (xmlHttpReq.readyState === 4 && xmlHttpReq.status === 200) { if (xmlHttpReq.readyState === 4 && xmlHttpReq.status === 200) {
callback(xmlHttpReq.responseText, true); callback(xmlHttpReq.responseText.trim(), true);
} else if (xmlHttpReq.readyState === 4) { } else if (xmlHttpReq.readyState === 4) {
callback('Network Error.', false); callback('Network Error.', false);
} }
@ -177,7 +177,7 @@ function wwa_auth() {
alert(wwa_php_vars.i18n_31); alert(wwa_php_vars.i18n_31);
return; return;
} }
let wwa_username = this.parentNode.previousElementSibling.previousElementSibling.getElementsByClassName('wwa-user-name')[0].value; let wwa_username = this.closest('.wwa-login-form').getElementsByClassName('wwa-user-name')[0].value;
if (wwa_username === '' && wwa_php_vars.usernameless !== 'true') { if (wwa_username === '' && wwa_php_vars.usernameless !== 'true') {
alert(wwa_php_vars.i18n_11); alert(wwa_php_vars.i18n_11);
return; return;
@ -561,7 +561,7 @@ function updateList() {
item_type_disabled = true; item_type_disabled = true;
} }
} }
htmlStr += `<tr><td>${item.name}</td><td>${item.type === 'none' ? wwa_php_vars.i18n_24 : (item.type === 'platform' ? wwa_php_vars.i18n_25 : wwa_php_vars.i18n_26)}${item_type_disabled ? wwa_php_vars.i18n_35 : ''}</td><td>${item.added}</td><td>${item.last_used}</td><td class="wwa-usernameless-td">${item.usernameless ? wwa_php_vars.i18n_1 + (wwa_php_vars.usernameless === 'true' ? '' : wwa_php_vars.i18n_9) : wwa_php_vars.i18n_8}</td><td class="wwa-key-${item.key}"><a href="javascript:renameAuthenticator('${item.key}', '${item.name}')">${wwa_php_vars.i18n_20}</a> | <a href="javascript:removeAuthenticator('${item.key}', '${item.name}')">${wwa_php_vars.i18n_27}</a></td></tr>`; htmlStr += `<tr><td>${item.name}</td><td>${item.type === 'none' ? wwa_php_vars.i18n_24 : (item.type === 'platform' ? wwa_php_vars.i18n_25 : wwa_php_vars.i18n_26)}${item_type_disabled ? wwa_php_vars.i18n_35 : ''}</td><td>${item.added}</td><td>${item.last_used}</td><td class="wwa-usernameless-td">${item.usernameless ? wwa_php_vars.i18n_1 + (wwa_php_vars.usernameless === 'true' ? '' : wwa_php_vars.i18n_9) : wwa_php_vars.i18n_8}</td><td class="wwa-key-${item.key}"><a href="javascript:renameAuthenticator('${item.key}', '${item.name.replaceAll('\'', '\\\'').replaceAll('&#039;', '\\&#039;').replaceAll('"', '\\"')}')">${wwa_php_vars.i18n_20}</a> | <a href="javascript:removeAuthenticator('${item.key}', '${item.name.replaceAll('\'', '\\\'').replaceAll('&#039;', '\\&#039;').replaceAll('"', '\\"')}')">${wwa_php_vars.i18n_27}</a></td></tr>`;
} }
wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = htmlStr }, 'class'); wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = htmlStr }, 'class');
if (has_usernameless || wwa_php_vars.usernameless === 'true') { if (has_usernameless || wwa_php_vars.usernameless === 'true') {

View File

@ -15,7 +15,7 @@ const wwa_ajax = function () {
xmlHttpReq.send(); xmlHttpReq.send();
xmlHttpReq.onreadystatechange = () => { xmlHttpReq.onreadystatechange = () => {
if (xmlHttpReq.readyState === 4 && xmlHttpReq.status === 200) { if (xmlHttpReq.readyState === 4 && xmlHttpReq.status === 200) {
callback(xmlHttpReq.responseText, true); callback(xmlHttpReq.responseText.trim(), true);
} else if (xmlHttpReq.readyState === 4) { } else if (xmlHttpReq.readyState === 4) {
callback('Network Error.', false); callback('Network Error.', false);
} }
@ -33,7 +33,7 @@ const wwa_ajax = function () {
xmlHttpReq.send(data); xmlHttpReq.send(data);
xmlHttpReq.onreadystatechange = () => { xmlHttpReq.onreadystatechange = () => {
if (xmlHttpReq.readyState === 4 && xmlHttpReq.status === 200) { if (xmlHttpReq.readyState === 4 && xmlHttpReq.status === 200) {
callback(xmlHttpReq.responseText, true); callback(xmlHttpReq.responseText.trim(), true);
} else if (xmlHttpReq.readyState === 4) { } else if (xmlHttpReq.readyState === 4) {
callback('Network Error.', false); callback('Network Error.', false);
} }

View File

@ -59,7 +59,7 @@ function updateList() {
item_type_disabled = true; item_type_disabled = true;
} }
} }
htmlStr += `<tr><td>${item.name}</td><td>${item.type === 'none' ? php_vars.i18n_9 : (item.type === 'platform' ? php_vars.i18n_10 : php_vars.i18n_11)}${item_type_disabled ? php_vars.i18n_29 : ''}</td><td>${item.added}</td><td>${item.last_used}</td><td class="wwa-usernameless-td">${item.usernameless ? php_vars.i18n_24 + (configs.usernameless === 'true' ? '' : php_vars.i18n_26) : php_vars.i18n_25}</td><td id="${item.key}"><a href="javascript:renameAuthenticator('${item.key}', '${item.name}')">${php_vars.i18n_20}</a> | <a href="javascript:removeAuthenticator('${item.key}', '${item.name}')">${php_vars.i18n_12}</a></td></tr>`; htmlStr += `<tr><td>${item.name}</td><td>${item.type === 'none' ? php_vars.i18n_9 : (item.type === 'platform' ? php_vars.i18n_10 : php_vars.i18n_11)}${item_type_disabled ? php_vars.i18n_29 : ''}</td><td>${item.added}</td><td>${item.last_used}</td><td class="wwa-usernameless-td">${item.usernameless ? php_vars.i18n_24 + (configs.usernameless === 'true' ? '' : php_vars.i18n_26) : php_vars.i18n_25}</td><td id="${item.key}"><a href="javascript:renameAuthenticator('${item.key}', '${item.name.replaceAll('\'', '\\\'').replaceAll('&#039;', '\\&#039;').replaceAll('"', '\\"')}')">${php_vars.i18n_20}</a> | <a href="javascript:removeAuthenticator('${item.key}', '${item.name.replaceAll('\'', '\\\'').replaceAll('&#039;', '\\&#039;').replaceAll('"', '\\"')}')">${php_vars.i18n_12}</a></td></tr>`;
} }
jQuery('#wwa-authenticator-list').html(htmlStr); jQuery('#wwa-authenticator-list').html(htmlStr);
if (has_usernameless || configs.usernameless === 'true') { if (has_usernameless || configs.usernameless === 'true') {
@ -247,7 +247,7 @@ jQuery('#wwa-bind').click((e) => {
user_id: php_vars.user_id user_id: php_vars.user_id
}, },
success: function (data) { success: function (data) {
if (data === 'true') { if (data.trim() === 'true') {
// Registered // Registered
jQuery('#wwa-show-progress').html(php_vars.i18n_3); jQuery('#wwa-show-progress').html(php_vars.i18n_3);
jQuery('#wwa-bind').removeAttr('disabled'); jQuery('#wwa-bind').removeAttr('disabled');
@ -382,7 +382,7 @@ jQuery('#wwa-test, #wwa-test_usernameless').click((e) => {
user_id: php_vars.user_id user_id: php_vars.user_id
}, },
success: function (data) { success: function (data) {
if (data === 'true') { if (data.trim() === 'true') {
jQuery(tip_id).html(php_vars.i18n_16); jQuery(tip_id).html(php_vars.i18n_16);
jQuery('#wwa-test, #wwa-test_usernameless').removeAttr('disabled'); jQuery('#wwa-test, #wwa-test_usernameless').removeAttr('disabled');
updateList(); updateList();

View File

@ -1,8 +1,8 @@
# Copyright (C) 2023 Axton # Copyright (C) 2024 Axton
# This file is distributed under the same license as the WP-WebAuthn plugin. # This file is distributed under the GPLv3.
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: WP-WebAuthn 1.3.1\n" "Project-Id-Version: WP-WebAuthn 1.3.2\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wp-webauthn\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wp-webauthn\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,22 +18,27 @@ msgstr ""
"X-Domain: wp-webauthn\n" "X-Domain: wp-webauthn\n"
#. Plugin Name of the plugin #. Plugin Name of the plugin
#: wp-webauthn.php
msgid "WP-WebAuthn" msgid "WP-WebAuthn"
msgstr "" msgstr ""
#. Plugin URI of the plugin #. Plugin URI of the plugin
#: wp-webauthn.php
msgid "https://flyhigher.top" msgid "https://flyhigher.top"
msgstr "" msgstr ""
#. Description of the plugin #. Description of the plugin
#: wp-webauthn.php
msgid "WP-WebAuthn allows you to safely login to your WordPress site without password." msgid "WP-WebAuthn allows you to safely login to your WordPress site without password."
msgstr "" msgstr ""
#. Author of the plugin #. Author of the plugin
#: wp-webauthn.php
msgid "Axton" msgid "Axton"
msgstr "" msgstr ""
#. Author URI of the plugin #. Author URI of the plugin
#: wp-webauthn.php
msgid "https://axton.cc" msgid "https://axton.cc"
msgstr "" msgstr ""
@ -42,7 +47,7 @@ msgid "User verification is disabled by default because some mobile devices do n
msgstr "" msgstr ""
#: wwa-admin-content.php:7 #: wwa-admin-content.php:7
#: wwa-admin-content.php:303 #: wwa-admin-content.php:307
msgid "Log count: " msgid "Log count: "
msgstr "" msgstr ""
@ -68,198 +73,199 @@ msgstr ""
msgid "WebAuthn features are restricted to websites in secure contexts. Please make sure your website is served over HTTPS or locally with <code>localhost</code>." msgid "WebAuthn features are restricted to websites in secure contexts. Please make sure your website is served over HTTPS or locally with <code>localhost</code>."
msgstr "" msgstr ""
#: wwa-admin-content.php:127 #: wwa-admin-content.php:131
msgid "Settings saved." msgid "Settings saved."
msgstr "" msgstr ""
#: wwa-admin-content.php:129 #: wwa-admin-content.php:133
msgid "Settings NOT saved." msgid "Settings NOT saved."
msgstr "" msgstr ""
#: wwa-admin-content.php:144 #: wwa-admin-content.php:148
msgid "Preferred login method" msgid "Preferred login method"
msgstr "" msgstr ""
#: wwa-admin-content.php:148 #: wwa-admin-content.php:152
msgid "Prefer WebAuthn" msgid "Prefer WebAuthn"
msgstr "" msgstr ""
#: wwa-admin-content.php:149 #: wwa-admin-content.php:153
msgid "Prefer password" msgid "Prefer password"
msgstr "" msgstr ""
#: wwa-admin-content.php:150 #: wwa-admin-content.php:154
#: wwa-profile-content.php:81 #: wwa-profile-content.php:82
msgid "WebAuthn Only" msgid "WebAuthn Only"
msgstr "" msgstr ""
#: wwa-admin-content.php:152 #: wwa-admin-content.php:156
msgid "When using \"WebAuthn Only\", password login will be completely disabled. Please make sure your browser supports WebAuthn, otherwise you may unable to login.<br>User that doesn't have any registered authenticator (e.g. new user) will unable to login when using \"WebAuthn Only\".<br>When the browser does not support WebAuthn, the login method will default to password if password login is not disabled." msgid "When using \"WebAuthn Only\", password login will be completely disabled. Please make sure your browser supports WebAuthn, otherwise you may unable to login.<br>User that doesn't have any registered authenticator (e.g. new user) will unable to login when using \"WebAuthn Only\".<br>When the browser does not support WebAuthn, the login method will default to password if password login is not disabled."
msgstr "" msgstr ""
#: wwa-admin-content.php:156 #: wwa-admin-content.php:160
msgid "Website identifier" msgid "Website identifier"
msgstr "" msgstr ""
#: wwa-admin-content.php:159 #: wwa-admin-content.php:163
msgid "This identifier is for identification purpose only and <strong>DOES NOT</strong> affect the authentication process in anyway." msgid "This identifier is for identification purpose only and <strong>DOES NOT</strong> affect the authentication process in anyway."
msgstr "" msgstr ""
#: wwa-admin-content.php:163 #: wwa-admin-content.php:167
msgid "Website domain" msgid "Website domain"
msgstr "" msgstr ""
#: wwa-admin-content.php:166 #: wwa-admin-content.php:170
msgid "This field <strong>MUST</strong> be exactly the same with the current domain or parent domain." msgid "This field <strong>MUST</strong> be exactly the same with the current domain or parent domain."
msgstr "" msgstr ""
#: wwa-admin-content.php:173 #: wwa-admin-content.php:177
msgid "Allow to remember login" msgid "Allow to remember login"
msgstr "" msgstr ""
#: wwa-admin-content.php:182 #: wwa-admin-content.php:186
#: wwa-admin-content.php:198 #: wwa-admin-content.php:202
#: wwa-admin-content.php:209 #: wwa-admin-content.php:213
#: wwa-admin-content.php:225 #: wwa-admin-content.php:229
#: wwa-admin-content.php:300 #: wwa-admin-content.php:304
#: wwa-profile-content.php:155 #: wwa-profile-content.php:157
#: wwa-shortcodes.php:118 #: wwa-shortcodes.php:135
msgid "Enable" msgid "Enable"
msgstr "" msgstr ""
#: wwa-admin-content.php:183 #: wwa-admin-content.php:187
#: wwa-admin-content.php:199 #: wwa-admin-content.php:203
#: wwa-admin-content.php:210 #: wwa-admin-content.php:214
#: wwa-admin-content.php:226 #: wwa-admin-content.php:230
#: wwa-admin-content.php:301 #: wwa-admin-content.php:305
#: wwa-profile-content.php:156 #: wwa-profile-content.php:158
#: wwa-shortcodes.php:118 #: wwa-shortcodes.php:135
msgid "Disable" msgid "Disable"
msgstr "" msgstr ""
#: wwa-admin-content.php:184 #: wwa-admin-content.php:188
msgid "Show the 'Remember Me' checkbox beside the login form when using WebAuthn." msgid "Show the 'Remember Me' checkbox beside the login form when using WebAuthn."
msgstr "" msgstr ""
#: wwa-admin-content.php:189 #: wwa-admin-content.php:193
msgid "Allow to login with email addresses" msgid "Allow to login with email addresses"
msgstr "" msgstr ""
#: wwa-admin-content.php:200 #: wwa-admin-content.php:204
msgid "Allow to find users via email addresses when logging in.<br><strong>Note that if enabled attackers may be able to brute force the correspondences between email addresses and users.</strong>" msgid "Allow to find users via email addresses when logging in.<br><strong>Note that if enabled attackers may be able to brute force the correspondences between email addresses and users.</strong>"
msgstr "" msgstr ""
#: wwa-admin-content.php:205 #: wwa-admin-content.php:209
msgid "Require user verification" msgid "Require user verification"
msgstr "" msgstr ""
#: wwa-admin-content.php:211 #: wwa-admin-content.php:215
msgid "User verification can improve security, but is not fully supported by mobile devices. <br> If you cannot register or verify your authenticators, please consider disabling user verification." msgid "User verification can improve security, but is not fully supported by mobile devices. <br> If you cannot register or verify your authenticators, please consider disabling user verification."
msgstr "" msgstr ""
#: wwa-admin-content.php:216 #: wwa-admin-content.php:220
msgid "Allow to login without username" msgid "Allow to login without username"
msgstr "" msgstr ""
#: wwa-admin-content.php:227 #: wwa-admin-content.php:231
msgid "Allow users to register authenticator with usernameless authentication feature and login without username.<br><strong>User verification will be enabled automatically when authenticating with usernameless authentication feature.</strong><br>Some authenticators and some browsers <strong>DO NOT</strong> support this feature." msgid "Allow users to register authenticator with usernameless authentication feature and login without username.<br><strong>User verification will be enabled automatically when authenticating with usernameless authentication feature.</strong><br>Some authenticators and some browsers <strong>DO NOT</strong> support this feature."
msgstr "" msgstr ""
#: wwa-admin-content.php:232 #: wwa-admin-content.php:236
msgid "Allow a specific type of authenticator" msgid "Allow a specific type of authenticator"
msgstr "" msgstr ""
#: wwa-admin-content.php:241 #: wwa-admin-content.php:245
#: wwa-profile-content.php:15 #: wwa-profile-content.php:15
#: wwa-profile-content.php:136 #: wwa-profile-content.php:138
#: wwa-shortcodes.php:33 #: wwa-shortcodes.php:33
#: wwa-shortcodes.php:118 #: wwa-shortcodes.php:127
msgid "Any" msgid "Any"
msgstr "" msgstr ""
#: wwa-admin-content.php:242 #: wwa-admin-content.php:246
msgid "Platform (e.g. Passkey or built-in sensors)" msgid "Platform (e.g. Passkey or built-in sensors)"
msgstr "" msgstr ""
#: wwa-admin-content.php:243 #: wwa-admin-content.php:247
#: wwa-profile-content.php:138 #: wwa-profile-content.php:140
#: wwa-shortcodes.php:118 #: wwa-shortcodes.php:129
msgid "Roaming (e.g. USB security keys)" msgid "Roaming (e.g. USB security keys)"
msgstr "" msgstr ""
#: wwa-admin-content.php:245 #: wwa-admin-content.php:249
msgid "If a type is selected, the browser will only prompt for authenticators of selected type when authenticating and user can only register authenticators of selected type." msgid "If a type is selected, the browser will only prompt for authenticators of selected type when authenticating and user can only register authenticators of selected type."
msgstr "" msgstr ""
#: wwa-admin-content.php:252 #: wwa-admin-content.php:256
msgid "Disable password reset for" msgid "Disable password reset for"
msgstr "" msgstr ""
#: wwa-admin-content.php:261 #: wwa-admin-content.php:265
msgid "Off" msgid "Off"
msgstr "" msgstr ""
#: wwa-admin-content.php:262 #: wwa-admin-content.php:266
msgid "Everyone except administrators" msgid "Everyone except administrators"
msgstr "" msgstr ""
#: wwa-admin-content.php:263 #: wwa-admin-content.php:267
msgid "Everyone" msgid "Everyone"
msgstr "" msgstr ""
#: wwa-admin-content.php:265 #: wwa-admin-content.php:269
msgid "Disable the \"Set new password\" and \"Forgot password\" features, and remove the \"Forgot password\" link on the login page. This may be useful when enabling \"WebAuthn Only\".<br>If \"Everyone except administrators\" is selected, only administrators with the \"Edit user\" permission will be able to update passwords (for all users)." msgid "Disable the \"Set new password\" and \"Forgot password\" features, and remove the \"Forgot password\" link on the login page. This may be useful when enabling \"WebAuthn Only\".<br>If \"Everyone except administrators\" is selected, only administrators with the \"Edit user\" permission will be able to update passwords (for all users)."
msgstr "" msgstr ""
#: wwa-admin-content.php:272 #: wwa-admin-content.php:276
msgid "After User Registration" msgid "After User Registration"
msgstr "" msgstr ""
#: wwa-admin-content.php:281 #: wwa-admin-content.php:285
msgid "No action" msgid "No action"
msgstr "" msgstr ""
#: wwa-admin-content.php:282 #: wwa-admin-content.php:286
msgid "Log user in and redirect to user's profile" msgid "Log user in and redirect to user's profile"
msgstr "" msgstr ""
#: wwa-admin-content.php:284 #: wwa-admin-content.php:288
msgid "What to do when a new user registered.<br>By default, new users have to login manually after registration. If \"WebAuthn Only\" is enabled, they will not be able to login.<br>When using \"Log user in\", new users will be logged in automatically and redirected to their profile settings so that they can set up WebAuthn authenticators." msgid "What to do when a new user registered.<br>By default, new users have to login manually after registration. If \"WebAuthn Only\" is enabled, they will not be able to login.<br>When using \"Log user in\", new users will be logged in automatically and redirected to their profile settings so that they can set up WebAuthn authenticators."
msgstr "" msgstr ""
#: wwa-admin-content.php:291 #: wwa-admin-content.php:295
msgid "Logging" msgid "Logging"
msgstr "" msgstr ""
#: wwa-admin-content.php:303 #: wwa-admin-content.php:307
msgid "Clear log" msgid "Clear log"
msgstr "" msgstr ""
#: wwa-admin-content.php:305 #: wwa-admin-content.php:309
msgid "For debugging only. Enable only when needed.<br><strong>Note: Logs may contain sensitive information.</strong>" msgid "For debugging only. Enable only when needed.<br><strong>Note: Logs may contain sensitive information.</strong>"
msgstr "" msgstr ""
#: wwa-admin-content.php:314 #: wwa-admin-content.php:318
msgid "Log" msgid "Log"
msgstr "" msgstr ""
#: wwa-admin-content.php:316 #: wwa-admin-content.php:320
msgid "Automatic update every 5 seconds." msgid "Automatic update every 5 seconds."
msgstr "" msgstr ""
#: wwa-admin-content.php:320 #. translators: %s: admin profile url
#: wwa-admin-content.php:325
msgid "To register a new authenticator or edit your authenticators, please go to <a href=\"%s#wwa-webauthn-start\">your profile</a>." msgid "To register a new authenticator or edit your authenticators, please go to <a href=\"%s#wwa-webauthn-start\">your profile</a>."
msgstr "" msgstr ""
#: wwa-functions.php:159 #: wwa-functions.php:159
#: wwa-shortcodes.php:88 #: wwa-shortcodes.php:91
msgid "Auth" msgid "Auth"
msgstr "" msgstr ""
#: wwa-functions.php:160 #: wwa-functions.php:160
#: wwa-shortcodes.php:11 #: wwa-shortcodes.php:11
#: wwa-shortcodes.php:85 #: wwa-shortcodes.php:81
#: wwa-shortcodes.php:88 #: wwa-shortcodes.php:90
msgid "Authenticate with WebAuthn" msgid "Authenticate with WebAuthn"
msgstr "" msgstr ""
@ -293,7 +299,7 @@ msgid "It looks like your browser doesn't support WebAuthn, which means you may
msgstr "" msgstr ""
#: wwa-functions.php:167 #: wwa-functions.php:167
#: wwa-shortcodes.php:88 #: wwa-shortcodes.php:87
msgid "Username" msgid "Username"
msgstr "" msgstr ""
@ -314,36 +320,42 @@ msgstr ""
msgid "Logging in with password has been disabled for this account." msgid "Logging in with password has been disabled for this account."
msgstr "" msgstr ""
#: wwa-functions.php:275 #. translators: %s: 'the site' or 'your account', and admin profile url
#: wwa-functions.php:276
msgid "Logging in with password has been disabled for %s but you haven't register any WebAuthn authenticator yet. You may unable to login again once you log out. <a href=\"%s#wwa-webauthn-start\">Register</a>" msgid "Logging in with password has been disabled for %s but you haven't register any WebAuthn authenticator yet. You may unable to login again once you log out. <a href=\"%s#wwa-webauthn-start\">Register</a>"
msgstr "" msgstr ""
#: wwa-functions.php:275 #. translators: %s: 'the site' or 'your account', and admin profile url
#: wwa-functions.php:320 #. translators: %s: 'the site' or 'your account'
#: wwa-functions.php:276
#: wwa-functions.php:322
msgid "the site" msgid "the site"
msgstr "" msgstr ""
#: wwa-functions.php:275 #. translators: %s: 'the site' or 'your account', and admin profile url
#: wwa-functions.php:276
msgid "your account" msgid "your account"
msgstr "" msgstr ""
#: wwa-functions.php:320 #. translators: %s: 'the site' or 'your account'
#: wwa-functions.php:322
msgid "Logging in with password has been disabled for %s but <strong>this account</strong> haven't register any WebAuthn authenticator yet. This user may unable to login." msgid "Logging in with password has been disabled for %s but <strong>this account</strong> haven't register any WebAuthn authenticator yet. This user may unable to login."
msgstr "" msgstr ""
#: wwa-functions.php:320 #. translators: %s: 'the site' or 'your account'
#: wwa-functions.php:322
msgid "this account" msgid "this account"
msgstr "" msgstr ""
#: wwa-functions.php:348 #: wwa-functions.php:350
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
#: wwa-functions.php:356 #: wwa-functions.php:358
msgid "GitHub" msgid "GitHub"
msgstr "" msgstr ""
#: wwa-functions.php:357 #: wwa-functions.php:359
msgid "Documentation" msgid "Documentation"
msgstr "" msgstr ""
@ -487,139 +499,142 @@ msgstr ""
msgid "The site administrator only allow roaming authenticators currently." msgid "The site administrator only allow roaming authenticators currently."
msgstr "" msgstr ""
#: wwa-profile-content.php:63 #: wwa-profile-content.php:64
msgid "You've successfully registered! Now you can register your authenticators below." msgid "You've successfully registered! Now you can register your authenticators below."
msgstr "" msgstr ""
#: wwa-profile-content.php:75 #: wwa-profile-content.php:76
msgid "This site is not correctly configured to use WebAuthn. Please contact the site administrator." msgid "This site is not correctly configured to use WebAuthn. Please contact the site administrator."
msgstr "" msgstr ""
#: wwa-profile-content.php:85 #: wwa-profile-content.php:86
msgid "Disable password login for this account" msgid "Disable password login for this account"
msgstr "" msgstr ""
#: wwa-profile-content.php:87 #: wwa-profile-content.php:88
msgid "When checked, password login will be completely disabled. Please make sure your browser supports WebAuthn and you have a registered authenticator, otherwise you may unable to login." msgid "When checked, password login will be completely disabled. Please make sure your browser supports WebAuthn and you have a registered authenticator, otherwise you may unable to login."
msgstr "" msgstr ""
#: wwa-profile-content.php:87 #: wwa-profile-content.php:88
msgid "The site administrator has disabled password login for the whole site." msgid "The site administrator has disabled password login for the whole site."
msgstr "" msgstr ""
#: wwa-profile-content.php:91 #: wwa-profile-content.php:92
msgid "Registered WebAuthn Authenticators" msgid "Registered WebAuthn Authenticators"
msgstr "" msgstr ""
#: wwa-profile-content.php:96
#: wwa-profile-content.php:111
#: wwa-shortcodes.php:156
#: wwa-shortcodes.php:158
msgid "Identifier"
msgstr ""
#: wwa-profile-content.php:97 #: wwa-profile-content.php:97
#: wwa-profile-content.php:112 #: wwa-profile-content.php:112
#: wwa-shortcodes.php:156 #: wwa-shortcodes.php:175
#: wwa-shortcodes.php:158 #: wwa-shortcodes.php:177
msgid "Type" msgid "Identifier"
msgstr "" msgstr ""
#: wwa-profile-content.php:98 #: wwa-profile-content.php:98
#: wwa-profile-content.php:113 #: wwa-profile-content.php:113
#: wwa-shortcodes.php:156 #: wwa-shortcodes.php:175
#: wwa-shortcodes.php:158 #: wwa-shortcodes.php:177
msgctxt "time" msgid "Type"
msgid "Registered"
msgstr "" msgstr ""
#: wwa-profile-content.php:99 #: wwa-profile-content.php:99
#: wwa-profile-content.php:114 #: wwa-profile-content.php:114
msgid "Last used" #: wwa-shortcodes.php:175
#: wwa-shortcodes.php:177
msgctxt "time"
msgid "Registered"
msgstr "" msgstr ""
#: wwa-profile-content.php:100 #: wwa-profile-content.php:100
#: wwa-profile-content.php:115 #: wwa-profile-content.php:115
#: wwa-shortcodes.php:156 #: wwa-shortcodes.php:175
#: wwa-shortcodes.php:158 #: wwa-shortcodes.php:177
msgid "Usernameless" msgid "Last used"
msgstr "" msgstr ""
#: wwa-profile-content.php:101 #: wwa-profile-content.php:101
#: wwa-profile-content.php:116 #: wwa-profile-content.php:116
#: wwa-shortcodes.php:156 #: wwa-shortcodes.php:175
#: wwa-shortcodes.php:158 #: wwa-shortcodes.php:177
msgid "Usernameless"
msgstr ""
#: wwa-profile-content.php:102
#: wwa-profile-content.php:117
#: wwa-shortcodes.php:175
#: wwa-shortcodes.php:177
msgid "Action" msgid "Action"
msgstr "" msgstr ""
#: wwa-profile-content.php:106 #: wwa-profile-content.php:107
#: wwa-shortcodes.php:157 #: wwa-shortcodes.php:176
msgid "Loading..." msgid "Loading..."
msgstr "" msgstr ""
#: wwa-profile-content.php:123 #: wwa-profile-content.php:124
#: wwa-profile-content.php:126 #: wwa-profile-content.php:127
msgid "Register New Authenticator" msgid "Register New Authenticator"
msgstr "" msgstr ""
#: wwa-profile-content.php:123 #: wwa-profile-content.php:124
#: wwa-profile-content.php:167 #: wwa-profile-content.php:169
msgid "Verify Authenticator" msgid "Verify Authenticator"
msgstr "" msgstr ""
#: wwa-profile-content.php:127 #. translators: %s: user login name
#: wwa-profile-content.php:129
msgid "You are about to associate an authenticator with the current account <strong>%s</strong>.<br>You can register multiple authenticators for an account." msgid "You are about to associate an authenticator with the current account <strong>%s</strong>.<br>You can register multiple authenticators for an account."
msgstr "" msgstr ""
#: wwa-profile-content.php:130 #: wwa-profile-content.php:132
#: wwa-shortcodes.php:118 #: wwa-shortcodes.php:125
msgid "Type of authenticator" msgid "Type of authenticator"
msgstr "" msgstr ""
#: wwa-profile-content.php:137 #: wwa-profile-content.php:139
#: wwa-shortcodes.php:118 #: wwa-shortcodes.php:128
msgid "Platform (e.g. built-in fingerprint sensors)" msgid "Platform (e.g. built-in fingerprint sensors)"
msgstr "" msgstr ""
#: wwa-profile-content.php:140 #: wwa-profile-content.php:142
#: wwa-shortcodes.php:118 #: wwa-shortcodes.php:131
msgid "If a type is selected, the browser will only prompt for authenticators of selected type. <br> Regardless of the type, you can only log in with the very same authenticators you've registered." msgid "If a type is selected, the browser will only prompt for authenticators of selected type. <br> Regardless of the type, you can only log in with the very same authenticators you've registered."
msgstr "" msgstr ""
#: wwa-profile-content.php:144 #: wwa-profile-content.php:146
msgid "Authenticator Identifier" msgid "Authenticator Identifier"
msgstr "" msgstr ""
#: wwa-profile-content.php:147 #: wwa-profile-content.php:149
#: wwa-shortcodes.php:118 #: wwa-shortcodes.php:134
msgid "An easily identifiable name for the authenticator. <strong>DOES NOT</strong> affect the authentication process in anyway." msgid "An easily identifiable name for the authenticator. <strong>DOES NOT</strong> affect the authentication process in anyway."
msgstr "" msgstr ""
#: wwa-profile-content.php:152 #: wwa-profile-content.php:154
#: wwa-shortcodes.php:118 #: wwa-shortcodes.php:135
msgid "Login without username" msgid "Login without username"
msgstr "" msgstr ""
#: wwa-profile-content.php:157 #: wwa-profile-content.php:159
#: wwa-shortcodes.php:118 #: wwa-shortcodes.php:135
msgid "If registered authenticator with this feature, you can login without enter your username.<br>Some authenticators like U2F-only authenticators and some browsers <strong>DO NOT</strong> support this feature.<br>A record will be stored in the authenticator permanently untill you reset it." msgid "If registered authenticator with this feature, you can login without enter your username.<br>Some authenticators like U2F-only authenticators and some browsers <strong>DO NOT</strong> support this feature.<br>A record will be stored in the authenticator permanently untill you reset it."
msgstr "" msgstr ""
#: wwa-profile-content.php:163 #: wwa-profile-content.php:165
msgid "Start Registration" msgid "Start Registration"
msgstr "" msgstr ""
#: wwa-profile-content.php:168 #: wwa-profile-content.php:170
msgid "Click Test Login to verify that the registered authenticators are working." msgid "Click Test Login to verify that the registered authenticators are working."
msgstr "" msgstr ""
#: wwa-profile-content.php:169 #: wwa-profile-content.php:171
#: wwa-shortcodes.php:144 #: wwa-shortcodes.php:163
msgid "Test Login" msgid "Test Login"
msgstr "" msgstr ""
#: wwa-profile-content.php:171 #: wwa-profile-content.php:173
#: wwa-shortcodes.php:144 #: wwa-shortcodes.php:163
msgid "Test Login (usernameless)" msgid "Test Login (usernameless)"
msgstr "" msgstr ""
@ -627,20 +642,20 @@ msgstr ""
msgid "Error: The username field is empty." msgid "Error: The username field is empty."
msgstr "" msgstr ""
#: wwa-shortcodes.php:88 #: wwa-shortcodes.php:91
msgid "Authenticate with password" msgid "Authenticate with password"
msgstr "" msgstr ""
#: wwa-shortcodes.php:105 #: wwa-shortcodes.php:110
#: wwa-shortcodes.php:133 #: wwa-shortcodes.php:152
#: wwa-shortcodes.php:166 #: wwa-shortcodes.php:185
msgid "You haven't logged in yet." msgid "You haven't logged in yet."
msgstr "" msgstr ""
#: wwa-shortcodes.php:118 #: wwa-shortcodes.php:132
msgid "Authenticator identifier" msgid "Authenticator identifier"
msgstr "" msgstr ""
#: wwa-shortcodes.php:118 #: wwa-shortcodes.php:136
msgid "Start registration" msgid "Start registration"
msgstr "" msgstr ""

View File

@ -1,10 +1,10 @@
=== WP-WebAuthn === === WP-WebAuthn ===
Contributors: axton Contributors: axton
Donate link: https://flyhigher.top/about Donate link: https://flyhigher.top/about
Tags: u2f, fido, fido2, webauthn, passkey, login, security, password, authentication Tags: u2f, webauthn, passkey, login, security
Requires at least: 5.0 Requires at least: 5.0
Tested up to: 6.3 Tested up to: 6.6
Stable tag: 1.3.1 Stable tag: 1.3.4
Requires PHP: 7.2 Requires PHP: 7.2
License: GPLv3 License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html License URI: https://www.gnu.org/licenses/gpl-3.0.html
@ -80,6 +80,19 @@ To use FaceID or TouchID, you need to use iOS/iPadOS 14+.
== Changelog == == Changelog ==
= 1.3.4 =
Fix: Make sure AJAX works with extra spaces/new lines
Note: We'll soon drop support for PHP 7.4 and below. Please upgrade your PHP version to 8.0+.
= 1.3.3 =
Fix: Support for PHP 7.2+
= 1.3.2 =
Fix: XSS issues in login form shortcode
Fix: Wrong user variable check (thanks to bfren)
Fix: Javascript error in login form shortcode (thanks to David Stone)
Fix: Javascript error with certain authenticator names in authenticator list tables
= 1.3.1 = = 1.3.1 =
Update: Translations Update: Translations

View File

@ -22,4 +22,4 @@ if (PHP_VERSION_ID < 50600) {
require_once __DIR__ . '/composer/autoload_real.php'; require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit09e765e3690d5165ed98a315471eec7d::getLoader(); return ComposerAutoloaderInite99fdfd0dbb5e609b534e430fe6b54ef::getLoader();

View File

@ -91,6 +91,7 @@ return array(
'CBOR\\TextStringWithChunkObject' => $vendorDir . '/spomky-labs/cbor-php/src/TextStringWithChunkObject.php', 'CBOR\\TextStringWithChunkObject' => $vendorDir . '/spomky-labs/cbor-php/src/TextStringWithChunkObject.php',
'CBOR\\UnsignedIntegerObject' => $vendorDir . '/spomky-labs/cbor-php/src/UnsignedIntegerObject.php', 'CBOR\\UnsignedIntegerObject' => $vendorDir . '/spomky-labs/cbor-php/src/UnsignedIntegerObject.php',
'CBOR\\Utils' => $vendorDir . '/spomky-labs/cbor-php/src/Utils.php', 'CBOR\\Utils' => $vendorDir . '/spomky-labs/cbor-php/src/Utils.php',
'CURLStringFile' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'Cose\\Algorithm\\Algorithm' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Algorithm.php', 'Cose\\Algorithm\\Algorithm' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Algorithm.php',
'Cose\\Algorithm\\Mac\\HS256' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Mac/HS256.php', 'Cose\\Algorithm\\Mac\\HS256' => $vendorDir . '/web-auth/cose-lib/src/Algorithm/Mac/HS256.php',

View File

@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer // autoload_real.php @generated by Composer
class ComposerAutoloaderInit09e765e3690d5165ed98a315471eec7d class ComposerAutoloaderInite99fdfd0dbb5e609b534e430fe6b54ef
{ {
private static $loader; private static $loader;
@ -22,16 +22,16 @@ class ComposerAutoloaderInit09e765e3690d5165ed98a315471eec7d
return self::$loader; return self::$loader;
} }
spl_autoload_register(array('ComposerAutoloaderInit09e765e3690d5165ed98a315471eec7d', 'loadClassLoader'), true, true); spl_autoload_register(array('ComposerAutoloaderInite99fdfd0dbb5e609b534e430fe6b54ef', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit09e765e3690d5165ed98a315471eec7d', 'loadClassLoader')); spl_autoload_unregister(array('ComposerAutoloaderInite99fdfd0dbb5e609b534e430fe6b54ef', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php'; require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit09e765e3690d5165ed98a315471eec7d::getInitializer($loader)); call_user_func(\Composer\Autoload\ComposerStaticInite99fdfd0dbb5e609b534e430fe6b54ef::getInitializer($loader));
$loader->register(true); $loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInit09e765e3690d5165ed98a315471eec7d::$files; $filesToLoad = \Composer\Autoload\ComposerStaticInite99fdfd0dbb5e609b534e430fe6b54ef::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) { $requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;

View File

@ -4,7 +4,7 @@
namespace Composer\Autoload; namespace Composer\Autoload;
class ComposerStaticInit09e765e3690d5165ed98a315471eec7d class ComposerStaticInite99fdfd0dbb5e609b534e430fe6b54ef
{ {
public static $files = array ( public static $files = array (
'a4ecaeafb8cfb009ad0e052c90355e98' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/functions.php', 'a4ecaeafb8cfb009ad0e052c90355e98' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/functions.php',
@ -359,6 +359,7 @@ class ComposerStaticInit09e765e3690d5165ed98a315471eec7d
'CBOR\\TextStringWithChunkObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/TextStringWithChunkObject.php', 'CBOR\\TextStringWithChunkObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/TextStringWithChunkObject.php',
'CBOR\\UnsignedIntegerObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/UnsignedIntegerObject.php', 'CBOR\\UnsignedIntegerObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/UnsignedIntegerObject.php',
'CBOR\\Utils' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Utils.php', 'CBOR\\Utils' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/Utils.php',
'CURLStringFile' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'Cose\\Algorithm\\Algorithm' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Algorithm.php', 'Cose\\Algorithm\\Algorithm' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Algorithm.php',
'Cose\\Algorithm\\Mac\\HS256' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Mac/HS256.php', 'Cose\\Algorithm\\Mac\\HS256' => __DIR__ . '/..' . '/web-auth/cose-lib/src/Algorithm/Mac/HS256.php',
@ -939,9 +940,9 @@ class ComposerStaticInit09e765e3690d5165ed98a315471eec7d
public static function getInitializer(ClassLoader $loader) public static function getInitializer(ClassLoader $loader)
{ {
return \Closure::bind(function () use ($loader) { return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit09e765e3690d5165ed98a315471eec7d::$prefixLengthsPsr4; $loader->prefixLengthsPsr4 = ComposerStaticInite99fdfd0dbb5e609b534e430fe6b54ef::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit09e765e3690d5165ed98a315471eec7d::$prefixDirsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInite99fdfd0dbb5e609b534e430fe6b54ef::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit09e765e3690d5165ed98a315471eec7d::$classMap; $loader->classMap = ComposerStaticInite99fdfd0dbb5e609b534e430fe6b54ef::$classMap;
}, null, ClassLoader::class); }, null, ClassLoader::class);
} }

View File

@ -232,17 +232,17 @@
}, },
{ {
"name": "league/uri", "name": "league/uri",
"version": "6.7.2", "version": "6.8.0",
"version_normalized": "6.7.2.0", "version_normalized": "6.8.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/thephpleague/uri.git", "url": "https://github.com/thephpleague/uri.git",
"reference": "d3b50812dd51f3fbf176344cc2981db03d10fe06" "reference": "a700b4656e4c54371b799ac61e300ab25a2d1d39"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/thephpleague/uri/zipball/d3b50812dd51f3fbf176344cc2981db03d10fe06", "url": "https://api.github.com/repos/thephpleague/uri/zipball/a700b4656e4c54371b799ac61e300ab25a2d1d39",
"reference": "d3b50812dd51f3fbf176344cc2981db03d10fe06", "reference": "a700b4656e4c54371b799ac61e300ab25a2d1d39",
"shasum": "", "shasum": "",
"mirrors": [ "mirrors": [
{ {
@ -254,22 +254,23 @@
"require": { "require": {
"ext-json": "*", "ext-json": "*",
"league/uri-interfaces": "^2.3", "league/uri-interfaces": "^2.3",
"php": "^7.4 || ^8.0", "php": "^8.1",
"psr/http-message": "^1.0" "psr/http-message": "^1.0.1"
}, },
"conflict": { "conflict": {
"league/uri-schemes": "^1.0" "league/uri-schemes": "^1.0"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "^v3.3.2", "friendsofphp/php-cs-fixer": "^v3.9.5",
"nyholm/psr7": "^1.5", "nyholm/psr7": "^1.5.1",
"php-http/psr7-integration-tests": "^1.1", "php-http/psr7-integration-tests": "^1.1.1",
"phpstan/phpstan": "^1.2.0", "phpbench/phpbench": "^1.2.6",
"phpstan/phpstan": "^1.8.5",
"phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.0.0", "phpstan/phpstan-phpunit": "^1.1.1",
"phpstan/phpstan-strict-rules": "^1.1.0", "phpstan/phpstan-strict-rules": "^1.4.3",
"phpunit/phpunit": "^9.5.10", "phpunit/phpunit": "^9.5.24",
"psr/http-factory": "^1.0" "psr/http-factory": "^1.0.1"
}, },
"suggest": { "suggest": {
"ext-fileinfo": "Needed to create Data URI from a filepath", "ext-fileinfo": "Needed to create Data URI from a filepath",
@ -277,7 +278,7 @@
"league/uri-components": "Needed to easily manipulate URI objects", "league/uri-components": "Needed to easily manipulate URI objects",
"psr/http-factory": "Needed to use the URI factory" "psr/http-factory": "Needed to use the URI factory"
}, },
"time": "2022-09-13T19:50:42+00:00", "time": "2022-09-13T19:58:47+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@ -328,7 +329,7 @@
"docs": "https://uri.thephpleague.com", "docs": "https://uri.thephpleague.com",
"forum": "https://thephpleague.slack.com", "forum": "https://thephpleague.slack.com",
"issues": "https://github.com/thephpleague/uri/issues", "issues": "https://github.com/thephpleague/uri/issues",
"source": "https://github.com/thephpleague/uri/tree/6.7.2" "source": "https://github.com/thephpleague/uri/tree/6.8.0"
}, },
"funding": [ "funding": [
{ {
@ -420,17 +421,17 @@
}, },
{ {
"name": "nyholm/psr7", "name": "nyholm/psr7",
"version": "1.8.0", "version": "1.8.2",
"version_normalized": "1.8.0.0", "version_normalized": "1.8.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Nyholm/psr7.git", "url": "https://github.com/Nyholm/psr7.git",
"reference": "3cb4d163b58589e47b35103e8e5e6a6a475b47be" "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Nyholm/psr7/zipball/3cb4d163b58589e47b35103e8e5e6a6a475b47be", "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
"reference": "3cb4d163b58589e47b35103e8e5e6a6a475b47be", "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
"shasum": "", "shasum": "",
"mirrors": [ "mirrors": [
{ {
@ -456,7 +457,7 @@
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
"symfony/error-handler": "^4.4" "symfony/error-handler": "^4.4"
}, },
"time": "2023-05-02T11:26:24+00:00", "time": "2024-09-09T07:06:30+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@ -491,7 +492,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/Nyholm/psr7/issues", "issues": "https://github.com/Nyholm/psr7/issues",
"source": "https://github.com/Nyholm/psr7/tree/1.8.0" "source": "https://github.com/Nyholm/psr7/tree/1.8.2"
}, },
"funding": [ "funding": [
{ {
@ -582,17 +583,17 @@
}, },
{ {
"name": "psr/http-client", "name": "psr/http-client",
"version": "1.0.1", "version": "1.0.3",
"version_normalized": "1.0.1.0", "version_normalized": "1.0.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-fig/http-client.git", "url": "https://github.com/php-fig/http-client.git",
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
"shasum": "", "shasum": "",
"mirrors": [ "mirrors": [
{ {
@ -603,9 +604,9 @@
}, },
"require": { "require": {
"php": "^7.0 || ^8.0", "php": "^7.0 || ^8.0",
"psr/http-message": "^1.0" "psr/http-message": "^1.0 || ^2.0"
}, },
"time": "2020-06-29T06:28:15+00:00", "time": "2023-09-23T14:17:50+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@ -625,7 +626,7 @@
"authors": [ "authors": [
{ {
"name": "PHP-FIG", "name": "PHP-FIG",
"homepage": "http://www.php-fig.org/" "homepage": "https://www.php-fig.org/"
} }
], ],
"description": "Common interface for HTTP clients", "description": "Common interface for HTTP clients",
@ -637,7 +638,7 @@
"psr-18" "psr-18"
], ],
"support": { "support": {
"source": "https://github.com/php-fig/http-client/tree/master" "source": "https://github.com/php-fig/http-client"
}, },
"install-path": "../psr/http-client" "install-path": "../psr/http-client"
}, },
@ -1197,17 +1198,17 @@
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.27.0", "version": "v1.31.0",
"version_normalized": "1.27.0.0", "version_normalized": "1.31.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git", "url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a" "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a", "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": "", "shasum": "",
"mirrors": [ "mirrors": [
{ {
@ -1217,7 +1218,7 @@
] ]
}, },
"require": { "require": {
"php": ">=7.1" "php": ">=7.2"
}, },
"provide": { "provide": {
"ext-ctype": "*" "ext-ctype": "*"
@ -1225,12 +1226,9 @@
"suggest": { "suggest": {
"ext-ctype": "For best performance" "ext-ctype": "For best performance"
}, },
"time": "2022-11-03T14:55:06+00:00", "time": "2024-09-09T11:45:10+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill" "url": "https://github.com/symfony/polyfill"
@ -1268,7 +1266,7 @@
"portable" "portable"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
}, },
"funding": [ "funding": [
{ {
@ -1288,17 +1286,17 @@
}, },
{ {
"name": "symfony/polyfill-php80", "name": "symfony/polyfill-php80",
"version": "v1.27.0", "version": "v1.31.0",
"version_normalized": "1.27.0.0", "version_normalized": "1.31.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php80.git", "url": "https://github.com/symfony/polyfill-php80.git",
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
"shasum": "", "shasum": "",
"mirrors": [ "mirrors": [
{ {
@ -1308,14 +1306,11 @@
] ]
}, },
"require": { "require": {
"php": ">=7.1" "php": ">=7.2"
}, },
"time": "2022-11-03T14:55:06+00:00", "time": "2024-09-09T11:45:10+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill" "url": "https://github.com/symfony/polyfill"
@ -1360,7 +1355,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0"
}, },
"funding": [ "funding": [
{ {
@ -1380,17 +1375,17 @@
}, },
{ {
"name": "symfony/polyfill-php81", "name": "symfony/polyfill-php81",
"version": "v1.27.0", "version": "v1.30.0",
"version_normalized": "1.27.0.0", "version_normalized": "1.30.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php81.git", "url": "https://github.com/symfony/polyfill-php81.git",
"reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af",
"reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af",
"shasum": "", "shasum": "",
"mirrors": [ "mirrors": [
{ {
@ -1402,12 +1397,9 @@
"require": { "require": {
"php": ">=7.1" "php": ">=7.1"
}, },
"time": "2022-11-03T14:55:06+00:00", "time": "2024-06-19T12:30:46+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill" "url": "https://github.com/symfony/polyfill"
@ -1448,7 +1440,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0"
}, },
"funding": [ "funding": [
{ {
@ -1468,17 +1460,17 @@
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
"version": "v5.4.28", "version": "v5.4.40",
"version_normalized": "5.4.28.0", "version_normalized": "5.4.40.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/process.git", "url": "https://github.com/symfony/process.git",
"reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b" "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b", "url": "https://api.github.com/repos/symfony/process/zipball/deedcb3bb4669cae2148bc920eafd2b16dc7c046",
"reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b", "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046",
"shasum": "", "shasum": "",
"mirrors": [ "mirrors": [
{ {
@ -1491,7 +1483,7 @@
"php": ">=7.2.5", "php": ">=7.2.5",
"symfony/polyfill-php80": "^1.16" "symfony/polyfill-php80": "^1.16"
}, },
"time": "2023-08-07T10:36:04+00:00", "time": "2024-05-31T14:33:22+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
@ -1519,7 +1511,7 @@
"description": "Executes commands in sub-processes", "description": "Executes commands in sub-processes",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/process/tree/v5.4.28" "source": "https://github.com/symfony/process/tree/v5.4.40"
}, },
"funding": [ "funding": [
{ {
@ -2009,6 +2001,7 @@
"type": "patreon" "type": "patreon"
} }
], ],
"abandoned": "web-token/jwt-library",
"install-path": "../web-token/jwt-core" "install-path": "../web-token/jwt-core"
}, },
{ {
@ -2095,6 +2088,7 @@
"type": "patreon" "type": "patreon"
} }
], ],
"abandoned": "web-token/jwt-library",
"install-path": "../web-token/jwt-key-mgmt" "install-path": "../web-token/jwt-key-mgmt"
}, },
{ {
@ -2180,6 +2174,7 @@
"type": "patreon" "type": "patreon"
} }
], ],
"abandoned": "web-token/jwt-library",
"install-path": "../web-token/jwt-signature" "install-path": "../web-token/jwt-signature"
}, },
{ {
@ -2258,6 +2253,7 @@
"type": "patreon" "type": "patreon"
} }
], ],
"abandoned": "web-token/jwt-library",
"install-path": "../web-token/jwt-signature-algorithm-ecdsa" "install-path": "../web-token/jwt-signature-algorithm-ecdsa"
}, },
{ {
@ -2336,6 +2332,7 @@
"type": "patreon" "type": "patreon"
} }
], ],
"abandoned": "web-token/jwt-library",
"install-path": "../web-token/jwt-signature-algorithm-eddsa" "install-path": "../web-token/jwt-signature-algorithm-eddsa"
}, },
{ {
@ -2419,6 +2416,7 @@
"type": "patreon" "type": "patreon"
} }
], ],
"abandoned": "web-token/jwt-library",
"install-path": "../web-token/jwt-signature-algorithm-rsa" "install-path": "../web-token/jwt-signature-algorithm-rsa"
} }
], ],

View File

@ -1,9 +1,9 @@
<?php return array( <?php return array(
'root' => array( 'root' => array(
'name' => '__root__', 'name' => '__root__',
'pretty_version' => '1.3.1', 'pretty_version' => '1.3.4',
'version' => '1.3.1.0', 'version' => '1.3.4.0',
'reference' => '7ba904e1020a15ea593301b05ecfc4d74aa61570', 'reference' => '88207a7a7032f209d572a80f06699769886cdeb3',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@ -11,9 +11,9 @@
), ),
'versions' => array( 'versions' => array(
'__root__' => array( '__root__' => array(
'pretty_version' => '1.3.1', 'pretty_version' => '1.3.4',
'version' => '1.3.1.0', 'version' => '1.3.4.0',
'reference' => '7ba904e1020a15ea593301b05ecfc4d74aa61570', 'reference' => '88207a7a7032f209d572a80f06699769886cdeb3',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@ -47,9 +47,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'league/uri' => array( 'league/uri' => array(
'pretty_version' => '6.7.2', 'pretty_version' => '6.8.0',
'version' => '6.7.2.0', 'version' => '6.8.0.0',
'reference' => 'd3b50812dd51f3fbf176344cc2981db03d10fe06', 'reference' => 'a700b4656e4c54371b799ac61e300ab25a2d1d39',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../league/uri', 'install_path' => __DIR__ . '/../league/uri',
'aliases' => array(), 'aliases' => array(),
@ -65,9 +65,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'nyholm/psr7' => array( 'nyholm/psr7' => array(
'pretty_version' => '1.8.0', 'pretty_version' => '1.8.2',
'version' => '1.8.0.0', 'version' => '1.8.2.0',
'reference' => '3cb4d163b58589e47b35103e8e5e6a6a475b47be', 'reference' => 'a71f2b11690f4b24d099d6b16690a90ae14fc6f3',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../nyholm/psr7', 'install_path' => __DIR__ . '/../nyholm/psr7',
'aliases' => array(), 'aliases' => array(),
@ -89,9 +89,9 @@
), ),
), ),
'psr/http-client' => array( 'psr/http-client' => array(
'pretty_version' => '1.0.1', 'pretty_version' => '1.0.3',
'version' => '1.0.1.0', 'version' => '1.0.3.0',
'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621', 'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-client', 'install_path' => __DIR__ . '/../psr/http-client',
'aliases' => array(), 'aliases' => array(),
@ -179,36 +179,36 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-ctype' => array( 'symfony/polyfill-ctype' => array(
'pretty_version' => 'v1.27.0', 'pretty_version' => 'v1.31.0',
'version' => '1.27.0.0', 'version' => '1.31.0.0',
'reference' => '5bbc823adecdae860bb64756d639ecfec17b050a', 'reference' => 'a3cc8b044a6ea513310cbd48ef7333b384945638',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-ctype', 'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-php80' => array( 'symfony/polyfill-php80' => array(
'pretty_version' => 'v1.27.0', 'pretty_version' => 'v1.31.0',
'version' => '1.27.0.0', 'version' => '1.31.0.0',
'reference' => '7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936', 'reference' => '60328e362d4c2c802a54fcbf04f9d3fb892b4cf8',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'install_path' => __DIR__ . '/../symfony/polyfill-php80',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-php81' => array( 'symfony/polyfill-php81' => array(
'pretty_version' => 'v1.27.0', 'pretty_version' => 'v1.30.0',
'version' => '1.27.0.0', 'version' => '1.30.0.0',
'reference' => '707403074c8ea6e2edaf8794b0157a0bfa52157a', 'reference' => '3fb075789fb91f9ad9af537c4012d523085bd5af',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php81', 'install_path' => __DIR__ . '/../symfony/polyfill-php81',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/process' => array( 'symfony/process' => array(
'pretty_version' => 'v5.4.28', 'pretty_version' => 'v5.4.40',
'version' => '5.4.28.0', 'version' => '5.4.40.0',
'reference' => '45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b', 'reference' => 'deedcb3bb4669cae2148bc920eafd2b16dc7c046',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/process', 'install_path' => __DIR__ . '/../symfony/process',
'aliases' => array(), 'aliases' => array(),

View File

@ -44,21 +44,22 @@
} }
], ],
"require": { "require": {
"php": "^7.4 || ^8.0", "php": "^8.1",
"ext-json": "*", "ext-json": "*",
"psr/http-message": "^1.0", "psr/http-message": "^1.0.1",
"league/uri-interfaces": "^2.3" "league/uri-interfaces": "^2.3"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "^v3.3.2", "friendsofphp/php-cs-fixer": "^v3.9.5",
"nyholm/psr7": "^1.5", "nyholm/psr7": "^1.5.1",
"php-http/psr7-integration-tests": "^1.1", "php-http/psr7-integration-tests": "^1.1.1",
"phpstan/phpstan": "^1.2.0", "phpbench/phpbench": "^1.2.6",
"phpstan/phpstan": "^1.8.5",
"phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.0.0", "phpstan/phpstan-phpunit": "^1.1.1",
"phpstan/phpstan-strict-rules": "^1.1.0", "phpstan/phpstan-strict-rules": "^1.4.3",
"phpunit/phpunit": "^9.5.10", "phpunit/phpunit": "^9.5.24",
"psr/http-factory": "^1.0" "psr/http-factory": "^1.0.1"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@ -74,6 +75,7 @@
"league/uri-schemes": "^1.0" "league/uri-schemes": "^1.0"
}, },
"scripts": { "scripts": {
"benchmark": "phpbench run src --report=default",
"phpcs": "php-cs-fixer fix -v --diff --dry-run --allow-risky=yes --ansi", "phpcs": "php-cs-fixer fix -v --diff --dry-run --allow-risky=yes --ansi",
"phpcs:fix": "php-cs-fixer fix -vvv --allow-risky=yes --ansi", "phpcs:fix": "php-cs-fixer fix -vvv --allow-risky=yes --ansi",
"phpstan": "phpstan analyse -l max -c phpstan.neon src --ansi --memory-limit=256M", "phpstan": "phpstan analyse -l max -c phpstan.neon src --ansi --memory-limit=256M",

View File

@ -17,19 +17,14 @@ use JsonSerializable;
use League\Uri\Contracts\UriInterface; use League\Uri\Contracts\UriInterface;
use League\Uri\Exceptions\SyntaxError; use League\Uri\Exceptions\SyntaxError;
use Psr\Http\Message\UriInterface as Psr7UriInterface; use Psr\Http\Message\UriInterface as Psr7UriInterface;
use function is_object; use Stringable;
use function is_scalar; use function is_scalar;
use function method_exists;
use function sprintf;
final class Http implements Psr7UriInterface, JsonSerializable final class Http implements Psr7UriInterface, JsonSerializable
{ {
private UriInterface $uri; private function __construct(private readonly UriInterface $uri)
private function __construct(UriInterface $uri)
{ {
$this->validate($uri); $this->validate($this->uri);
$this->uri = $uri;
} }
/** /**
@ -39,19 +34,18 @@ final class Http implements Psr7UriInterface, JsonSerializable
*/ */
private function validate(UriInterface $uri): void private function validate(UriInterface $uri): void
{ {
$scheme = $uri->getScheme(); if (null === $uri->getScheme() && '' === $uri->getHost()) {
if (null === $scheme && '' === $uri->getHost()) { throw new SyntaxError('An URI without scheme can not contains a empty host string according to PSR-7: '.$uri);
throw new SyntaxError(sprintf('an URI without scheme can not contains a empty host string according to PSR-7: %s', (string) $uri));
} }
$port = $uri->getPort(); $port = $uri->getPort();
if (null !== $port && ($port < 0 || $port > 65535)) { if (null !== $port && ($port < 0 || $port > 65535)) {
throw new SyntaxError(sprintf('The URI port is outside the established TCP and UDP port ranges: %s', (string) $uri->getPort())); throw new SyntaxError('The URI port is outside the established TCP and UDP port ranges: '.$uri);
} }
} }
/** /**
* Static method called by PHP's var export. * @param array{uri:UriInterface} $components
*/ */
public static function __set_state(array $components): self public static function __set_state(array $components): self
{ {
@ -60,10 +54,8 @@ final class Http implements Psr7UriInterface, JsonSerializable
/** /**
* Create a new instance from a string. * Create a new instance from a string.
*
* @param string|mixed $uri
*/ */
public static function createFromString($uri = ''): self public static function createFromString(Stringable|UriInterface|String $uri = ''): self
{ {
return new self(Uri::createFromString($uri)); return new self(Uri::createFromString($uri));
} }
@ -91,21 +83,18 @@ final class Http implements Psr7UriInterface, JsonSerializable
* Create a new instance from a URI and a Base URI. * Create a new instance from a URI and a Base URI.
* *
* The returned URI must be absolute. * The returned URI must be absolute.
*
* @param mixed $uri the input URI to create
* @param mixed $base_uri the base URI used for reference
*/ */
public static function createFromBaseUri($uri, $base_uri = null): self public static function createFromBaseUri(
{ Stringable|UriInterface|String $uri,
Stringable|UriInterface|String $base_uri = null
): self {
return new self(Uri::createFromBaseUri($uri, $base_uri)); return new self(Uri::createFromBaseUri($uri, $base_uri));
} }
/** /**
* Create a new instance from a URI object. * Create a new instance from a URI object.
*
* @param Psr7UriInterface|UriInterface $uri the input URI to create
*/ */
public static function createFromUri($uri): self public static function createFromUri(Psr7UriInterface|UriInterface $uri): self
{ {
if ($uri instanceof UriInterface) { if ($uri instanceof UriInterface) {
return new self($uri); return new self($uri);
@ -178,145 +167,6 @@ final class Http implements Psr7UriInterface, JsonSerializable
return (string) $this->uri->getFragment(); return (string) $this->uri->getFragment();
} }
/**
* {@inheritDoc}
*/
public function withScheme($scheme): self
{
/** @var string $scheme */
$scheme = $this->filterInput($scheme);
if ('' === $scheme) {
$scheme = null;
}
$uri = $this->uri->withScheme($scheme);
if ((string) $uri === (string) $this->uri) {
return $this;
}
return new self($uri);
}
/**
* Safely stringify input when possible.
*
* @param mixed $str the value to evaluate as a string
*
* @throws SyntaxError if the submitted data can not be converted to string
*
* @return string|mixed
*/
private function filterInput($str)
{
if (is_scalar($str) || (is_object($str) && method_exists($str, '__toString'))) {
return (string) $str;
}
return $str;
}
/**
* {@inheritDoc}
*/
public function withUserInfo($user, $password = null): self
{
/** @var string $user */
$user = $this->filterInput($user);
if ('' === $user) {
$user = null;
}
$uri = $this->uri->withUserInfo($user, $password);
if ((string) $uri === (string) $this->uri) {
return $this;
}
return new self($uri);
}
/**
* {@inheritDoc}
*/
public function withHost($host): self
{
/** @var string $host */
$host = $this->filterInput($host);
if ('' === $host) {
$host = null;
}
$uri = $this->uri->withHost($host);
if ((string) $uri === (string) $this->uri) {
return $this;
}
return new self($uri);
}
/**
* {@inheritDoc}
*/
public function withPort($port): self
{
$uri = $this->uri->withPort($port);
if ((string) $uri === (string) $this->uri) {
return $this;
}
return new self($uri);
}
/**
* {@inheritDoc}
*/
public function withPath($path): self
{
$uri = $this->uri->withPath($path);
if ((string) $uri === (string) $this->uri) {
return $this;
}
return new self($uri);
}
/**
* {@inheritDoc}
*/
public function withQuery($query): self
{
/** @var string $query */
$query = $this->filterInput($query);
if ('' === $query) {
$query = null;
}
$uri = $this->uri->withQuery($query);
if ((string) $uri === (string) $this->uri) {
return $this;
}
return new self($uri);
}
/**
* {@inheritDoc}
*/
public function withFragment($fragment): self
{
/** @var string $fragment */
$fragment = $this->filterInput($fragment);
if ('' === $fragment) {
$fragment = null;
}
$uri = $this->uri->withFragment($fragment);
if ((string) $uri === (string) $this->uri) {
return $this;
}
return new self($uri);
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -332,4 +182,88 @@ final class Http implements Psr7UriInterface, JsonSerializable
{ {
return $this->uri->__toString(); return $this->uri->__toString();
} }
/**
* Safely stringify input when possible for League UriInterface compatibility.
*
* @throws SyntaxError
*/
private function filterInput(mixed $str): string|null
{
if (!is_scalar($str) && !$str instanceof Stringable) {
throw new SyntaxError('The component must be a string, a scalar or a Stringable object; `'.gettype($str).'` given.');
}
$str = (string) $str;
if ('' === $str) {
return null;
}
return $str;
}
private function newInstance(UriInterface $uri): self
{
if ((string) $uri === (string) $this->uri) {
return $this;
}
return new self($uri);
}
/**
* {@inheritDoc}
*/
public function withScheme($scheme): self
{
return $this->newInstance($this->uri->withScheme($this->filterInput($scheme)));
}
/**
* {@inheritDoc}
*/
public function withUserInfo($user, $password = null): self
{
return $this->newInstance($this->uri->withUserInfo($this->filterInput($user), $password));
}
/**
* {@inheritDoc}
*/
public function withHost($host): self
{
return $this->newInstance($this->uri->withHost($this->filterInput($host)));
}
/**
* {@inheritDoc}
*/
public function withPort($port): self
{
return $this->newInstance($this->uri->withPort($port));
}
/**
* {@inheritDoc}
*/
public function withPath($path): self
{
return $this->newInstance($this->uri->withPath($path));
}
/**
* {@inheritDoc}
*/
public function withQuery($query): self
{
return $this->newInstance($this->uri->withQuery($this->filterInput($query)));
}
/**
* {@inheritDoc}
*/
public function withFragment($fragment): self
{
return $this->newInstance($this->uri->withFragment($this->filterInput($fragment)));
}
} }

View File

@ -21,8 +21,11 @@ use League\Uri\Exceptions\IdnSupportMissing;
use League\Uri\Exceptions\SyntaxError; use League\Uri\Exceptions\SyntaxError;
use League\Uri\Idna\Idna; use League\Uri\Idna\Idna;
use Psr\Http\Message\UriInterface as Psr7UriInterface; use Psr\Http\Message\UriInterface as Psr7UriInterface;
use SensitiveParameter;
use Stringable;
use TypeError; use TypeError;
use function array_filter; use function array_filter;
use function array_key_first;
use function array_map; use function array_map;
use function base64_decode; use function base64_decode;
use function base64_encode; use function base64_encode;
@ -33,14 +36,15 @@ use function filter_var;
use function implode; use function implode;
use function in_array; use function in_array;
use function inet_pton; use function inet_pton;
use function is_object; use function is_int;
use function is_scalar; use function is_scalar;
use function method_exists; use function is_string;
use function ltrim;
use function preg_match; use function preg_match;
use function preg_replace; use function preg_replace;
use function preg_replace_callback; use function preg_replace_callback;
use function rawurlencode; use function rawurlencode;
use function sprintf; use function str_contains;
use function str_replace; use function str_replace;
use function strlen; use function strlen;
use function strpos; use function strpos;
@ -81,14 +85,14 @@ final class Uri implements UriInterface
* *
* @var string * @var string
*/ */
private const REGEXP_CHARS_UNRESERVED = 'A-Za-z0-9_\-\.~'; private const REGEXP_CHARS_UNRESERVED = 'A-Za-z\d_\-\.~';
/** /**
* RFC3986 schema regular expression pattern. * RFC3986 schema regular expression pattern.
* *
* @link https://tools.ietf.org/html/rfc3986#section-3.1 * @link https://tools.ietf.org/html/rfc3986#section-3.1
*/ */
private const REGEXP_SCHEME = ',^[a-z]([-a-z0-9+.]+)?$,i'; private const REGEXP_SCHEME = ',^[a-z]([-a-z\d+.]+)?$,i';
/** /**
* RFC3986 host identified by a registered name regular expression pattern. * RFC3986 host identified by a registered name regular expression pattern.
@ -96,9 +100,9 @@ final class Uri implements UriInterface
* @link https://tools.ietf.org/html/rfc3986#section-3.2.2 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
*/ */
private const REGEXP_HOST_REGNAME = '/^( private const REGEXP_HOST_REGNAME = '/^(
(?<unreserved>[a-z0-9_~\-\.])| (?<unreserved>[a-z\d_~\-\.])|
(?<sub_delims>[!$&\'()*+,;=])| (?<sub_delims>[!$&\'()*+,;=])|
(?<encoded>%[A-F0-9]{2}) (?<encoded>%[A-F\d]{2})
)+$/x'; )+$/x';
/** /**
@ -114,9 +118,9 @@ final class Uri implements UriInterface
* @link https://tools.ietf.org/html/rfc3986#section-3.2.2 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
*/ */
private const REGEXP_HOST_IPFUTURE = '/^ private const REGEXP_HOST_IPFUTURE = '/^
v(?<version>[A-F0-9])+\. v(?<version>[A-F\d])+\.
(?: (?:
(?<unreserved>[a-z0-9_~\-\.])| (?<unreserved>[a-z\d_~\-\.])|
(?<sub_delims>[!$&\'()*+,;=:]) # also include the : character (?<sub_delims>[!$&\'()*+,;=:]) # also include the : character
)+ )+
$/ix'; $/ix';
@ -176,20 +180,11 @@ final class Uri implements UriInterface
]; ];
/** /**
* URI validation methods per scheme. * Maximum number of formatted host cached.
* *
* @var array<string> * @var int
*/ */
private const SCHEME_VALIDATION_METHOD = [ private const MAXIMUM_FORMATTED_HOST_CACHED = 100;
'data' => 'isUriWithSchemeAndPathOnly',
'file' => 'isUriWithSchemeHostAndPathOnly',
'ftp' => 'isNonEmptyHostUriWithoutFragmentAndQuery',
'gopher' => 'isNonEmptyHostUriWithoutFragmentAndQuery',
'http' => 'isNonEmptyHostUri',
'https' => 'isNonEmptyHostUri',
'ws' => 'isNonEmptyHostUriWithoutFragment',
'wss' => 'isNonEmptyHostUriWithoutFragment',
];
/** /**
* All ASCII letters sorted by typical frequency of occurrence. * All ASCII letters sorted by typical frequency of occurrence.
@ -203,7 +198,7 @@ final class Uri implements UriInterface
private ?string $host; private ?string $host;
private ?int $port; private ?int $port;
private ?string $authority; private ?string $authority;
private string $path = ''; private string $path;
private ?string $query; private ?string $query;
private ?string $fragment; private ?string $fragment;
private ?string $uri; private ?string $uri;
@ -211,7 +206,7 @@ final class Uri implements UriInterface
private function __construct( private function __construct(
?string $scheme, ?string $scheme,
?string $user, ?string $user,
?string $pass, #[SensitiveParameter] ?string $pass,
?string $host, ?string $host,
?int $port, ?int $port,
string $path, string $path,
@ -226,49 +221,49 @@ final class Uri implements UriInterface
$this->path = $this->formatPath($path); $this->path = $this->formatPath($path);
$this->query = $this->formatQueryAndFragment($query); $this->query = $this->formatQueryAndFragment($query);
$this->fragment = $this->formatQueryAndFragment($fragment); $this->fragment = $this->formatQueryAndFragment($fragment);
$this->assertValidState(); $this->assertValidState();
} }
/** /**
* Format the Scheme and Host component. * Format the Scheme and Host component.
* *
* @param ?string $scheme
* @throws SyntaxError if the scheme is invalid * @throws SyntaxError if the scheme is invalid
*/ */
private function formatScheme(?string $scheme): ?string private function formatScheme(?string $scheme): ?string
{ {
if (null === $scheme) { if (null === $scheme || array_key_exists($scheme, self::SCHEME_DEFAULT_PORT)) {
return $scheme; return $scheme;
} }
$formatted_scheme = strtolower($scheme); $formatted_scheme = strtolower($scheme);
if (1 === preg_match(self::REGEXP_SCHEME, $formatted_scheme)) { if (array_key_exists($formatted_scheme, self::SCHEME_DEFAULT_PORT) || 1 === preg_match(self::REGEXP_SCHEME, $formatted_scheme)) {
return $formatted_scheme; return $formatted_scheme;
} }
throw new SyntaxError(sprintf('The scheme `%s` is invalid.', $scheme)); throw new SyntaxError('The scheme `'.$scheme.'` is invalid.');
} }
/** /**
* Set the UserInfo component. * Set the UserInfo component.
* @param ?string $user
* @param ?string $password
*/ */
private function formatUserInfo(?string $user, ?string $password): ?string private function formatUserInfo(
{ ?string $user,
#[SensitiveParameter] ?string $password
): ?string {
if (null === $user) { if (null === $user) {
return $user; return null;
} }
static $user_pattern = '/(?:[^%'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f0-9]{2}))/'; static $user_pattern = '/[^%'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f\d]{2})/';
$user = preg_replace_callback($user_pattern, [Uri::class, 'urlEncodeMatch'], $user); $user = preg_replace_callback($user_pattern, Uri::urlEncodeMatch(...), $user);
if (null === $password) { if (null === $password) {
return $user; return $user;
} }
static $password_pattern = '/(?:[^%:'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f0-9]{2}))/'; static $password_pattern = '/[^%:'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f\d]{2})/';
return $user.':'.preg_replace_callback($password_pattern, [Uri::class, 'urlEncodeMatch'], $password); return $user.':'.preg_replace_callback($password_pattern, Uri::urlEncodeMatch(...), $password);
} }
/** /**
@ -281,7 +276,6 @@ final class Uri implements UriInterface
/** /**
* Validate and Format the Host component. * Validate and Format the Host component.
* @param ?string $host
*/ */
private function formatHost(?string $host): ?string private function formatHost(?string $host): ?string
{ {
@ -289,11 +283,18 @@ final class Uri implements UriInterface
return $host; return $host;
} }
if ('[' !== $host[0]) { static $formattedHostCache = [];
return $this->formatRegisteredName($host); if (isset($formattedHostCache[$host])) {
return $formattedHostCache[$host];
} }
return $this->formatIp($host); $formattedHost = '[' === $host[0] ? $this->formatIp($host) : $this->formatRegisteredName($host);
$formattedHostCache[$host] = $formattedHost;
if (self::MAXIMUM_FORMATTED_HOST_CACHED < count($formattedHostCache)) {
unset($formattedHostCache[array_key_first($formattedHostCache)]);
}
return $formattedHost;
} }
/** /**
@ -312,7 +313,7 @@ final class Uri implements UriInterface
} }
if (1 === preg_match(self::REGEXP_HOST_GEN_DELIMS, $formatted_host)) { if (1 === preg_match(self::REGEXP_HOST_GEN_DELIMS, $formatted_host)) {
throw new SyntaxError(sprintf('The host `%s` is invalid : a registered name can not contain URI delimiters or spaces', $host)); throw new SyntaxError('The host `'.$host.'` is invalid : a registered name can not contain URI delimiters or spaces.');
} }
$info = Idna::toAscii($host, Idna::IDNA2008_ASCII); $info = Idna::toAscii($host, Idna::IDNA2008_ASCII);
@ -341,35 +342,33 @@ final class Uri implements UriInterface
$pos = strpos($ip, '%'); $pos = strpos($ip, '%');
if (false === $pos) { if (false === $pos) {
throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host)); throw new SyntaxError('The host `'.$host.'` is invalid : the IP host is malformed.');
} }
if (1 === preg_match(self::REGEXP_HOST_GEN_DELIMS, rawurldecode(substr($ip, $pos)))) { if (1 === preg_match(self::REGEXP_HOST_GEN_DELIMS, rawurldecode(substr($ip, $pos)))) {
throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host)); throw new SyntaxError('The host `'.$host.'` is invalid : the IP host is malformed.');
} }
$ip = substr($ip, 0, $pos); $ip = substr($ip, 0, $pos);
if (false === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { if (false === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host)); throw new SyntaxError('The host `'.$host.'` is invalid : the IP host is malformed.');
} }
//Only the address block fe80::/10 can have a Zone ID attach to //Only the address block fe80::/10 can have a Zone ID attach to
//let's detect the link local significant 10 bits //let's detect the link local significant 10 bits
if (0 === strpos((string) inet_pton($ip), self::HOST_ADDRESS_BLOCK)) { if (str_starts_with((string)inet_pton($ip), self::HOST_ADDRESS_BLOCK)) {
return $host; return $host;
} }
throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host)); throw new SyntaxError('The host `'.$host.'` is invalid : the IP host is malformed.');
} }
/** /**
* Format the Port component. * Format the Port component.
* *
* @param object|null|int|string $port
*
* @throws SyntaxError * @throws SyntaxError
*/ */
private function formatPort($port = null): ?int private function formatPort(Stringable|string|int|null $port = null): ?int
{ {
if (null === $port || '' === $port) { if (null === $port || '' === $port) {
return null; return null;
@ -381,7 +380,7 @@ final class Uri implements UriInterface
$port = (int) $port; $port = (int) $port;
if (0 > $port) { if (0 > $port) {
throw new SyntaxError(sprintf('The port `%s` is invalid', $port)); throw new SyntaxError('The port `'.$port.'` is invalid.');
} }
$defaultPort = self::SCHEME_DEFAULT_PORT[$this->scheme] ?? null; $defaultPort = self::SCHEME_DEFAULT_PORT[$this->scheme] ?? null;
@ -392,9 +391,6 @@ final class Uri implements UriInterface
return $port; return $port;
} }
/**
* {@inheritDoc}
*/
public static function __set_state(array $components): self public static function __set_state(array $components): self
{ {
$components['user'] = null; $components['user'] = null;
@ -416,22 +412,21 @@ final class Uri implements UriInterface
} }
/** /**
* Create a new instance from a URI and a Base URI. * Creates a new instance from a URI and a Base URI.
* *
* The returned URI must be absolute. * The returned URI must be absolute.
*
* @param mixed $uri the input URI to create
* @param null|mixed $base_uri the base URI used for reference
*/ */
public static function createFromBaseUri($uri, $base_uri = null): UriInterface public static function createFromBaseUri(
{ Stringable|UriInterface|String $uri,
Stringable|UriInterface|String $base_uri = null
): UriInterface {
if (!$uri instanceof UriInterface) { if (!$uri instanceof UriInterface) {
$uri = self::createFromString($uri); $uri = self::createFromString($uri);
} }
if (null === $base_uri) { if (null === $base_uri) {
if (null === $uri->getScheme()) { if (null === $uri->getScheme()) {
throw new SyntaxError(sprintf('the URI `%s` must be absolute', (string) $uri)); throw new SyntaxError('the URI `'.$uri.'` must be absolute.');
} }
if (null === $uri->getAuthority()) { if (null === $uri->getAuthority()) {
@ -449,7 +444,7 @@ final class Uri implements UriInterface
} }
if (null === $base_uri->getScheme()) { if (null === $base_uri->getScheme()) {
throw new SyntaxError(sprintf('the base URI `%s` must be absolute', (string) $base_uri)); throw new SyntaxError('the base URI `'.$base_uri.'` must be absolute.');
} }
/** @var UriInterface $uri */ /** @var UriInterface $uri */
@ -460,10 +455,8 @@ final class Uri implements UriInterface
/** /**
* Create a new instance from a string. * Create a new instance from a string.
*
* @param string|mixed $uri
*/ */
public static function createFromString($uri = ''): self public static function createFromString(Stringable|string $uri = ''): self
{ {
$components = UriString::parse($uri); $components = UriString::parse($uri);
@ -517,7 +510,7 @@ final class Uri implements UriInterface
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
if (!$finfo_support) { if (!$finfo_support) {
throw new FileinfoSupportMissing(sprintf('Please install ext/fileinfo to use the %s() method.', __METHOD__)); throw new FileinfoSupportMissing('Please install ext/fileinfo to use the '.__METHOD__.'() method.');
} }
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
@ -528,9 +521,12 @@ final class Uri implements UriInterface
$mime_args[] = $context; $mime_args[] = $context;
} }
$raw = @file_get_contents(...$file_args); set_error_handler(fn (int $errno, string $errstr, string $errfile, int $errline) => true);
$raw = file_get_contents(...$file_args);
restore_error_handler();
if (false === $raw) { if (false === $raw) {
throw new SyntaxError(sprintf('The file `%s` does not exist or is not readable', $path)); throw new SyntaxError('The file `'.$path.'` does not exist or is not readable.');
} }
$mimetype = (string) (new finfo(FILEINFO_MIME))->file(...$mime_args); $mimetype = (string) (new finfo(FILEINFO_MIME))->file(...$mime_args);
@ -546,7 +542,7 @@ final class Uri implements UriInterface
*/ */
public static function createFromUnixPath(string $uri = ''): self public static function createFromUnixPath(string $uri = ''): self
{ {
$uri = implode('/', array_map('rawurlencode', explode('/', $uri))); $uri = implode('/', array_map(rawurlencode(...), explode('/', $uri)));
if ('/' !== ($uri[0] ?? '')) { if ('/' !== ($uri[0] ?? '')) {
return Uri::createFromComponents(['path' => $uri]); return Uri::createFromComponents(['path' => $uri]);
} }
@ -565,7 +561,7 @@ final class Uri implements UriInterface
$uri = substr($uri, strlen($root)); $uri = substr($uri, strlen($root));
} }
$uri = str_replace('\\', '/', $uri); $uri = str_replace('\\', '/', $uri);
$uri = implode('/', array_map('rawurlencode', explode('/', $uri))); $uri = implode('/', array_map(rawurlencode(...), explode('/', $uri)));
//Local Windows absolute path //Local Windows absolute path
if ('' !== $root) { if ('' !== $root) {
@ -573,7 +569,7 @@ final class Uri implements UriInterface
} }
//UNC Windows Path //UNC Windows Path
if ('//' !== substr($uri, 0, 2)) { if (!str_starts_with($uri, '//')) {
return Uri::createFromComponents(['path' => $uri]); return Uri::createFromComponents(['path' => $uri]);
} }
@ -584,10 +580,8 @@ final class Uri implements UriInterface
/** /**
* Create a new instance from a URI object. * Create a new instance from a URI object.
*
* @param Psr7UriInterface|UriInterface $uri the input URI to create
*/ */
public static function createFromUri($uri): self public static function createFromUri(Psr7UriInterface|UriInterface $uri): self
{ {
if ($uri instanceof UriInterface) { if ($uri instanceof UriInterface) {
$user_info = $uri->getUserInfo(); $user_info = $uri->getUserInfo();
@ -609,10 +603,6 @@ final class Uri implements UriInterface
); );
} }
if (!$uri instanceof Psr7UriInterface) {
throw new TypeError(sprintf('The object must implement the `%s` or the `%s`', Psr7UriInterface::class, UriInterface::class));
}
$scheme = $uri->getScheme(); $scheme = $uri->getScheme();
if ('' === $scheme) { if ('' === $scheme) {
$scheme = null; $scheme = null;
@ -657,19 +647,12 @@ final class Uri implements UriInterface
*/ */
public static function createFromServer(array $server): self public static function createFromServer(array $server): self
{ {
[$user, $pass] = self::fetchUserInfo($server); $components = ['scheme' => self::fetchScheme($server)];
[$host, $port] = self::fetchHostname($server); [$components['user'], $components['pass']] = self::fetchUserInfo($server);
[$path, $query] = self::fetchRequestUri($server); [$components['host'], $components['port']] = self::fetchHostname($server);
[$components['path'], $components['query']] = self::fetchRequestUri($server);
return Uri::createFromComponents([ return Uri::createFromComponents($components);
'scheme' => self::fetchScheme($server),
'user' => $user,
'pass' => $pass,
'host' => $host,
'port' => $port,
'path' => $path,
'query' => $query,
]);
} }
/** /**
@ -686,14 +669,14 @@ final class Uri implements UriInterface
/** /**
* Returns the environment user info. * Returns the environment user info.
* *
* @return array{0:?string, 1:?string} * @return non-empty-array{0:?string, 1:?string}
*/ */
private static function fetchUserInfo(array $server): array private static function fetchUserInfo(array $server): array
{ {
$server += ['PHP_AUTH_USER' => null, 'PHP_AUTH_PW' => null, 'HTTP_AUTHORIZATION' => '']; $server += ['PHP_AUTH_USER' => null, 'PHP_AUTH_PW' => null, 'HTTP_AUTHORIZATION' => ''];
$user = $server['PHP_AUTH_USER']; $user = $server['PHP_AUTH_USER'];
$pass = $server['PHP_AUTH_PW']; $pass = $server['PHP_AUTH_PW'];
if (0 === strpos(strtolower($server['HTTP_AUTHORIZATION']), 'basic')) { if (str_starts_with(strtolower($server['HTTP_AUTHORIZATION']), 'basic')) {
$userinfo = base64_decode(substr($server['HTTP_AUTHORIZATION'], 6), true); $userinfo = base64_decode(substr($server['HTTP_AUTHORIZATION'], 6), true);
if (false === $userinfo) { if (false === $userinfo) {
throw new SyntaxError('The user info could not be detected'); throw new SyntaxError('The user info could not be detected');
@ -751,20 +734,17 @@ final class Uri implements UriInterface
/** /**
* Returns the environment path. * Returns the environment path.
* *
* @return array{0:?string, 1:?string} * @return non-empty-array{0:?string, 1:?string}
*/ */
private static function fetchRequestUri(array $server): array private static function fetchRequestUri(array $server): array
{ {
$server += ['IIS_WasUrlRewritten' => null, 'UNENCODED_URL' => '', 'PHP_SELF' => '', 'QUERY_STRING' => null]; $server += ['IIS_WasUrlRewritten' => null, 'UNENCODED_URL' => '', 'PHP_SELF' => '', 'QUERY_STRING' => null];
if ('1' === $server['IIS_WasUrlRewritten'] && '' !== $server['UNENCODED_URL']) { if ('1' === $server['IIS_WasUrlRewritten'] && '' !== $server['UNENCODED_URL']) {
/** @var array{0:?string, 1:?string} $retval */ return explode('?', $server['UNENCODED_URL'], 2) + [1 => null];
$retval = explode('?', $server['UNENCODED_URL'], 2) + [1 => null];
return $retval;
} }
if (isset($server['REQUEST_URI'])) { if (isset($server['REQUEST_URI'])) {
[$path, ] = explode('?', $server['REQUEST_URI'], 2); [$path] = explode('?', $server['REQUEST_URI'], 2);
$query = ('' !== $server['QUERY_STRING']) ? $server['QUERY_STRING'] : null; $query = ('' !== $server['QUERY_STRING']) ? $server['QUERY_STRING'] : null;
return [$path, $query]; return [$path, $query];
@ -799,13 +779,21 @@ final class Uri implements UriInterface
*/ */
private function formatPath(string $path): string private function formatPath(string $path): string
{ {
$path = $this->formatDataPath($path); if ('data' === $this->scheme) {
$path = $this->formatDataPath($path);
}
static $pattern = '/(?:[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.'%:@\/}{]++|%(?![A-Fa-f0-9]{2}))/'; if ('/' !== $path) {
static $pattern = '/[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.':@\/}{]++|%(?![A-Fa-f\d]{2})/';
$path = (string) preg_replace_callback($pattern, [Uri::class, 'urlEncodeMatch'], $path); $path = (string) preg_replace_callback($pattern, Uri::urlEncodeMatch(...), $path);
}
return $this->formatFilePath($path); if ('file' === $this->scheme) {
$path = $this->formatFilePath($path);
}
return $path;
} }
/** /**
@ -817,16 +805,12 @@ final class Uri implements UriInterface
*/ */
private function formatDataPath(string $path): string private function formatDataPath(string $path): string
{ {
if ('data' !== $this->scheme) {
return $path;
}
if ('' == $path) { if ('' == $path) {
return 'text/plain;charset=us-ascii,'; return 'text/plain;charset=us-ascii,';
} }
if (strlen($path) !== strspn($path, self::ASCII) || false === strpos($path, ',')) { if (strlen($path) !== strspn($path, self::ASCII) || !str_contains($path, ',')) {
throw new SyntaxError(sprintf('The path `%s` is invalid according to RFC2937', $path)); throw new SyntaxError('The path `'.$path.'` is invalid according to RFC2937.');
} }
$parts = explode(',', $path, 2) + [1 => null]; $parts = explode(',', $path, 2) + [1 => null];
@ -857,7 +841,7 @@ final class Uri implements UriInterface
private function assertValidPath(string $mimetype, string $parameters, string $data): void private function assertValidPath(string $mimetype, string $parameters, string $data): void
{ {
if (1 !== preg_match(self::REGEXP_MIMETYPE, $mimetype)) { if (1 !== preg_match(self::REGEXP_MIMETYPE, $mimetype)) {
throw new SyntaxError(sprintf('The path mimetype `%s` is invalid', $mimetype)); throw new SyntaxError('The path mimetype `'.$mimetype.'` is invalid.');
} }
$is_binary = 1 === preg_match(self::REGEXP_BINARY, $parameters, $matches); $is_binary = 1 === preg_match(self::REGEXP_BINARY, $parameters, $matches);
@ -865,9 +849,9 @@ final class Uri implements UriInterface
$parameters = substr($parameters, 0, - strlen($matches[0])); $parameters = substr($parameters, 0, - strlen($matches[0]));
} }
$res = array_filter(array_filter(explode(';', $parameters), [$this, 'validateParameter'])); $res = array_filter(array_filter(explode(';', $parameters), $this->validateParameter(...)));
if ([] !== $res) { if ([] !== $res) {
throw new SyntaxError(sprintf('The path paremeters `%s` is invalid', $parameters)); throw new SyntaxError('The path paremeters `'.$parameters.'` is invalid.');
} }
if (!$is_binary) { if (!$is_binary) {
@ -876,7 +860,7 @@ final class Uri implements UriInterface
$res = base64_decode($data, true); $res = base64_decode($data, true);
if (false === $res || $data !== base64_encode($res)) { if (false === $res || $data !== base64_encode($res)) {
throw new SyntaxError(sprintf('The path data `%s` is invalid', $data)); throw new SyntaxError('The path data `'.$data.'` is invalid.');
} }
} }
@ -895,10 +879,6 @@ final class Uri implements UriInterface
*/ */
private function formatFilePath(string $path): string private function formatFilePath(string $path): string
{ {
if ('file' !== $this->scheme) {
return $path;
}
$replace = static function (array $matches): string { $replace = static function (array $matches): string {
return $matches['delim'].$matches['volume'].':'.$matches['rest']; return $matches['delim'].$matches['volume'].':'.$matches['rest'];
}; };
@ -915,8 +895,6 @@ final class Uri implements UriInterface
* <li> a boolean flag telling wether the delimiter is to be added to the component * <li> a boolean flag telling wether the delimiter is to be added to the component
* when building the URI string representation</li> * when building the URI string representation</li>
* </ul> * </ul>
*
* @param ?string $component
*/ */
private function formatQueryAndFragment(?string $component): ?string private function formatQueryAndFragment(?string $component): ?string
{ {
@ -924,8 +902,8 @@ final class Uri implements UriInterface
return $component; return $component;
} }
static $pattern = '/(?:[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/'; static $pattern = '/[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.':@\/?]++|%(?![A-Fa-f\d]{2})/';
return preg_replace_callback($pattern, [Uri::class, 'urlEncodeMatch'], $component); return preg_replace_callback($pattern, Uri::urlEncodeMatch(...), $component);
} }
/** /**
@ -943,27 +921,31 @@ final class Uri implements UriInterface
throw new SyntaxError('If an authority is present the path must be empty or start with a `/`.'); throw new SyntaxError('If an authority is present the path must be empty or start with a `/`.');
} }
if (null === $this->authority && 0 === strpos($this->path, '//')) { if (null === $this->authority && str_starts_with($this->path, '//')) {
throw new SyntaxError(sprintf('If there is no authority the path `%s` can not start with a `//`.', $this->path)); throw new SyntaxError('If there is no authority the path `'.$this->path.'` can not start with a `//`.');
} }
$pos = strpos($this->path, ':'); $pos = strpos($this->path, ':');
if (null === $this->authority if (null === $this->authority
&& null === $this->scheme && null === $this->scheme
&& false !== $pos && false !== $pos
&& false === strpos(substr($this->path, 0, $pos), '/') && !str_contains(substr($this->path, 0, $pos), '/')
) { ) {
throw new SyntaxError('In absence of a scheme and an authority the first path segment cannot contain a colon (":") character.'); throw new SyntaxError('In absence of a scheme and an authority the first path segment cannot contain a colon (":") character.');
} }
$validationMethod = self::SCHEME_VALIDATION_METHOD[$this->scheme] ?? null; $this->uri = null;
if (null === $validationMethod || true === $this->$validationMethod()) {
$this->uri = null;
return; if (! match ($this->scheme) {
'data' => $this->isUriWithSchemeAndPathOnly(),
'file' => $this->isUriWithSchemeHostAndPathOnly(),
'ftp', 'gopher' => $this->isNonEmptyHostUriWithoutFragmentAndQuery(),
'http', 'https' => $this->isNonEmptyHostUri(),
'ws', 'wss' => $this->isNonEmptyHostUriWithoutFragment(),
default => true,
}) {
throw new SyntaxError('The uri `'.$this.'` is invalid for the `'.$this->scheme.'` scheme.');
} }
throw new SyntaxError(sprintf('The uri `%s` is invalid for the `%s` scheme.', (string) $this, $this->scheme));
} }
/** /**
@ -1019,11 +1001,6 @@ final class Uri implements UriInterface
* Generate the URI string representation from its components. * Generate the URI string representation from its components.
* *
* @link https://tools.ietf.org/html/rfc3986#section-5.3 * @link https://tools.ietf.org/html/rfc3986#section-5.3
*
* @param ?string $scheme
* @param ?string $authority
* @param ?string $query
* @param ?string $fragment
*/ */
private function getUriString( private function getUriString(
?string $scheme, ?string $scheme,
@ -1053,15 +1030,13 @@ final class Uri implements UriInterface
public function toString(): string public function toString(): string
{ {
$this->uri = $this->uri ?? $this->getUriString( return $this->uri ??= $this->getUriString(
$this->scheme, $this->scheme,
$this->authority, $this->authority,
$this->path, $this->path,
$this->query, $this->query,
$this->fragment $this->fragment
); );
return $this->uri;
} }
/** /**
@ -1081,9 +1056,15 @@ final class Uri implements UriInterface
} }
/** /**
* {@inheritDoc} * @return array{
* * scheme:?string,
* @return array{scheme:?string, user_info:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} * user_info:?string,
* host:?string,
* port:?int,
* path:string,
* query:?string,
* fragment:?string
* }
*/ */
public function __debugInfo(): array public function __debugInfo(): array
{ {
@ -1143,7 +1124,7 @@ final class Uri implements UriInterface
*/ */
public function getPath(): string public function getPath(): string
{ {
if (0 === strpos($this->path, '//')) { if (str_starts_with($this->path, '//')) {
return '/'.ltrim($this->path, '/'); return '/'.ltrim($this->path, '/');
} }
@ -1192,18 +1173,14 @@ final class Uri implements UriInterface
* *
* @throws SyntaxError if the submitted data can not be converted to string * @throws SyntaxError if the submitted data can not be converted to string
*/ */
private function filterString($str): ?string private function filterString(mixed $str): ?string
{ {
if (null === $str) { if (null === $str) {
return $str; return null;
} }
if (is_object($str) && method_exists($str, '__toString')) { if (!is_scalar($str) && !$str instanceof Stringable) {
$str = (string) $str; throw new SyntaxError('The component must be a string, a scalar or a Stringable object; `'.gettype($str).'` given.');
}
if (!is_scalar($str)) {
throw new SyntaxError(sprintf('The component must be a string, a scalar or a stringable object; `%s` given.', gettype($str)));
} }
$str = (string) $str; $str = (string) $str;
@ -1211,14 +1188,16 @@ final class Uri implements UriInterface
return $str; return $str;
} }
throw new SyntaxError(sprintf('The component `%s` contains invalid characters.', $str)); throw new SyntaxError('The component `'.$str.'` contains invalid characters.');
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function withUserInfo($user, $password = null): UriInterface public function withUserInfo(
{ $user,
#[SensitiveParameter] $password = null
): UriInterface {
$user_info = null; $user_info = null;
$user = $this->filterString($user); $user = $this->filterString($user);
if (null !== $password) { if (null !== $password) {

View File

@ -15,18 +15,15 @@ namespace League\Uri;
use League\Uri\Contracts\UriInterface; use League\Uri\Contracts\UriInterface;
use Psr\Http\Message\UriInterface as Psr7UriInterface; use Psr\Http\Message\UriInterface as Psr7UriInterface;
use TypeError;
use function explode; use function explode;
use function implode; use function implode;
use function preg_replace_callback; use function preg_replace_callback;
use function rawurldecode; use function rawurldecode;
use function sprintf;
final class UriInfo final class UriInfo
{ {
private const REGEXP_ENCODED_CHARS = ',%(2[D|E]|3[0-9]|4[1-9|A-F]|5[0-9|AF]|6[1-9|A-F]|7[0-9|E]),i'; private const REGEXP_ENCODED_CHARS = ',%(2[D|E]|3\d|4[1-9|A-F]|5[\d|A|F]|6[1-9|A-F]|7[\d|E]),i';
private const WHATWG_SPECIAL_SCHEMES = ['ftp', 'http', 'https', 'ws', 'wss'];
private const WHATWG_SPECIAL_SCHEMES = ['ftp' => 21, 'http' => 80, 'https' => 443, 'ws' => 80, 'wss' => 443];
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
@ -35,46 +32,17 @@ final class UriInfo
{ {
} }
/**
* @param Psr7UriInterface|UriInterface $uri private static function emptyComponentValue(Psr7UriInterface|UriInterface $uri): ?string
*/
private static function emptyComponentValue($uri): ?string
{ {
return $uri instanceof Psr7UriInterface ? '' : null; return $uri instanceof Psr7UriInterface ? '' : null;
} }
/** /**
* Filter the URI object. * Normalizes an URI for comparison.
*
* To be valid an URI MUST implement at least one of the following interface:
* - League\Uri\UriInterface
* - Psr\Http\Message\UriInterface
*
* @param mixed $uri the URI to validate
*
* @throws TypeError if the URI object does not implements the supported interfaces.
*
* @return Psr7UriInterface|UriInterface
*/ */
private static function filterUri($uri) private static function normalize(Psr7UriInterface|UriInterface $uri): Psr7UriInterface|UriInterface
{ {
if ($uri instanceof Psr7UriInterface || $uri instanceof UriInterface) {
return $uri;
}
throw new TypeError(sprintf('The uri must be a valid URI object received `%s`', is_object($uri) ? get_class($uri) : gettype($uri)));
}
/**
* Normalize an URI for comparison.
*
* @param Psr7UriInterface|UriInterface $uri
*
* @return Psr7UriInterface|UriInterface
*/
private static function normalize($uri)
{
$uri = self::filterUri($uri);
$null = self::emptyComponentValue($uri); $null = self::emptyComponentValue($uri);
$path = $uri->getPath(); $path = $uri->getPath();
@ -107,36 +75,28 @@ final class UriInfo
} }
/** /**
* Tell whether the URI represents an absolute URI. * Tells whether the URI represents an absolute URI.
*
* @param Psr7UriInterface|UriInterface $uri
*/ */
public static function isAbsolute($uri): bool public static function isAbsolute(Psr7UriInterface|UriInterface $uri): bool
{ {
return self::emptyComponentValue($uri) !== self::filterUri($uri)->getScheme(); return self::emptyComponentValue($uri) !== $uri->getScheme();
} }
/** /**
* Tell whether the URI represents a network path. * Tell whether the URI represents a network path.
*
* @param Psr7UriInterface|UriInterface $uri
*/ */
public static function isNetworkPath($uri): bool public static function isNetworkPath(Psr7UriInterface|UriInterface $uri): bool
{ {
$uri = self::filterUri($uri);
$null = self::emptyComponentValue($uri); $null = self::emptyComponentValue($uri);
return $null === $uri->getScheme() && $null !== $uri->getAuthority(); return $null === $uri->getScheme() && $null !== $uri->getAuthority();
} }
/** /**
* Tell whether the URI represents an absolute path. * Tells whether the URI represents an absolute path.
*
* @param Psr7UriInterface|UriInterface $uri
*/ */
public static function isAbsolutePath($uri): bool public static function isAbsolutePath(Psr7UriInterface|UriInterface $uri): bool
{ {
$uri = self::filterUri($uri);
$null = self::emptyComponentValue($uri); $null = self::emptyComponentValue($uri);
return $null === $uri->getScheme() return $null === $uri->getScheme()
@ -147,11 +107,9 @@ final class UriInfo
/** /**
* Tell whether the URI represents a relative path. * Tell whether the URI represents a relative path.
* *
* @param Psr7UriInterface|UriInterface $uri
*/ */
public static function isRelativePath($uri): bool public static function isRelativePath(Psr7UriInterface|UriInterface $uri): bool
{ {
$uri = self::filterUri($uri);
$null = self::emptyComponentValue($uri); $null = self::emptyComponentValue($uri);
return $null === $uri->getScheme() return $null === $uri->getScheme()
@ -160,12 +118,9 @@ final class UriInfo
} }
/** /**
* Tell whether both URI refers to the same document. * Tells whether both URI refers to the same document.
*
* @param Psr7UriInterface|UriInterface $uri
* @param Psr7UriInterface|UriInterface $base_uri
*/ */
public static function isSameDocument($uri, $base_uri): bool public static function isSameDocument(Psr7UriInterface|UriInterface $uri, Psr7UriInterface|UriInterface $base_uri): bool
{ {
$uri = self::normalize($uri); $uri = self::normalize($uri);
$base_uri = self::normalize($base_uri); $base_uri = self::normalize($base_uri);
@ -182,31 +137,30 @@ final class UriInfo
* For URI without a special scheme the method returns null * For URI without a special scheme the method returns null
* For URI with the file scheme the method will return null (as this is left to the implementation decision) * For URI with the file scheme the method will return null (as this is left to the implementation decision)
* For URI with a special scheme the method returns the scheme followed by its authority (without the userinfo part) * For URI with a special scheme the method returns the scheme followed by its authority (without the userinfo part)
*
* @param Psr7UriInterface|UriInterface $uri
*/ */
public static function getOrigin($uri): ?string public static function getOrigin(Psr7UriInterface|UriInterface $uri): ?string
{ {
$scheme = self::filterUri($uri)->getScheme(); $scheme = $uri->getScheme();
if ('blob' === $scheme) { if ('blob' === $scheme) {
$uri = Uri::createFromString($uri->getPath()); $uri = Uri::createFromString($uri->getPath());
$scheme = $uri->getScheme(); $scheme = $uri->getScheme();
} }
if (null === $scheme || !array_key_exists($scheme, self::WHATWG_SPECIAL_SCHEMES)) { if (in_array($scheme, self::WHATWG_SPECIAL_SCHEMES, true)) {
return null; $null = self::emptyComponentValue($uri);
return (string) $uri->withFragment($null)->withQuery($null)->withPath('')->withUserInfo($null, null);
} }
$null = self::emptyComponentValue($uri); return null;
return (string) $uri->withFragment($null)->withQuery($null)->withPath('')->withUserInfo($null);
} }
/** /**
* @param Psr7UriInterface|UriInterface $uri * Tells whether two URI do not share the same origin.
* @param Psr7UriInterface|UriInterface $base_uri *
* @see UriInfo::getOrigin()
*/ */
public static function isCrossOrigin($uri, $base_uri): bool public static function isCrossOrigin(Psr7UriInterface|UriInterface $uri, Psr7UriInterface|UriInterface $base_uri): bool
{ {
return null === ($uriString = self::getOrigin(Uri::createFromUri($uri))) return null === ($uriString = self::getOrigin(Uri::createFromUri($uri)))
|| null === ($baseUriString = self::getOrigin(Uri::createFromUri($base_uri))) || null === ($baseUriString = self::getOrigin(Uri::createFromUri($base_uri)))

View File

@ -15,16 +15,13 @@ namespace League\Uri;
use League\Uri\Contracts\UriInterface; use League\Uri\Contracts\UriInterface;
use Psr\Http\Message\UriInterface as Psr7UriInterface; use Psr\Http\Message\UriInterface as Psr7UriInterface;
use TypeError;
use function array_pop; use function array_pop;
use function array_reduce; use function array_reduce;
use function count; use function count;
use function end; use function end;
use function explode; use function explode;
use function gettype;
use function implode; use function implode;
use function in_array; use function in_array;
use function sprintf;
use function str_repeat; use function str_repeat;
use function strpos; use function strpos;
use function substr; use function substr;
@ -44,20 +41,13 @@ final class UriResolver
} }
/** /**
* Resolve an URI against a base URI using RFC3986 rules. * Resolves an URI against a base URI using RFC3986 rules.
* *
* If the first argument is a UriInterface the method returns a UriInterface object * If the first argument is a UriInterface the method returns a UriInterface object
* If the first argument is a Psr7UriInterface the method returns a Psr7UriInterface object * If the first argument is a Psr7UriInterface the method returns a Psr7UriInterface object
*
* @param Psr7UriInterface|UriInterface $uri
* @param Psr7UriInterface|UriInterface $base_uri
*
* @return Psr7UriInterface|UriInterface
*/ */
public static function resolve($uri, $base_uri) public static function resolve(Psr7UriInterface|UriInterface $uri, Psr7UriInterface|UriInterface $base_uri): Psr7UriInterface|UriInterface
{ {
self::filterUri($uri);
self::filterUri($base_uri);
$null = $uri instanceof Psr7UriInterface ? '' : null; $null = $uri instanceof Psr7UriInterface ? '' : null;
if ($null !== $uri->getScheme()) { if ($null !== $uri->getScheme()) {
@ -90,38 +80,24 @@ final class UriResolver
; ;
} }
/**
* Filter the URI object.
*
* @param mixed $uri an URI object
*
* @throws TypeError if the URI object does not implements the supported interfaces.
*/
private static function filterUri($uri): void
{
if (!$uri instanceof UriInterface && !$uri instanceof Psr7UriInterface) {
throw new TypeError(sprintf('The uri must be a valid URI object received `%s`', gettype($uri)));
}
}
/** /**
* Remove dot segments from the URI path. * Remove dot segments from the URI path.
*/ */
private static function removeDotSegments(string $path): string private static function removeDotSegments(string $path): string
{ {
if (false === strpos($path, '.')) { if (!str_contains($path, '.')) {
return $path; return $path;
} }
$old_segments = explode('/', $path); $old_segments = explode('/', $path);
$new_path = implode('/', array_reduce($old_segments, [UriResolver::class, 'reducer'], [])); $new_path = implode('/', array_reduce($old_segments, UriResolver::reducer(...), []));
if (isset(self::DOT_SEGMENTS[end($old_segments)])) { if (isset(self::DOT_SEGMENTS[end($old_segments)])) {
$new_path .= '/'; $new_path .= '/';
} }
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
// added because some PSR-7 implementations do not respect RFC3986 // added because some PSR-7 implementations do not respect RFC3986
if (0 === strpos($path, '/') && 0 !== strpos($new_path, '/')) { if (str_starts_with($path, '/') && !str_starts_with($new_path, '/')) {
return '/'.$new_path; return '/'.$new_path;
} }
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
@ -150,21 +126,20 @@ final class UriResolver
} }
/** /**
* Resolve an URI path and query component. * Resolves an URI path and query component.
*
* @param Psr7UriInterface|UriInterface $uri
* @param Psr7UriInterface|UriInterface $base_uri
* *
* @return array{0:string, 1:string|null} * @return array{0:string, 1:string|null}
*/ */
private static function resolvePathAndQuery($uri, $base_uri): array private static function resolvePathAndQuery(
{ Psr7UriInterface|UriInterface $uri,
Psr7UriInterface|UriInterface $base_uri
): array {
$target_path = $uri->getPath(); $target_path = $uri->getPath();
$target_query = $uri->getQuery(); $target_query = $uri->getQuery();
$null = $uri instanceof Psr7UriInterface ? '' : null; $null = $uri instanceof Psr7UriInterface ? '' : null;
$baseNull = $base_uri instanceof Psr7UriInterface ? '' : null; $baseNull = $base_uri instanceof Psr7UriInterface ? '' : null;
if (0 === strpos($target_path, '/')) { if (str_starts_with($target_path, '/')) {
return [$target_path, $target_query]; return [$target_path, $target_query];
} }
@ -176,7 +151,7 @@ final class UriResolver
$target_path = $base_uri->getPath(); $target_path = $base_uri->getPath();
//@codeCoverageIgnoreStart //@codeCoverageIgnoreStart
//because some PSR-7 Uri implementations allow this RFC3986 forbidden construction //because some PSR-7 Uri implementations allow this RFC3986 forbidden construction
if ($baseNull !== $base_uri->getAuthority() && 0 !== strpos($target_path, '/')) { if ($baseNull !== $base_uri->getAuthority() && !str_starts_with($target_path, '/')) {
$target_path = '/'.$target_path; $target_path = '/'.$target_path;
} }
//@codeCoverageIgnoreEnd //@codeCoverageIgnoreEnd
@ -201,23 +176,18 @@ final class UriResolver
} }
/** /**
* Relativize an URI according to a base URI. * Relativizes an URI according to a base URI.
* *
* This method MUST retain the state of the submitted URI instance, and return * This method MUST retain the state of the submitted URI instance, and return
* an URI instance of the same type that contains the applied modifications. * an URI instance of the same type that contains the applied modifications.
* *
* This method MUST be transparent when dealing with error and exceptions. * This method MUST be transparent when dealing with error and exceptions.
* It MUST not alter of silence them apart from validating its own parameters. * It MUST not alter of silence them apart from validating its own parameters.
*
* @param Psr7UriInterface|UriInterface $uri
* @param Psr7UriInterface|UriInterface $base_uri
*
* @return Psr7UriInterface|UriInterface
*/ */
public static function relativize($uri, $base_uri) public static function relativize(
{ Psr7UriInterface|UriInterface $uri,
self::filterUri($uri); Psr7UriInterface|UriInterface $base_uri
self::filterUri($base_uri); ): Psr7UriInterface|UriInterface {
$uri = self::formatHost($uri); $uri = self::formatHost($uri);
$base_uri = self::formatHost($base_uri); $base_uri = self::formatHost($base_uri);
if (!self::isRelativizable($uri, $base_uri)) { if (!self::isRelativizable($uri, $base_uri)) {
@ -231,7 +201,7 @@ final class UriResolver
return $uri->withPath(self::relativizePath($target_path, $base_uri->getPath())); return $uri->withPath(self::relativizePath($target_path, $base_uri->getPath()));
} }
if (self::componentEquals('getQuery', $uri, $base_uri)) { if (self::componentEquals('query', $uri, $base_uri)) {
return $uri->withPath('')->withQuery($null); return $uri->withPath('')->withQuery($null);
} }
@ -244,23 +214,26 @@ final class UriResolver
/** /**
* Tells whether the component value from both URI object equals. * Tells whether the component value from both URI object equals.
*
* @param Psr7UriInterface|UriInterface $uri
* @param Psr7UriInterface|UriInterface $base_uri
*/ */
private static function componentEquals(string $method, $uri, $base_uri): bool private static function componentEquals(
{ string $property,
return self::getComponent($method, $uri) === self::getComponent($method, $base_uri); Psr7UriInterface|UriInterface $uri,
Psr7UriInterface|UriInterface $base_uri
): bool {
return self::getComponent($property, $uri) === self::getComponent($property, $base_uri);
} }
/** /**
* Returns the component value from the submitted URI object. * Returns the component value from the submitted URI object.
*
* @param Psr7UriInterface|UriInterface $uri
*/ */
private static function getComponent(string $method, $uri): ?string private static function getComponent(string $property, Psr7UriInterface|UriInterface $uri): ?string
{ {
$component = $uri->$method(); $component = match ($property) {
'query' => $uri->getQuery(),
'authority' => $uri->getAuthority(),
default => $uri->getScheme(), //scheme
};
if ($uri instanceof Psr7UriInterface && '' === $component) { if ($uri instanceof Psr7UriInterface && '' === $component) {
return null; return null;
} }
@ -270,14 +243,8 @@ final class UriResolver
/** /**
* Filter the URI object. * Filter the URI object.
*
* @param Psr7UriInterface|UriInterface $uri
*
* @throws TypeError if the URI object does not implements the supported interfaces.
*
* @return Psr7UriInterface|UriInterface
*/ */
private static function formatHost($uri) private static function formatHost(Psr7UriInterface|UriInterface $uri): Psr7UriInterface|UriInterface
{ {
if (!$uri instanceof Psr7UriInterface) { if (!$uri instanceof Psr7UriInterface) {
return $uri; return $uri;
@ -292,20 +259,19 @@ final class UriResolver
} }
/** /**
* Tell whether the submitted URI object can be relativize. * Tells whether the submitted URI object can be relativize.
*
* @param Psr7UriInterface|UriInterface $uri
* @param Psr7UriInterface|UriInterface $base_uri
*/ */
private static function isRelativizable($uri, $base_uri): bool private static function isRelativizable(
{ Psr7UriInterface|UriInterface $uri,
Psr7UriInterface|UriInterface $base_uri
): bool {
return !UriInfo::isRelativePath($uri) return !UriInfo::isRelativePath($uri)
&& self::componentEquals('getScheme', $uri, $base_uri) && self::componentEquals('scheme', $uri, $base_uri)
&& self::componentEquals('getAuthority', $uri, $base_uri); && self::componentEquals('authority', $uri, $base_uri);
} }
/** /**
* Relative the URI for a authority-less target URI. * Relatives the URI for an authority-less target URI.
*/ */
private static function relativizePath(string $path, string $basepath): string private static function relativizePath(string $path, string $basepath): string
{ {

View File

@ -17,15 +17,11 @@ use League\Uri\Exceptions\IdnaConversionFailed;
use League\Uri\Exceptions\IdnSupportMissing; use League\Uri\Exceptions\IdnSupportMissing;
use League\Uri\Exceptions\SyntaxError; use League\Uri\Exceptions\SyntaxError;
use League\Uri\Idna\Idna; use League\Uri\Idna\Idna;
use TypeError; use Stringable;
use function array_merge; use function array_merge;
use function explode; use function explode;
use function filter_var; use function filter_var;
use function gettype;
use function inet_pton; use function inet_pton;
use function is_object;
use function is_scalar;
use function method_exists;
use function preg_match; use function preg_match;
use function rawurldecode; use function rawurldecode;
use function sprintf; use function sprintf;
@ -87,7 +83,7 @@ final class UriString
* *
* @link https://tools.ietf.org/html/rfc3986#section-3.1 * @link https://tools.ietf.org/html/rfc3986#section-3.1
*/ */
private const REGEXP_URI_SCHEME = '/^([a-z][a-z\d\+\.\-]*)?$/i'; private const REGEXP_URI_SCHEME = '/^([a-z][a-z\d+.-]*)?$/i';
/** /**
* IPvFuture regular expression. * IPvFuture regular expression.
@ -157,16 +153,7 @@ final class UriString
* @link https://tools.ietf.org/html/rfc3986#section-5.3 * @link https://tools.ietf.org/html/rfc3986#section-5.3
* @link https://tools.ietf.org/html/rfc3986#section-7.5 * @link https://tools.ietf.org/html/rfc3986#section-7.5
* *
* @param array{ * @param array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:?string, query:?string, fragment:?string} $components
* scheme:?string,
* user:?string,
* pass:?string,
* host:?string,
* port:?int,
* path:?string,
* query:?string,
* fragment:?string
* } $components
*/ */
public static function build(array $components): string public static function build(array $components): string
{ {
@ -242,35 +229,17 @@ final class UriString
* *
* @link https://tools.ietf.org/html/rfc3986 * @link https://tools.ietf.org/html/rfc3986
* *
* @param mixed $uri any scalar or stringable object * @param Stringable|string|int|float $uri any scalar or stringable object
* *
* @throws SyntaxError if the URI contains invalid characters * @throws SyntaxError if the URI contains invalid characters
* @throws SyntaxError if the URI contains an invalid scheme * @throws SyntaxError if the URI contains an invalid scheme
* @throws SyntaxError if the URI contains an invalid path * @throws SyntaxError if the URI contains an invalid path
* *
* @return array{ * @return array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string}
* scheme:?string,
* user:?string,
* pass:?string,
* host:?string,
* port:?int,
* path:string,
* query:?string,
* fragment:?string
* }
*/ */
public static function parse($uri): array public static function parse(Stringable|string|int|float $uri): array
{ {
if (is_object($uri) && method_exists($uri, '__toString')) {
$uri = (string) $uri;
}
if (!is_scalar($uri)) {
throw new TypeError(sprintf('The uri must be a scalar or a stringable object `%s` given', gettype($uri)));
}
$uri = (string) $uri; $uri = (string) $uri;
if (isset(self::URI_SCHORTCUTS[$uri])) { if (isset(self::URI_SCHORTCUTS[$uri])) {
/** @var array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} $components */ /** @var array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} $components */
$components = array_merge(self::URI_COMPONENTS, self::URI_SCHORTCUTS[$uri]); $components = array_merge(self::URI_COMPONENTS, self::URI_SCHORTCUTS[$uri]);
@ -395,7 +364,7 @@ final class UriString
return $host; return $host;
} }
if ('[' !== $host[0] || ']' !== substr($host, -1)) { if ('[' !== $host[0] || !str_ends_with($host, ']')) {
return self::filterRegisteredName($host); return self::filterRegisteredName($host);
} }
@ -462,6 +431,6 @@ final class UriString
$ip_host = substr($ip_host, 0, $pos); $ip_host = substr($ip_host, 0, $pos);
return false !== filter_var($ip_host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) return false !== filter_var($ip_host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)
&& 0 === strpos((string) inet_pton($ip_host), self::ZONE_ID_ADDRESS_BLOCK); && str_starts_with((string)inet_pton($ip_host), self::ZONE_ID_ADDRESS_BLOCK);
} }
} }

View File

@ -19,7 +19,7 @@ use League\Uri\Exceptions\SyntaxError;
use League\Uri\Exceptions\TemplateCanNotBeExpanded; use League\Uri\Exceptions\TemplateCanNotBeExpanded;
use League\Uri\UriTemplate\Template; use League\Uri\UriTemplate\Template;
use League\Uri\UriTemplate\VariableBag; use League\Uri\UriTemplate\VariableBag;
use TypeError; use Stringable;
/** /**
* Defines the URI Template syntax and the process for expanding a URI Template into a URI reference. * Defines the URI Template syntax and the process for expanding a URI Template into a URI reference.
@ -34,25 +34,22 @@ use TypeError;
*/ */
final class UriTemplate final class UriTemplate
{ {
private Template $template; public readonly Template $template;
private VariableBag $defaultVariables; public readonly VariableBag $defaultVariables;
/** /**
* @param object|string $template a string or an object with the __toString method
*
* @throws TypeError if the template is not a string or an object with the __toString method
* @throws SyntaxError if the template syntax is invalid * @throws SyntaxError if the template syntax is invalid
* @throws TemplateCanNotBeExpanded if the template variables are invalid * @throws TemplateCanNotBeExpanded if the template variables are invalid
*/ */
public function __construct($template, array $defaultVariables = []) public function __construct(Template|Stringable|string $template, VariableBag|array $defaultVariables = [])
{ {
$this->template = Template::createFromString($template); $this->template = $template instanceof Template ? $template : Template::createFromString($template);
$this->defaultVariables = $this->filterVariables($defaultVariables); $this->defaultVariables = $this->filterVariables($defaultVariables);
} }
public static function __set_state(array $properties): self public static function __set_state(array $properties): self
{ {
return new self($properties['template']->toString(), $properties['defaultVariables']->all()); return new self($properties['template'], $properties['defaultVariables']);
} }
/** /**
@ -60,16 +57,19 @@ final class UriTemplate
* *
* @param array<string,string|array<string>> $variables * @param array<string,string|array<string>> $variables
*/ */
private function filterVariables(array $variables): VariableBag private function filterVariables(VariableBag|array $variables): VariableBag
{ {
$output = new VariableBag(); return array_reduce(
foreach ($this->template->variableNames() as $name) { $this->template->variableNames,
if (isset($variables[$name])) { function (VariableBag $curry, string $name) use ($variables): VariableBag {
$output->assign($name, $variables[$name]); if (isset($variables[$name])) {
} $curry[$name] = $variables[$name];
} }
return $output; return $curry;
},
new VariableBag()
);
} }
/** /**
@ -77,7 +77,7 @@ final class UriTemplate
*/ */
public function getTemplate(): string public function getTemplate(): string
{ {
return $this->template->toString(); return $this->template->value;
} }
/** /**
@ -87,7 +87,7 @@ final class UriTemplate
*/ */
public function getVariableNames(): array public function getVariableNames(): array
{ {
return $this->template->variableNames(); return $this->template->variableNames;
} }
/** /**
@ -111,19 +111,16 @@ final class UriTemplate
* If present, variables whose name is not part of the current template * If present, variables whose name is not part of the current template
* possible variable names are removed. * possible variable names are removed.
*/ */
public function withDefaultVariables(array $defaultDefaultVariables): self public function withDefaultVariables(VariableBag|array $defaultDefaultVariables): self
{ {
return new self( return new self($this->template, $defaultDefaultVariables);
$this->template->toString(),
$this->filterVariables($defaultDefaultVariables)->all()
);
} }
/** /**
* @throws TemplateCanNotBeExpanded if the variable contains nested array values * @throws TemplateCanNotBeExpanded if the variable contains nested array values
* @throws UriException if the resulting expansion can not be converted to a UriInterface instance * @throws UriException if the resulting expansion can not be converted to a UriInterface instance
*/ */
public function expand(array $variables = []): UriInterface public function expand(VariableBag|array $variables = []): UriInterface
{ {
return Uri::createFromString( return Uri::createFromString(
$this->template->expand( $this->template->expand(

View File

@ -16,7 +16,6 @@ namespace League\Uri\UriTemplate;
use League\Uri\Exceptions\SyntaxError; use League\Uri\Exceptions\SyntaxError;
use League\Uri\Exceptions\TemplateCanNotBeExpanded; use League\Uri\Exceptions\TemplateCanNotBeExpanded;
use function array_filter; use function array_filter;
use function array_keys;
use function array_map; use function array_map;
use function array_unique; use function array_unique;
use function explode; use function explode;
@ -24,7 +23,6 @@ use function implode;
use function preg_match; use function preg_match;
use function rawurlencode; use function rawurlencode;
use function str_replace; use function str_replace;
use function strpos;
use function substr; use function substr;
final class Expression final class Expression
@ -64,46 +62,29 @@ final class Expression
'&' => ['prefix' => '&', 'joiner' => '&', 'query' => true], '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true],
]; ];
private string $operator;
/** @var array<VarSpecifier> */ /** @var array<VarSpecifier> */
private array $varSpecifiers; private array $varSpecifiers;
private string $joiner; private string $joiner;
/** @var array<string> */ /** @var array<string> */
private array $variableNames; public readonly array $variableNames;
private string $expressionString; public readonly string $value;
private function __construct(string $operator, VarSpecifier ...$varSpecifiers) private function __construct(private string $operator, VarSpecifier ...$varSpecifiers)
{ {
$this->operator = $operator;
$this->varSpecifiers = $varSpecifiers; $this->varSpecifiers = $varSpecifiers;
$this->joiner = self::OPERATOR_HASH_LOOKUP[$operator]['joiner']; $this->joiner = self::OPERATOR_HASH_LOOKUP[$operator]['joiner'];
$this->variableNames = $this->setVariableNames(); $this->variableNames = array_unique(array_map(
$this->expressionString = $this->setExpressionString(); static fn (VarSpecifier $varSpecifier): string => $varSpecifier->name,
$varSpecifiers
));
$this->value = '{'.$operator.implode(',', array_map(
static fn (VarSpecifier $varSpecifier): string => $varSpecifier->toString(),
$varSpecifiers
)).'}';
} }
/** /**
* @return array<string> * @param array{operator:string, varSpecifiers:array<VarSpecifier>} $properties
*/
private function setVariableNames(): array
{
return array_unique(array_map(
static fn (VarSpecifier $varSpecifier): string => $varSpecifier->name(),
$this->varSpecifiers
));
}
private function setExpressionString(): string
{
$varSpecifierString = implode(',', array_map(
static fn (VarSpecifier $variable): string => $variable->toString(),
$this->varSpecifiers
));
return '{'.$this->operator.$varSpecifierString.'}';
}
/**
* {@inheritDoc}
*/ */
public static function __set_state(array $properties): self public static function __set_state(array $properties): self
{ {
@ -123,7 +104,7 @@ final class Expression
/** @var array{operator:string, variables:string} $parts */ /** @var array{operator:string, variables:string} $parts */
$parts = $parts + ['operator' => '']; $parts = $parts + ['operator' => ''];
if ('' !== $parts['operator'] && false !== strpos(self::RESERVED_OPERATOR, $parts['operator'])) { if ('' !== $parts['operator'] && str_contains(self::RESERVED_OPERATOR, $parts['operator'])) {
throw new SyntaxError('The operator used in the expression "'.$expression.'" is reserved.'); throw new SyntaxError('The operator used in the expression "'.$expression.'" is reserved.');
} }
@ -136,13 +117,18 @@ final class Expression
/** /**
* Returns the expression string representation. * Returns the expression string representation.
* *
* @deprecated since version 6.6.0 use the readonly property instead
* @codeCoverageIgnore
*/ */
public function toString(): string public function toString(): string
{ {
return $this->expressionString; return $this->value;
} }
/** /**
* @deprecated since version 6.6.0 use the readonly property instead
* @codeCoverageIgnore
*
* @return array<string> * @return array<string>
*/ */
public function variableNames(): array public function variableNames(): array
@ -178,7 +164,7 @@ final class Expression
*/ */
private function replace(VarSpecifier $varSpec, VariableBag $variables): string private function replace(VarSpecifier $varSpec, VariableBag $variables): string
{ {
$value = $variables->fetch($varSpec->name()); $value = $variables->fetch($varSpec->name);
if (null === $value) { if (null === $value) {
return ''; return '';
} }
@ -190,10 +176,10 @@ final class Expression
} }
if ('&' !== $this->joiner && '' === $expanded) { if ('&' !== $this->joiner && '' === $expanded) {
return $varSpec->name(); return $varSpec->name;
} }
return $varSpec->name().'='.$expanded; return $varSpec->name.'='.$expanded;
} }
/** /**
@ -201,7 +187,7 @@ final class Expression
* *
* @return array{0:string, 1:bool} * @return array{0:string, 1:bool}
*/ */
private function inject($value, VarSpecifier $varSpec, bool $useQuery): array private function inject(array|string $value, VarSpecifier $varSpec, bool $useQuery): array
{ {
if (is_string($value)) { if (is_string($value)) {
return $this->replaceString($value, $varSpec, $useQuery); return $this->replaceString($value, $varSpec, $useQuery);
@ -217,16 +203,15 @@ final class Expression
*/ */
private function replaceString(string $value, VarSpecifier $varSpec, bool $useQuery): array private function replaceString(string $value, VarSpecifier $varSpec, bool $useQuery): array
{ {
if (':' === $varSpec->modifier()) { if (':' === $varSpec->modifier) {
$value = substr($value, 0, $varSpec->position()); $value = substr($value, 0, $varSpec->position);
} }
$expanded = rawurlencode($value); if (in_array($this->operator, ['+', '#'], true)) {
if ('+' === $this->operator || '#' === $this->operator) { return [$this->decodeReserved(rawurlencode($value)), $useQuery];
return [$this->decodeReserved($expanded), $useQuery];
} }
return [$expanded, $useQuery]; return [rawurlencode($value), $useQuery];
} }
/** /**
@ -244,48 +229,45 @@ final class Expression
return ['', false]; return ['', false];
} }
if (':' === $varSpec->modifier()) { if (':' === $varSpec->modifier) {
throw TemplateCanNotBeExpanded::dueToUnableToProcessValueListWithPrefix($varSpec->name()); throw TemplateCanNotBeExpanded::dueToUnableToProcessValueListWithPrefix($varSpec->name);
} }
$pairs = []; $pairs = [];
$isAssoc = $this->isAssoc($value); $isList = array_is_list($value);
foreach ($value as $key => $var) { foreach ($value as $key => $var) {
if ($isAssoc) { if (!$isList) {
$key = rawurlencode((string) $key); $key = rawurlencode((string) $key);
} }
$var = rawurlencode($var); $var = rawurlencode($var);
if ('+' === $this->operator || '#' === $this->operator) { if (in_array($this->operator, ['+', '#'], true)) {
$var = $this->decodeReserved($var); $var = $this->decodeReserved($var);
} }
if ('*' === $varSpec->modifier()) { if ('*' === $varSpec->modifier) {
if ($isAssoc) { if (!$isList) {
$var = $key.'='.$var; $var = $key.'='.$var;
} elseif ($key > 0 && $useQuery) { } elseif ($key > 0 && $useQuery) {
$var = $varSpec->name().'='.$var; $var = $varSpec->name.'='.$var;
} }
} }
$pairs[$key] = $var; $pairs[$key] = $var;
} }
if ('*' === $varSpec->modifier()) { if ('*' === $varSpec->modifier) {
if ($isAssoc) { if (!$isList) {
// Don't prepend the value name when using the explode // Don't prepend the value name when using the `explode` modifier with an associative array.
// modifier with an associative array.
$useQuery = false; $useQuery = false;
} }
return [implode($this->joiner, $pairs), $useQuery]; return [implode($this->joiner, $pairs), $useQuery];
} }
if ($isAssoc) { if (!$isList) {
// When an associative array is encountered and the // When an associative array is encountered and the `explode` modifier is not set, then
// explode modifier is not set, then the result must be // the result must be a comma separated list of keys followed by their respective values.
// a comma separated list of keys followed by their
// respective values.
foreach ($pairs as $offset => &$data) { foreach ($pairs as $offset => &$data) {
$data = $offset.','.$data; $data = $offset.','.$data;
} }
@ -296,19 +278,6 @@ final class Expression
return [implode(',', $pairs), $useQuery]; return [implode(',', $pairs), $useQuery];
} }
/**
* Determines if an array is associative.
*
* This makes the assumption that input arrays are sequences or hashes.
* This assumption is a trade-off for accuracy in favor of speed, but it
* should work in almost every case where input is supplied for a URI
* template.
*/
private function isAssoc(array $array): bool
{
return [] !== $array && 0 !== array_keys($array)[0];
}
/** /**
* Removes percent encoding on reserved characters (used with + and # modifiers). * Removes percent encoding on reserved characters (used with + and # modifiers).
*/ */

View File

@ -15,17 +15,11 @@ namespace League\Uri\UriTemplate;
use League\Uri\Exceptions\SyntaxError; use League\Uri\Exceptions\SyntaxError;
use League\Uri\Exceptions\TemplateCanNotBeExpanded; use League\Uri\Exceptions\TemplateCanNotBeExpanded;
use TypeError; use Stringable;
use function array_merge;
use function array_unique; use function array_unique;
use function gettype;
use function is_object;
use function is_string;
use function method_exists;
use function preg_match_all; use function preg_match_all;
use function preg_replace; use function preg_replace;
use function sprintf; use function str_contains;
use function strpos;
use const PREG_SET_ORDER; use const PREG_SET_ORDER;
final class Template final class Template
@ -33,53 +27,42 @@ final class Template
/** /**
* Expression regular expression pattern. * Expression regular expression pattern.
*/ */
private const REGEXP_EXPRESSION_DETECTOR = '/\{[^\}]*\}/x'; private const REGEXP_EXPRESSION_DETECTOR = '/\{[^}]*}/x';
private string $template;
/** @var array<string, Expression> */ /** @var array<string, Expression> */
private array $expressions = []; private array $expressions = [];
/** @var array<string> */ /** @var array<string> */
private array $variableNames; public readonly array $variableNames;
private function __construct(string $template, Expression ...$expressions) private function __construct(public readonly string $value, Expression ...$expressions)
{ {
$this->template = $template;
$variableNames = []; $variableNames = [];
foreach ($expressions as $expression) { foreach ($expressions as $expression) {
$this->expressions[$expression->toString()] = $expression; $this->expressions[$expression->value] = $expression;
$variableNames[] = $expression->variableNames(); $variableNames = [...$variableNames, ...$expression->variableNames];
} }
$this->variableNames = array_unique(array_merge([], ...$variableNames));
$this->variableNames = array_unique($variableNames);
} }
/** /**
* {@inheritDoc} * @param array{value:string, template?:string, expressions:array<string, Expression>} $properties
*/ */
public static function __set_state(array $properties): self public static function __set_state(array $properties): self
{ {
return new self($properties['template'], ...array_values($properties['expressions'])); return new self($properties['template'] ?? $properties['value'], ...array_values($properties['expressions']));
} }
/** /**
* @param object|string $template a string or an object with the __toString method
*
* @throws TypeError if the template is not a string or an object with the __toString method
* @throws SyntaxError if the template contains invalid expressions * @throws SyntaxError if the template contains invalid expressions
* @throws SyntaxError if the template contains invalid variable specification * @throws SyntaxError if the template contains invalid variable specification
*/ */
public static function createFromString($template): self public static function createFromString(Stringable|string $template): self
{ {
if (is_object($template) && method_exists($template, '__toString')) { $template = (string) $template;
$template = (string) $template;
}
if (!is_string($template)) {
throw new TypeError(sprintf('The template must be a string or a stringable object %s given.', gettype($template)));
}
/** @var string $remainder */ /** @var string $remainder */
$remainder = preg_replace(self::REGEXP_EXPRESSION_DETECTOR, '', $template); $remainder = preg_replace(self::REGEXP_EXPRESSION_DETECTOR, '', $template);
if (false !== strpos($remainder, '{') || false !== strpos($remainder, '}')) { if (str_contains($remainder, '{') || str_contains($remainder, '}')) {
throw new SyntaxError('The template "'.$template.'" contains invalid expressions.'); throw new SyntaxError('The template "'.$template.'" contains invalid expressions.');
} }
@ -96,12 +79,19 @@ final class Template
return new self($template, ...$arguments); return new self($template, ...$arguments);
} }
/**
* @deprecated since version 6.6.0 use the readonly property instead
* @codeCoverageIgnore
*/
public function toString(): string public function toString(): string
{ {
return $this->template; return $this->value;
} }
/** /**
* @deprecated since version 6.6.0 use the readonly property instead
* @codeCoverageIgnore
*
* @return array<string> * @return array<string>
*/ */
public function variableNames(): array public function variableNames(): array
@ -115,7 +105,7 @@ final class Template
*/ */
public function expand(VariableBag $variables): string public function expand(VariableBag $variables): string
{ {
$uriString = $this->template; $uriString = $this->value;
/** @var Expression $expression */ /** @var Expression $expression */
foreach ($this->expressions as $pattern => $expression) { foreach ($this->expressions as $pattern => $expression) {
$uriString = str_replace($pattern, $expression->expand($variables), $uriString); $uriString = str_replace($pattern, $expression->expand($variables), $uriString);

View File

@ -28,19 +28,15 @@ final class VarSpecifier
(?<modifier>\:(?<position>\d+)|\*)? (?<modifier>\:(?<position>\d+)|\*)?
$/x'; $/x';
private string $name; private function __construct(
private string $modifier; public readonly string $name,
private int $position; public readonly string $modifier,
public readonly int $position
private function __construct(string $name, string $modifier, int $position) ) {
{
$this->name = $name;
$this->modifier = $modifier;
$this->position = $position;
} }
/** /**
* {@inheritDoc} * @param array{name: string, modifier:string, position:int} $properties
*/ */
public static function __set_state(array $properties): self public static function __set_state(array $properties): self
{ {
@ -79,16 +75,28 @@ final class VarSpecifier
return $this->name.$this->modifier; return $this->name.$this->modifier;
} }
/**
* @codeCoverageIgnore
* @deprecated since version 6.6.0 use the readonly property instead
*/
public function name(): string public function name(): string
{ {
return $this->name; return $this->name;
} }
/**
* @codeCoverageIgnore
* @deprecated since version 6.6.0 use the readonly property instead
*/
public function modifier(): string public function modifier(): string
{ {
return $this->modifier; return $this->modifier;
} }
/**
* @codeCoverageIgnore
* @deprecated since version 6.6.0 use the readonly property instead
*/
public function position(): int public function position(): int
{ {
return $this->position; return $this->position;

View File

@ -13,17 +13,18 @@ declare(strict_types=1);
namespace League\Uri\UriTemplate; namespace League\Uri\UriTemplate;
use ArrayAccess;
use Countable;
use League\Uri\Exceptions\TemplateCanNotBeExpanded; use League\Uri\Exceptions\TemplateCanNotBeExpanded;
use TypeError; use Stringable;
use function gettype;
use function is_array;
use function is_bool; use function is_bool;
use function is_object; use function is_object;
use function is_scalar; use function is_scalar;
use function method_exists;
use function sprintf;
final class VariableBag /**
* @implements ArrayAccess<string, string|bool|int|float|array<string|bool|int|float>>
*/
final class VariableBag implements ArrayAccess, Countable
{ {
/** /**
* @var array<string,string|array<string>> * @var array<string,string|array<string>>
@ -40,11 +41,39 @@ final class VariableBag
} }
} }
public function count(): int
{
return count($this->variables);
}
/**
* @param array{variables: array<string,string|array<string>>} $properties
*/
public static function __set_state(array $properties): self public static function __set_state(array $properties): self
{ {
return new self($properties['variables']); return new self($properties['variables']);
} }
public function offsetExists(mixed $offset): bool
{
return array_key_exists($offset, $this->variables);
}
public function offsetUnset(mixed $offset): void
{
unset($this->variables[$offset]);
}
public function offsetSet(mixed $offset, mixed $value): void
{
$this->assign($offset, $value); /* @phpstan-ignore-line */
}
public function offsetGet(mixed $offset): mixed
{
return $this->fetch($offset);
}
/** /**
* @return array<string,string|array<string>> * @return array<string,string|array<string>>
*/ */
@ -53,55 +82,45 @@ final class VariableBag
return $this->variables; return $this->variables;
} }
/**
* Tells whether the bag is empty or not.
*/
public function isEmpty(): bool
{
return [] === $this->variables;
}
/** /**
* Fetches the variable value if none found returns null. * Fetches the variable value if none found returns null.
* *
* @return null|string|array<string> * @return null|string|array<string>
*/ */
public function fetch(string $name) public function fetch(string $name): null|string|array
{ {
return $this->variables[$name] ?? null; return $this->variables[$name] ?? null;
} }
/** /**
* @param string|bool|int|float|array<string|bool|int|float> $value * @param string|bool|int|float|null|array<string|bool|int|float> $value
*/ */
public function assign(string $name, $value): void public function assign(string $name, string|bool|int|float|array|null $value): void
{ {
$this->variables[$name] = $this->normalizeValue($value, $name, true); $this->variables[$name] = $this->normalizeValue($value, $name, true);
} }
/** /**
* @param mixed $value the value to be expanded * @param Stringable|string|float|int|bool|null $value the value to be expanded
* *
* @throws TemplateCanNotBeExpanded if the value contains nested list * @throws TemplateCanNotBeExpanded if the value contains nested list
*
* @return string|array<string>
*/ */
private function normalizeValue($value, string $name, bool $isNestedListAllowed) private function normalizeValue(Stringable|array|string|float|int|bool|null $value, string $name, bool $isNestedListAllowed): array|string
{ {
if (is_bool($value)) { return match (true) {
return true === $value ? '1' : '0'; is_bool($value) => true === $value ? '1' : '0',
} (null === $value || is_scalar($value) || is_object($value)) => (string) $value,
!$isNestedListAllowed => throw TemplateCanNotBeExpanded::dueToNestedListOfValue($name),
if (null === $value || is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) { default => array_map(fn ($var): array|string => self::normalizeValue($var, $name, false), $value),
return (string) $value; };
}
if (!is_array($value)) {
throw new TypeError(sprintf('The variable '.$name.' must be NULL, a scalar or a stringable object `%s` given', gettype($value)));
}
if (!$isNestedListAllowed) {
throw TemplateCanNotBeExpanded::dueToNestedListOfValue($name);
}
foreach ($value as &$var) {
$var = self::normalizeValue($var, $name, false);
}
unset($var);
return $value;
} }
/** /**

View File

@ -1,25 +0,0 @@
<?php
declare(strict_types=1);
$finder = PhpCsFixer\Finder::create()
->in(__DIR__.'/src')
->in(__DIR__.'/tests');
$config = new PhpCsFixer\Config();
return $config->setRules([
'@Symfony' => true,
'@Symfony:risky' => true,
'native_function_invocation' => ['include'=> ['@all']],
'native_constant_invocation' => true,
'ordered_imports' => true,
'declare_strict_types' => false,
'linebreak_after_opening_tag' => false,
'single_import_per_statement' => false,
'blank_line_after_opening_tag' => false,
'concat_space' => ['spacing'=>'one'],
'phpdoc_align' => ['align'=>'left'],
])
->setRiskyAllowed(true)
->setFinder($finder);

View File

@ -2,6 +2,14 @@
All notable changes to this project will be documented in this file, in reverse chronological order by release. All notable changes to this project will be documented in this file, in reverse chronological order by release.
## 1.8.2
- Fix deprecation warnings in PHP 8.4
## 1.8.1
- Fix error handling in Stream::getContents()
## 1.8.0 ## 1.8.0
- Deprecate HttplugFactory, use Psr17Factory instead - Deprecate HttplugFactory, use Psr17Factory instead

View File

@ -1,36 +0,0 @@
parameters:
ignoreErrors:
-
message: "#^Result of && is always false\\.$#"
count: 1
path: src/Response.php
-
message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#"
count: 1
path: src/Response.php
-
message: "#^Result of && is always false\\.$#"
count: 1
path: src/ServerRequest.php
-
message: "#^Strict comparison using \\!\\=\\= between null and null will always evaluate to false\\.$#"
count: 1
path: src/ServerRequest.php
-
message: "#^Result of && is always false\\.$#"
count: 1
path: src/Stream.php
-
message: "#^Result of && is always false\\.$#"
count: 2
path: src/UploadedFile.php
-
message: "#^Strict comparison using \\=\\=\\= between false and true will always evaluate to false\\.$#"
count: 2
path: src/UploadedFile.php

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="4.30.0@d0bc6e25d89f649e4f36a534f330f8bb4643dd69">
<file src="src/Stream.php">
<NoValue occurrences="1">
<code>return \trigger_error((string) $e, \E_USER_ERROR);</code>
</NoValue>
</file>
</files>

View File

@ -57,7 +57,7 @@ class Psr17Factory implements RequestFactoryInterface, ResponseFactoryInterface,
return Stream::create($resource); return Stream::create($resource);
} }
public function createUploadedFile(StreamInterface $stream, int $size = null, int $error = \UPLOAD_ERR_OK, string $clientFilename = null, string $clientMediaType = null): UploadedFileInterface public function createUploadedFile(StreamInterface $stream, ?int $size = null, int $error = \UPLOAD_ERR_OK, ?string $clientFilename = null, ?string $clientMediaType = null): UploadedFileInterface
{ {
if (null === $size) { if (null === $size) {
$size = $stream->getSize(); $size = $stream->getSize();

View File

@ -39,7 +39,7 @@ class Response implements ResponseInterface
* @param string $version Protocol version * @param string $version Protocol version
* @param string|null $reason Reason phrase (when empty a default will be used based on the status code) * @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
*/ */
public function __construct(int $status = 200, array $headers = [], $body = null, string $version = '1.1', string $reason = null) public function __construct(int $status = 200, array $headers = [], $body = null, string $version = '1.1', ?string $reason = null)
{ {
// If we got no body, defer initialization of the stream until Response::getBody() // If we got no body, defer initialization of the stream until Response::getBody()
if ('' !== $body && null !== $body) { if ('' !== $body && null !== $body) {

View File

@ -260,11 +260,19 @@ class Stream implements StreamInterface
throw new \RuntimeException('Stream is detached'); throw new \RuntimeException('Stream is detached');
} }
if (false === $contents = @\stream_get_contents($this->stream)) { $exception = null;
throw new \RuntimeException('Unable to read stream contents: ' . (\error_get_last()['message'] ?? ''));
}
return $contents; \set_error_handler(static function ($type, $message) use (&$exception) {
throw $exception = new \RuntimeException('Unable to read stream contents: ' . $message);
});
try {
return \stream_get_contents($this->stream);
} catch (\Throwable $e) {
throw $e === $exception ? $e : new \RuntimeException('Unable to read stream contents: ' . $e->getMessage(), 0, $e);
} finally {
\restore_error_handler();
}
} }
/** /**

View File

@ -2,6 +2,14 @@
All notable changes to this project will be documented in this file, in reverse chronological order by release. All notable changes to this project will be documented in this file, in reverse chronological order by release.
## 1.0.3
Add `source` link in composer.json. No code changes.
## 1.0.2
Allow PSR-7 (psr/http-message) 2.0. No code changes.
## 1.0.1 ## 1.0.1
Allow installation with PHP 8. No code changes. Allow installation with PHP 8. No code changes.

View File

@ -7,6 +7,6 @@ Note that this is not a HTTP Client implementation of its own. It is merely abst
The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist. The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist.
[psr-url]: http://www.php-fig.org/psr/psr-18 [psr-url]: https://www.php-fig.org/psr/psr-18
[package-url]: https://packagist.org/packages/psr/http-client [package-url]: https://packagist.org/packages/psr/http-client
[implementation-url]: https://packagist.org/providers/psr/http-client-implementation [implementation-url]: https://packagist.org/providers/psr/http-client-implementation

View File

@ -7,12 +7,15 @@
"authors": [ "authors": [
{ {
"name": "PHP-FIG", "name": "PHP-FIG",
"homepage": "http://www.php-fig.org/" "homepage": "https://www.php-fig.org/"
} }
], ],
"support": {
"source": "https://github.com/php-fig/http-client"
},
"require": { "require": {
"php": "^7.0 || ^8.0", "php": "^7.0 || ^8.0",
"psr/http-message": "^1.0" "psr/http-message": "^1.0 || ^2.0"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@ -1,4 +1,4 @@
Copyright (c) 2018-2019 Fabien Potencier Copyright (c) 2018-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -16,7 +16,7 @@
} }
], ],
"require": { "require": {
"php": ">=7.1" "php": ">=7.2"
}, },
"provide": { "provide": {
"ext-ctype": "*" "ext-ctype": "*"
@ -30,9 +30,6 @@
}, },
"minimum-stability": "dev", "minimum-stability": "dev",
"extra": { "extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill" "url": "https://github.com/symfony/polyfill"

View File

@ -1,4 +1,4 @@
Copyright (c) 2020 Fabien Potencier Copyright (c) 2020-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -20,7 +20,7 @@
} }
], ],
"require": { "require": {
"php": ">=7.1" "php": ">=7.2"
}, },
"autoload": { "autoload": {
"psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, "psr-4": { "Symfony\\Polyfill\\Php80\\": "" },
@ -29,9 +29,6 @@
}, },
"minimum-stability": "dev", "minimum-stability": "dev",
"extra": { "extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill" "url": "https://github.com/symfony/polyfill"

View File

@ -1,4 +1,4 @@
Copyright (c) 2021 Fabien Potencier Copyright (c) 2021-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -7,6 +7,7 @@ This component provides features added to PHP 8.1 core:
- [`enum_exists`](https://php.net/enum-exists) - [`enum_exists`](https://php.net/enum-exists)
- [`MYSQLI_REFRESH_REPLICA`](https://php.net/mysqli.constants#constantmysqli-refresh-replica) constant - [`MYSQLI_REFRESH_REPLICA`](https://php.net/mysqli.constants#constantmysqli-refresh-replica) constant
- [`ReturnTypeWillChange`](https://wiki.php.net/rfc/internal_method_return_types) - [`ReturnTypeWillChange`](https://wiki.php.net/rfc/internal_method_return_types)
- [`CURLStringFile`](https://php.net/CURLStringFile) (but only if PHP >= 7.4 is used)
More information can be found in the More information can be found in the
[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).

View File

@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (\PHP_VERSION_ID >= 70400 && extension_loaded('curl')) {
/**
* @property string $data
*/
class CURLStringFile extends CURLFile
{
private $data;
public function __construct(string $data, string $postname, string $mime = 'application/octet-stream')
{
$this->data = $data;
parent::__construct('data://application/octet-stream;base64,'.base64_encode($data), $mime, $postname);
}
public function __set(string $name, $value): void
{
if ('data' !== $name) {
$this->$name = $value;
return;
}
if (is_object($value) ? !method_exists($value, '__toString') : !is_scalar($value)) {
throw new TypeError('Cannot assign '.gettype($value).' to property CURLStringFile::$data of type string');
}
$this->name = 'data://application/octet-stream;base64,'.base64_encode($value);
}
public function __isset(string $name): bool
{
return isset($this->$name);
}
public function &__get(string $name)
{
return $this->$name;
}
}
}

View File

@ -25,9 +25,6 @@
}, },
"minimum-stability": "dev", "minimum-stability": "dev",
"extra": { "extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill" "url": "https://github.com/symfony/polyfill"

View File

@ -46,7 +46,7 @@ class ExecutableFinder
* *
* @return string|null * @return string|null
*/ */
public function find(string $name, string $default = null, array $extraDirs = []) public function find(string $name, ?string $default = null, array $extraDirs = [])
{ {
if (\ini_get('open_basedir')) { if (\ini_get('open_basedir')) {
$searchPath = array_merge(explode(\PATH_SEPARATOR, \ini_get('open_basedir')), $extraDirs); $searchPath = array_merge(explode(\PATH_SEPARATOR, \ini_get('open_basedir')), $extraDirs);

View File

@ -30,7 +30,7 @@ class InputStream implements \IteratorAggregate
/** /**
* Sets a callback that is called when the write buffer becomes empty. * Sets a callback that is called when the write buffer becomes empty.
*/ */
public function onEmpty(callable $onEmpty = null) public function onEmpty(?callable $onEmpty = null)
{ {
$this->onEmpty = $onEmpty; $this->onEmpty = $onEmpty;
} }

View File

@ -35,7 +35,7 @@ class PhpExecutableFinder
{ {
if ($php = getenv('PHP_BINARY')) { if ($php = getenv('PHP_BINARY')) {
if (!is_executable($php)) { if (!is_executable($php)) {
$command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v'; $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v --';
if ($php = strtok(exec($command.' '.escapeshellarg($php)), \PHP_EOL)) { if ($php = strtok(exec($command.' '.escapeshellarg($php)), \PHP_EOL)) {
if (!is_executable($php)) { if (!is_executable($php)) {
return false; return false;

View File

@ -32,7 +32,7 @@ class PhpProcess extends Process
* @param int $timeout The timeout in seconds * @param int $timeout The timeout in seconds
* @param array|null $php Path to the PHP binary to use with any additional arguments * @param array|null $php Path to the PHP binary to use with any additional arguments
*/ */
public function __construct(string $script, string $cwd = null, array $env = null, int $timeout = 60, array $php = null) public function __construct(string $script, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null)
{ {
if (null === $php) { if (null === $php) {
$executableFinder = new PhpExecutableFinder(); $executableFinder = new PhpExecutableFinder();
@ -53,7 +53,7 @@ class PhpProcess extends Process
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60) public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, $input = null, ?float $timeout = 60)
{ {
throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class)); throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
} }
@ -61,7 +61,7 @@ class PhpProcess extends Process
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function start(callable $callback = null, array $env = []) public function start(?callable $callback = null, array $env = [])
{ {
if (null === $this->getCommandLine()) { if (null === $this->getCommandLine()) {
throw new RuntimeException('Unable to find the PHP executable.'); throw new RuntimeException('Unable to find the PHP executable.');

View File

@ -149,7 +149,7 @@ class WindowsPipes extends AbstractPipes
if ($w) { if ($w) {
@stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6); @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
} elseif ($this->fileHandles) { } elseif ($this->fileHandles) {
usleep(Process::TIMEOUT_PRECISION * 1E6); usleep((int) (Process::TIMEOUT_PRECISION * 1E6));
} }
} }
foreach ($this->fileHandles as $type => $fileHandle) { foreach ($this->fileHandles as $type => $fileHandle) {

View File

@ -80,6 +80,7 @@ class Process implements \IteratorAggregate
private $processPipes; private $processPipes;
private $latestSignal; private $latestSignal;
private $cachedExitCode;
private static $sigchild; private static $sigchild;
@ -140,7 +141,7 @@ class Process implements \IteratorAggregate
* *
* @throws LogicException When proc_open is not installed * @throws LogicException When proc_open is not installed
*/ */
public function __construct(array $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60) public function __construct(array $command, ?string $cwd = null, ?array $env = null, $input = null, ?float $timeout = 60)
{ {
if (!\function_exists('proc_open')) { if (!\function_exists('proc_open')) {
throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.'); throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.');
@ -189,7 +190,7 @@ class Process implements \IteratorAggregate
* *
* @throws LogicException When proc_open is not installed * @throws LogicException When proc_open is not installed
*/ */
public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60) public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, $input = null, ?float $timeout = 60)
{ {
$process = new static([], $cwd, $env, $input, $timeout); $process = new static([], $cwd, $env, $input, $timeout);
$process->commandline = $command; $process->commandline = $command;
@ -247,7 +248,7 @@ class Process implements \IteratorAggregate
* *
* @final * @final
*/ */
public function run(callable $callback = null, array $env = []): int public function run(?callable $callback = null, array $env = []): int
{ {
$this->start($callback, $env); $this->start($callback, $env);
@ -266,7 +267,7 @@ class Process implements \IteratorAggregate
* *
* @final * @final
*/ */
public function mustRun(callable $callback = null, array $env = []): self public function mustRun(?callable $callback = null, array $env = []): self
{ {
if (0 !== $this->run($callback, $env)) { if (0 !== $this->run($callback, $env)) {
throw new ProcessFailedException($this); throw new ProcessFailedException($this);
@ -294,7 +295,7 @@ class Process implements \IteratorAggregate
* @throws RuntimeException When process is already running * @throws RuntimeException When process is already running
* @throws LogicException In case a callback is provided and output has been disabled * @throws LogicException In case a callback is provided and output has been disabled
*/ */
public function start(callable $callback = null, array $env = []) public function start(?callable $callback = null, array $env = [])
{ {
if ($this->isRunning()) { if ($this->isRunning()) {
throw new RuntimeException('Process is already running.'); throw new RuntimeException('Process is already running.');
@ -385,7 +386,7 @@ class Process implements \IteratorAggregate
* *
* @final * @final
*/ */
public function restart(callable $callback = null, array $env = []): self public function restart(?callable $callback = null, array $env = []): self
{ {
if ($this->isRunning()) { if ($this->isRunning()) {
throw new RuntimeException('Process is already running.'); throw new RuntimeException('Process is already running.');
@ -412,7 +413,7 @@ class Process implements \IteratorAggregate
* @throws ProcessSignaledException When process stopped after receiving signal * @throws ProcessSignaledException When process stopped after receiving signal
* @throws LogicException When process is not yet started * @throws LogicException When process is not yet started
*/ */
public function wait(callable $callback = null) public function wait(?callable $callback = null)
{ {
$this->requireProcessIsStarted(__FUNCTION__); $this->requireProcessIsStarted(__FUNCTION__);
@ -914,7 +915,7 @@ class Process implements \IteratorAggregate
* *
* @return int|null The exit-code of the process or null if it's not running * @return int|null The exit-code of the process or null if it's not running
*/ */
public function stop(float $timeout = 10, int $signal = null) public function stop(float $timeout = 10, ?int $signal = null)
{ {
$timeoutMicro = microtime(true) + $timeout; $timeoutMicro = microtime(true) + $timeout;
if ($this->isRunning()) { if ($this->isRunning()) {
@ -1310,7 +1311,7 @@ class Process implements \IteratorAggregate
* *
* @return \Closure * @return \Closure
*/ */
protected function buildCallback(callable $callback = null) protected function buildCallback(?callable $callback = null)
{ {
if ($this->outputDisabled) { if ($this->outputDisabled) {
return function ($type, $data) use ($callback): bool { return function ($type, $data) use ($callback): bool {
@ -1345,6 +1346,19 @@ class Process implements \IteratorAggregate
$this->processInformation = proc_get_status($this->process); $this->processInformation = proc_get_status($this->process);
$running = $this->processInformation['running']; $running = $this->processInformation['running'];
// In PHP < 8.3, "proc_get_status" only returns the correct exit status on the first call.
// Subsequent calls return -1 as the process is discarded. This workaround caches the first
// retrieved exit status for consistent results in later calls, mimicking PHP 8.3 behavior.
if (\PHP_VERSION_ID < 80300) {
if (!isset($this->cachedExitCode) && !$running && -1 !== $this->processInformation['exitcode']) {
$this->cachedExitCode = $this->processInformation['exitcode'];
}
if (isset($this->cachedExitCode) && !$running && -1 === $this->processInformation['exitcode']) {
$this->processInformation['exitcode'] = $this->cachedExitCode;
}
}
$this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running); $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);
if ($this->fallbackStatus && $this->isSigchildEnabled()) { if ($this->fallbackStatus && $this->isSigchildEnabled()) {

View File

@ -3,7 +3,7 @@
Plugin Name: WP-WebAuthn Plugin Name: WP-WebAuthn
Plugin URI: https://flyhigher.top Plugin URI: https://flyhigher.top
Description: WP-WebAuthn allows you to safely login to your WordPress site without password. Description: WP-WebAuthn allows you to safely login to your WordPress site without password.
Version: 1.3.1 Version: 1.3.4
Author: Axton Author: Axton
Author URI: https://axton.cc Author URI: https://axton.cc
License: GPLv3 License: GPLv3
@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License along with thi
register_activation_hook(__FILE__, 'wwa_init'); register_activation_hook(__FILE__, 'wwa_init');
function wwa_init(){ function wwa_init(){
if(version_compare(get_bloginfo('version'), '4.4', '<')){ if(version_compare(get_bloginfo('version'), '5.0', '<')){
deactivate_plugins(basename(__FILE__)); //disable deactivate_plugins(basename(__FILE__)); //disable
}else{ }else{
wwa_init_data(); wwa_init_data();
@ -32,7 +32,7 @@ wwa_init_data();
function wwa_init_data(){ function wwa_init_data(){
if(!get_option('wwa_init')){ if(!get_option('wwa_init')){
// Init // Init
$site_domain = parse_url(site_url(), PHP_URL_HOST); $site_domain = wp_parse_url(site_url(), PHP_URL_HOST);
$wwa_init_options = array( $wwa_init_options = array(
'user_credentials' => '{}', 'user_credentials' => '{}',
'user_credentials_meta' => '{}', 'user_credentials_meta' => '{}',
@ -53,7 +53,7 @@ function wwa_init_data(){
include('wwa-version.php'); include('wwa-version.php');
update_option('wwa_version', $wwa_version); update_option('wwa_version', $wwa_version);
update_option('wwa_log', array()); update_option('wwa_log', array());
update_option('wwa_init', md5(date('Y-m-d H:i:s'))); update_option('wwa_init', md5(date('Y-m-d H:i:s'))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
}else{ }else{
include('wwa-version.php'); include('wwa-version.php');
if(!get_option('wwa_version') || get_option('wwa_version')['version'] != $wwa_version['version']){ if(!get_option('wwa_version') || get_option('wwa_version')['version'] != $wwa_version['version']){
@ -83,4 +83,3 @@ include('wwa-menus.php');
include('wwa-functions.php'); include('wwa-functions.php');
include('wwa-ajax.php'); include('wwa-ajax.php');
include('wwa-shortcodes.php'); include('wwa-shortcodes.php');
?>

View File

@ -24,7 +24,7 @@ if(!function_exists('sodium_crypto_sign_detached')){
add_settings_error('wwa_settings', 'sodium_error', __("PHP extension sodium doesn't seem to exist, rendering WP-WebAuthn unable to function.", 'wp-webauthn')); add_settings_error('wwa_settings', 'sodium_error', __("PHP extension sodium doesn't seem to exist, rendering WP-WebAuthn unable to function.", 'wp-webauthn'));
$wwa_not_allowed = true; $wwa_not_allowed = true;
} }
if(!wwa_check_ssl() && (parse_url(site_url(), PHP_URL_HOST) !== 'localhost' && parse_url(site_url(), PHP_URL_HOST) !== '127.0.0.1')){ if(!wwa_check_ssl() && (wp_parse_url(site_url(), PHP_URL_HOST) !== 'localhost' && wp_parse_url(site_url(), PHP_URL_HOST) !== '127.0.0.1')){
add_settings_error('wwa_settings', 'https_error', __('WebAuthn features are restricted to websites in secure contexts. Please make sure your website is served over HTTPS or locally with <code>localhost</code>.', 'wp-webauthn')); add_settings_error('wwa_settings', 'https_error', __('WebAuthn features are restricted to websites in secure contexts. Please make sure your website is served over HTTPS or locally with <code>localhost</code>.', 'wp-webauthn'));
$wwa_not_allowed = true; $wwa_not_allowed = true;
} }
@ -33,18 +33,22 @@ if(
(isset($_POST['wwa_ref']) && $_POST['wwa_ref'] === 'true') (isset($_POST['wwa_ref']) && $_POST['wwa_ref'] === 'true')
&& check_admin_referer('wwa_options_update') && check_admin_referer('wwa_options_update')
&& wwa_validate_privileges() && wwa_validate_privileges()
&& ($_POST['first_choice'] === 'true' || $_POST['first_choice'] === 'false' || $_POST['first_choice'] === 'webauthn') && (isset($_POST['first_choice']) && ($_POST['first_choice'] === 'true' || $_POST['first_choice'] === 'false' || $_POST['first_choice'] === 'webauthn'))
&& ($_POST['remember_me'] === 'true' || $_POST['remember_me'] === 'false') && (isset($_POST['remember_me']) && ($_POST['remember_me'] === 'true' || $_POST['remember_me'] === 'false'))
&& ($_POST['email_login'] === 'true' || $_POST['email_login'] === 'false') && (isset($_POST['email_login']) && ($_POST['email_login'] === 'true' || $_POST['email_login'] === 'false'))
&& ($_POST['user_verification'] === 'true' || $_POST['user_verification'] === 'false') && (isset($_POST['user_verification']) && ($_POST['user_verification'] === 'true' || $_POST['user_verification'] === 'false'))
&& ($_POST['usernameless_login'] === 'true' || $_POST['usernameless_login'] === 'false') && (isset($_POST['usernameless_login']) && ($_POST['usernameless_login'] === 'true' || $_POST['usernameless_login'] === 'false'))
&& ($_POST['allow_authenticator_type'] === 'none' || $_POST['allow_authenticator_type'] === 'platform' || $_POST['allow_authenticator_type'] === 'cross-platform') && (isset($_POST['allow_authenticator_type']) && ($_POST['allow_authenticator_type'] === 'none' || $_POST['allow_authenticator_type'] === 'platform' || $_POST['allow_authenticator_type'] === 'cross-platform'))
&& ($_POST['password_reset'] === 'off' || $_POST['password_reset'] === 'admin' || $_POST['password_reset'] === 'all') && (isset($_POST['password_reset']) && ($_POST['password_reset'] === 'off' || $_POST['password_reset'] === 'admin' || $_POST['password_reset'] === 'all'))
&& ($_POST['after_user_registration'] === 'none' || $_POST['after_user_registration'] === 'login') && (isset($_POST['after_user_registration']) && ($_POST['after_user_registration'] === 'none' || $_POST['after_user_registration'] === 'login'))
&& ($_POST['logging'] === 'true' || $_POST['logging'] === 'false') && (isset($_POST['logging']) && ($_POST['logging'] === 'true' || $_POST['logging'] === 'false'))
&& isset($_POST['website_name'])
&& isset($_POST['website_domain'])
){ ){
$res_id = wwa_generate_random_string(5); $res_id = wwa_generate_random_string(5);
if(sanitize_text_field($_POST['logging']) === 'true' && wwa_get_option('logging') === 'false'){
$post_logging = sanitize_text_field(wp_unslash($_POST['logging']));
if($post_logging === 'true' && wwa_get_option('logging') === 'false'){
// Initialize log // Initialize log
if(!function_exists('gmp_intval')){ if(!function_exists('gmp_intval')){
wwa_add_log($res_id, 'Warning: PHP extension gmp not found', true); wwa_add_log($res_id, 'Warning: PHP extension gmp not found', true);
@ -55,70 +59,70 @@ if(
if(!function_exists('sodium_crypto_sign_detached')){ if(!function_exists('sodium_crypto_sign_detached')){
wwa_add_log($res_id, 'Warning: PHP extension sodium not found', true); wwa_add_log($res_id, 'Warning: PHP extension sodium not found', true);
} }
if(!wwa_check_ssl() && (parse_url(site_url(), PHP_URL_HOST) !== 'localhost' && parse_url(site_url(), PHP_URL_HOST) !== '127.0.0.1')){ if(!wwa_check_ssl() && (wp_parse_url(site_url(), PHP_URL_HOST) !== 'localhost' && wp_parse_url(site_url(), PHP_URL_HOST) !== '127.0.0.1')){
wwa_add_log($res_id, 'Warning: Not in security context', true); wwa_add_log($res_id, 'Warning: Not in security context', true);
} }
wwa_add_log($res_id, 'PHP Version => '.phpversion().', WordPress Version => '.get_bloginfo('version').', WP-WebAuthn Version => '.get_option('wwa_version')['version'], true); wwa_add_log($res_id, 'PHP Version => '.phpversion().', WordPress Version => '.get_bloginfo('version').', WP-WebAuthn Version => '.get_option('wwa_version')['version'], true);
wwa_add_log($res_id, 'Current config: first_choice => "'.wwa_get_option('first_choice').'", website_name => "'.wwa_get_option('website_name').'", website_domain => "'.wwa_get_option('website_domain').'", remember_me => "'.wwa_get_option('remember_me').'", email_login => "'.wwa_get_option('email_login').'", user_verification => "'.wwa_get_option('user_verification').'", allow_authenticator_type => "'.wwa_get_option('allow_authenticator_type').'", usernameless_login => "'.wwa_get_option('usernameless_login').'", password_reset => "'.wwa_get_option('password_reset').'", after_user_registration => "'.wwa_get_option('after_user_registration').'"', true); wwa_add_log($res_id, 'Current config: first_choice => "'.wwa_get_option('first_choice').'", website_name => "'.wwa_get_option('website_name').'", website_domain => "'.wwa_get_option('website_domain').'", remember_me => "'.wwa_get_option('remember_me').'", email_login => "'.wwa_get_option('email_login').'", user_verification => "'.wwa_get_option('user_verification').'", allow_authenticator_type => "'.wwa_get_option('allow_authenticator_type').'", usernameless_login => "'.wwa_get_option('usernameless_login').'", password_reset => "'.wwa_get_option('password_reset').'", after_user_registration => "'.wwa_get_option('after_user_registration').'"', true);
wwa_add_log($res_id, 'Logger initialized', true); wwa_add_log($res_id, 'Logger initialized', true);
} }
wwa_update_option('logging', sanitize_text_field($_POST['logging'])); wwa_update_option('logging', $post_logging);
$post_first_choice = sanitize_text_field($_POST['first_choice']); $post_first_choice = sanitize_text_field(wp_unslash($_POST['first_choice']));
if($post_first_choice !== wwa_get_option('first_choice')){ if($post_first_choice !== wwa_get_option('first_choice')){
wwa_add_log($res_id, 'first_choice: "'.wwa_get_option('first_choice').'"->"'.$post_first_choice.'"'); wwa_add_log($res_id, 'first_choice: "'.wwa_get_option('first_choice').'"->"'.$post_first_choice.'"');
} }
wwa_update_option('first_choice', $post_first_choice); wwa_update_option('first_choice', $post_first_choice);
$post_website_name = sanitize_text_field($_POST['website_name']); $post_website_name = sanitize_text_field(wp_unslash($_POST['website_name']));
if($post_website_name !== wwa_get_option('website_name')){ if($post_website_name !== wwa_get_option('website_name')){
wwa_add_log($res_id, 'website_name: "'.wwa_get_option('website_name').'"->"'.$post_website_name.'"'); wwa_add_log($res_id, 'website_name: "'.wwa_get_option('website_name').'"->"'.$post_website_name.'"');
} }
wwa_update_option('website_name', $post_website_name); wwa_update_option('website_name', $post_website_name);
$post_website_domain = str_replace('https:', '', str_replace('/', '', sanitize_text_field($_POST['website_domain']))); $post_website_domain = str_replace('https:', '', str_replace('/', '', sanitize_text_field(wp_unslash($_POST['website_domain']))));
if($post_website_domain !== wwa_get_option('website_domain')){ if($post_website_domain !== wwa_get_option('website_domain')){
wwa_add_log($res_id, 'website_domain: "'.wwa_get_option('website_domain').'"->"'.$post_website_domain.'"'); wwa_add_log($res_id, 'website_domain: "'.wwa_get_option('website_domain').'"->"'.$post_website_domain.'"');
} }
wwa_update_option('website_domain', $post_website_domain); wwa_update_option('website_domain', $post_website_domain);
$post_remember_me = sanitize_text_field($_POST['remember_me']); $post_remember_me = sanitize_text_field(wp_unslash($_POST['remember_me']));
if($post_remember_me !== wwa_get_option('remember_me')){ if($post_remember_me !== wwa_get_option('remember_me')){
wwa_add_log($res_id, 'remember_me: "'.wwa_get_option('remember_me').'"->"'.$post_remember_me.'"'); wwa_add_log($res_id, 'remember_me: "'.wwa_get_option('remember_me').'"->"'.$post_remember_me.'"');
} }
wwa_update_option('remember_me', $post_remember_me); wwa_update_option('remember_me', $post_remember_me);
$post_email_login = sanitize_text_field($_POST['email_login']); $post_email_login = sanitize_text_field(wp_unslash($_POST['email_login']));
if($post_email_login !== wwa_get_option('email_login')){ if($post_email_login !== wwa_get_option('email_login')){
wwa_add_log($res_id, 'email_login: "'.wwa_get_option('email_login').'"->"'.$post_email_login.'"'); wwa_add_log($res_id, 'email_login: "'.wwa_get_option('email_login').'"->"'.$post_email_login.'"');
} }
wwa_update_option('email_login', $post_email_login); wwa_update_option('email_login', $post_email_login);
$post_user_verification = sanitize_text_field($_POST['user_verification']); $post_user_verification = sanitize_text_field(wp_unslash($_POST['user_verification']));
if($post_user_verification !== wwa_get_option('user_verification')){ if($post_user_verification !== wwa_get_option('user_verification')){
wwa_add_log($res_id, 'user_verification: "'.wwa_get_option('user_verification').'"->"'.$post_user_verification.'"'); wwa_add_log($res_id, 'user_verification: "'.wwa_get_option('user_verification').'"->"'.$post_user_verification.'"');
} }
wwa_update_option('user_verification', $post_user_verification); wwa_update_option('user_verification', $post_user_verification);
$post_allow_authenticator_type = sanitize_text_field($_POST['allow_authenticator_type']); $post_allow_authenticator_type = sanitize_text_field(wp_unslash($_POST['allow_authenticator_type']));
if($post_allow_authenticator_type !== wwa_get_option('allow_authenticator_type')){ if($post_allow_authenticator_type !== wwa_get_option('allow_authenticator_type')){
wwa_add_log($res_id, 'allow_authenticator_type: "'.wwa_get_option('allow_authenticator_type').'"->"'.$post_allow_authenticator_type.'"'); wwa_add_log($res_id, 'allow_authenticator_type: "'.wwa_get_option('allow_authenticator_type').'"->"'.$post_allow_authenticator_type.'"');
} }
wwa_update_option('allow_authenticator_type', $post_allow_authenticator_type); wwa_update_option('allow_authenticator_type', $post_allow_authenticator_type);
$post_usernameless_login = sanitize_text_field($_POST['usernameless_login']); $post_usernameless_login = sanitize_text_field(wp_unslash($_POST['usernameless_login']));
if($post_usernameless_login !== wwa_get_option('usernameless_login')){ if($post_usernameless_login !== wwa_get_option('usernameless_login')){
wwa_add_log($res_id, 'usernameless_login: "'.wwa_get_option('usernameless_login').'"->"'.$post_usernameless_login.'"'); wwa_add_log($res_id, 'usernameless_login: "'.wwa_get_option('usernameless_login').'"->"'.$post_usernameless_login.'"');
} }
wwa_update_option('usernameless_login', $post_usernameless_login); wwa_update_option('usernameless_login', $post_usernameless_login);
$post_password_reset = sanitize_text_field($_POST['password_reset']); $post_password_reset = sanitize_text_field(wp_unslash($_POST['password_reset']));
if($post_password_reset !== wwa_get_option('password_reset')){ if($post_password_reset !== wwa_get_option('password_reset')){
wwa_add_log($res_id, 'password_reset: "'.wwa_get_option('password_reset').'"->"'.$post_password_reset.'"'); wwa_add_log($res_id, 'password_reset: "'.wwa_get_option('password_reset').'"->"'.$post_password_reset.'"');
} }
wwa_update_option('password_reset', $post_password_reset); wwa_update_option('password_reset', $post_password_reset);
$post_after_user_registration = sanitize_text_field($_POST['after_user_registration']); $post_after_user_registration = sanitize_text_field(wp_unslash($_POST['after_user_registration']));
if($post_after_user_registration !== wwa_get_option('after_user_registration')){ if($post_after_user_registration !== wwa_get_option('after_user_registration')){
wwa_add_log($res_id, 'after_user_registration: "'.wwa_get_option('after_user_registration').'"->"'.$post_after_user_registration.'"'); wwa_add_log($res_id, 'after_user_registration: "'.wwa_get_option('after_user_registration').'"->"'.$post_after_user_registration.'"');
} }
@ -138,7 +142,7 @@ if(wwa_validate_privileges()){ ?>
<?php <?php
wp_nonce_field('wwa_options_update'); wp_nonce_field('wwa_options_update');
?> ?>
<input type='hidden' name='wwa_ref' value='true'> <input type="hidden" name="wwa_ref" value="true">
<table class="form-table"> <table class="form-table">
<tr> <tr>
<th scope="row"><label for="first_choice"><?php _e('Preferred login method', 'wp-webauthn');?></label></th> <th scope="row"><label for="first_choice"><?php _e('Preferred login method', 'wp-webauthn');?></label></th>
@ -316,6 +320,7 @@ if($wwa_v_log === false){
<p class="description"><?php _e('Automatic update every 5 seconds.', 'wp-webauthn');?></p> <p class="description"><?php _e('Automatic update every 5 seconds.', 'wp-webauthn');?></p>
<br> <br>
</div> </div>
<?php }}?> <?php }}
/* translators: %s: admin profile url */ ?>
<p class="description"><?php printf(__('To register a new authenticator or edit your authenticators, please go to <a href="%s#wwa-webauthn-start">your profile</a>.', 'wp-webauthn'), admin_url('profile.php'));?></p> <p class="description"><?php printf(__('To register a new authenticator or edit your authenticators, please go to <a href="%s#wwa-webauthn-start">your profile</a>.', 'wp-webauthn'), admin_url('profile.php'));?></p>
</div> </div>

View File

@ -67,14 +67,14 @@ class PublicKeyCredentialSourceRepository implements PublicKeyCredentialSourceRe
public function updateCredentialLastUsed(string $publicKeyCredentialId): void { public function updateCredentialLastUsed(string $publicKeyCredentialId): void {
$credential = $this->findOneMetaByCredentialId($publicKeyCredentialId); $credential = $this->findOneMetaByCredentialId($publicKeyCredentialId);
if($credential !== null){ if($credential !== null){
$credential["last_used"] = date('Y-m-d H:i:s', current_time('timestamp')); $credential["last_used"] = date('Y-m-d H:i:s', current_time('timestamp')); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
$meta = json_decode(wwa_get_option("user_credentials_meta"), true); $meta = json_decode(wwa_get_option("user_credentials_meta"), true);
$meta[base64_encode($publicKeyCredentialId)] = $credential; $meta[base64_encode($publicKeyCredentialId)] = $credential;
wwa_update_option("user_credentials_meta", json_encode($meta)); wwa_update_option("user_credentials_meta", wp_json_encode($meta));
} }
} }
// List all authenticators // List all authenticators for the front-end
public function getShowList(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array { public function getShowList(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array {
$data = json_decode(wwa_get_option("user_credentials_meta"), true); $data = json_decode(wwa_get_option("user_credentials_meta"), true);
$arr = array(); $arr = array();
@ -91,7 +91,7 @@ class PublicKeyCredentialSourceRepository implements PublicKeyCredentialSourceRe
)); ));
} }
} }
return array_map(function($item){return array("key" => $item["key"], "name" => $item["name"], "type" => $item["type"], "added" => $item["added"], "usernameless" => $item["usernameless"], "last_used" => $item["last_used"]);}, $arr); return array_map(function($item){return array("key" => $item["key"], "name" => esc_html($item["name"]), "type" => $item["type"], "added" => $item["added"], "usernameless" => $item["usernameless"], "last_used" => $item["last_used"]);}, $arr);
} }
// Modify an authenticator // Modify an authenticator
@ -105,7 +105,7 @@ class PublicKeyCredentialSourceRepository implements PublicKeyCredentialSourceRe
if(base64_encode($item->getPublicKeyCredentialId()) === base64_decode(str_pad(strtr($id, '-_', '+/'), strlen($id) % 4, '=', STR_PAD_RIGHT))){ if(base64_encode($item->getPublicKeyCredentialId()) === base64_decode(str_pad(strtr($id, '-_', '+/'), strlen($id) % 4, '=', STR_PAD_RIGHT))){
if($action === "rename"){ if($action === "rename"){
$this->renameCredential(base64_encode($item->getPublicKeyCredentialId()), $name, $res_id); $this->renameCredential(base64_encode($item->getPublicKeyCredentialId()), $name, $res_id);
}else if($action === "remove"){ }elseif($action === "remove"){
$this->removeCredential(base64_encode($item->getPublicKeyCredentialId()), $res_id); $this->removeCredential(base64_encode($item->getPublicKeyCredentialId()), $res_id);
} }
wwa_add_log($res_id, "ajax_modify_authenticator: Done"); wwa_add_log($res_id, "ajax_modify_authenticator: Done");
@ -122,7 +122,7 @@ class PublicKeyCredentialSourceRepository implements PublicKeyCredentialSourceRe
$meta = json_decode(wwa_get_option("user_credentials_meta"), true); $meta = json_decode(wwa_get_option("user_credentials_meta"), true);
wwa_add_log($res_id, "ajax_modify_authenticator: Rename \"".base64_decode($meta[$id]["human_name"])."\" -> \"".$name."\""); wwa_add_log($res_id, "ajax_modify_authenticator: Rename \"".base64_decode($meta[$id]["human_name"])."\" -> \"".$name."\"");
$meta[$id]["human_name"] = base64_encode($name); $meta[$id]["human_name"] = base64_encode($name);
wwa_update_option("user_credentials_meta", json_encode($meta)); wwa_update_option("user_credentials_meta", wp_json_encode($meta));
} }
// Remove a credential from database by credential ID // Remove a credential from database by credential ID
@ -133,7 +133,7 @@ class PublicKeyCredentialSourceRepository implements PublicKeyCredentialSourceRe
$meta = json_decode(wwa_get_option("user_credentials_meta"), true); $meta = json_decode(wwa_get_option("user_credentials_meta"), true);
wwa_add_log($res_id, "ajax_modify_authenticator: Remove \"".base64_decode($meta[$id]["human_name"])."\""); wwa_add_log($res_id, "ajax_modify_authenticator: Remove \"".base64_decode($meta[$id]["human_name"])."\"");
unset($meta[$id]); unset($meta[$id]);
wwa_update_option("user_credentials_meta", json_encode($meta)); wwa_update_option("user_credentials_meta", wp_json_encode($meta));
} }
// Read credential database // Read credential database
@ -150,14 +150,21 @@ class PublicKeyCredentialSourceRepository implements PublicKeyCredentialSourceRe
// Save credentials data // Save credentials data
private function write(array $data, string $key, bool $usernameless = false): void { private function write(array $data, string $key, bool $usernameless = false): void {
if(isset($_POST["type"]) && ($_POST["type"] === "platform" || $_POST["type"] == "cross-platform" || $_POST["type"] === "none") && $key !== ''){ if(isset($_POST["name"]) && isset($_POST["type"]) && ($_POST["type"] === "platform" || $_POST["type"] == "cross-platform" || $_POST["type"] === "none") && $key !== ''){
// Save credentials's meta separately // Save credentials's meta separately
$source = $data[$key]->getUserHandle(); $source = $data[$key]->getUserHandle();
$meta = json_decode(wwa_get_option("user_credentials_meta"), true); $meta = json_decode(wwa_get_option("user_credentials_meta"), true);
$meta[$key] = array("human_name" => base64_encode(sanitize_text_field($_POST["name"])), "added" => date('Y-m-d H:i:s', current_time('timestamp')), "authenticator_type" => $_POST["type"], "user" => $source, "usernameless" => $usernameless, "last_used" => "-"); $meta[$key] = array(
wwa_update_option("user_credentials_meta", json_encode($meta)); "human_name" => base64_encode(sanitize_text_field(wp_unslash($_POST["name"]))),
"added" => date('Y-m-d H:i:s', current_time('timestamp')), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
"authenticator_type" => sanitize_text_field(wp_unslash($_POST["type"])),
"user" => $source,
"usernameless" => $usernameless,
"last_used" => "-"
);
wwa_update_option("user_credentials_meta", wp_json_encode($meta));
} }
wwa_update_option("user_credentials", json_encode($data)); wwa_update_option("user_credentials", wp_json_encode($data));
} }
} }
@ -188,16 +195,16 @@ function wwa_ajax_create(){
}else{ }else{
// Sanitize the input // Sanitize the input
$wwa_get = array(); $wwa_get = array();
$wwa_get["name"] = sanitize_text_field($_GET["name"]); $wwa_get["name"] = sanitize_text_field(wp_unslash($_GET["name"]));
$wwa_get["type"] = sanitize_text_field($_GET["type"]); $wwa_get["type"] = sanitize_text_field(wp_unslash($_GET["type"]));
$wwa_get["usernameless"] = sanitize_text_field($_GET["usernameless"]); $wwa_get["usernameless"] = sanitize_text_field(wp_unslash($_GET["usernameless"]));
wwa_add_log($res_id, "ajax_create: name => \"".$wwa_get["name"]."\", type => \"".$wwa_get["type"]."\", usernameless => \"".$wwa_get["usernameless"]."\""); wwa_add_log($res_id, "ajax_create: name => \"".$wwa_get["name"]."\", type => \"".$wwa_get["type"]."\", usernameless => \"".$wwa_get["usernameless"]."\"");
} }
$user_info = wp_get_current_user(); $user_info = wp_get_current_user();
if(isset($_GET["user_id"])){ if(isset($_GET["user_id"])){
$user_id = intval(sanitize_text_field($_GET["user_id"])); $user_id = intval(sanitize_text_field(wp_unslash($_GET["user_id"])));
if($user_id <= 0){ if($user_id <= 0){
wwa_add_log($res_id, "ajax_create: (ERROR)Wrong parameters, exit"); wwa_add_log($res_id, "ajax_create: (ERROR)Wrong parameters, exit");
wwa_wp_die("Bad Request."); wwa_wp_die("Bad Request.");
@ -287,12 +294,12 @@ function wwa_ajax_create(){
return $credential->getPublicKeyCredentialDescriptor(); return $credential->getPublicKeyCredentialDescriptor();
}, $credentialSources); }, $credentialSources);
wwa_add_log($res_id, "ajax_create: excludeCredentials => ".json_encode($excludeCredentials)); wwa_add_log($res_id, "ajax_create: excludeCredentials => ".wp_json_encode($excludeCredentials));
// Set authenticator type // Set authenticator type
if($wwa_get["type"] === "platform"){ if($wwa_get["type"] === "platform"){
$authenticator_type = AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_PLATFORM; $authenticator_type = AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_PLATFORM;
}else if($wwa_get["type"] === "cross-platform"){ }elseif($wwa_get["type"] === "cross-platform"){
$authenticator_type = AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM; $authenticator_type = AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM;
}else{ }else{
$authenticator_type = AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE; $authenticator_type = AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE;
@ -335,9 +342,9 @@ function wwa_ajax_create(){
wwa_set_temp_val("bind_config", array("name" => $wwa_get["name"], "type" => $wwa_get["type"], "usernameless" => $resident_key), $client_id); wwa_set_temp_val("bind_config", array("name" => $wwa_get["name"], "type" => $wwa_get["type"], "usernameless" => $resident_key), $client_id);
header("Content-Type: application/json"); header("Content-Type: application/json");
$publicKeyCredentialCreationOptions = json_decode(json_encode($publicKeyCredentialCreationOptions), true); $publicKeyCredentialCreationOptions = json_decode(wp_json_encode($publicKeyCredentialCreationOptions), true);
$publicKeyCredentialCreationOptions["clientID"] = $client_id; $publicKeyCredentialCreationOptions["clientID"] = $client_id;
echo json_encode($publicKeyCredentialCreationOptions); echo wp_json_encode($publicKeyCredentialCreationOptions);
wwa_add_log($res_id, "ajax_create: Challenge sent"); wwa_add_log($res_id, "ajax_create: Challenge sent");
exit; exit;
}catch(\Exception $exception){ }catch(\Exception $exception){
@ -368,12 +375,13 @@ function wwa_ajax_create_response(){
wwa_add_log($res_id, "ajax_create_response: (ERROR)Missing parameters, exit"); wwa_add_log($res_id, "ajax_create_response: (ERROR)Missing parameters, exit");
wp_die("Bad Request."); wp_die("Bad Request.");
}else{ }else{
if(strlen($_POST["clientid"]) < 34 || strlen($_POST["clientid"]) > 35){ // Sanitize the input
$post_client_id = sanitize_text_field(wp_unslash($_POST["clientid"]));
if(strlen($post_client_id) < 34 || strlen($post_client_id) > 35){
wwa_add_log($res_id, "ajax_create_response: (ERROR)Wrong client ID, exit"); wwa_add_log($res_id, "ajax_create_response: (ERROR)Wrong client ID, exit");
wwa_wp_die("Bad Request.", $client_id); wwa_wp_die("Bad Request.", $client_id);
} }
// Sanitize the input $client_id = $post_client_id;
$client_id = sanitize_text_field($_POST["clientid"]);
} }
if(!current_user_can("read")){ if(!current_user_can("read")){
@ -388,15 +396,15 @@ function wwa_ajax_create_response(){
}else{ }else{
// Sanitize the input // Sanitize the input
$wwa_post = array(); $wwa_post = array();
$wwa_post["name"] = sanitize_text_field($_POST["name"]); $wwa_post["name"] = sanitize_text_field(wp_unslash($_POST["name"]));
$wwa_post["type"] = sanitize_text_field($_POST["type"]); $wwa_post["type"] = sanitize_text_field(wp_unslash($_POST["type"]));
$wwa_post["usernameless"] = sanitize_text_field($_POST["usernameless"]); $wwa_post["usernameless"] = sanitize_text_field(wp_unslash($_POST["usernameless"]));
wwa_add_log($res_id, "ajax_create_response: name => \"".$wwa_post["name"]."\", type => \"".$wwa_post["type"]."\", usernameless => \"".$wwa_post["usernameless"]."\""); wwa_add_log($res_id, "ajax_create_response: name => \"".$wwa_post["name"]."\", type => \"".$wwa_post["type"]."\", usernameless => \"".$wwa_post["usernameless"]."\"");
wwa_add_log($res_id, "ajax_create_response: data => ".base64_decode($_POST["data"])); wwa_add_log($res_id, "ajax_create_response: data => ".base64_decode(sanitize_text_field(wp_unslash($_POST["data"]))));
} }
if(isset($_GET["user_id"])){ if(isset($_POST["user_id"])){
$user_id = intval(sanitize_text_field($_POST["user_id"])); $user_id = intval(sanitize_text_field(wp_unslash($_POST["user_id"])));
if($user_id <= 0){ if($user_id <= 0){
wwa_add_log($res_id, "ajax_create_response: (ERROR)Wrong parameters, exit"); wwa_add_log($res_id, "ajax_create_response: (ERROR)Wrong parameters, exit");
wwa_wp_die("Bad Request."); wwa_wp_die("Bad Request.");
@ -432,8 +440,13 @@ function wwa_ajax_create_response(){
wwa_wp_die("Bad Request.", $client_id); wwa_wp_die("Bad Request.", $client_id);
} }
if(!isset($_POST["data"]) || $_POST["data"] === ""){
wwa_add_log($res_id, "ajax_create_response: (ERROR)Empty data, exit");
wwa_wp_die("Bad Request.", $client_id);
}
// Check global unique credential ID // Check global unique credential ID
$credential_id = base64_decode(json_decode(base64_decode($_POST["data"]), true)["rawId"]); $credential_id = base64_decode(json_decode(base64_decode(sanitize_text_field(wp_unslash($_POST["data"]))), true)["rawId"]);
$publicKeyCredentialSourceRepository = new PublicKeyCredentialSourceRepository(); $publicKeyCredentialSourceRepository = new PublicKeyCredentialSourceRepository();
if($publicKeyCredentialSourceRepository->findOneMetaByCredentialId($credential_id) !== null){ if($publicKeyCredentialSourceRepository->findOneMetaByCredentialId($credential_id) !== null){
wwa_add_log($res_id, "ajax_create_response: (ERROR)Credential ID not unique, ID => \"".base64_encode($credential_id)."\" , exit"); wwa_add_log($res_id, "ajax_create_response: (ERROR)Credential ID not unique, ID => \"".base64_encode($credential_id)."\" , exit");
@ -473,7 +486,7 @@ function wwa_ajax_create_response(){
// Verify // Verify
try { try {
$publicKeyCredentialSource = $server->loadAndCheckAttestationResponse( $publicKeyCredentialSource = $server->loadAndCheckAttestationResponse(
base64_decode($_POST["data"]), base64_decode(sanitize_text_field(wp_unslash($_POST["data"]))),
unserialize(base64_decode($temp_val["pkcco"])), unserialize(base64_decode($temp_val["pkcco"])),
$serverRequest $serverRequest
); );
@ -532,12 +545,12 @@ function wwa_ajax_auth_start(){
}else{ }else{
// Sanitize the input // Sanitize the input
$wwa_get = array(); $wwa_get = array();
$wwa_get["type"] = sanitize_text_field($_GET["type"]); $wwa_get["type"] = sanitize_text_field(wp_unslash($_GET["type"]));
if(isset($_GET["user"])){ if(isset($_GET["user"])){
$wwa_get["user"] = sanitize_text_field($_GET["user"]); $wwa_get["user"] = sanitize_text_field(wp_unslash($_GET["user"]));
} }
if(isset($_GET["usernameless"])){ if(isset($_GET["usernameless"])){
$wwa_get["usernameless"] = sanitize_text_field($_GET["usernameless"]); $wwa_get["usernameless"] = sanitize_text_field(wp_unslash($_GET["usernameless"]));
// Usernameless authentication not allowed // Usernameless authentication not allowed
if($wwa_get["usernameless"] === "true" && wwa_get_option("usernameless_login") !== "true"){ if($wwa_get["usernameless"] === "true" && wwa_get_option("usernameless_login") !== "true"){
wwa_add_log($res_id, "ajax_auth: (ERROR)Usernameless authentication not allowed, exit"); wwa_add_log($res_id, "ajax_auth: (ERROR)Usernameless authentication not allowed, exit");
@ -563,7 +576,7 @@ function wwa_ajax_auth_start(){
$user_info = wp_get_current_user(); $user_info = wp_get_current_user();
if(isset($_GET["user_id"])){ if(isset($_GET["user_id"])){
$user_id = intval(sanitize_text_field($_GET["user_id"])); $user_id = intval(sanitize_text_field(wp_unslash($_GET["user_id"])));
if($user_id <= 0){ if($user_id <= 0){
wwa_add_log($res_id, "ajax_auth: (ERROR)Wrong parameters, exit"); wwa_add_log($res_id, "ajax_auth: (ERROR)Wrong parameters, exit");
wwa_wp_die("Bad Request."); wwa_wp_die("Bad Request.");
@ -617,7 +630,7 @@ function wwa_ajax_auth_start(){
$user_icon = get_avatar_url($user_info->user_email, array("scheme" => "https")); $user_icon = get_avatar_url($user_info->user_email, array("scheme" => "https"));
wwa_add_log($res_id, "ajax_auth: type => \"auth\", user => \"".$user_info->user_login."\""); wwa_add_log($res_id, "ajax_auth: type => \"auth\", user => \"".$user_info->user_login."\"");
if(!isset(wwa_get_option("user_id")[$user_info->user_login])){ if(!isset(wwa_get_option("user_id")[$user_info->user_login])){
wwa_add_log($res_id, "ajax_auth: User not initialized, initialize"); wwa_add_log($res_id, "ajax_auth: User found but not initialized, create a fake id");
$user_key = hash("sha256", $wwa_get["user"]."-".$wwa_get["user"]."-".wwa_generate_random_string(10)); $user_key = hash("sha256", $wwa_get["user"]."-".$wwa_get["user"]."-".wwa_generate_random_string(10));
$user_exist = false; $user_exist = false;
}else{ }else{
@ -654,8 +667,8 @@ function wwa_ajax_auth_start(){
$credentialSourceRepository = new PublicKeyCredentialSourceRepository(); $credentialSourceRepository = new PublicKeyCredentialSourceRepository();
$rpEntity = new PublicKeyCredentialRpEntity( $rpEntity = new PublicKeyCredentialRpEntity(
wwa_get_option('website_name'), wwa_get_option("website_name"),
wwa_get_option('website_domain') wwa_get_option("website_domain")
); );
$server = new Server( $server = new Server(
@ -671,16 +684,16 @@ function wwa_ajax_auth_start(){
}else{ }else{
// Get the list of authenticators associated to the user // Get the list of authenticators associated to the user
// $credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity); // $credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity);
$allow_authenticator_type = wwa_get_option('allow_authenticator_type'); $allow_authenticator_type = wwa_get_option("allow_authenticator_type");
if($allow_authenticator_type === false || $allow_authenticator_type === 'none'){ if($allow_authenticator_type === false || $allow_authenticator_type === "none"){
$credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity); $credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity);
}else if($allow_authenticator_type !== false && $allow_authenticator_type !== 'none'){ }elseif($allow_authenticator_type !== false && $allow_authenticator_type !== "none"){
wwa_add_log($res_id, "ajax_auth: allow_authenticator_type => \"".$allow_authenticator_type."\", filter authenticators"); wwa_add_log($res_id, "ajax_auth: allow_authenticator_type => \"".$allow_authenticator_type."\", filter authenticators");
$credentialSources = $credentialSourceRepository->findCredentialsForUserEntityByType($userEntity, $allow_authenticator_type); $credentialSources = $credentialSourceRepository->findCredentialsForUserEntityByType($userEntity, $allow_authenticator_type);
} }
// Logged in and testing, if the user haven't bind a authenticator yet, exit // Logged in and testing, if the user haven't bind a authenticator yet, exit
if(count($credentialSources) === 0 && $wwa_get["type"] === "test" && current_user_can('read')){ if(count($credentialSources) === 0 && $wwa_get["type"] === "test" && current_user_can("read")){
wwa_add_log($res_id, "ajax_auth: (ERROR)No authenticator, exit"); wwa_add_log($res_id, "ajax_auth: (ERROR)No authenticator, exit");
wwa_wp_die("User not inited.", $client_id); wwa_wp_die("User not inited.", $client_id);
} }
@ -690,7 +703,7 @@ function wwa_ajax_auth_start(){
return $credential->getPublicKeyCredentialDescriptor(); return $credential->getPublicKeyCredentialDescriptor();
}, $credentialSources); }, $credentialSources);
wwa_add_log($res_id, "ajax_auth: allowedCredentials => ".json_encode($allowedCredentials)); wwa_add_log($res_id, "ajax_auth: allowedCredentials => ".wp_json_encode($allowedCredentials));
} }
// Set user verification // Set user verification
@ -728,9 +741,9 @@ function wwa_ajax_auth_start(){
} }
header("Content-Type: application/json"); header("Content-Type: application/json");
$publicKeyCredentialRequestOptions = json_decode(json_encode($publicKeyCredentialRequestOptions), true); $publicKeyCredentialRequestOptions = json_decode(wp_json_encode($publicKeyCredentialRequestOptions), true);
$publicKeyCredentialRequestOptions["clientID"] = $client_id; $publicKeyCredentialRequestOptions["clientID"] = $client_id;
echo json_encode($publicKeyCredentialRequestOptions); echo wp_json_encode($publicKeyCredentialRequestOptions);
wwa_add_log($res_id, "ajax_auth: Challenge sent"); wwa_add_log($res_id, "ajax_auth: Challenge sent");
exit; exit;
}catch(\Exception $exception){ }catch(\Exception $exception){
@ -761,12 +774,13 @@ function wwa_ajax_auth(){
wwa_add_log($res_id, "ajax_auth_response: (ERROR)Missing parameters, exit"); wwa_add_log($res_id, "ajax_auth_response: (ERROR)Missing parameters, exit");
wp_die("Bad Request."); wp_die("Bad Request.");
}else{ }else{
if(strlen($_POST["clientid"]) < 34 || strlen($_POST["clientid"]) > 35){ // Sanitize the input
$post_client_id = sanitize_text_field(wp_unslash($_POST["clientid"]));
if(strlen($post_client_id) < 34 || strlen($post_client_id) > 35){
wwa_add_log($res_id, "ajax_auth_response: (ERROR)Wrong client ID, exit"); wwa_add_log($res_id, "ajax_auth_response: (ERROR)Wrong client ID, exit");
wwa_wp_die("Bad Request.", $client_id); wwa_wp_die("Bad Request.", $client_id);
} }
// Sanitize the input $client_id = $post_client_id;
$client_id = sanitize_text_field($_POST["clientid"]);
} }
// Check POST // Check POST
@ -776,8 +790,8 @@ function wwa_ajax_auth(){
}else{ }else{
// Sanitize the input // Sanitize the input
$wwa_post = array(); $wwa_post = array();
$wwa_post["type"] = sanitize_text_field($_POST["type"]); $wwa_post["type"] = sanitize_text_field(wp_unslash($_POST["type"]));
$wwa_post["remember"] = sanitize_text_field($_POST["remember"]); $wwa_post["remember"] = sanitize_text_field(wp_unslash($_POST["remember"]));
} }
$temp_val = array( $temp_val = array(
@ -798,7 +812,7 @@ function wwa_ajax_auth(){
if($wwa_post["remember"] !== "true" && $wwa_post["remember"] !== "false"){ if($wwa_post["remember"] !== "true" && $wwa_post["remember"] !== "false"){
wwa_add_log($res_id, "ajax_auth_response: (ERROR)Wrong parameters, exit"); wwa_add_log($res_id, "ajax_auth_response: (ERROR)Wrong parameters, exit");
wwa_wp_die("Bad Request.", $client_id); wwa_wp_die("Bad Request.", $client_id);
}else if(wwa_get_option("remember_me") !== "true" && $wwa_post["remember"] === "true"){ }elseif(wwa_get_option("remember_me") !== "true" && $wwa_post["remember"] === "true"){
wwa_add_log($res_id, "ajax_auth_response: (ERROR)Wrong parameters, exit"); wwa_add_log($res_id, "ajax_auth_response: (ERROR)Wrong parameters, exit");
wwa_wp_die("Bad Request.", $client_id); wwa_wp_die("Bad Request.", $client_id);
} }
@ -843,8 +857,8 @@ function wwa_ajax_auth(){
if($wwa_post["type"] === "test" && current_user_can('read') && !$usernameless_flag){ if($wwa_post["type"] === "test" && current_user_can('read') && !$usernameless_flag){
$user_info = wp_get_current_user(); $user_info = wp_get_current_user();
if(isset($_GET["user_id"])){ if(isset($_POST["user_id"])){
$user_id = intval(sanitize_text_field($_POST["user_id"])); $user_id = intval(sanitize_text_field(wp_unslash($_POST["user_id"])));
if($user_id <= 0){ if($user_id <= 0){
wwa_add_log($res_id, "ajax_auth_response: (ERROR)Wrong parameters, exit"); wwa_add_log($res_id, "ajax_auth_response: (ERROR)Wrong parameters, exit");
wwa_wp_die("Bad Request."); wwa_wp_die("Bad Request.");
@ -882,7 +896,7 @@ function wwa_ajax_auth(){
wwa_add_log($res_id, "ajax_auth_response: type => \"test\", user => \"".$user_info->user_login."\""); wwa_add_log($res_id, "ajax_auth_response: type => \"test\", user => \"".$user_info->user_login."\"");
}else{ }else{
if($usernameless_flag){ if($usernameless_flag){
$data_array = json_decode(base64_decode($_POST["data"]), true); $data_array = json_decode(base64_decode(sanitize_text_field(wp_unslash($_POST["data"]))), true);
if(!isset($data_array["response"]["userHandle"]) || !isset($data_array["rawId"])){ if(!isset($data_array["response"]["userHandle"]) || !isset($data_array["rawId"])){
wwa_add_log($res_id, "ajax_auth_response: (ERROR)Client data not correct, exit"); wwa_add_log($res_id, "ajax_auth_response: (ERROR)Client data not correct, exit");
wwa_wp_die("Bad request.", $client_id); wwa_wp_die("Bad request.", $client_id);
@ -964,7 +978,8 @@ function wwa_ajax_auth(){
} }
} }
wwa_add_log($res_id, "ajax_auth_response: data => ".base64_decode($_POST["data"])); $decoded_data = base64_decode(sanitize_text_field(wp_unslash($_POST["data"])));
wwa_add_log($res_id, "ajax_auth_response: data => ".$decoded_data);
if($temp_val["user_exist"]){ if($temp_val["user_exist"]){
$rpEntity = new PublicKeyCredentialRpEntity( $rpEntity = new PublicKeyCredentialRpEntity(
@ -988,7 +1003,7 @@ function wwa_ajax_auth(){
// Verify // Verify
try { try {
$server->loadAndCheckAssertionResponse( $server->loadAndCheckAssertionResponse(
base64_decode($_POST["data"]), $decoded_data,
unserialize(base64_decode($temp_val["pkcco_auth"])), unserialize(base64_decode($temp_val["pkcco_auth"])),
$userEntity, $userEntity,
$serverRequest $serverRequest
@ -997,7 +1012,7 @@ function wwa_ajax_auth(){
wwa_add_log($res_id, "ajax_auth_response: Challenge verified"); wwa_add_log($res_id, "ajax_auth_response: Challenge verified");
// Success // Success
$publicKeyCredentialSourceRepository->updateCredentialLastUsed(base64_decode(json_decode(base64_decode($_POST["data"]), true)["rawId"])); $publicKeyCredentialSourceRepository->updateCredentialLastUsed(base64_decode(json_decode($decoded_data, true)["rawId"]));
if(!($wwa_post["type"] === "test" && current_user_can("read"))){ if(!($wwa_post["type"] === "test" && current_user_can("read"))){
// Log user in // Log user in
if (!is_user_logged_in()) { if (!is_user_logged_in()) {
@ -1011,7 +1026,7 @@ function wwa_ajax_auth(){
$user = get_user_by("login", $user_login); $user = get_user_by("login", $user_login);
if($user_info === false){ if($user === false){
wwa_add_log($res_id, "ajax_auth_response: (ERROR)Wrong user ID, exit"); wwa_add_log($res_id, "ajax_auth_response: (ERROR)Wrong user ID, exit");
wwa_wp_die("Something went wrong."); wwa_wp_die("Something went wrong.");
} }
@ -1081,7 +1096,7 @@ function wwa_ajax_authenticator_list(){
$user_info = wp_get_current_user(); $user_info = wp_get_current_user();
if(isset($_GET["user_id"])){ if(isset($_GET["user_id"])){
$user_id = intval(sanitize_text_field($_GET["user_id"])); $user_id = intval(sanitize_text_field(wp_unslash($_GET["user_id"])));
if($user_id <= 0){ if($user_id <= 0){
wwa_add_log($res_id, "ajax_authenticator_list: (ERROR)Wrong parameters, exit"); wwa_add_log($res_id, "ajax_authenticator_list: (ERROR)Wrong parameters, exit");
wwa_wp_die("Bad Request."); wwa_wp_die("Bad Request.");
@ -1120,7 +1135,7 @@ function wwa_ajax_authenticator_list(){
); );
$publicKeyCredentialSourceRepository = new PublicKeyCredentialSourceRepository(); $publicKeyCredentialSourceRepository = new PublicKeyCredentialSourceRepository();
echo json_encode($publicKeyCredentialSourceRepository->getShowList($userEntity)); echo wp_json_encode($publicKeyCredentialSourceRepository->getShowList($userEntity));
exit; exit;
} }
add_action("wp_ajax_wwa_authenticator_list" , "wwa_ajax_authenticator_list"); add_action("wp_ajax_wwa_authenticator_list" , "wwa_ajax_authenticator_list");
@ -1147,7 +1162,7 @@ function wwa_ajax_modify_authenticator(){
$user_info = wp_get_current_user(); $user_info = wp_get_current_user();
if(isset($_GET["user_id"])){ if(isset($_GET["user_id"])){
$user_id = intval(sanitize_text_field($_GET["user_id"])); $user_id = intval(sanitize_text_field(wp_unslash($_GET["user_id"])));
if($user_id <= 0){ if($user_id <= 0){
wwa_add_log($res_id, "ajax_modify_authenticator: (ERROR)Wrong parameters, exit"); wwa_add_log($res_id, "ajax_modify_authenticator: (ERROR)Wrong parameters, exit");
wwa_wp_die("Bad Request."); wwa_wp_die("Bad Request.");
@ -1198,9 +1213,9 @@ function wwa_ajax_modify_authenticator(){
$publicKeyCredentialSourceRepository = new PublicKeyCredentialSourceRepository(); $publicKeyCredentialSourceRepository = new PublicKeyCredentialSourceRepository();
if($_GET["target"] === "rename"){ if($_GET["target"] === "rename"){
echo $publicKeyCredentialSourceRepository->modifyAuthenticator($_GET["id"], sanitize_text_field($_GET["name"]), $userEntity, "rename", $res_id); echo $publicKeyCredentialSourceRepository->modifyAuthenticator(sanitize_text_field(wp_unslash($_GET["id"])), sanitize_text_field(wp_unslash($_GET["name"])), $userEntity, "rename", $res_id);
}else if($_GET["target"] === "remove"){ }elseif($_GET["target"] === "remove"){
echo $publicKeyCredentialSourceRepository->modifyAuthenticator($_GET["id"], "", $userEntity, "remove", $res_id); echo $publicKeyCredentialSourceRepository->modifyAuthenticator(sanitize_text_field(wp_unslash($_GET["id"])), "", $userEntity, "remove", $res_id);
} }
exit; exit;
}catch(\Exception $exception){ }catch(\Exception $exception){
@ -1230,7 +1245,7 @@ function wwa_ajax_get_log(){
if($log === false){ if($log === false){
echo "[]"; echo "[]";
}else{ }else{
echo json_encode($log); echo wp_json_encode($log);
} }
exit; exit;
@ -1253,4 +1268,3 @@ function wwa_ajax_clear_log(){
exit; exit;
} }
add_action("wp_ajax_wwa_clear_log" , "wwa_ajax_clear_log"); add_action("wp_ajax_wwa_clear_log" , "wwa_ajax_clear_log");
?>

View File

@ -3,4 +3,3 @@
if(has_action('wp_login', array('Two_Factor_Core', 'wp_login')) !== false){ if(has_action('wp_login', array('Two_Factor_Core', 'wp_login')) !== false){
remove_action('wp_login', array('Two_Factor_Core', 'wp_login'), 10, 2); remove_action('wp_login', array('Two_Factor_Core', 'wp_login'), 10, 2);
} }
?>

View File

@ -29,7 +29,7 @@ function wwa_wp_die($message = '', $client_id = false){
if($client_id !== false){ if($client_id !== false){
wwa_destroy_temp_val($client_id); wwa_destroy_temp_val($client_id);
} }
wp_die($message); wp_die(esc_html($message));
} }
// Init data for new options // Init data for new options
@ -65,7 +65,7 @@ function wwa_generate_random_string($length = 10){
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'; $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_';
$randomString = ''; $randomString = '';
for($i = 0; $i < $length; $i++){ for($i = 0; $i < $length; $i++){
$randomString .= $characters[rand(0, strlen($characters) - 1)]; $randomString .= $characters[wp_rand(0, strlen($characters) - 1)];
} }
return $randomString; return $randomString;
} }
@ -132,8 +132,8 @@ function wwa_delete_user($user_id){
} }
} }
wwa_update_option('user_id', $all_user_meta); wwa_update_option('user_id', $all_user_meta);
wwa_update_option('user_credentials_meta', json_encode($all_credentials_meta)); wwa_update_option('user_credentials_meta', wp_json_encode($all_credentials_meta));
wwa_update_option('user_credentials', json_encode($all_credentials)); wwa_update_option('user_credentials', wp_json_encode($all_credentials));
wwa_add_log($res_id, "Deleted user => \"".$user_data->user_login."\""); wwa_add_log($res_id, "Deleted user => \"".$user_data->user_login."\"");
} }
add_action('delete_user', 'wwa_delete_user'); add_action('delete_user', 'wwa_delete_user');
@ -141,7 +141,7 @@ add_action('delete_user', 'wwa_delete_user');
// Add CSS and JS in login page // Add CSS and JS in login page
function wwa_login_js(){ function wwa_login_js(){
$wwa_not_allowed = false; $wwa_not_allowed = false;
if(!function_exists('mb_substr') || !function_exists('gmp_intval') || !wwa_check_ssl() && (parse_url(site_url(), PHP_URL_HOST) !== 'localhost' && parse_url(site_url(), PHP_URL_HOST) !== '127.0.0.1')){ if(!function_exists('mb_substr') || !function_exists('gmp_intval') || !wwa_check_ssl() && (wp_parse_url(site_url(), PHP_URL_HOST) !== 'localhost' && wp_parse_url(site_url(), PHP_URL_HOST) !== '127.0.0.1')){
$wwa_not_allowed = true; $wwa_not_allowed = true;
} }
wp_enqueue_script('wwa_login', plugins_url('js/login.js', __FILE__), array(), get_option('wwa_version')['version'], true); wp_enqueue_script('wwa_login', plugins_url('js/login.js', __FILE__), array(), get_option('wwa_version')['version'], true);
@ -178,7 +178,7 @@ add_action('login_enqueue_scripts', 'wwa_login_js', 999);
// Disable password login // Disable password login
function wwa_disable_password($user){ function wwa_disable_password($user){
if(!function_exists('mb_substr') || !function_exists('gmp_intval') || !wwa_check_ssl() && (parse_url(site_url(), PHP_URL_HOST) !== 'localhost' && parse_url(site_url(), PHP_URL_HOST) !== '127.0.0.1')){ if(!function_exists('mb_substr') || !function_exists('gmp_intval') || !wwa_check_ssl() && (wp_parse_url(site_url(), PHP_URL_HOST) !== 'localhost' && wp_parse_url(site_url(), PHP_URL_HOST) !== '127.0.0.1')){
return $user; return $user;
} }
if(wwa_get_option('first_choice') === 'webauthn'){ if(wwa_get_option('first_choice') === 'webauthn'){
@ -209,7 +209,7 @@ add_action('register_new_user', 'wwa_handle_user_register');
// Disable Password Reset URL & Redirect // Disable Password Reset URL & Redirect
function wwa_disable_lost_password(){ function wwa_disable_lost_password(){
if((wwa_get_option('password_reset') === 'admin' || wwa_get_option('password_reset') === 'all') && isset( $_GET['action'] )){ if((wwa_get_option('password_reset') === 'admin' || wwa_get_option('password_reset') === 'all') && isset($_GET['action'])){
if(in_array($_GET['action'], array('lostpassword', 'retrievepassword', 'resetpass', 'rp'))){ if(in_array($_GET['action'], array('lostpassword', 'retrievepassword', 'resetpass', 'rp'))){
wp_redirect(wp_login_url(), 302); wp_redirect(wp_login_url(), 302);
exit; exit;
@ -272,6 +272,7 @@ function wwa_no_authenticator_warning(){
if($show_notice_flag){?> if($show_notice_flag){?>
<div class="notice notice-warning"> <div class="notice notice-warning">
<?php /* translators: %s: 'the site' or 'your account', and admin profile url */ ?>
<p><?php printf(__('Logging in with password has been disabled for %s but you haven\'t register any WebAuthn authenticator yet. You may unable to login again once you log out. <a href="%s#wwa-webauthn-start">Register</a>', 'wp-webauthn'), $first_choice === 'webauthn' ? __('the site', 'wp-webauthn') : __('your account', 'wp-webauthn'), admin_url('profile.php'));?></p> <p><?php printf(__('Logging in with password has been disabled for %s but you haven\'t register any WebAuthn authenticator yet. You may unable to login again once you log out. <a href="%s#wwa-webauthn-start">Register</a>', 'wp-webauthn'), $first_choice === 'webauthn' ? __('the site', 'wp-webauthn') : __('your account', 'wp-webauthn'), admin_url('profile.php'));?></p>
</div> </div>
<?php } <?php }
@ -317,6 +318,7 @@ function wwa_no_authenticator_warning(){
if($show_notice_flag){ ?> if($show_notice_flag){ ?>
<div class="notice notice-warning"> <div class="notice notice-warning">
<?php /* translators: %s: 'the site' or 'your account' */ ?>
<p><?php printf(__('Logging in with password has been disabled for %s but <strong>this account</strong> haven\'t register any WebAuthn authenticator yet. This user may unable to login.', 'wp-webauthn'), $first_choice === 'webauthn' ? __('the site', 'wp-webauthn') : __('this account', 'wp-webauthn'));?></p> <p><?php printf(__('Logging in with password has been disabled for %s but <strong>this account</strong> haven\'t register any WebAuthn authenticator yet. This user may unable to login.', 'wp-webauthn'), $first_choice === 'webauthn' ? __('the site', 'wp-webauthn') : __('this account', 'wp-webauthn'));?></p>
</div> </div>
<?php } <?php }
@ -398,4 +400,3 @@ function wwa_get_user($username) {
return get_user_by('login', $username); return get_user_by('login', $username);
} }
} }
?>

View File

@ -15,7 +15,7 @@ add_action('show_user_profile', 'wwa_user_profile_fields');
// Save setting to profile page // Save setting to profile page
function wwa_save_user_profile_fields($user_id){ function wwa_save_user_profile_fields($user_id){
if(empty($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'update-user_'.$user_id)){ if(empty($_POST['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'update-user_'.$user_id)){
return; return;
} }
@ -29,7 +29,7 @@ function wwa_save_user_profile_fields($user_id){
if(!isset($_POST['webauthn_only'])){ if(!isset($_POST['webauthn_only'])){
update_user_meta($user_id, 'webauthn_only', 'false'); update_user_meta($user_id, 'webauthn_only', 'false');
}else if(sanitize_text_field($_POST['webauthn_only']) === 'true'){ }elseif(sanitize_text_field(wp_unslash($_POST['webauthn_only'])) === 'true'){
update_user_meta($user_id, 'webauthn_only', 'true'); update_user_meta($user_id, 'webauthn_only', 'true');
}else{ }else{
update_user_meta($user_id, 'webauthn_only', 'false'); update_user_meta($user_id, 'webauthn_only', 'false');

View File

@ -52,6 +52,7 @@ if(isset($_GET['wwa_registered']) && $_GET['wwa_registered'] === 'true'){
foreach($data as $key => $value){ foreach($data as $key => $value){
if($user_id === $value["user"]){ if($user_id === $value["user"]){
$count++; $count++;
break;
} }
} }
} }
@ -67,7 +68,7 @@ if(isset($_GET['wwa_registered']) && $_GET['wwa_registered'] === 'true'){
} }
} }
$wwa_not_allowed = false; $wwa_not_allowed = false;
if(!function_exists("mb_substr") || !function_exists("gmp_intval") || !wwa_check_ssl() && (parse_url(site_url(), PHP_URL_HOST) !== 'localhost' && parse_url(site_url(), PHP_URL_HOST) !== '127.0.0.1')){ if(!function_exists("mb_substr") || !function_exists("gmp_intval") || !wwa_check_ssl() && (wp_parse_url(site_url(), PHP_URL_HOST) !== 'localhost' && wp_parse_url(site_url(), PHP_URL_HOST) !== '127.0.0.1')){
$wwa_not_allowed = true; $wwa_not_allowed = true;
?> ?>
<div id="wp-webauthn-error-container"> <div id="wp-webauthn-error-container">
@ -124,6 +125,7 @@ if(!function_exists("mb_substr") || !function_exists("gmp_intval") || !wwa_check
<div id="wwa-new-block" tabindex="-1"> <div id="wwa-new-block" tabindex="-1">
<button class="button button-small wwa-cancel"><?php _e('Close');?></button> <button class="button button-small wwa-cancel"><?php _e('Close');?></button>
<h2><?php _e('Register New Authenticator', 'wp-webauthn');?></h2> <h2><?php _e('Register New Authenticator', 'wp-webauthn');?></h2>
<?php /* translators: %s: user login name */ ?>
<p class="description"><?php printf(__('You are about to associate an authenticator with the current account <strong>%s</strong>.<br>You can register multiple authenticators for an account.', 'wp-webauthn'), $user->user_login);?></p> <p class="description"><?php printf(__('You are about to associate an authenticator with the current account <strong>%s</strong>.<br>You can register multiple authenticators for an account.', 'wp-webauthn'), $user->user_login);?></p>
<table class="form-table"> <table class="form-table">
<tr> <tr>

View File

@ -70,22 +70,27 @@ function wwa_login_form_shortcode($vals){
$html_form = '<div class="wwa-login-form">'; $html_form = '<div class="wwa-login-form">';
$args = array('echo' => false, 'value_username' => $username); $args = array('echo' => false, 'value_username' => sanitize_user($username));
$to_wwa = ""; $to_wwa = '';
if($to !== ""){ if($to !== ""){
$args["redirect"] = $to; $args['redirect'] = sanitize_url($to);
if(substr($to, 0, 7) !== "http://" && substr($to, 0, 8) !== "https://" && substr($to, 0, 6) !== "ftp://" && substr($to, 0, 7) !== "mailto:"){ $to_wwa = '<input type="hidden" name="wwa-redirect-to" class="wwa-redirect-to" id="wwa-redirect-to" value="'.$args["redirect"].'">';
$to_wwa = '<input type="hidden" name="wwa-redirect-to" class="wwa-redirect-to" id="wwa-redirect-to" value="http://'.$to.'">';
}else{
$to_wwa = '<input type="hidden" name="wwa-redirect-to" class="wwa-redirect-to" id="wwa-redirect-to" value="'.$to.'">';
}
} }
if($traditional === 'true' && wwa_get_option('first_choice') !== 'webauthn'){ if($traditional === 'true' && wwa_get_option('first_choice') !== 'webauthn'){
$html_form .= '<div class="wwa-login-form-traditional">'.wp_login_form($args).'<br><a class="wwa-t2w" href="#"><span>'.__('Authenticate with WebAuthn', 'wp-webauthn').'</span></a></div>'; $html_form .= '<div class="wwa-login-form-traditional">'.wp_login_form($args).'<br><a class="wwa-t2w" href="#"><span>'.__('Authenticate with WebAuthn', 'wp-webauthn').'</span></a></div>';
} }
$html_form .= '<div class="wwa-login-form-webauthn"><p class="wwa-login-username"><label for="wwa-user-name">'.(wwa_get_option('email_login') !== 'true' ? __('Username', 'wp-webauthn') : __('Username or Email Address')).'</label><input type="text" name="wwa-user-name" id="wwa-user-name" class="wwa-user-name" value="'.$username.'" size="20"></p><div class="wp-webauthn-notice">'.__('Authenticate with WebAuthn', 'wp-webauthn').'</div><p class="wwa-login-submit-p">'.$to_wwa.'<div class="wwa-form-left">'.((wwa_get_option('remember_me') === false ? 'false' : wwa_get_option('remember_me') !== 'false') ? '<label class="wwa-remember-label"><input name="wwa-rememberme" type="checkbox" id="wwa-rememberme" value="forever"> '.__('Remember Me').'</label>' : '').'<a class="wwa-w2t" href="#">'.__('Authenticate with password', 'wp-webauthn').'</a></div><input type="button" name="wwa-login-submit" id="wwa-login-submit" class="wwa-login-submit button button-primary" value="'.__('Auth', 'wp-webauthn').'"></p></div></div>'; $html_form .= '
<div class="wwa-login-form-webauthn">
<p class="wwa-login-username">
<label for="wwa-user-name">'.(wwa_get_option('email_login') !== 'true' ? __('Username', 'wp-webauthn') : __('Username or Email Address')).'</label>
<input type="text" name="wwa-user-name" id="wwa-user-name" class="wwa-user-name" value="'.esc_attr(sanitize_user($username, true)).'" size="20">
</p>
<div class="wp-webauthn-notice">'.__('Authenticate with WebAuthn', 'wp-webauthn').'</div>
<p class="wwa-login-submit-p">'.$to_wwa.'<div class="wwa-form-left">'.((wwa_get_option('remember_me') === false ? 'false' : wwa_get_option('remember_me') !== 'false') ? '<label class="wwa-remember-label"><input name="wwa-rememberme" type="checkbox" id="wwa-rememberme" value="forever"> '.__('Remember Me').'</label>' : '').'<a class="wwa-w2t" href="#">'.__('Authenticate with password', 'wp-webauthn').'</a></div><input type="button" name="wwa-login-submit" id="wwa-login-submit" class="wwa-login-submit button button-primary" value="'.__('Auth', 'wp-webauthn').'"></p>
</div>
</div>';
return $html_form; return $html_form;
} }
@ -115,7 +120,21 @@ function wwa_register_form_shortcode($vals){
wp_enqueue_style('wwa_frondend_css', plugins_url('css/frontend.css', __FILE__), array(), get_option('wwa_version')['version']); wp_enqueue_style('wwa_frondend_css', plugins_url('css/frontend.css', __FILE__), array(), get_option('wwa_version')['version']);
$allowed_type = wwa_get_option('allow_authenticator_type') === false ? 'none' : wwa_get_option('allow_authenticator_type'); $allowed_type = wwa_get_option('allow_authenticator_type') === false ? 'none' : wwa_get_option('allow_authenticator_type');
return '<div class="wwa-register-form"><label for="wwa-authenticator-type">'.__('Type of authenticator', 'wp-webauthn').'</label><select name="wwa-authenticator-type" class="wwa-authenticator-type" id="wwa-authenticator-type"><option value="none" class="wwa-type-none"'.($allowed_type !== 'none' ? ' disabled' : '').'>'.__('Any', 'wp-webauthn').'</option><option value="platform" class="wwa-type-platform"'.($allowed_type === 'cross-platform' ? ' disabled' : '').'>'.__('Platform (e.g. built-in fingerprint sensors)', 'wp-webauthn').'</option><option value="cross-platform" class="wwa-type-cross-platform"'.($allowed_type === 'platform' ? ' disabled' : '').'>'.__('Roaming (e.g. USB security keys)', 'wp-webauthn').'</option></select><p class="wwa-bind-name-description">'.__('If a type is selected, the browser will only prompt for authenticators of selected type. <br> Regardless of the type, you can only log in with the very same authenticators you\'ve registered.', 'wp-webauthn').'</p><label for="wwa-authenticator-name">'.__('Authenticator identifier', 'wp-webauthn').'</label><input required name="wwa-authenticator-name" type="text" class="wwa-authenticator-name" id="wwa-authenticator-name"><p class="wwa-bind-name-description">'.__('An easily identifiable name for the authenticator. <strong>DOES NOT</strong> affect the authentication process in anyway.', 'wp-webauthn').'</p>'.((wwa_get_option('usernameless_login') === "true") ? '<label for="wwa-authenticator-usernameless">'.__('Login without username', 'wp-webauthn').'<br><label><input type="radio" name="wwa-authenticator-usernameless" class="wwa-authenticator-usernameless" value="true"> '.__("Enable", "wp-webauthn").'</label><br><label><input type="radio" name="wwa-authenticator-usernameless" class="wwa-authenticator-usernameless" value="false" checked="checked"> '.__("Disable", "wp-webauthn").'</label></label><br><p class="wwa-bind-usernameless-description">'.__('If registered authenticator with this feature, you can login without enter your username.<br>Some authenticators like U2F-only authenticators and some browsers <strong>DO NOT</strong> support this feature.<br>A record will be stored in the authenticator permanently untill you reset it.', 'wp-webauthn').'</p>' : '').'<p class="wwa-bind"><button class="wwa-bind-submit">'.__('Start registration', 'wp-webauthn').'</button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="wwa-show-progress"></span></p></div>'; return '
<div class="wwa-register-form">
<label for="wwa-authenticator-type">'.__('Type of authenticator', 'wp-webauthn').'</label>
<select name="wwa-authenticator-type" class="wwa-authenticator-type" id="wwa-authenticator-type">
<option value="none" class="wwa-type-none"'.($allowed_type !== 'none' ? ' disabled' : '').'>'.__('Any', 'wp-webauthn').'</option>
<option value="platform" class="wwa-type-platform"'.($allowed_type === 'cross-platform' ? ' disabled' : '').'>'.__('Platform (e.g. built-in fingerprint sensors)', 'wp-webauthn').'</option>
<option value="cross-platform" class="wwa-type-cross-platform"'.($allowed_type === 'platform' ? ' disabled' : '').'>'.__('Roaming (e.g. USB security keys)', 'wp-webauthn').'</option>
</select>
<p class="wwa-bind-name-description">'.__('If a type is selected, the browser will only prompt for authenticators of selected type. <br> Regardless of the type, you can only log in with the very same authenticators you\'ve registered.', 'wp-webauthn').'</p>
<label for="wwa-authenticator-name">'.__('Authenticator identifier', 'wp-webauthn').'</label>
<input required name="wwa-authenticator-name" type="text" class="wwa-authenticator-name" id="wwa-authenticator-name">
<p class="wwa-bind-name-description">'.__('An easily identifiable name for the authenticator. <strong>DOES NOT</strong> affect the authentication process in anyway.', 'wp-webauthn').'</p>'.(
(wwa_get_option('usernameless_login') === "true") ? '<label for="wwa-authenticator-usernameless">'.__('Login without username', 'wp-webauthn').'<br><label><input type="radio" name="wwa-authenticator-usernameless" class="wwa-authenticator-usernameless" value="true"> '.__("Enable", "wp-webauthn").'</label><br><label><input type="radio" name="wwa-authenticator-usernameless" class="wwa-authenticator-usernameless" value="false" checked="checked"> '.__("Disable", "wp-webauthn").'</label></label><br><p class="wwa-bind-usernameless-description">'.__('If registered authenticator with this feature, you can login without enter your username.<br>Some authenticators like U2F-only authenticators and some browsers <strong>DO NOT</strong> support this feature.<br>A record will be stored in the authenticator permanently untill you reset it.', 'wp-webauthn').'</p>' : ''
).'<p class="wwa-bind"><button class="wwa-bind-submit">'.__('Start registration', 'wp-webauthn').'</button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="wwa-show-progress"></span></p>
</div>';
} }
add_shortcode('wwa_register_form', 'wwa_register_form_shortcode'); add_shortcode('wwa_register_form', 'wwa_register_form_shortcode');
@ -153,9 +172,9 @@ function wwa_list_shortcode($vals){
), $vals) ), $vals)
); );
$thead = '<div class="wwa-table-container"><table class="wwa-list-table"><thead><tr><th>'.__('Identifier', 'wp-webauthn').'</th><th>'.__('Type', 'wp-webauthn').'</th><th>'._x('Registered', 'time', 'wp-webauthn').'</th><th>'.__('Last used', 'time', 'wp-webauthn').'</th><th class="wwa-usernameless-th">'.__('Usernameless', 'wp-webauthn').'</th><th>'.__('Action', 'wp-webauthn').'</th></tr></thead><tbody class="wwa-authenticator-list">'; $thead = '<div class="wwa-table-container"><table class="wwa-list-table"><thead><tr><th>'.__('Identifier', 'wp-webauthn').'</th><th>'.__('Type', 'wp-webauthn').'</th><th>'._x('Registered', 'time', 'wp-webauthn').'</th><th>'.__('Last used', 'wp-webauthn').'</th><th class="wwa-usernameless-th">'.__('Usernameless', 'wp-webauthn').'</th><th>'.__('Action', 'wp-webauthn').'</th></tr></thead><tbody class="wwa-authenticator-list">';
$tbody = '<tr><td colspan="5">'.__('Loading...', 'wp-webauthn').'</td></tr>'; $tbody = '<tr><td colspan="5">'.__('Loading...', 'wp-webauthn').'</td></tr>';
$tfoot = '</tbody><tfoot><tr><th>'.__('Identifier', 'wp-webauthn').'</th><th>'.__('Type', 'wp-webauthn').'</th><th>'._x('Registered', 'time', 'wp-webauthn').'</th><th>'.__('Last used', 'time', 'wp-webauthn').'</th><th class="wwa-usernameless-th">'.__('Usernameless', 'wp-webauthn').'</th><th>'.__('Action', 'wp-webauthn').'</th></tr></tfoot></table></div><p class="wwa-authenticator-list-usernameless-tip"></p><p class="wwa-authenticator-list-type-tip"></p>'; $tfoot = '</tbody><tfoot><tr><th>'.__('Identifier', 'wp-webauthn').'</th><th>'.__('Type', 'wp-webauthn').'</th><th>'._x('Registered', 'time', 'wp-webauthn').'</th><th>'.__('Last used', 'wp-webauthn').'</th><th class="wwa-usernameless-th">'.__('Usernameless', 'wp-webauthn').'</th><th>'.__('Action', 'wp-webauthn').'</th></tr></tfoot></table></div><p class="wwa-authenticator-list-usernameless-tip"></p><p class="wwa-authenticator-list-type-tip"></p>';
// If always display // If always display
if(!current_user_can("read")){ if(!current_user_can("read")){
@ -178,4 +197,3 @@ function wwa_list_shortcode($vals){
return $thead.$tbody.$tfoot; return $thead.$tbody.$tfoot;
} }
add_shortcode('wwa_list', 'wwa_list_shortcode'); add_shortcode('wwa_list', 'wwa_list_shortcode');
?>

View File

@ -1,6 +1,5 @@
<?php <?php
$wwa_version = array( $wwa_version = array(
'version' => '1.3.1', 'version' => '1.3.4',
'commit' => '79260d5' 'commit' => 'b7ef5ce'
); );
?>