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.onreadystatechange = () => {
if (xmlHttpReq.readyState === 4 && xmlHttpReq.status === 200) {
callback(xmlHttpReq.responseText, true);
callback(xmlHttpReq.responseText.trim(), true);
} else if (xmlHttpReq.readyState === 4) {
callback('Network Error.', false);
}
@ -33,7 +33,7 @@ const wwa_ajax = function () {
xmlHttpReq.send(data);
xmlHttpReq.onreadystatechange = () => {
if (xmlHttpReq.readyState === 4 && xmlHttpReq.status === 200) {
callback(xmlHttpReq.responseText, true);
callback(xmlHttpReq.responseText.trim(), true);
} else if (xmlHttpReq.readyState === 4) {
callback('Network Error.', false);
}
@ -177,7 +177,7 @@ function wwa_auth() {
alert(wwa_php_vars.i18n_31);
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') {
alert(wwa_php_vars.i18n_11);
return;
@ -561,7 +561,7 @@ function updateList() {
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');
if (has_usernameless || wwa_php_vars.usernameless === 'true') {

View File

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

View File

@ -59,7 +59,7 @@ function updateList() {
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);
if (has_usernameless || configs.usernameless === 'true') {
@ -247,7 +247,7 @@ jQuery('#wwa-bind').click((e) => {
user_id: php_vars.user_id
},
success: function (data) {
if (data === 'true') {
if (data.trim() === 'true') {
// Registered
jQuery('#wwa-show-progress').html(php_vars.i18n_3);
jQuery('#wwa-bind').removeAttr('disabled');
@ -382,7 +382,7 @@ jQuery('#wwa-test, #wwa-test_usernameless').click((e) => {
user_id: php_vars.user_id
},
success: function (data) {
if (data === 'true') {
if (data.trim() === 'true') {
jQuery(tip_id).html(php_vars.i18n_16);
jQuery('#wwa-test, #wwa-test_usernameless').removeAttr('disabled');
updateList();

View File

@ -1,8 +1,8 @@
# Copyright (C) 2023 Axton
# This file is distributed under the same license as the WP-WebAuthn plugin.
# Copyright (C) 2024 Axton
# This file is distributed under the GPLv3.
msgid ""
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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,22 +18,27 @@ msgstr ""
"X-Domain: wp-webauthn\n"
#. Plugin Name of the plugin
#: wp-webauthn.php
msgid "WP-WebAuthn"
msgstr ""
#. Plugin URI of the plugin
#: wp-webauthn.php
msgid "https://flyhigher.top"
msgstr ""
#. Description of the plugin
#: wp-webauthn.php
msgid "WP-WebAuthn allows you to safely login to your WordPress site without password."
msgstr ""
#. Author of the plugin
#: wp-webauthn.php
msgid "Axton"
msgstr ""
#. Author URI of the plugin
#: wp-webauthn.php
msgid "https://axton.cc"
msgstr ""
@ -42,7 +47,7 @@ msgid "User verification is disabled by default because some mobile devices do n
msgstr ""
#: wwa-admin-content.php:7
#: wwa-admin-content.php:303
#: wwa-admin-content.php:307
msgid "Log count: "
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>."
msgstr ""
#: wwa-admin-content.php:127
#: wwa-admin-content.php:131
msgid "Settings saved."
msgstr ""
#: wwa-admin-content.php:129
#: wwa-admin-content.php:133
msgid "Settings NOT saved."
msgstr ""
#: wwa-admin-content.php:144
#: wwa-admin-content.php:148
msgid "Preferred login method"
msgstr ""
#: wwa-admin-content.php:148
#: wwa-admin-content.php:152
msgid "Prefer WebAuthn"
msgstr ""
#: wwa-admin-content.php:149
#: wwa-admin-content.php:153
msgid "Prefer password"
msgstr ""
#: wwa-admin-content.php:150
#: wwa-profile-content.php:81
#: wwa-admin-content.php:154
#: wwa-profile-content.php:82
msgid "WebAuthn Only"
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."
msgstr ""
#: wwa-admin-content.php:156
#: wwa-admin-content.php:160
msgid "Website identifier"
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."
msgstr ""
#: wwa-admin-content.php:163
#: wwa-admin-content.php:167
msgid "Website domain"
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."
msgstr ""
#: wwa-admin-content.php:173
#: wwa-admin-content.php:177
msgid "Allow to remember login"
msgstr ""
#: wwa-admin-content.php:182
#: wwa-admin-content.php:198
#: wwa-admin-content.php:209
#: wwa-admin-content.php:225
#: wwa-admin-content.php:300
#: wwa-profile-content.php:155
#: wwa-shortcodes.php:118
#: wwa-admin-content.php:186
#: wwa-admin-content.php:202
#: wwa-admin-content.php:213
#: wwa-admin-content.php:229
#: wwa-admin-content.php:304
#: wwa-profile-content.php:157
#: wwa-shortcodes.php:135
msgid "Enable"
msgstr ""
#: wwa-admin-content.php:183
#: wwa-admin-content.php:199
#: wwa-admin-content.php:210
#: wwa-admin-content.php:226
#: wwa-admin-content.php:301
#: wwa-profile-content.php:156
#: wwa-shortcodes.php:118
#: wwa-admin-content.php:187
#: wwa-admin-content.php:203
#: wwa-admin-content.php:214
#: wwa-admin-content.php:230
#: wwa-admin-content.php:305
#: wwa-profile-content.php:158
#: wwa-shortcodes.php:135
msgid "Disable"
msgstr ""
#: wwa-admin-content.php:184
#: wwa-admin-content.php:188
msgid "Show the 'Remember Me' checkbox beside the login form when using WebAuthn."
msgstr ""
#: wwa-admin-content.php:189
#: wwa-admin-content.php:193
msgid "Allow to login with email addresses"
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>"
msgstr ""
#: wwa-admin-content.php:205
#: wwa-admin-content.php:209
msgid "Require user verification"
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."
msgstr ""
#: wwa-admin-content.php:216
#: wwa-admin-content.php:220
msgid "Allow to login without username"
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."
msgstr ""
#: wwa-admin-content.php:232
#: wwa-admin-content.php:236
msgid "Allow a specific type of authenticator"
msgstr ""
#: wwa-admin-content.php:241
#: wwa-admin-content.php:245
#: wwa-profile-content.php:15
#: wwa-profile-content.php:136
#: wwa-profile-content.php:138
#: wwa-shortcodes.php:33
#: wwa-shortcodes.php:118
#: wwa-shortcodes.php:127
msgid "Any"
msgstr ""
#: wwa-admin-content.php:242
#: wwa-admin-content.php:246
msgid "Platform (e.g. Passkey or built-in sensors)"
msgstr ""
#: wwa-admin-content.php:243
#: wwa-profile-content.php:138
#: wwa-shortcodes.php:118
#: wwa-admin-content.php:247
#: wwa-profile-content.php:140
#: wwa-shortcodes.php:129
msgid "Roaming (e.g. USB security keys)"
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."
msgstr ""
#: wwa-admin-content.php:252
#: wwa-admin-content.php:256
msgid "Disable password reset for"
msgstr ""
#: wwa-admin-content.php:261
#: wwa-admin-content.php:265
msgid "Off"
msgstr ""
#: wwa-admin-content.php:262
#: wwa-admin-content.php:266
msgid "Everyone except administrators"
msgstr ""
#: wwa-admin-content.php:263
#: wwa-admin-content.php:267
msgid "Everyone"
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)."
msgstr ""
#: wwa-admin-content.php:272
#: wwa-admin-content.php:276
msgid "After User Registration"
msgstr ""
#: wwa-admin-content.php:281
#: wwa-admin-content.php:285
msgid "No action"
msgstr ""
#: wwa-admin-content.php:282
#: wwa-admin-content.php:286
msgid "Log user in and redirect to user's profile"
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."
msgstr ""
#: wwa-admin-content.php:291
#: wwa-admin-content.php:295
msgid "Logging"
msgstr ""
#: wwa-admin-content.php:303
#: wwa-admin-content.php:307
msgid "Clear log"
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>"
msgstr ""
#: wwa-admin-content.php:314
#: wwa-admin-content.php:318
msgid "Log"
msgstr ""
#: wwa-admin-content.php:316
#: wwa-admin-content.php:320
msgid "Automatic update every 5 seconds."
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>."
msgstr ""
#: wwa-functions.php:159
#: wwa-shortcodes.php:88
#: wwa-shortcodes.php:91
msgid "Auth"
msgstr ""
#: wwa-functions.php:160
#: wwa-shortcodes.php:11
#: wwa-shortcodes.php:85
#: wwa-shortcodes.php:88
#: wwa-shortcodes.php:81
#: wwa-shortcodes.php:90
msgid "Authenticate with WebAuthn"
msgstr ""
@ -293,7 +299,7 @@ msgid "It looks like your browser doesn't support WebAuthn, which means you may
msgstr ""
#: wwa-functions.php:167
#: wwa-shortcodes.php:88
#: wwa-shortcodes.php:87
msgid "Username"
msgstr ""
@ -314,36 +320,42 @@ msgstr ""
msgid "Logging in with password has been disabled for this account."
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>"
msgstr ""
#: wwa-functions.php:275
#: wwa-functions.php:320
#. translators: %s: 'the site' or 'your account', and admin profile url
#. translators: %s: 'the site' or 'your account'
#: wwa-functions.php:276
#: wwa-functions.php:322
msgid "the site"
msgstr ""
#: wwa-functions.php:275
#. translators: %s: 'the site' or 'your account', and admin profile url
#: wwa-functions.php:276
msgid "your account"
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."
msgstr ""
#: wwa-functions.php:320
#. translators: %s: 'the site' or 'your account'
#: wwa-functions.php:322
msgid "this account"
msgstr ""
#: wwa-functions.php:348
#: wwa-functions.php:350
msgid "Settings"
msgstr ""
#: wwa-functions.php:356
#: wwa-functions.php:358
msgid "GitHub"
msgstr ""
#: wwa-functions.php:357
#: wwa-functions.php:359
msgid "Documentation"
msgstr ""
@ -487,139 +499,142 @@ msgstr ""
msgid "The site administrator only allow roaming authenticators currently."
msgstr ""
#: wwa-profile-content.php:63
#: wwa-profile-content.php:64
msgid "You've successfully registered! Now you can register your authenticators below."
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."
msgstr ""
#: wwa-profile-content.php:85
#: wwa-profile-content.php:86
msgid "Disable password login for this account"
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."
msgstr ""
#: wwa-profile-content.php:87
#: wwa-profile-content.php:88
msgid "The site administrator has disabled password login for the whole site."
msgstr ""
#: wwa-profile-content.php:91
#: wwa-profile-content.php:92
msgid "Registered WebAuthn Authenticators"
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:112
#: wwa-shortcodes.php:156
#: wwa-shortcodes.php:158
msgid "Type"
#: wwa-shortcodes.php:175
#: wwa-shortcodes.php:177
msgid "Identifier"
msgstr ""
#: wwa-profile-content.php:98
#: wwa-profile-content.php:113
#: wwa-shortcodes.php:156
#: wwa-shortcodes.php:158
msgctxt "time"
msgid "Registered"
#: wwa-shortcodes.php:175
#: wwa-shortcodes.php:177
msgid "Type"
msgstr ""
#: wwa-profile-content.php:99
#: wwa-profile-content.php:114
msgid "Last used"
#: wwa-shortcodes.php:175
#: wwa-shortcodes.php:177
msgctxt "time"
msgid "Registered"
msgstr ""
#: wwa-profile-content.php:100
#: wwa-profile-content.php:115
#: wwa-shortcodes.php:156
#: wwa-shortcodes.php:158
msgid "Usernameless"
#: wwa-shortcodes.php:175
#: wwa-shortcodes.php:177
msgid "Last used"
msgstr ""
#: wwa-profile-content.php:101
#: wwa-profile-content.php:116
#: wwa-shortcodes.php:156
#: wwa-shortcodes.php:158
#: wwa-shortcodes.php:175
#: 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"
msgstr ""
#: wwa-profile-content.php:106
#: wwa-shortcodes.php:157
#: wwa-profile-content.php:107
#: wwa-shortcodes.php:176
msgid "Loading..."
msgstr ""
#: wwa-profile-content.php:123
#: wwa-profile-content.php:126
#: wwa-profile-content.php:124
#: wwa-profile-content.php:127
msgid "Register New Authenticator"
msgstr ""
#: wwa-profile-content.php:123
#: wwa-profile-content.php:167
#: wwa-profile-content.php:124
#: wwa-profile-content.php:169
msgid "Verify Authenticator"
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."
msgstr ""
#: wwa-profile-content.php:130
#: wwa-shortcodes.php:118
#: wwa-profile-content.php:132
#: wwa-shortcodes.php:125
msgid "Type of authenticator"
msgstr ""
#: wwa-profile-content.php:137
#: wwa-shortcodes.php:118
#: wwa-profile-content.php:139
#: wwa-shortcodes.php:128
msgid "Platform (e.g. built-in fingerprint sensors)"
msgstr ""
#: wwa-profile-content.php:140
#: wwa-shortcodes.php:118
#: wwa-profile-content.php:142
#: 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."
msgstr ""
#: wwa-profile-content.php:144
#: wwa-profile-content.php:146
msgid "Authenticator Identifier"
msgstr ""
#: wwa-profile-content.php:147
#: wwa-shortcodes.php:118
#: wwa-profile-content.php:149
#: wwa-shortcodes.php:134
msgid "An easily identifiable name for the authenticator. <strong>DOES NOT</strong> affect the authentication process in anyway."
msgstr ""
#: wwa-profile-content.php:152
#: wwa-shortcodes.php:118
#: wwa-profile-content.php:154
#: wwa-shortcodes.php:135
msgid "Login without username"
msgstr ""
#: wwa-profile-content.php:157
#: wwa-shortcodes.php:118
#: wwa-profile-content.php:159
#: 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."
msgstr ""
#: wwa-profile-content.php:163
#: wwa-profile-content.php:165
msgid "Start Registration"
msgstr ""
#: wwa-profile-content.php:168
#: wwa-profile-content.php:170
msgid "Click Test Login to verify that the registered authenticators are working."
msgstr ""
#: wwa-profile-content.php:169
#: wwa-shortcodes.php:144
#: wwa-profile-content.php:171
#: wwa-shortcodes.php:163
msgid "Test Login"
msgstr ""
#: wwa-profile-content.php:171
#: wwa-shortcodes.php:144
#: wwa-profile-content.php:173
#: wwa-shortcodes.php:163
msgid "Test Login (usernameless)"
msgstr ""
@ -627,20 +642,20 @@ msgstr ""
msgid "Error: The username field is empty."
msgstr ""
#: wwa-shortcodes.php:88
#: wwa-shortcodes.php:91
msgid "Authenticate with password"
msgstr ""
#: wwa-shortcodes.php:105
#: wwa-shortcodes.php:133
#: wwa-shortcodes.php:166
#: wwa-shortcodes.php:110
#: wwa-shortcodes.php:152
#: wwa-shortcodes.php:185
msgid "You haven't logged in yet."
msgstr ""
#: wwa-shortcodes.php:118
#: wwa-shortcodes.php:132
msgid "Authenticator identifier"
msgstr ""
#: wwa-shortcodes.php:118
#: wwa-shortcodes.php:136
msgid "Start registration"
msgstr ""

View File

@ -1,10 +1,10 @@
=== WP-WebAuthn ===
Contributors: axton
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
Tested up to: 6.3
Stable tag: 1.3.1
Tested up to: 6.6
Stable tag: 1.3.4
Requires PHP: 7.2
License: GPLv3
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 ==
= 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 =
Update: Translations

View File

@ -22,4 +22,4 @@ if (PHP_VERSION_ID < 50600) {
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\\UnsignedIntegerObject' => $vendorDir . '/spomky-labs/cbor-php/src/UnsignedIntegerObject.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',
'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',

View File

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

View File

@ -4,7 +4,7 @@
namespace Composer\Autoload;
class ComposerStaticInit09e765e3690d5165ed98a315471eec7d
class ComposerStaticInite99fdfd0dbb5e609b534e430fe6b54ef
{
public static $files = array (
'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\\UnsignedIntegerObject' => __DIR__ . '/..' . '/spomky-labs/cbor-php/src/UnsignedIntegerObject.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',
'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',
@ -939,9 +940,9 @@ class ComposerStaticInit09e765e3690d5165ed98a315471eec7d
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit09e765e3690d5165ed98a315471eec7d::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit09e765e3690d5165ed98a315471eec7d::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit09e765e3690d5165ed98a315471eec7d::$classMap;
$loader->prefixLengthsPsr4 = ComposerStaticInite99fdfd0dbb5e609b534e430fe6b54ef::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInite99fdfd0dbb5e609b534e430fe6b54ef::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInite99fdfd0dbb5e609b534e430fe6b54ef::$classMap;
}, null, ClassLoader::class);
}

View File

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

View File

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

View File

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

View File

@ -17,19 +17,14 @@ use JsonSerializable;
use League\Uri\Contracts\UriInterface;
use League\Uri\Exceptions\SyntaxError;
use Psr\Http\Message\UriInterface as Psr7UriInterface;
use function is_object;
use Stringable;
use function is_scalar;
use function method_exists;
use function sprintf;
final class Http implements Psr7UriInterface, JsonSerializable
{
private UriInterface $uri;
private function __construct(UriInterface $uri)
private function __construct(private readonly UriInterface $uri)
{
$this->validate($uri);
$this->uri = $uri;
$this->validate($this->uri);
}
/**
@ -39,19 +34,18 @@ final class Http implements Psr7UriInterface, JsonSerializable
*/
private function validate(UriInterface $uri): void
{
$scheme = $uri->getScheme();
if (null === $scheme && '' === $uri->getHost()) {
throw new SyntaxError(sprintf('an URI without scheme can not contains a empty host string according to PSR-7: %s', (string) $uri));
if (null === $uri->getScheme() && '' === $uri->getHost()) {
throw new SyntaxError('An URI without scheme can not contains a empty host string according to PSR-7: '.$uri);
}
$port = $uri->getPort();
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
{
@ -60,10 +54,8 @@ final class Http implements Psr7UriInterface, JsonSerializable
/**
* 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));
}
@ -91,21 +83,18 @@ final class Http implements Psr7UriInterface, JsonSerializable
* Create a new instance from a URI and a Base URI.
*
* 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));
}
/**
* 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) {
return new self($uri);
@ -178,145 +167,6 @@ final class Http implements Psr7UriInterface, JsonSerializable
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}
*/
@ -332,4 +182,88 @@ final class Http implements Psr7UriInterface, JsonSerializable
{
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\Idna\Idna;
use Psr\Http\Message\UriInterface as Psr7UriInterface;
use SensitiveParameter;
use Stringable;
use TypeError;
use function array_filter;
use function array_key_first;
use function array_map;
use function base64_decode;
use function base64_encode;
@ -33,14 +36,15 @@ use function filter_var;
use function implode;
use function in_array;
use function inet_pton;
use function is_object;
use function is_int;
use function is_scalar;
use function method_exists;
use function is_string;
use function ltrim;
use function preg_match;
use function preg_replace;
use function preg_replace_callback;
use function rawurlencode;
use function sprintf;
use function str_contains;
use function str_replace;
use function strlen;
use function strpos;
@ -81,14 +85,14 @@ final class Uri implements UriInterface
*
* @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.
*
* @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.
@ -96,9 +100,9 @@ final class Uri implements UriInterface
* @link https://tools.ietf.org/html/rfc3986#section-3.2.2
*/
private const REGEXP_HOST_REGNAME = '/^(
(?<unreserved>[a-z0-9_~\-\.])|
(?<unreserved>[a-z\d_~\-\.])|
(?<sub_delims>[!$&\'()*+,;=])|
(?<encoded>%[A-F0-9]{2})
(?<encoded>%[A-F\d]{2})
)+$/x';
/**
@ -114,9 +118,9 @@ final class Uri implements UriInterface
* @link https://tools.ietf.org/html/rfc3986#section-3.2.2
*/
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
)+
$/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 = [
'data' => 'isUriWithSchemeAndPathOnly',
'file' => 'isUriWithSchemeHostAndPathOnly',
'ftp' => 'isNonEmptyHostUriWithoutFragmentAndQuery',
'gopher' => 'isNonEmptyHostUriWithoutFragmentAndQuery',
'http' => 'isNonEmptyHostUri',
'https' => 'isNonEmptyHostUri',
'ws' => 'isNonEmptyHostUriWithoutFragment',
'wss' => 'isNonEmptyHostUriWithoutFragment',
];
private const MAXIMUM_FORMATTED_HOST_CACHED = 100;
/**
* All ASCII letters sorted by typical frequency of occurrence.
@ -203,7 +198,7 @@ final class Uri implements UriInterface
private ?string $host;
private ?int $port;
private ?string $authority;
private string $path = '';
private string $path;
private ?string $query;
private ?string $fragment;
private ?string $uri;
@ -211,7 +206,7 @@ final class Uri implements UriInterface
private function __construct(
?string $scheme,
?string $user,
?string $pass,
#[SensitiveParameter] ?string $pass,
?string $host,
?int $port,
string $path,
@ -226,49 +221,49 @@ final class Uri implements UriInterface
$this->path = $this->formatPath($path);
$this->query = $this->formatQueryAndFragment($query);
$this->fragment = $this->formatQueryAndFragment($fragment);
$this->assertValidState();
}
/**
* Format the Scheme and Host component.
*
* @param ?string $scheme
* @throws SyntaxError if the scheme is invalid
*/
private function formatScheme(?string $scheme): ?string
{
if (null === $scheme) {
if (null === $scheme || array_key_exists($scheme, self::SCHEME_DEFAULT_PORT)) {
return $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;
}
throw new SyntaxError(sprintf('The scheme `%s` is invalid.', $scheme));
throw new SyntaxError('The scheme `'.$scheme.'` is invalid.');
}
/**
* 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) {
return $user;
return null;
}
static $user_pattern = '/(?:[^%'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f0-9]{2}))/';
$user = preg_replace_callback($user_pattern, [Uri::class, 'urlEncodeMatch'], $user);
static $user_pattern = '/[^%'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f\d]{2})/';
$user = preg_replace_callback($user_pattern, Uri::urlEncodeMatch(...), $user);
if (null === $password) {
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.
* @param ?string $host
*/
private function formatHost(?string $host): ?string
{
@ -289,11 +283,18 @@ final class Uri implements UriInterface
return $host;
}
if ('[' !== $host[0]) {
return $this->formatRegisteredName($host);
static $formattedHostCache = [];
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)) {
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);
@ -341,35 +342,33 @@ final class Uri implements UriInterface
$pos = strpos($ip, '%');
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)))) {
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);
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
//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;
}
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.
*
* @param object|null|int|string $port
*
* @throws SyntaxError
*/
private function formatPort($port = null): ?int
private function formatPort(Stringable|string|int|null $port = null): ?int
{
if (null === $port || '' === $port) {
return null;
@ -381,7 +380,7 @@ final class Uri implements UriInterface
$port = (int) $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;
@ -392,9 +391,6 @@ final class Uri implements UriInterface
return $port;
}
/**
* {@inheritDoc}
*/
public static function __set_state(array $components): self
{
$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.
*
* @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) {
$uri = self::createFromString($uri);
}
if (null === $base_uri) {
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()) {
@ -449,7 +444,7 @@ final class Uri implements UriInterface
}
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 */
@ -460,10 +455,8 @@ final class Uri implements UriInterface
/**
* 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);
@ -517,7 +510,7 @@ final class Uri implements UriInterface
// @codeCoverageIgnoreStart
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
@ -528,9 +521,12 @@ final class Uri implements UriInterface
$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) {
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);
@ -546,7 +542,7 @@ final class Uri implements UriInterface
*/
public static function createFromUnixPath(string $uri = ''): self
{
$uri = implode('/', array_map('rawurlencode', explode('/', $uri)));
$uri = implode('/', array_map(rawurlencode(...), explode('/', $uri)));
if ('/' !== ($uri[0] ?? '')) {
return Uri::createFromComponents(['path' => $uri]);
}
@ -565,7 +561,7 @@ final class Uri implements UriInterface
$uri = substr($uri, strlen($root));
}
$uri = str_replace('\\', '/', $uri);
$uri = implode('/', array_map('rawurlencode', explode('/', $uri)));
$uri = implode('/', array_map(rawurlencode(...), explode('/', $uri)));
//Local Windows absolute path
if ('' !== $root) {
@ -573,7 +569,7 @@ final class Uri implements UriInterface
}
//UNC Windows Path
if ('//' !== substr($uri, 0, 2)) {
if (!str_starts_with($uri, '//')) {
return Uri::createFromComponents(['path' => $uri]);
}
@ -584,10 +580,8 @@ final class Uri implements UriInterface
/**
* 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) {
$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();
if ('' === $scheme) {
$scheme = null;
@ -657,19 +647,12 @@ final class Uri implements UriInterface
*/
public static function createFromServer(array $server): self
{
[$user, $pass] = self::fetchUserInfo($server);
[$host, $port] = self::fetchHostname($server);
[$path, $query] = self::fetchRequestUri($server);
$components = ['scheme' => self::fetchScheme($server)];
[$components['user'], $components['pass']] = self::fetchUserInfo($server);
[$components['host'], $components['port']] = self::fetchHostname($server);
[$components['path'], $components['query']] = self::fetchRequestUri($server);
return Uri::createFromComponents([
'scheme' => self::fetchScheme($server),
'user' => $user,
'pass' => $pass,
'host' => $host,
'port' => $port,
'path' => $path,
'query' => $query,
]);
return Uri::createFromComponents($components);
}
/**
@ -686,14 +669,14 @@ final class Uri implements UriInterface
/**
* 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
{
$server += ['PHP_AUTH_USER' => null, 'PHP_AUTH_PW' => null, 'HTTP_AUTHORIZATION' => ''];
$user = $server['PHP_AUTH_USER'];
$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);
if (false === $userinfo) {
throw new SyntaxError('The user info could not be detected');
@ -751,20 +734,17 @@ final class Uri implements UriInterface
/**
* 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
{
$server += ['IIS_WasUrlRewritten' => null, 'UNENCODED_URL' => '', 'PHP_SELF' => '', 'QUERY_STRING' => null];
if ('1' === $server['IIS_WasUrlRewritten'] && '' !== $server['UNENCODED_URL']) {
/** @var array{0:?string, 1:?string} $retval */
$retval = explode('?', $server['UNENCODED_URL'], 2) + [1 => null];
return $retval;
return explode('?', $server['UNENCODED_URL'], 2) + [1 => null];
}
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;
return [$path, $query];
@ -799,13 +779,21 @@ final class Uri implements UriInterface
*/
private function formatPath(string $path): string
{
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
{
if ('data' !== $this->scheme) {
return $path;
}
if ('' == $path) {
return 'text/plain;charset=us-ascii,';
}
if (strlen($path) !== strspn($path, self::ASCII) || false === strpos($path, ',')) {
throw new SyntaxError(sprintf('The path `%s` is invalid according to RFC2937', $path));
if (strlen($path) !== strspn($path, self::ASCII) || !str_contains($path, ',')) {
throw new SyntaxError('The path `'.$path.'` is invalid according to RFC2937.');
}
$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
{
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);
@ -865,9 +849,9 @@ final class Uri implements UriInterface
$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) {
throw new SyntaxError(sprintf('The path paremeters `%s` is invalid', $parameters));
throw new SyntaxError('The path paremeters `'.$parameters.'` is invalid.');
}
if (!$is_binary) {
@ -876,7 +860,7 @@ final class Uri implements UriInterface
$res = base64_decode($data, true);
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
{
if ('file' !== $this->scheme) {
return $path;
}
$replace = static function (array $matches): string {
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
* when building the URI string representation</li>
* </ul>
*
* @param ?string $component
*/
private function formatQueryAndFragment(?string $component): ?string
{
@ -924,8 +902,8 @@ final class Uri implements UriInterface
return $component;
}
static $pattern = '/(?:[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/';
return preg_replace_callback($pattern, [Uri::class, 'urlEncodeMatch'], $component);
static $pattern = '/[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.':@\/?]++|%(?![A-Fa-f\d]{2})/';
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 `/`.');
}
if (null === $this->authority && 0 === strpos($this->path, '//')) {
throw new SyntaxError(sprintf('If there is no authority the path `%s` can not start with a `//`.', $this->path));
if (null === $this->authority && str_starts_with($this->path, '//')) {
throw new SyntaxError('If there is no authority the path `'.$this->path.'` can not start with a `//`.');
}
$pos = strpos($this->path, ':');
if (null === $this->authority
&& null === $this->scheme
&& 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.');
}
$validationMethod = self::SCHEME_VALIDATION_METHOD[$this->scheme] ?? 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.
*
* @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(
?string $scheme,
@ -1053,15 +1030,13 @@ final class Uri implements UriInterface
public function toString(): string
{
$this->uri = $this->uri ?? $this->getUriString(
return $this->uri ??= $this->getUriString(
$this->scheme,
$this->authority,
$this->path,
$this->query,
$this->fragment
);
return $this->uri;
}
/**
@ -1081,9 +1056,15 @@ final class Uri implements UriInterface
}
/**
* {@inheritDoc}
*
* @return array{scheme:?string, user_info:?string, host:?string, port:?int, path:string, query:?string, fragment:?string}
* @return array{
* scheme:?string,
* user_info:?string,
* host:?string,
* port:?int,
* path:string,
* query:?string,
* fragment:?string
* }
*/
public function __debugInfo(): array
{
@ -1143,7 +1124,7 @@ final class Uri implements UriInterface
*/
public function getPath(): string
{
if (0 === strpos($this->path, '//')) {
if (str_starts_with($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
*/
private function filterString($str): ?string
private function filterString(mixed $str): ?string
{
if (null === $str) {
return $str;
return null;
}
if (is_object($str) && method_exists($str, '__toString')) {
$str = (string) $str;
}
if (!is_scalar($str)) {
throw new SyntaxError(sprintf('The component must be a string, a scalar or a stringable object; `%s` given.', gettype($str)));
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;
@ -1211,14 +1188,16 @@ final class Uri implements UriInterface
return $str;
}
throw new SyntaxError(sprintf('The component `%s` contains invalid characters.', $str));
throw new SyntaxError('The component `'.$str.'` contains invalid characters.');
}
/**
* {@inheritDoc}
*/
public function withUserInfo($user, $password = null): UriInterface
{
public function withUserInfo(
$user,
#[SensitiveParameter] $password = null
): UriInterface {
$user_info = null;
$user = $this->filterString($user);
if (null !== $password) {

View File

@ -15,18 +15,15 @@ namespace League\Uri;
use League\Uri\Contracts\UriInterface;
use Psr\Http\Message\UriInterface as Psr7UriInterface;
use TypeError;
use function explode;
use function implode;
use function preg_replace_callback;
use function rawurldecode;
use function sprintf;
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 WHATWG_SPECIAL_SCHEMES = ['ftp' => 21, 'http' => 80, 'https' => 443, 'ws' => 80, 'wss' => 443];
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'];
/**
* @codeCoverageIgnore
@ -35,46 +32,17 @@ final class UriInfo
{
}
/**
* @param Psr7UriInterface|UriInterface $uri
*/
private static function emptyComponentValue($uri): ?string
private static function emptyComponentValue(Psr7UriInterface|UriInterface $uri): ?string
{
return $uri instanceof Psr7UriInterface ? '' : null;
}
/**
* Filter the URI object.
*
* 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
* Normalizes an URI for comparison.
*/
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);
$path = $uri->getPath();
@ -107,36 +75,28 @@ final class UriInfo
}
/**
* Tell whether the URI represents an absolute URI.
*
* @param Psr7UriInterface|UriInterface $uri
* Tells whether the URI represents an absolute 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.
*
* @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);
return $null === $uri->getScheme() && $null !== $uri->getAuthority();
}
/**
* Tell whether the URI represents an absolute path.
*
* @param Psr7UriInterface|UriInterface $uri
* Tells whether the URI represents an absolute path.
*/
public static function isAbsolutePath($uri): bool
public static function isAbsolutePath(Psr7UriInterface|UriInterface $uri): bool
{
$uri = self::filterUri($uri);
$null = self::emptyComponentValue($uri);
return $null === $uri->getScheme()
@ -147,11 +107,9 @@ final class UriInfo
/**
* 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);
return $null === $uri->getScheme()
@ -160,12 +118,9 @@ final class UriInfo
}
/**
* Tell whether both URI refers to the same document.
*
* @param Psr7UriInterface|UriInterface $uri
* @param Psr7UriInterface|UriInterface $base_uri
* Tells whether both URI refers to the same document.
*/
public static function isSameDocument($uri, $base_uri): bool
public static function isSameDocument(Psr7UriInterface|UriInterface $uri, Psr7UriInterface|UriInterface $base_uri): bool
{
$uri = self::normalize($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 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)
*
* @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) {
$uri = Uri::createFromString($uri->getPath());
$scheme = $uri->getScheme();
}
if (null === $scheme || !array_key_exists($scheme, self::WHATWG_SPECIAL_SCHEMES)) {
if (in_array($scheme, self::WHATWG_SPECIAL_SCHEMES, true)) {
$null = self::emptyComponentValue($uri);
return (string) $uri->withFragment($null)->withQuery($null)->withPath('')->withUserInfo($null, null);
}
return null;
}
$null = self::emptyComponentValue($uri);
return (string) $uri->withFragment($null)->withQuery($null)->withPath('')->withUserInfo($null);
}
/**
* @param Psr7UriInterface|UriInterface $uri
* @param Psr7UriInterface|UriInterface $base_uri
* Tells whether two URI do not share the same origin.
*
* @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)))
|| null === ($baseUriString = self::getOrigin(Uri::createFromUri($base_uri)))

View File

@ -15,16 +15,13 @@ namespace League\Uri;
use League\Uri\Contracts\UriInterface;
use Psr\Http\Message\UriInterface as Psr7UriInterface;
use TypeError;
use function array_pop;
use function array_reduce;
use function count;
use function end;
use function explode;
use function gettype;
use function implode;
use function in_array;
use function sprintf;
use function str_repeat;
use function strpos;
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 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;
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.
*/
private static function removeDotSegments(string $path): string
{
if (false === strpos($path, '.')) {
if (!str_contains($path, '.')) {
return $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)])) {
$new_path .= '/';
}
// @codeCoverageIgnoreStart
// 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;
}
// @codeCoverageIgnoreEnd
@ -150,21 +126,20 @@ final class UriResolver
}
/**
* Resolve an URI path and query component.
*
* @param Psr7UriInterface|UriInterface $uri
* @param Psr7UriInterface|UriInterface $base_uri
* Resolves an URI path and query component.
*
* @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_query = $uri->getQuery();
$null = $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];
}
@ -176,7 +151,7 @@ final class UriResolver
$target_path = $base_uri->getPath();
//@codeCoverageIgnoreStart
//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;
}
//@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
* an URI instance of the same type that contains the applied modifications.
*
* This method MUST be transparent when dealing with error and exceptions.
* 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)
{
self::filterUri($uri);
self::filterUri($base_uri);
public static function relativize(
Psr7UriInterface|UriInterface $uri,
Psr7UriInterface|UriInterface $base_uri
): Psr7UriInterface|UriInterface {
$uri = self::formatHost($uri);
$base_uri = self::formatHost($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()));
}
if (self::componentEquals('getQuery', $uri, $base_uri)) {
if (self::componentEquals('query', $uri, $base_uri)) {
return $uri->withPath('')->withQuery($null);
}
@ -244,23 +214,26 @@ final class UriResolver
/**
* 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
{
return self::getComponent($method, $uri) === self::getComponent($method, $base_uri);
private static function componentEquals(
string $property,
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.
*
* @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) {
return null;
}
@ -270,14 +243,8 @@ final class UriResolver
/**
* 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) {
return $uri;
@ -292,20 +259,19 @@ final class UriResolver
}
/**
* Tell whether the submitted URI object can be relativize.
*
* @param Psr7UriInterface|UriInterface $uri
* @param Psr7UriInterface|UriInterface $base_uri
* Tells whether the submitted URI object can be relativize.
*/
private static function isRelativizable($uri, $base_uri): bool
{
private static function isRelativizable(
Psr7UriInterface|UriInterface $uri,
Psr7UriInterface|UriInterface $base_uri
): bool {
return !UriInfo::isRelativePath($uri)
&& self::componentEquals('getScheme', $uri, $base_uri)
&& self::componentEquals('getAuthority', $uri, $base_uri);
&& self::componentEquals('scheme', $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
{

View File

@ -17,15 +17,11 @@ use League\Uri\Exceptions\IdnaConversionFailed;
use League\Uri\Exceptions\IdnSupportMissing;
use League\Uri\Exceptions\SyntaxError;
use League\Uri\Idna\Idna;
use TypeError;
use Stringable;
use function array_merge;
use function explode;
use function filter_var;
use function gettype;
use function inet_pton;
use function is_object;
use function is_scalar;
use function method_exists;
use function preg_match;
use function rawurldecode;
use function sprintf;
@ -87,7 +83,7 @@ final class UriString
*
* @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.
@ -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-7.5
*
* @param array{
* scheme:?string,
* user:?string,
* pass:?string,
* host:?string,
* port:?int,
* path:?string,
* query:?string,
* fragment:?string
* } $components
* @param array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:?string, query:?string, fragment:?string} $components
*/
public static function build(array $components): string
{
@ -242,35 +229,17 @@ final class UriString
*
* @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 an invalid scheme
* @throws SyntaxError if the URI contains an invalid path
*
* @return array{
* scheme:?string,
* user:?string,
* pass:?string,
* host:?string,
* port:?int,
* path:string,
* query:?string,
* fragment:?string
* }
* @return array{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;
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 */
$components = array_merge(self::URI_COMPONENTS, self::URI_SCHORTCUTS[$uri]);
@ -395,7 +364,7 @@ final class UriString
return $host;
}
if ('[' !== $host[0] || ']' !== substr($host, -1)) {
if ('[' !== $host[0] || !str_ends_with($host, ']')) {
return self::filterRegisteredName($host);
}
@ -462,6 +431,6 @@ final class UriString
$ip_host = substr($ip_host, 0, $pos);
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\UriTemplate\Template;
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.
@ -34,25 +34,22 @@ use TypeError;
*/
final class UriTemplate
{
private Template $template;
private VariableBag $defaultVariables;
public readonly Template $template;
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 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);
}
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
*/
private function filterVariables(array $variables): VariableBag
private function filterVariables(VariableBag|array $variables): VariableBag
{
$output = new VariableBag();
foreach ($this->template->variableNames() as $name) {
return array_reduce(
$this->template->variableNames,
function (VariableBag $curry, string $name) use ($variables): VariableBag {
if (isset($variables[$name])) {
$output->assign($name, $variables[$name]);
}
$curry[$name] = $variables[$name];
}
return $output;
return $curry;
},
new VariableBag()
);
}
/**
@ -77,7 +77,7 @@ final class UriTemplate
*/
public function getTemplate(): string
{
return $this->template->toString();
return $this->template->value;
}
/**
@ -87,7 +87,7 @@ final class UriTemplate
*/
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
* possible variable names are removed.
*/
public function withDefaultVariables(array $defaultDefaultVariables): self
public function withDefaultVariables(VariableBag|array $defaultDefaultVariables): self
{
return new self(
$this->template->toString(),
$this->filterVariables($defaultDefaultVariables)->all()
);
return new self($this->template, $defaultDefaultVariables);
}
/**
* @throws TemplateCanNotBeExpanded if the variable contains nested array values
* @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(
$this->template->expand(

View File

@ -16,7 +16,6 @@ namespace League\Uri\UriTemplate;
use League\Uri\Exceptions\SyntaxError;
use League\Uri\Exceptions\TemplateCanNotBeExpanded;
use function array_filter;
use function array_keys;
use function array_map;
use function array_unique;
use function explode;
@ -24,7 +23,6 @@ use function implode;
use function preg_match;
use function rawurlencode;
use function str_replace;
use function strpos;
use function substr;
final class Expression
@ -64,46 +62,29 @@ final class Expression
'&' => ['prefix' => '&', 'joiner' => '&', 'query' => true],
];
private string $operator;
/** @var array<VarSpecifier> */
private array $varSpecifiers;
private string $joiner;
/** @var array<string> */
private array $variableNames;
private string $expressionString;
public readonly array $variableNames;
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->joiner = self::OPERATOR_HASH_LOOKUP[$operator]['joiner'];
$this->variableNames = $this->setVariableNames();
$this->expressionString = $this->setExpressionString();
$this->variableNames = array_unique(array_map(
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>
*/
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}
* @param array{operator:string, varSpecifiers:array<VarSpecifier>} $properties
*/
public static function __set_state(array $properties): self
{
@ -123,7 +104,7 @@ final class Expression
/** @var array{operator:string, variables:string} $parts */
$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.');
}
@ -136,13 +117,18 @@ final class Expression
/**
* Returns the expression string representation.
*
* @deprecated since version 6.6.0 use the readonly property instead
* @codeCoverageIgnore
*/
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>
*/
public function variableNames(): array
@ -178,7 +164,7 @@ final class Expression
*/
private function replace(VarSpecifier $varSpec, VariableBag $variables): string
{
$value = $variables->fetch($varSpec->name());
$value = $variables->fetch($varSpec->name);
if (null === $value) {
return '';
}
@ -190,10 +176,10 @@ final class Expression
}
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}
*/
private function inject($value, VarSpecifier $varSpec, bool $useQuery): array
private function inject(array|string $value, VarSpecifier $varSpec, bool $useQuery): array
{
if (is_string($value)) {
return $this->replaceString($value, $varSpec, $useQuery);
@ -217,16 +203,15 @@ final class Expression
*/
private function replaceString(string $value, VarSpecifier $varSpec, bool $useQuery): array
{
if (':' === $varSpec->modifier()) {
$value = substr($value, 0, $varSpec->position());
if (':' === $varSpec->modifier) {
$value = substr($value, 0, $varSpec->position);
}
$expanded = rawurlencode($value);
if ('+' === $this->operator || '#' === $this->operator) {
return [$this->decodeReserved($expanded), $useQuery];
if (in_array($this->operator, ['+', '#'], true)) {
return [$this->decodeReserved(rawurlencode($value)), $useQuery];
}
return [$expanded, $useQuery];
return [rawurlencode($value), $useQuery];
}
/**
@ -244,48 +229,45 @@ final class Expression
return ['', false];
}
if (':' === $varSpec->modifier()) {
throw TemplateCanNotBeExpanded::dueToUnableToProcessValueListWithPrefix($varSpec->name());
if (':' === $varSpec->modifier) {
throw TemplateCanNotBeExpanded::dueToUnableToProcessValueListWithPrefix($varSpec->name);
}
$pairs = [];
$isAssoc = $this->isAssoc($value);
$isList = array_is_list($value);
foreach ($value as $key => $var) {
if ($isAssoc) {
if (!$isList) {
$key = rawurlencode((string) $key);
}
$var = rawurlencode($var);
if ('+' === $this->operator || '#' === $this->operator) {
if (in_array($this->operator, ['+', '#'], true)) {
$var = $this->decodeReserved($var);
}
if ('*' === $varSpec->modifier()) {
if ($isAssoc) {
if ('*' === $varSpec->modifier) {
if (!$isList) {
$var = $key.'='.$var;
} elseif ($key > 0 && $useQuery) {
$var = $varSpec->name().'='.$var;
$var = $varSpec->name.'='.$var;
}
}
$pairs[$key] = $var;
}
if ('*' === $varSpec->modifier()) {
if ($isAssoc) {
// Don't prepend the value name when using the explode
// modifier with an associative array.
if ('*' === $varSpec->modifier) {
if (!$isList) {
// Don't prepend the value name when using the `explode` modifier with an associative array.
$useQuery = false;
}
return [implode($this->joiner, $pairs), $useQuery];
}
if ($isAssoc) {
// When an associative array is encountered and the
// explode modifier is not set, then the result must be
// a comma separated list of keys followed by their
// respective values.
if (!$isList) {
// When an associative array is encountered and the `explode` modifier is not set, then
// the result must be a comma separated list of keys followed by their respective values.
foreach ($pairs as $offset => &$data) {
$data = $offset.','.$data;
}
@ -296,19 +278,6 @@ final class Expression
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).
*/

View File

@ -15,17 +15,11 @@ namespace League\Uri\UriTemplate;
use League\Uri\Exceptions\SyntaxError;
use League\Uri\Exceptions\TemplateCanNotBeExpanded;
use TypeError;
use function array_merge;
use Stringable;
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_replace;
use function sprintf;
use function strpos;
use function str_contains;
use const PREG_SET_ORDER;
final class Template
@ -33,53 +27,42 @@ final class Template
/**
* Expression regular expression pattern.
*/
private const REGEXP_EXPRESSION_DETECTOR = '/\{[^\}]*\}/x';
private const REGEXP_EXPRESSION_DETECTOR = '/\{[^}]*}/x';
private string $template;
/** @var array<string, Expression> */
private array $expressions = [];
/** @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 = [];
foreach ($expressions as $expression) {
$this->expressions[$expression->toString()] = $expression;
$variableNames[] = $expression->variableNames();
$this->expressions[$expression->value] = $expression;
$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
{
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 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;
}
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 */
$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.');
}
@ -96,12 +79,19 @@ final class Template
return new self($template, ...$arguments);
}
/**
* @deprecated since version 6.6.0 use the readonly property instead
* @codeCoverageIgnore
*/
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>
*/
public function variableNames(): array
@ -115,7 +105,7 @@ final class Template
*/
public function expand(VariableBag $variables): string
{
$uriString = $this->template;
$uriString = $this->value;
/** @var Expression $expression */
foreach ($this->expressions as $pattern => $expression) {
$uriString = str_replace($pattern, $expression->expand($variables), $uriString);

View File

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

View File

@ -13,17 +13,18 @@ declare(strict_types=1);
namespace League\Uri\UriTemplate;
use ArrayAccess;
use Countable;
use League\Uri\Exceptions\TemplateCanNotBeExpanded;
use TypeError;
use function gettype;
use function is_array;
use Stringable;
use function is_bool;
use function is_object;
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>>
@ -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
{
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>>
*/
@ -53,55 +82,45 @@ final class VariableBag
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.
*
* @return null|string|array<string>
*/
public function fetch(string $name)
public function fetch(string $name): null|string|array
{
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);
}
/**
* @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
*
* @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 true === $value ? '1' : '0';
}
if (null === $value || is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) {
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;
return match (true) {
is_bool($value) => true === $value ? '1' : '0',
(null === $value || is_scalar($value) || is_object($value)) => (string) $value,
!$isNestedListAllowed => throw TemplateCanNotBeExpanded::dueToNestedListOfValue($name),
default => array_map(fn ($var): array|string => self::normalizeValue($var, $name, false), $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.
## 1.8.2
- Fix deprecation warnings in PHP 8.4
## 1.8.1
- Fix error handling in Stream::getContents()
## 1.8.0
- 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);
}
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) {
$size = $stream->getSize();

View File

@ -39,7 +39,7 @@ class Response implements ResponseInterface
* @param string $version Protocol version
* @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 ('' !== $body && null !== $body) {

View File

@ -260,11 +260,19 @@ class Stream implements StreamInterface
throw new \RuntimeException('Stream is detached');
}
if (false === $contents = @\stream_get_contents($this->stream)) {
throw new \RuntimeException('Unable to read stream contents: ' . (\error_get_last()['message'] ?? ''));
}
$exception = null;
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.
## 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
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.
[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
[implementation-url]: https://packagist.org/providers/psr/http-client-implementation

View File

@ -7,12 +7,15 @@
"authors": [
{
"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": {
"php": "^7.0 || ^8.0",
"psr/http-message": "^1.0"
"psr/http-message": "^1.0 || ^2.0"
},
"autoload": {
"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
of this software and associated documentation files (the "Software"), to deal

View File

@ -16,7 +16,7 @@
}
],
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
@ -30,9 +30,6 @@
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": {
"name": "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
of this software and associated documentation files (the "Software"), to deal

View File

@ -20,7 +20,7 @@
}
],
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"autoload": {
"psr-4": { "Symfony\\Polyfill\\Php80\\": "" },
@ -29,9 +29,6 @@
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": {
"name": "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
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)
- [`MYSQLI_REFRESH_REPLICA`](https://php.net/mysqli.constants#constantmysqli-refresh-replica) constant
- [`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
[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",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"

View File

@ -46,7 +46,7 @@ class ExecutableFinder
*
* @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')) {
$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.
*/
public function onEmpty(callable $onEmpty = null)
public function onEmpty(?callable $onEmpty = null)
{
$this->onEmpty = $onEmpty;
}

View File

@ -35,7 +35,7 @@ class PhpExecutableFinder
{
if ($php = getenv('PHP_BINARY')) {
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 (!is_executable($php)) {
return false;

View File

@ -32,7 +32,7 @@ class PhpProcess extends Process
* @param int $timeout The timeout in seconds
* @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) {
$executableFinder = new PhpExecutableFinder();
@ -53,7 +53,7 @@ class PhpProcess extends Process
/**
* {@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));
}
@ -61,7 +61,7 @@ class PhpProcess extends Process
/**
* {@inheritdoc}
*/
public function start(callable $callback = null, array $env = [])
public function start(?callable $callback = null, array $env = [])
{
if (null === $this->getCommandLine()) {
throw new RuntimeException('Unable to find the PHP executable.');

View File

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

View File

@ -80,6 +80,7 @@ class Process implements \IteratorAggregate
private $processPipes;
private $latestSignal;
private $cachedExitCode;
private static $sigchild;
@ -140,7 +141,7 @@ class Process implements \IteratorAggregate
*
* @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')) {
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
*/
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->commandline = $command;
@ -247,7 +248,7 @@ class Process implements \IteratorAggregate
*
* @final
*/
public function run(callable $callback = null, array $env = []): int
public function run(?callable $callback = null, array $env = []): int
{
$this->start($callback, $env);
@ -266,7 +267,7 @@ class Process implements \IteratorAggregate
*
* @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)) {
throw new ProcessFailedException($this);
@ -294,7 +295,7 @@ class Process implements \IteratorAggregate
* @throws RuntimeException When process is already running
* @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()) {
throw new RuntimeException('Process is already running.');
@ -385,7 +386,7 @@ class Process implements \IteratorAggregate
*
* @final
*/
public function restart(callable $callback = null, array $env = []): self
public function restart(?callable $callback = null, array $env = []): self
{
if ($this->isRunning()) {
throw new RuntimeException('Process is already running.');
@ -412,7 +413,7 @@ class Process implements \IteratorAggregate
* @throws ProcessSignaledException When process stopped after receiving signal
* @throws LogicException When process is not yet started
*/
public function wait(callable $callback = null)
public function wait(?callable $callback = null)
{
$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
*/
public function stop(float $timeout = 10, int $signal = null)
public function stop(float $timeout = 10, ?int $signal = null)
{
$timeoutMicro = microtime(true) + $timeout;
if ($this->isRunning()) {
@ -1310,7 +1311,7 @@ class Process implements \IteratorAggregate
*
* @return \Closure
*/
protected function buildCallback(callable $callback = null)
protected function buildCallback(?callable $callback = null)
{
if ($this->outputDisabled) {
return function ($type, $data) use ($callback): bool {
@ -1345,6 +1346,19 @@ class Process implements \IteratorAggregate
$this->processInformation = proc_get_status($this->process);
$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);
if ($this->fallbackStatus && $this->isSigchildEnabled()) {

View File

@ -3,7 +3,7 @@
Plugin Name: WP-WebAuthn
Plugin URI: https://flyhigher.top
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 URI: https://axton.cc
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');
function wwa_init(){
if(version_compare(get_bloginfo('version'), '4.4', '<')){
if(version_compare(get_bloginfo('version'), '5.0', '<')){
deactivate_plugins(basename(__FILE__)); //disable
}else{
wwa_init_data();
@ -32,7 +32,7 @@ wwa_init_data();
function wwa_init_data(){
if(!get_option('wwa_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(
'user_credentials' => '{}',
'user_credentials_meta' => '{}',
@ -53,7 +53,7 @@ function wwa_init_data(){
include('wwa-version.php');
update_option('wwa_version', $wwa_version);
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{
include('wwa-version.php');
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-ajax.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'));
$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'));
$wwa_not_allowed = true;
}
@ -33,18 +33,22 @@ if(
(isset($_POST['wwa_ref']) && $_POST['wwa_ref'] === 'true')
&& check_admin_referer('wwa_options_update')
&& wwa_validate_privileges()
&& ($_POST['first_choice'] === 'true' || $_POST['first_choice'] === 'false' || $_POST['first_choice'] === 'webauthn')
&& ($_POST['remember_me'] === 'true' || $_POST['remember_me'] === 'false')
&& ($_POST['email_login'] === 'true' || $_POST['email_login'] === 'false')
&& ($_POST['user_verification'] === 'true' || $_POST['user_verification'] === 'false')
&& ($_POST['usernameless_login'] === 'true' || $_POST['usernameless_login'] === 'false')
&& ($_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')
&& ($_POST['after_user_registration'] === 'none' || $_POST['after_user_registration'] === 'login')
&& ($_POST['logging'] === 'true' || $_POST['logging'] === 'false')
&& (isset($_POST['first_choice']) && ($_POST['first_choice'] === 'true' || $_POST['first_choice'] === 'false' || $_POST['first_choice'] === 'webauthn'))
&& (isset($_POST['remember_me']) && ($_POST['remember_me'] === 'true' || $_POST['remember_me'] === 'false'))
&& (isset($_POST['email_login']) && ($_POST['email_login'] === 'true' || $_POST['email_login'] === 'false'))
&& (isset($_POST['user_verification']) && ($_POST['user_verification'] === 'true' || $_POST['user_verification'] === 'false'))
&& (isset($_POST['usernameless_login']) && ($_POST['usernameless_login'] === 'true' || $_POST['usernameless_login'] === 'false'))
&& (isset($_POST['allow_authenticator_type']) && ($_POST['allow_authenticator_type'] === 'none' || $_POST['allow_authenticator_type'] === 'platform' || $_POST['allow_authenticator_type'] === 'cross-platform'))
&& (isset($_POST['password_reset']) && ($_POST['password_reset'] === 'off' || $_POST['password_reset'] === 'admin' || $_POST['password_reset'] === 'all'))
&& (isset($_POST['after_user_registration']) && ($_POST['after_user_registration'] === 'none' || $_POST['after_user_registration'] === 'login'))
&& (isset($_POST['logging']) && ($_POST['logging'] === 'true' || $_POST['logging'] === 'false'))
&& isset($_POST['website_name'])
&& isset($_POST['website_domain'])
){
$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
if(!function_exists('gmp_intval')){
wwa_add_log($res_id, 'Warning: PHP extension gmp not found', true);
@ -55,70 +59,70 @@ if(
if(!function_exists('sodium_crypto_sign_detached')){
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, '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, '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')){
wwa_add_log($res_id, 'first_choice: "'.wwa_get_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')){
wwa_add_log($res_id, 'website_name: "'.wwa_get_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')){
wwa_add_log($res_id, 'website_domain: "'.wwa_get_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')){
wwa_add_log($res_id, 'remember_me: "'.wwa_get_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')){
wwa_add_log($res_id, 'email_login: "'.wwa_get_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')){
wwa_add_log($res_id, 'user_verification: "'.wwa_get_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')){
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);
$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')){
wwa_add_log($res_id, 'usernameless_login: "'.wwa_get_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')){
wwa_add_log($res_id, 'password_reset: "'.wwa_get_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')){
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
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">
<tr>
<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>
<br>
</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>
</div>

View File

@ -67,14 +67,14 @@ class PublicKeyCredentialSourceRepository implements PublicKeyCredentialSourceRe
public function updateCredentialLastUsed(string $publicKeyCredentialId): void {
$credential = $this->findOneMetaByCredentialId($publicKeyCredentialId);
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[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 {
$data = json_decode(wwa_get_option("user_credentials_meta"), true);
$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
@ -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($action === "rename"){
$this->renameCredential(base64_encode($item->getPublicKeyCredentialId()), $name, $res_id);
}else if($action === "remove"){
}elseif($action === "remove"){
$this->removeCredential(base64_encode($item->getPublicKeyCredentialId()), $res_id);
}
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);
wwa_add_log($res_id, "ajax_modify_authenticator: Rename \"".base64_decode($meta[$id]["human_name"])."\" -> \"".$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
@ -133,7 +133,7 @@ class PublicKeyCredentialSourceRepository implements PublicKeyCredentialSourceRe
$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"])."\"");
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
@ -150,14 +150,21 @@ class PublicKeyCredentialSourceRepository implements PublicKeyCredentialSourceRe
// Save credentials data
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
$source = $data[$key]->getUserHandle();
$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" => "-");
wwa_update_option("user_credentials_meta", json_encode($meta));
$meta[$key] = array(
"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{
// Sanitize the input
$wwa_get = array();
$wwa_get["name"] = sanitize_text_field($_GET["name"]);
$wwa_get["type"] = sanitize_text_field($_GET["type"]);
$wwa_get["usernameless"] = sanitize_text_field($_GET["usernameless"]);
$wwa_get["name"] = sanitize_text_field(wp_unslash($_GET["name"]));
$wwa_get["type"] = sanitize_text_field(wp_unslash($_GET["type"]));
$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"]."\"");
}
$user_info = wp_get_current_user();
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){
wwa_add_log($res_id, "ajax_create: (ERROR)Wrong parameters, exit");
wwa_wp_die("Bad Request.");
@ -287,12 +294,12 @@ function wwa_ajax_create(){
return $credential->getPublicKeyCredentialDescriptor();
}, $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
if($wwa_get["type"] === "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;
}else{
$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);
header("Content-Type: application/json");
$publicKeyCredentialCreationOptions = json_decode(json_encode($publicKeyCredentialCreationOptions), true);
$publicKeyCredentialCreationOptions = json_decode(wp_json_encode($publicKeyCredentialCreationOptions), true);
$publicKeyCredentialCreationOptions["clientID"] = $client_id;
echo json_encode($publicKeyCredentialCreationOptions);
echo wp_json_encode($publicKeyCredentialCreationOptions);
wwa_add_log($res_id, "ajax_create: Challenge sent");
exit;
}catch(\Exception $exception){
@ -368,12 +375,13 @@ function wwa_ajax_create_response(){
wwa_add_log($res_id, "ajax_create_response: (ERROR)Missing parameters, exit");
wp_die("Bad Request.");
}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_wp_die("Bad Request.", $client_id);
}
// Sanitize the input
$client_id = sanitize_text_field($_POST["clientid"]);
$client_id = $post_client_id;
}
if(!current_user_can("read")){
@ -388,15 +396,15 @@ function wwa_ajax_create_response(){
}else{
// Sanitize the input
$wwa_post = array();
$wwa_post["name"] = sanitize_text_field($_POST["name"]);
$wwa_post["type"] = sanitize_text_field($_POST["type"]);
$wwa_post["usernameless"] = sanitize_text_field($_POST["usernameless"]);
$wwa_post["name"] = sanitize_text_field(wp_unslash($_POST["name"]));
$wwa_post["type"] = sanitize_text_field(wp_unslash($_POST["type"]));
$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: 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"])){
$user_id = intval(sanitize_text_field($_POST["user_id"]));
if(isset($_POST["user_id"])){
$user_id = intval(sanitize_text_field(wp_unslash($_POST["user_id"])));
if($user_id <= 0){
wwa_add_log($res_id, "ajax_create_response: (ERROR)Wrong parameters, exit");
wwa_wp_die("Bad Request.");
@ -432,8 +440,13 @@ function wwa_ajax_create_response(){
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
$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();
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");
@ -473,7 +486,7 @@ function wwa_ajax_create_response(){
// Verify
try {
$publicKeyCredentialSource = $server->loadAndCheckAttestationResponse(
base64_decode($_POST["data"]),
base64_decode(sanitize_text_field(wp_unslash($_POST["data"]))),
unserialize(base64_decode($temp_val["pkcco"])),
$serverRequest
);
@ -532,12 +545,12 @@ function wwa_ajax_auth_start(){
}else{
// Sanitize the input
$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"])){
$wwa_get["user"] = sanitize_text_field($_GET["user"]);
$wwa_get["user"] = sanitize_text_field(wp_unslash($_GET["user"]));
}
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
if($wwa_get["usernameless"] === "true" && wwa_get_option("usernameless_login") !== "true"){
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();
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){
wwa_add_log($res_id, "ajax_auth: (ERROR)Wrong parameters, exit");
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"));
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])){
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_exist = false;
}else{
@ -654,8 +667,8 @@ function wwa_ajax_auth_start(){
$credentialSourceRepository = new PublicKeyCredentialSourceRepository();
$rpEntity = new PublicKeyCredentialRpEntity(
wwa_get_option('website_name'),
wwa_get_option('website_domain')
wwa_get_option("website_name"),
wwa_get_option("website_domain")
);
$server = new Server(
@ -671,16 +684,16 @@ function wwa_ajax_auth_start(){
}else{
// Get the list of authenticators associated to the user
// $credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity);
$allow_authenticator_type = wwa_get_option('allow_authenticator_type');
if($allow_authenticator_type === false || $allow_authenticator_type === 'none'){
$allow_authenticator_type = wwa_get_option("allow_authenticator_type");
if($allow_authenticator_type === false || $allow_authenticator_type === "none"){
$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");
$credentialSources = $credentialSourceRepository->findCredentialsForUserEntityByType($userEntity, $allow_authenticator_type);
}
// 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_wp_die("User not inited.", $client_id);
}
@ -690,7 +703,7 @@ function wwa_ajax_auth_start(){
return $credential->getPublicKeyCredentialDescriptor();
}, $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
@ -728,9 +741,9 @@ function wwa_ajax_auth_start(){
}
header("Content-Type: application/json");
$publicKeyCredentialRequestOptions = json_decode(json_encode($publicKeyCredentialRequestOptions), true);
$publicKeyCredentialRequestOptions = json_decode(wp_json_encode($publicKeyCredentialRequestOptions), true);
$publicKeyCredentialRequestOptions["clientID"] = $client_id;
echo json_encode($publicKeyCredentialRequestOptions);
echo wp_json_encode($publicKeyCredentialRequestOptions);
wwa_add_log($res_id, "ajax_auth: Challenge sent");
exit;
}catch(\Exception $exception){
@ -761,12 +774,13 @@ function wwa_ajax_auth(){
wwa_add_log($res_id, "ajax_auth_response: (ERROR)Missing parameters, exit");
wp_die("Bad Request.");
}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_wp_die("Bad Request.", $client_id);
}
// Sanitize the input
$client_id = sanitize_text_field($_POST["clientid"]);
$client_id = $post_client_id;
}
// Check POST
@ -776,8 +790,8 @@ function wwa_ajax_auth(){
}else{
// Sanitize the input
$wwa_post = array();
$wwa_post["type"] = sanitize_text_field($_POST["type"]);
$wwa_post["remember"] = sanitize_text_field($_POST["remember"]);
$wwa_post["type"] = sanitize_text_field(wp_unslash($_POST["type"]));
$wwa_post["remember"] = sanitize_text_field(wp_unslash($_POST["remember"]));
}
$temp_val = array(
@ -798,7 +812,7 @@ function wwa_ajax_auth(){
if($wwa_post["remember"] !== "true" && $wwa_post["remember"] !== "false"){
wwa_add_log($res_id, "ajax_auth_response: (ERROR)Wrong parameters, exit");
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_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){
$user_info = wp_get_current_user();
if(isset($_GET["user_id"])){
$user_id = intval(sanitize_text_field($_POST["user_id"]));
if(isset($_POST["user_id"])){
$user_id = intval(sanitize_text_field(wp_unslash($_POST["user_id"])));
if($user_id <= 0){
wwa_add_log($res_id, "ajax_auth_response: (ERROR)Wrong parameters, exit");
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."\"");
}else{
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"])){
wwa_add_log($res_id, "ajax_auth_response: (ERROR)Client data not correct, exit");
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"]){
$rpEntity = new PublicKeyCredentialRpEntity(
@ -988,7 +1003,7 @@ function wwa_ajax_auth(){
// Verify
try {
$server->loadAndCheckAssertionResponse(
base64_decode($_POST["data"]),
$decoded_data,
unserialize(base64_decode($temp_val["pkcco_auth"])),
$userEntity,
$serverRequest
@ -997,7 +1012,7 @@ function wwa_ajax_auth(){
wwa_add_log($res_id, "ajax_auth_response: Challenge verified");
// 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"))){
// Log user in
if (!is_user_logged_in()) {
@ -1011,7 +1026,7 @@ function wwa_ajax_auth(){
$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_wp_die("Something went wrong.");
}
@ -1081,7 +1096,7 @@ function wwa_ajax_authenticator_list(){
$user_info = wp_get_current_user();
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){
wwa_add_log($res_id, "ajax_authenticator_list: (ERROR)Wrong parameters, exit");
wwa_wp_die("Bad Request.");
@ -1120,7 +1135,7 @@ function wwa_ajax_authenticator_list(){
);
$publicKeyCredentialSourceRepository = new PublicKeyCredentialSourceRepository();
echo json_encode($publicKeyCredentialSourceRepository->getShowList($userEntity));
echo wp_json_encode($publicKeyCredentialSourceRepository->getShowList($userEntity));
exit;
}
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();
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){
wwa_add_log($res_id, "ajax_modify_authenticator: (ERROR)Wrong parameters, exit");
wwa_wp_die("Bad Request.");
@ -1198,9 +1213,9 @@ function wwa_ajax_modify_authenticator(){
$publicKeyCredentialSourceRepository = new PublicKeyCredentialSourceRepository();
if($_GET["target"] === "rename"){
echo $publicKeyCredentialSourceRepository->modifyAuthenticator($_GET["id"], sanitize_text_field($_GET["name"]), $userEntity, "rename", $res_id);
}else if($_GET["target"] === "remove"){
echo $publicKeyCredentialSourceRepository->modifyAuthenticator($_GET["id"], "", $userEntity, "remove", $res_id);
echo $publicKeyCredentialSourceRepository->modifyAuthenticator(sanitize_text_field(wp_unslash($_GET["id"])), sanitize_text_field(wp_unslash($_GET["name"])), $userEntity, "rename", $res_id);
}elseif($_GET["target"] === "remove"){
echo $publicKeyCredentialSourceRepository->modifyAuthenticator(sanitize_text_field(wp_unslash($_GET["id"])), "", $userEntity, "remove", $res_id);
}
exit;
}catch(\Exception $exception){
@ -1230,7 +1245,7 @@ function wwa_ajax_get_log(){
if($log === false){
echo "[]";
}else{
echo json_encode($log);
echo wp_json_encode($log);
}
exit;
@ -1253,4 +1268,3 @@ function wwa_ajax_clear_log(){
exit;
}
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){
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){
wwa_destroy_temp_val($client_id);
}
wp_die($message);
wp_die(esc_html($message));
}
// Init data for new options
@ -65,7 +65,7 @@ function wwa_generate_random_string($length = 10){
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_';
$randomString = '';
for($i = 0; $i < $length; $i++){
$randomString .= $characters[rand(0, strlen($characters) - 1)];
$randomString .= $characters[wp_rand(0, strlen($characters) - 1)];
}
return $randomString;
}
@ -132,8 +132,8 @@ function wwa_delete_user($user_id){
}
}
wwa_update_option('user_id', $all_user_meta);
wwa_update_option('user_credentials_meta', json_encode($all_credentials_meta));
wwa_update_option('user_credentials', json_encode($all_credentials));
wwa_update_option('user_credentials_meta', wp_json_encode($all_credentials_meta));
wwa_update_option('user_credentials', wp_json_encode($all_credentials));
wwa_add_log($res_id, "Deleted user => \"".$user_data->user_login."\"");
}
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
function wwa_login_js(){
$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;
}
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
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;
}
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
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'))){
wp_redirect(wp_login_url(), 302);
exit;
@ -272,6 +272,7 @@ function wwa_no_authenticator_warning(){
if($show_notice_flag){?>
<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>
</div>
<?php }
@ -317,6 +318,7 @@ function wwa_no_authenticator_warning(){
if($show_notice_flag){ ?>
<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>
</div>
<?php }
@ -398,4 +400,3 @@ function wwa_get_user($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
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;
}
@ -29,7 +29,7 @@ function wwa_save_user_profile_fields($user_id){
if(!isset($_POST['webauthn_only'])){
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');
}else{
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){
if($user_id === $value["user"]){
$count++;
break;
}
}
}
@ -67,7 +68,7 @@ if(isset($_GET['wwa_registered']) && $_GET['wwa_registered'] === 'true'){
}
}
$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;
?>
<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">
<button class="button button-small wwa-cancel"><?php _e('Close');?></button>
<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>
<table class="form-table">
<tr>

View File

@ -70,22 +70,27 @@ function wwa_login_form_shortcode($vals){
$html_form = '<div class="wwa-login-form">';
$args = array('echo' => false, 'value_username' => $username);
$to_wwa = "";
$args = array('echo' => false, 'value_username' => sanitize_user($username));
$to_wwa = '';
if($to !== ""){
$args["redirect"] = $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="http://'.$to.'">';
}else{
$to_wwa = '<input type="hidden" name="wwa-redirect-to" class="wwa-redirect-to" id="wwa-redirect-to" value="'.$to.'">';
}
$args['redirect'] = sanitize_url($to);
$to_wwa = '<input type="hidden" name="wwa-redirect-to" class="wwa-redirect-to" id="wwa-redirect-to" value="'.$args["redirect"].'">';
}
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-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;
}
@ -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']);
$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');
@ -153,9 +172,9 @@ function wwa_list_shortcode($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>';
$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(!current_user_can("read")){
@ -178,4 +197,3 @@ function wwa_list_shortcode($vals){
return $thead.$tbody.$tfoot;
}
add_shortcode('wwa_list', 'wwa_list_shortcode');
?>

View File

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